Merge branch 'main' into new-admin
This commit is contained in:
commit
0bbb2113e9
|
@ -67,10 +67,10 @@
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/glob": "^7.2.0",
|
"@types/glob": "^7.2.0",
|
||||||
"@types/graceful-fs": "^4.1.5",
|
"@types/graceful-fs": "^4.1.5",
|
||||||
"@types/html-to-text": "^8.0.1",
|
"@types/html-to-text": "^8.1.1",
|
||||||
"@types/jest": "^28.1.7",
|
"@types/jest": "^28.1.8",
|
||||||
"@types/lodash": "^4.14.184",
|
"@types/lodash": "^4.14.184",
|
||||||
"@types/node": "^18.7.11",
|
"@types/node": "^18.7.13",
|
||||||
"@types/node-cron": "^3.0.2",
|
"@types/node-cron": "^3.0.2",
|
||||||
"@types/nodemailer": "^6.4.5",
|
"@types/nodemailer": "^6.4.5",
|
||||||
"@types/request": "^2.48.8",
|
"@types/request": "^2.48.8",
|
||||||
|
|
|
@ -11,23 +11,22 @@ import {
|
||||||
JSONObject,
|
JSONObject,
|
||||||
MonitoringResponse,
|
MonitoringResponse,
|
||||||
MonitoringToken,
|
MonitoringToken,
|
||||||
|
NodeMonitoringStateResponse,
|
||||||
toMonitoringResponse,
|
toMonitoringResponse,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
const isValidToken = forConstraint(CONSTRAINTS.token, false);
|
const isValidToken = forConstraint(CONSTRAINTS.token, false);
|
||||||
|
|
||||||
// FIXME: Get rid of any
|
async function doGetAll(
|
||||||
async function doGetAll(req: Request): Promise<{ total: number; result: any }> {
|
req: Request
|
||||||
|
): Promise<{ total: number; result: NodeMonitoringStateResponse[] }> {
|
||||||
const restParams = await Resources.getValidRestParams("list", null, req);
|
const restParams = await Resources.getValidRestParams("list", null, req);
|
||||||
const { monitoringStates, total } = await MonitoringService.getAll(
|
const { monitoringStates, total } = await MonitoringService.getAll(
|
||||||
restParams
|
restParams
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
total,
|
total,
|
||||||
result: monitoringStates.map((state) => {
|
result: monitoringStates,
|
||||||
state.mapId = state.mac.toLowerCase().replace(/:/g, "");
|
|
||||||
return state;
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ import {
|
||||||
Hostname,
|
Hostname,
|
||||||
isBoolean,
|
isBoolean,
|
||||||
isDomain,
|
isDomain,
|
||||||
|
isMailType,
|
||||||
isMonitoringSortField,
|
isMonitoringSortField,
|
||||||
|
isMonitoringState,
|
||||||
isOnlineState,
|
isOnlineState,
|
||||||
isPlainObject,
|
isPlainObject,
|
||||||
isSite,
|
isSite,
|
||||||
|
@ -30,12 +32,17 @@ import {
|
||||||
isUndefined,
|
isUndefined,
|
||||||
JSONValue,
|
JSONValue,
|
||||||
MAC,
|
MAC,
|
||||||
|
MailId,
|
||||||
MailType,
|
MailType,
|
||||||
|
MapId,
|
||||||
|
mapIdFromMAC,
|
||||||
MonitoringSortField,
|
MonitoringSortField,
|
||||||
MonitoringState,
|
MonitoringState,
|
||||||
MonitoringToken,
|
MonitoringToken,
|
||||||
NodeId,
|
NodeId,
|
||||||
|
NodeMonitoringStateResponse,
|
||||||
NodeStateData,
|
NodeStateData,
|
||||||
|
NodeStateId,
|
||||||
OnlineState,
|
OnlineState,
|
||||||
parseJSON,
|
parseJSON,
|
||||||
RunResult,
|
RunResult,
|
||||||
|
@ -55,13 +62,13 @@ import {
|
||||||
} from "../utils/time";
|
} from "../utils/time";
|
||||||
|
|
||||||
type NodeStateRow = {
|
type NodeStateRow = {
|
||||||
id: number;
|
id: NodeStateId;
|
||||||
created_at: UnixTimestampSeconds;
|
created_at: UnixTimestampSeconds;
|
||||||
domain: Domain | null;
|
domain: Domain | null;
|
||||||
hostname: Hostname | null;
|
hostname: Hostname | null;
|
||||||
import_timestamp: UnixTimestampSeconds;
|
import_timestamp: UnixTimestampSeconds;
|
||||||
last_seen: UnixTimestampSeconds;
|
last_seen: UnixTimestampSeconds;
|
||||||
last_status_mail_sent: string | null;
|
last_status_mail_sent: UnixTimestampSeconds | null;
|
||||||
last_status_mail_type: string | null;
|
last_status_mail_type: string | null;
|
||||||
mac: MAC;
|
mac: MAC;
|
||||||
modified_at: UnixTimestampSeconds;
|
modified_at: UnixTimestampSeconds;
|
||||||
|
@ -356,7 +363,7 @@ export function parseNodesJson(body: string): NodesParsingResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateSkippedNode(
|
async function updateSkippedNode(
|
||||||
id: NodeId,
|
id: NodeStateId,
|
||||||
node?: StoredNode
|
node?: StoredNode
|
||||||
): Promise<RunResult> {
|
): Promise<RunResult> {
|
||||||
return await db.run(
|
return await db.run(
|
||||||
|
@ -370,7 +377,7 @@ async function updateSkippedNode(
|
||||||
async function sendMonitoringMailsBatched(
|
async function sendMonitoringMailsBatched(
|
||||||
name: string,
|
name: string,
|
||||||
mailType: MailType,
|
mailType: MailType,
|
||||||
findBatchFun: () => Promise<any[]>
|
findBatchFun: () => Promise<NodeStateRow[]>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
Logger.tag("monitoring", "mail-sending").debug(
|
Logger.tag("monitoring", "mail-sending").debug(
|
||||||
'Sending "%s" mails...',
|
'Sending "%s" mails...',
|
||||||
|
@ -477,8 +484,8 @@ async function sendOnlineAgainMails(
|
||||||
await sendMonitoringMailsBatched(
|
await sendMonitoringMailsBatched(
|
||||||
"online again",
|
"online again",
|
||||||
MailType.MONITORING_ONLINE_AGAIN,
|
MailType.MONITORING_ONLINE_AGAIN,
|
||||||
async (): Promise<any[]> =>
|
async (): Promise<NodeStateRow[]> =>
|
||||||
await db.all(
|
await db.all<NodeStateRow>(
|
||||||
"SELECT * FROM node_state " +
|
"SELECT * FROM node_state " +
|
||||||
"WHERE modified_at < ? AND state = ? AND last_status_mail_type IN (" +
|
"WHERE modified_at < ? AND state = ? AND last_status_mail_type IN (" +
|
||||||
"'monitoring-offline-1', 'monitoring-offline-2', 'monitoring-offline-3'" +
|
"'monitoring-offline-1', 'monitoring-offline-2', 'monitoring-offline-3'" +
|
||||||
|
@ -497,7 +504,7 @@ async function sendOfflineMails(
|
||||||
await sendMonitoringMailsBatched(
|
await sendMonitoringMailsBatched(
|
||||||
"offline " + mailNumber,
|
"offline " + mailNumber,
|
||||||
mailType,
|
mailType,
|
||||||
async (): Promise<any[]> => {
|
async (): Promise<NodeStateRow[]> => {
|
||||||
const previousType =
|
const previousType =
|
||||||
mailNumber === 1
|
mailNumber === 1
|
||||||
? "monitoring-online-again"
|
? "monitoring-online-again"
|
||||||
|
@ -510,7 +517,7 @@ async function sendOfflineMails(
|
||||||
const schedule = MONITORING_OFFLINE_MAILS_SCHEDULE[mailNumber];
|
const schedule = MONITORING_OFFLINE_MAILS_SCHEDULE[mailNumber];
|
||||||
const scheduledTimeBefore = subtract(now(), schedule);
|
const scheduledTimeBefore = subtract(now(), schedule);
|
||||||
|
|
||||||
return await db.all(
|
return await db.all<NodeStateRow>(
|
||||||
"SELECT * FROM node_state " +
|
"SELECT * FROM node_state " +
|
||||||
"WHERE modified_at < ? AND state = ? AND (last_status_mail_type = ?" +
|
"WHERE modified_at < ? AND state = ? AND (last_status_mail_type = ?" +
|
||||||
allowNull +
|
allowNull +
|
||||||
|
@ -667,10 +674,33 @@ async function retrieveNodeInformationForUrls(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Replace any[] by type.
|
function toResponse(row: NodeStateRow): NodeMonitoringStateResponse {
|
||||||
|
// TODO: Handle conversion errors.
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
created_at: row.created_at,
|
||||||
|
domain: row.domain || undefined,
|
||||||
|
hostname: row.hostname || undefined,
|
||||||
|
import_timestamp: row.import_timestamp,
|
||||||
|
last_seen: row.last_seen,
|
||||||
|
last_status_mail_sent: row.last_status_mail_sent || undefined,
|
||||||
|
last_status_mail_type: isMailType(row.last_status_mail_type)
|
||||||
|
? row.last_status_mail_type
|
||||||
|
: undefined,
|
||||||
|
mac: row.mac,
|
||||||
|
modified_at: row.modified_at,
|
||||||
|
monitoring_state: isMonitoringState(row.monitoring_state)
|
||||||
|
? row.monitoring_state
|
||||||
|
: undefined,
|
||||||
|
site: row.site || undefined,
|
||||||
|
state: isOnlineState(row.state) ? row.state : OnlineState.OFFLINE,
|
||||||
|
mapId: mapIdFromMAC(row.mac),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAll(
|
export async function getAll(
|
||||||
restParams: RestParams
|
restParams: RestParams
|
||||||
): Promise<{ total: number; monitoringStates: any[] }> {
|
): Promise<{ total: number; monitoringStates: NodeMonitoringStateResponse[] }> {
|
||||||
const filterFields = [
|
const filterFields = [
|
||||||
"hostname",
|
"hostname",
|
||||||
"mac",
|
"mac",
|
||||||
|
@ -695,12 +725,15 @@ export async function getAll(
|
||||||
filterFields
|
filterFields
|
||||||
);
|
);
|
||||||
|
|
||||||
const monitoringStates = await db.all(
|
const monitoringStates = await db.all<NodeStateRow>(
|
||||||
"SELECT * FROM node_state WHERE " + filter.query,
|
"SELECT * FROM node_state WHERE " + filter.query,
|
||||||
filter.params
|
filter.params
|
||||||
);
|
);
|
||||||
|
|
||||||
return { monitoringStates, total };
|
return {
|
||||||
|
monitoringStates: monitoringStates.map(toResponse),
|
||||||
|
total,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getByMacs(
|
export async function getByMacs(
|
||||||
|
@ -877,7 +910,7 @@ async function deleteNeverOnlineNodesBefore(
|
||||||
|
|
||||||
const placeholders = macs.map(() => "?").join(",");
|
const placeholders = macs.map(() => "?").join(",");
|
||||||
|
|
||||||
const rows: { mac: MAC }[] = await db.all(
|
const rows = await db.all<NodeStateRow>(
|
||||||
`SELECT * FROM node_state WHERE mac IN (${placeholders})`,
|
`SELECT * FROM node_state WHERE mac IN (${placeholders})`,
|
||||||
macs
|
macs
|
||||||
);
|
);
|
||||||
|
|
|
@ -368,6 +368,12 @@ export const isFastdKey = toIsNewtype(isString, "" as FastdKey);
|
||||||
export type MAC = string & { readonly __tag: unique symbol };
|
export type MAC = string & { readonly __tag: unique symbol };
|
||||||
export const isMAC = toIsNewtype(isString, "" as MAC);
|
export const isMAC = toIsNewtype(isString, "" as MAC);
|
||||||
|
|
||||||
|
export type MapId = string & { readonly __tag: unique symbol };
|
||||||
|
export const isMapId = toIsNewtype(isString, "" as MapId);
|
||||||
|
export function mapIdFromMAC(mac: MAC): MapId {
|
||||||
|
return mac.toLowerCase().replace(/:/g, "") as MapId;
|
||||||
|
}
|
||||||
|
|
||||||
export type DurationSeconds = number & { readonly __tag: unique symbol };
|
export type DurationSeconds = number & { readonly __tag: unique symbol };
|
||||||
export const isDurationSeconds = toIsNewtype(isNumber, NaN as DurationSeconds);
|
export const isDurationSeconds = toIsNewtype(isNumber, NaN as DurationSeconds);
|
||||||
|
|
||||||
|
@ -572,6 +578,49 @@ export function isMonitoringResponse(arg: unknown): arg is MonitoringResponse {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NodeStateId = number & { readonly __tag: unique symbol };
|
||||||
|
export const isNodeStateId = toIsNewtype(isNumber, 0 as NodeStateId);
|
||||||
|
|
||||||
|
export type NodeMonitoringStateResponse = {
|
||||||
|
id: NodeStateId;
|
||||||
|
created_at: UnixTimestampSeconds;
|
||||||
|
domain?: Domain;
|
||||||
|
hostname?: Hostname;
|
||||||
|
import_timestamp: UnixTimestampSeconds;
|
||||||
|
last_seen: UnixTimestampSeconds;
|
||||||
|
last_status_mail_sent?: UnixTimestampSeconds;
|
||||||
|
last_status_mail_type?: MailType;
|
||||||
|
mac: MAC;
|
||||||
|
modified_at: UnixTimestampSeconds;
|
||||||
|
monitoring_state?: MonitoringState;
|
||||||
|
site?: Site;
|
||||||
|
state: OnlineState;
|
||||||
|
mapId: MapId;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MailId = number & { readonly __tag: unique symbol };
|
||||||
|
export const isMailId = toIsNewtype(isNumber, NaN as MailId);
|
||||||
|
|
||||||
|
export type MailData = JSONObject;
|
||||||
|
|
||||||
|
export enum MailType {
|
||||||
|
MONITORING_OFFLINE_1 = "monitoring-offline-1",
|
||||||
|
MONITORING_OFFLINE_2 = "monitoring-offline-2",
|
||||||
|
MONITORING_OFFLINE_3 = "monitoring-offline-3",
|
||||||
|
MONITORING_ONLINE_AGAIN = "monitoring-online-again",
|
||||||
|
MONITORING_CONFIRMATION = "monitoring-confirmation",
|
||||||
|
}
|
||||||
|
export const isMailType = toIsEnum(MailType);
|
||||||
|
|
||||||
|
export type Mail = {
|
||||||
|
id: MailId;
|
||||||
|
email: MailType;
|
||||||
|
sender: EmailAddress;
|
||||||
|
recipient: EmailAddress;
|
||||||
|
data: MailData;
|
||||||
|
failures: number;
|
||||||
|
};
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
export enum NodeSortFieldEnum {
|
export enum NodeSortFieldEnum {
|
||||||
HOSTNAME = "hostname",
|
HOSTNAME = "hostname",
|
||||||
|
|
|
@ -2,9 +2,6 @@ import {
|
||||||
CreateOrUpdateNode,
|
CreateOrUpdateNode,
|
||||||
Domain,
|
Domain,
|
||||||
DomainSpecificNodeResponse,
|
DomainSpecificNodeResponse,
|
||||||
EmailAddress,
|
|
||||||
isNumber,
|
|
||||||
JSONObject,
|
|
||||||
MonitoringResponse,
|
MonitoringResponse,
|
||||||
MonitoringState,
|
MonitoringState,
|
||||||
MonitoringToken,
|
MonitoringToken,
|
||||||
|
@ -13,8 +10,6 @@ import {
|
||||||
OnlineState,
|
OnlineState,
|
||||||
Site,
|
Site,
|
||||||
StoredNode,
|
StoredNode,
|
||||||
toIsEnum,
|
|
||||||
toIsNewtype,
|
|
||||||
} from "../shared/types";
|
} from "../shared/types";
|
||||||
|
|
||||||
export * from "./config";
|
export * from "./config";
|
||||||
|
@ -98,27 +93,3 @@ export function toMonitoringResponse(node: StoredNode): MonitoringResponse {
|
||||||
export type NodeSecrets = {
|
export type NodeSecrets = {
|
||||||
monitoringToken?: MonitoringToken;
|
monitoringToken?: MonitoringToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MailId = number & { readonly __tag: unique symbol };
|
|
||||||
export const isMailId = toIsNewtype(isNumber, NaN as MailId);
|
|
||||||
|
|
||||||
export type MailData = JSONObject;
|
|
||||||
|
|
||||||
export enum MailType {
|
|
||||||
MONITORING_OFFLINE_1 = "monitoring-offline-1",
|
|
||||||
MONITORING_OFFLINE_2 = "monitoring-offline-2",
|
|
||||||
MONITORING_OFFLINE_3 = "monitoring-offline-3",
|
|
||||||
MONITORING_ONLINE_AGAIN = "monitoring-online-again",
|
|
||||||
MONITORING_CONFIRMATION = "monitoring-confirmation",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isMailType = toIsEnum(MailType);
|
|
||||||
|
|
||||||
export type Mail = {
|
|
||||||
id: MailId;
|
|
||||||
email: MailType;
|
|
||||||
sender: EmailAddress;
|
|
||||||
recipient: EmailAddress;
|
|
||||||
data: MailData;
|
|
||||||
failures: number;
|
|
||||||
};
|
|
||||||
|
|
|
@ -321,7 +321,7 @@ export function sort<
|
||||||
let order = 0;
|
let order = 0;
|
||||||
if (as < bs) {
|
if (as < bs) {
|
||||||
order = -1;
|
order = -1;
|
||||||
} else if (bs > as) {
|
} else if (as > bs) {
|
||||||
order = 1;
|
order = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -910,10 +910,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/html-to-text@^8.0.1":
|
"@types/html-to-text@^8.1.1":
|
||||||
version "8.1.0"
|
version "8.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-8.1.0.tgz#dad0bf5d199f7e3f67eae50a36c13eadb1b56d1b"
|
resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-8.1.1.tgz#0c5573207c14f618f24da5a2910c510285573094"
|
||||||
integrity sha512-54YF2fGmN4g62/w+T85uQ8n0FyBhMY5cjKZ1imsbIh4Pgbeno1mAaQktC/pv/+C2ToUYkTZis9ADgn9GRRz9nQ==
|
integrity sha512-QFcqfc7TiVbvIX8Fc2kWUxakruI1Ay6uitaGCYHzI5M0WHQROV5D2XeSaVrK0FmvssivXum4yERVnJsiuH61Ww==
|
||||||
|
|
||||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
|
@ -934,10 +934,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/istanbul-lib-report" "*"
|
"@types/istanbul-lib-report" "*"
|
||||||
|
|
||||||
"@types/jest@^28.1.7":
|
"@types/jest@^28.1.8":
|
||||||
version "28.1.7"
|
version "28.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.7.tgz#a680c5d05b69634c2d54a63cb106d7fb1adaba16"
|
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.8.tgz#6936409f3c9724ea431efd412ea0238a0f03b09b"
|
||||||
integrity sha512-acDN4VHD40V24tgu0iC44jchXavRNVFXQ/E6Z5XNsswgoSO/4NgsXoEYmPUGookKldlZQyIpmrEXsHI9cA3ZTA==
|
integrity sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==
|
||||||
dependencies:
|
dependencies:
|
||||||
expect "^28.0.0"
|
expect "^28.0.0"
|
||||||
pretty-format "^28.0.0"
|
pretty-format "^28.0.0"
|
||||||
|
@ -984,10 +984,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef"
|
||||||
integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
|
integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
|
||||||
|
|
||||||
"@types/node@^18.7.11":
|
"@types/node@^18.7.13":
|
||||||
version "18.7.11"
|
version "18.7.13"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.11.tgz#486e72cfccde88da24e1f23ff1b7d8bfb64e6250"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.13.tgz#23e6c5168333480d454243378b69e861ab5c011a"
|
||||||
integrity sha512-KZhFpSLlmK/sdocfSAjqPETTMd0ug6HIMIAwkwUpU79olnZdQtMxpQP+G1wDzCH7na+FltSIhbaZuKdwZ8RDrw==
|
integrity sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==
|
||||||
|
|
||||||
"@types/nodemailer@^6.4.5":
|
"@types/nodemailer@^6.4.5":
|
||||||
version "6.4.5"
|
version "6.4.5"
|
||||||
|
|
Loading…
Reference in a new issue