|
|
@@ -324,6 +324,9 @@ export class Database extends SQLiteController {
|
|
|
updValues.push([conf.active ?? defaults.serviceChecks.active ? 1 : 0, conf.id, 'active']);
|
|
|
status = conf.active ?? defaults.serviceChecks.active ? ServiceChangedStatus.Activated : ServiceChangedStatus.Deactivated;
|
|
|
}
|
|
|
+ if (oldConf.notify !== conf.notify) updValues.push([conf.notify ?? defaults.serviceChecks.notify ? 1 : 0, conf.id, 'notify']);
|
|
|
+ if (oldConf.notifyThreshold !== conf.notifyThreshold)
|
|
|
+ updValues.push([conf.notifyThreshold ?? defaults.serviceChecks.notifyThreshold, conf.id, 'notifyThreshold']);
|
|
|
if (updValues.length) {
|
|
|
for (const data of updValues) {
|
|
|
await this.run(`UPDATE HealthCheckParams SET Value = ? WHERE ConfigID = ? AND Key = ?;`, data);
|
|
|
@@ -342,8 +345,8 @@ export class Database extends SQLiteController {
|
|
|
});
|
|
|
|
|
|
if (delIDs.length) {
|
|
|
- const delSql = 'DELETE FROM HealthCheckParams WHERE ID IN (?);';
|
|
|
- await this.run(delSql, [delIDs]);
|
|
|
+ const delSql = `DELETE FROM HealthCheckParams WHERE ID IN (${delIDs.map(() => '?').join(',')});`;
|
|
|
+ await this.run(delSql, delIDs);
|
|
|
}
|
|
|
|
|
|
if (updValues.length) {
|
|
|
@@ -371,12 +374,16 @@ export class Database extends SQLiteController {
|
|
|
(?, ?, ?, ?),
|
|
|
(?, ?, ?, ?),
|
|
|
(?, ?, ?, ?),
|
|
|
+ (?, ?, ?, ?),
|
|
|
+ (?, ?, ?, ?),
|
|
|
(?, ?, ?, ?)${conf.checks.length ? `,${insCheckValues.map(() => '(?, ?, ?, ?)').join(',')}` : ''}`,
|
|
|
[
|
|
|
...[res.lastID, 'text', 'url', conf.url],
|
|
|
...[res.lastID, 'boolean', 'active', conf.active ?? defaults.serviceChecks.active ? 1 : 0],
|
|
|
...[res.lastID, 'number', 'interval', conf.interval],
|
|
|
...[res.lastID, 'number', 'timeout', conf.timeout ?? defaults.serviceChecks.httpTimeout],
|
|
|
+ ...[res.lastID, 'boolean', 'notify', conf.notify ?? defaults.serviceChecks.notify],
|
|
|
+ ...[res.lastID, 'number', 'notifyThreshold', conf.notifyThreshold ?? defaults.serviceChecks.notifyThreshold],
|
|
|
...conf.checks.reduce((ret, check) => [...ret, res.lastID, 'regexp', 'check', check], [] as any[])
|
|
|
]
|
|
|
);
|
|
|
@@ -418,27 +425,59 @@ export class Database extends SQLiteController {
|
|
|
async queryServiceCheckData(serverID: number, confID: number, from: Date, to: Date) {
|
|
|
const result = await this.stmt(
|
|
|
`
|
|
|
- SELECT
|
|
|
- HealthCheckDataEntry.*
|
|
|
- FROM HealthCheckDataEntry
|
|
|
- JOIN HealthCheckConfig ON HealthCheckConfig.ID = HealthCheckDataEntry.ConfigID
|
|
|
+ SELECT DataEntryChanges.*
|
|
|
+ FROM HealthCheckConfig
|
|
|
+ JOIN (
|
|
|
+ SELECT * FROM (
|
|
|
+ SELECT
|
|
|
+ *
|
|
|
+ FROM HealthCheckDataEntry
|
|
|
+ WHERE ConfigID = ?
|
|
|
+ AND Timestamp BETWEEN ? AND ?
|
|
|
+ ORDER BY ID
|
|
|
+ LIMIT 0, 1
|
|
|
+ ) AS FIRST_STATE
|
|
|
+
|
|
|
+ UNION --+--+--
|
|
|
+
|
|
|
+ SELECT
|
|
|
+ ID,
|
|
|
+ ConfigID,
|
|
|
+ Timestamp,
|
|
|
+ Status,
|
|
|
+ Message
|
|
|
+ FROM
|
|
|
+ (
|
|
|
+ SELECT
|
|
|
+ HealthCheckDataEntry.*,
|
|
|
+ LAG(Status) OVER (ORDER BY ConfigID, Timestamp) AS previous_state,
|
|
|
+ LAG(Message) OVER (ORDER BY ConfigID, Timestamp) AS previous_msg
|
|
|
+ FROM HealthCheckDataEntry
|
|
|
+ WHERE ConfigID = ?
|
|
|
+ )
|
|
|
+ WHERE Status <> previous_state
|
|
|
+ AND Message <> previous_msg
|
|
|
+
|
|
|
+ UNION --+--+--
|
|
|
+
|
|
|
+ SELECT * FROM (
|
|
|
+ SELECT
|
|
|
+ *
|
|
|
+ FROM HealthCheckDataEntry
|
|
|
+ WHERE ConfigID = ?
|
|
|
+ AND Timestamp BETWEEN ? AND ?
|
|
|
+ ORDER BY ID DESC
|
|
|
+ LIMIT 0, 1
|
|
|
+ ) AS LAST_STATE
|
|
|
+ ORDER BY ConfigID, Timestamp
|
|
|
+ ) AS DataEntryChanges ON DataEntryChanges.ConfigID = HealthCheckConfig.ID
|
|
|
WHERE HealthCheckConfig.ServerID = ?
|
|
|
- AND HealthCheckDataEntry.ConfigID = ?
|
|
|
- AND HealthCheckDataEntry.Timestamp BETWEEN ? AND ?
|
|
|
- ORDER BY Timestamp, ID;
|
|
|
- `,
|
|
|
- [serverID, confID, from.getTime(), to.getTime()]
|
|
|
+ AND DataEntryChanges.Timestamp BETWEEN ? AND ?
|
|
|
+ ORDER BY Timestamp, ID;`,
|
|
|
+ [confID, from.getTime(), to.getTime(), confID, confID, from.getTime(), to.getTime(), serverID, from.getTime(), to.getTime()]
|
|
|
);
|
|
|
|
|
|
- const mapByTimestamp = result.rows.reduce((res: Map<number, ServiceCheckDataEntry[]>, row) => {
|
|
|
- const time: number = row['Timestamp'];
|
|
|
- if (!res.has(time)) res.set(time, []);
|
|
|
- res.get(time)?.push({
|
|
|
- status: row['Status'] as number,
|
|
|
- message: row['Message']
|
|
|
- });
|
|
|
- return res;
|
|
|
- }, new Map()) as Map<number, ServiceCheckDataEntry[]>;
|
|
|
+ const mapByTimestamp = this.mapServiceCheckDataByTimestamp(result.rows);
|
|
|
|
|
|
const arr: ServiceCheckData[] = [];
|
|
|
for (const entry of mapByTimestamp.entries()) {
|
|
|
@@ -450,6 +489,52 @@ export class Database extends SQLiteController {
|
|
|
return arr;
|
|
|
}
|
|
|
|
|
|
+ public async getLastErrors(confID: number, threshold: number) {
|
|
|
+ const result = await this.stmt(
|
|
|
+ `SELECT * FROM HealthCheckDataEntry
|
|
|
+ WHERE ConfigID = ?
|
|
|
+ AND Timestamp IN (
|
|
|
+ SELECT Timestamp
|
|
|
+ FROM HealthCheckDataEntry
|
|
|
+ WHERE ConfigID = ?
|
|
|
+ GROUP BY Timestamp
|
|
|
+ ORDER BY Timestamp DESC
|
|
|
+ LIMIT 0, ?
|
|
|
+ )
|
|
|
+ ORDER BY Timestamp DESC, ID DESC`,
|
|
|
+ [confID, confID, threshold]
|
|
|
+ );
|
|
|
+
|
|
|
+ const mapByTimestamp = this.mapServiceCheckDataByTimestamp(result.rows);
|
|
|
+ const errors: ServiceCheckData[] = [];
|
|
|
+ for (const entry of mapByTimestamp.entries()) {
|
|
|
+ const time = entry[0];
|
|
|
+ const data = entry[1];
|
|
|
+
|
|
|
+ const errorData = data.filter(d => d.status !== HttpCheckStatus.OK);
|
|
|
+ if (!errorData.length) break;
|
|
|
+
|
|
|
+ errors.push({
|
|
|
+ time: new Date(time),
|
|
|
+ data: errorData
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return errors;
|
|
|
+ }
|
|
|
+
|
|
|
+ private mapServiceCheckDataByTimestamp(rows: any[]) {
|
|
|
+ return rows.reduce((res: Map<number, ServiceCheckDataEntry[]>, row) => {
|
|
|
+ const time: number = row['Timestamp'];
|
|
|
+ if (!res.has(time)) res.set(time, []);
|
|
|
+ res.get(time)?.push({
|
|
|
+ status: row['Status'] as number,
|
|
|
+ message: row['Message']
|
|
|
+ });
|
|
|
+ return res;
|
|
|
+ }, new Map()) as Map<number, ServiceCheckDataEntry[]>;
|
|
|
+ }
|
|
|
+
|
|
|
private configFromResultRows(rows: any[]) {
|
|
|
return rows.reduce((res: ServiceConfig[], line, i) => {
|
|
|
const configID = line['ID'];
|
|
|
@@ -502,6 +587,8 @@ export class Database extends SQLiteController {
|
|
|
active: (hcConf.params?.find(p => p.key === 'active')?.value as boolean) ?? defaults.serviceChecks.active,
|
|
|
interval: hcConf.params?.find(p => p.key === 'interval')?.value as number,
|
|
|
timeout: (hcConf.params?.find(p => p.key === 'timeout')?.value as number) ?? defaults.serviceChecks.httpTimeout,
|
|
|
+ notify: (hcConf.params?.find(p => p.key === 'notify')?.value as boolean) ?? defaults.serviceChecks.notify,
|
|
|
+ notifyThreshold: (hcConf.params?.find(p => p.key === 'notifyThreshold')?.value as number) ?? defaults.serviceChecks.notifyThreshold,
|
|
|
checks: hcConf.params?.reduce((res, p) => (p.key === 'check' && Array.isArray(p.value) ? [...res, ...p.value] : res), [] as string[])
|
|
|
};
|
|
|
return {
|