Merge branch 'main' into new-admin

This commit is contained in:
baldo 2022-08-24 18:22:41 +02:00
commit 0bbb2113e9
7 changed files with 116 additions and 64 deletions

View file

@ -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",

View file

@ -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;
}),
}; };
} }

View file

@ -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
); );

View file

@ -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",

View file

@ -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;
};

View file

@ -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;
} }

View file

@ -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"