mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-11-10 13:14:24 +08:00
chore: cleanup messy code (#8085)
This commit is contained in:
1 parent
73a50f9ce0
commit
9bdac74a88
61 files changed
+406
-786
No files matched your search
@@ -119,5 +119,12 @@ module.exports = {
|
||||
eqeqeq: "off",
|
||||
},
|
||||
},
|
||||
// Backend: no `any`
|
||||
{
|
||||
files: ["server/**/*.ts"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -7,7 +7,7 @@ const path = require("path");
|
||||
const esbuild = require("esbuild");
|
||||
|
||||
const root = path.resolve(__dirname, "..");
|
||||
const esmEntries = ["server/server.js", "server/util-server.js"];
|
||||
const esmEntries = ["server/server.js"];
|
||||
|
||||
/**
|
||||
* Collect every .ts file below a directory.
|
||||
|
||||
@@ -8,7 +8,7 @@ const PATTERNS = [
|
||||
"test/frontend-test/**/*.js",
|
||||
];
|
||||
const CONTAINER_MARKER = "testcontainers";
|
||||
const NODE_ARGS = ["--import=tsx", "--test"];
|
||||
const NODE_ARGS = ["--import=tsx", "--import=./test/setup.ts", "--test"];
|
||||
const extraArgs = process.argv.slice(2);
|
||||
|
||||
const files = PATTERNS.flatMap((pattern) => globSync(pattern, { exclude: ["**/node_modules/**"] })).sort();
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
import expressBasicAuth from "express-basic-auth";
|
||||
import dayjs from "dayjs";
|
||||
import { R } from "redbean-node";
|
||||
import type { Bean } from "redbean-node";
|
||||
import type express from "express";
|
||||
import { log } from "../src/util";
|
||||
import { loginRateLimiter, apiRateLimiter } from "./rate-limiter";
|
||||
@@ -28,7 +29,7 @@ export async function login(username: string, password: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user: any = await R.findOne("user", "TRIM(username) = ? AND active = 1 ", [username.trim()]);
|
||||
const user: Bean | null = await R.findOne("user", "TRIM(username) = ? AND active = 1 ", [username.trim()]);
|
||||
|
||||
if (user && passwordHash.verify(password, user.password)) {
|
||||
// Upgrade the hash to bcrypt
|
||||
@@ -77,7 +78,7 @@ async function verifyLegacyAPIKey(key: string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hash: any = await R.findOne("api_key", " id=? ", [index]);
|
||||
const hash: Bean | null = await R.findOne("api_key", " id=? ", [index]);
|
||||
|
||||
if (hash === null) {
|
||||
return false;
|
||||
|
||||
@@ -441,7 +441,7 @@ export async function checkPassword(username: string, password: string): Promise
|
||||
const { adapter, password: passwordVerifier } = await auth().$context;
|
||||
const internalAdapter = await authInternal();
|
||||
|
||||
const user = await adapter.findOne<Record<string, any>>({
|
||||
const user = await adapter.findOne<Record<string, unknown>>({
|
||||
model: "user",
|
||||
where: [{ field: "username", value: username }],
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { setSetting, setting } = require("./util-server");
|
||||
const { setSetting, setting } = require("./util");
|
||||
const axios = require("axios");
|
||||
const compareVersions = require("compare-versions");
|
||||
const { log } = require("../src/util");
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ const https = require("https");
|
||||
const fsAsync = require("fs").promises;
|
||||
const path = require("path");
|
||||
const Database = require("./database");
|
||||
const { axiosAbortSignal, fsExists } = require("./util-server");
|
||||
const { axiosAbortSignal, fsExists } = require("./util");
|
||||
|
||||
class DockerHost {
|
||||
static CertificateFileNameCA = "ca.pem";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getTotalClientInRoom } from "./util-server";
|
||||
import { getTotalClientInRoom } from "./util";
|
||||
|
||||
/**
|
||||
* Room every logged-in socket joins; v3 has a single dashboard.
|
||||
|
||||
@@ -2,7 +2,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { R } = require("redbean-node");
|
||||
const { log, TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../../src/util");
|
||||
const { parse: parseTld } = require("tldts");
|
||||
const { setting, setSetting } = require("../util-server");
|
||||
const { setting, setSetting } = require("../util");
|
||||
const { Notification } = require("../notification");
|
||||
const TranslatableError = require("../translatable-error");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
@@ -38,7 +38,7 @@ const {
|
||||
axiosAbortSignal,
|
||||
encodeBase64,
|
||||
checkCertExpiryNotifications,
|
||||
} = require("../util-server");
|
||||
} = require("../util");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification");
|
||||
|
||||
@@ -9,7 +9,7 @@ const { Feed } = require("feed");
|
||||
const config = require("../config");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
const { setting } = require("../util-server");
|
||||
const { setting } = require("../util");
|
||||
const {
|
||||
STATUS_PAGE_ALL_DOWN,
|
||||
STATUS_PAGE_ALL_UP,
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { PluginFunc, ConfigType } from "dayjs";
|
||||
|
||||
declare const plugin: PluginFunc;
|
||||
export = plugin;
|
||||
|
||||
declare module "dayjs" {
|
||||
interface Dayjs {
|
||||
tz(timezone?: string, keepLocalTime?: boolean): Dayjs;
|
||||
offsetName(type?: "short" | "long"): string | undefined;
|
||||
}
|
||||
|
||||
interface DayjsTimezone {
|
||||
(date: ConfigType, timezone?: string): Dayjs;
|
||||
(date: ConfigType, format: string, timezone?: string): Dayjs;
|
||||
guess(): string;
|
||||
setDefault(timezone?: string): void;
|
||||
}
|
||||
|
||||
const tz: DayjsTimezone;
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Copy from node_modules/dayjs/plugin/timezone.js
|
||||
* Try to fix https://github.com/louislam/uptime-kuma/issues/2318
|
||||
* Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc
|
||||
* License: MIT
|
||||
*/
|
||||
!(function (t, e) {
|
||||
// eslint-disable-next-line no-undef
|
||||
typeof exports == "object" && typeof module != "undefined"
|
||||
? (module.exports = e())
|
||||
: typeof define == "function" && define.amd
|
||||
? define(e)
|
||||
: ((t = typeof globalThis != "undefined" ? globalThis : t || self).dayjs_plugin_timezone = e());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
let t = {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 2,
|
||||
hour: 3,
|
||||
minute: 4,
|
||||
second: 5,
|
||||
};
|
||||
let e = {};
|
||||
return function (n, i, o) {
|
||||
let r;
|
||||
let a = function (t, n, i) {
|
||||
void 0 === i && (i = {});
|
||||
let o = new Date(t);
|
||||
let r = (function (t, n) {
|
||||
void 0 === n && (n = {});
|
||||
let i = n.timeZoneName || "short";
|
||||
let o = t + "|" + i;
|
||||
let r = e[o];
|
||||
return (
|
||||
r ||
|
||||
((r = new Intl.DateTimeFormat("en-US", {
|
||||
hour12: !1,
|
||||
timeZone: t,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZoneName: i,
|
||||
})),
|
||||
(e[o] = r)),
|
||||
r
|
||||
);
|
||||
})(n, i);
|
||||
return r.formatToParts(o);
|
||||
};
|
||||
let u = function (e, n) {
|
||||
let i = a(e, n);
|
||||
let r = [];
|
||||
let u = 0;
|
||||
for (; u < i.length; u += 1) {
|
||||
let f = i[u];
|
||||
let s = f.type;
|
||||
let m = f.value;
|
||||
let c = t[s];
|
||||
c >= 0 && (r[c] = parseInt(m, 10));
|
||||
}
|
||||
let d = r[3];
|
||||
let l = d === 24 ? 0 : d;
|
||||
let v = r[0] + "-" + r[1] + "-" + r[2] + " " + l + ":" + r[4] + ":" + r[5] + ":000";
|
||||
let h = +e;
|
||||
return (o.utc(v).valueOf() - (h -= h % 1e3)) / 6e4;
|
||||
};
|
||||
let f = i.prototype;
|
||||
((f.tz = function (t, e) {
|
||||
void 0 === t && (t = r);
|
||||
let n = this.utcOffset();
|
||||
let i = this.toDate();
|
||||
let a = i.toLocaleString("en-US", { timeZone: t }).replace("\u202f", " ");
|
||||
let u = Math.round((i - new Date(a)) / 1e3 / 60);
|
||||
let f = o(a)
|
||||
.$set("millisecond", this.$ms)
|
||||
.utcOffset(15 * -Math.round(i.getTimezoneOffset() / 15) - u, !0);
|
||||
if (e) {
|
||||
let s = f.utcOffset();
|
||||
f = f.add(n - s, "minute");
|
||||
}
|
||||
return ((f.$x.$timezone = t), f);
|
||||
}),
|
||||
(f.offsetName = function (t) {
|
||||
let e = this.$x.$timezone || o.tz.guess();
|
||||
let n = a(this.valueOf(), e, { timeZoneName: t }).find(function (t) {
|
||||
return t.type.toLowerCase() === "timezonename";
|
||||
});
|
||||
return n && n.value;
|
||||
}));
|
||||
let s = f.startOf;
|
||||
((f.startOf = function (t, e) {
|
||||
if (!this.$x || !this.$x.$timezone) {
|
||||
return s.call(this, t, e);
|
||||
}
|
||||
let n = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));
|
||||
return s.call(n, t, e).tz(this.$x.$timezone, !0);
|
||||
}),
|
||||
(o.tz = function (t, e, n) {
|
||||
let i = n && e;
|
||||
let a = n || e || r;
|
||||
let f = u(+o(), a);
|
||||
if (typeof t != "string") {
|
||||
return o(t).tz(a);
|
||||
}
|
||||
let s = (function (t, e, n) {
|
||||
let i = t - 60 * e * 1e3;
|
||||
let o = u(i, n);
|
||||
if (e === o) {
|
||||
return [i, e];
|
||||
}
|
||||
let r = u((i -= 60 * (o - e) * 1e3), n);
|
||||
return o === r ? [i, o] : [t - 60 * Math.min(o, r) * 1e3, Math.max(o, r)];
|
||||
})(o.utc(t, i).valueOf(), f, a);
|
||||
let m = s[0];
|
||||
let c = s[1];
|
||||
let d = o(m).utcOffset(c);
|
||||
return ((d.$x.$timezone = a), d);
|
||||
}),
|
||||
(o.tz.guess = function () {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}),
|
||||
(o.tz.setDefault = function (t) {
|
||||
r = t;
|
||||
}));
|
||||
};
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { checkCertificate, checkCertificateHostname } from "./util-server";
|
||||
import { checkCertificate, checkCertificateHostname } from "./util";
|
||||
|
||||
/** Minimal TLS socket shape needed for the certificate info */
|
||||
export interface TlsSocketLike {
|
||||
|
||||
@@ -8,7 +8,7 @@ const {
|
||||
encodeBase64,
|
||||
getDaysRemaining,
|
||||
checkCertExpiryNotifications,
|
||||
} = require("../util-server");
|
||||
} = require("../util");
|
||||
const { R } = require("redbean-node");
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { log, UP } = require("../../src/util");
|
||||
const { axiosAbortSignal } = require("../util-server");
|
||||
const { axiosAbortSignal } = require("../util");
|
||||
const axios = require("axios");
|
||||
|
||||
class RabbitMqMonitorType extends MonitorType {
|
||||
|
||||
@@ -7,7 +7,7 @@ const path = require("path");
|
||||
const Database = require("../database");
|
||||
const config = require("../config");
|
||||
const { RemoteBrowser } = require("../remote-browser");
|
||||
const { commandExists } = require("../util-server");
|
||||
const { commandExists } = require("../util");
|
||||
|
||||
/**
|
||||
* Cached instance of a browser
|
||||
|
||||
@@ -6,7 +6,7 @@ const {
|
||||
PING_PER_REQUEST_TIMEOUT_DEFAULT,
|
||||
} = require("../../src/util");
|
||||
const { Settings } = require("../settings");
|
||||
const { checkStatusCode } = require("../util-server");
|
||||
const { checkStatusCode } = require("../util");
|
||||
const { ping } = require("./ping");
|
||||
const axios = require("axios");
|
||||
const crypto = require("crypto");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const WebSocket = require("ws");
|
||||
const { UP } = require("../../src/util");
|
||||
const { checkStatusCode, getOidcTokenClientCredentials } = require("../util-server");
|
||||
const { checkStatusCode, getOidcTokenClientCredentials } = require("../util");
|
||||
// Define closing error codes https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
|
||||
const WS_ERR_CODE = {
|
||||
1002: "Protocol error",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const webpush = require("web-push");
|
||||
const { setting } = require("../util-server");
|
||||
const { setting } = require("../util");
|
||||
|
||||
class Webpush extends NotificationProvider {
|
||||
name = "Webpush";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const { setting } = require("../util");
|
||||
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const { setting } = require("../util");
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const { setting } = require("../util");
|
||||
const { UP } = require("../../src/util");
|
||||
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { setSettings, setting } = require("../util-server");
|
||||
const { setSettings, setting } = require("../util");
|
||||
const { getMonitorRelativeURL, UP, log } = require("../../src/util");
|
||||
const isUrl = require("is-url");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { setting } = require("../util-server");
|
||||
const { setting } = require("../util");
|
||||
const { DOWN, UP, getMonitorRelativeURL } = require("../../src/util");
|
||||
|
||||
class Teams extends NotificationProvider {
|
||||
|
||||
@@ -103,7 +103,7 @@ const AmootSMS = require("./notification-providers/amootsms");
|
||||
const SMSPlanet = require("./notification-providers/sms-planet");
|
||||
const SpugPush = require("./notification-providers/spugpush");
|
||||
const SMSIR = require("./notification-providers/smsir");
|
||||
const { commandExists } = require("./util-server");
|
||||
const { commandExists } = require("./util");
|
||||
const Whatsapp360messenger = require("./notification-providers/360messenger");
|
||||
const Webpush = require("./notification-providers/Webpush");
|
||||
const HaloPSA = require("./notification-providers/HaloPSA");
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
let express = require("express");
|
||||
const {
|
||||
allowDevAllOrigin,
|
||||
allowAllOrigin,
|
||||
percentageToColor,
|
||||
filterAndJoin,
|
||||
sendHttpError,
|
||||
} = require("../util-server");
|
||||
const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, sendHttpError } = require("../util");
|
||||
const { R } = require("redbean-node");
|
||||
const apicache = require("../modules/apicache");
|
||||
const Monitor = require("../model/monitor");
|
||||
|
||||
@@ -5,8 +5,7 @@ import { R } from "redbean-node";
|
||||
import { log } from "../../src/util";
|
||||
|
||||
// @ts-ignore
|
||||
import { allowDevOrigin } from "../util-server.js";
|
||||
import { generalErrorResponse } from "../util2";
|
||||
import { allowDevOrigin, generalErrorResponse } from "../util";
|
||||
|
||||
let processingSetup = false;
|
||||
let _hasUser = false;
|
||||
|
||||
@@ -2,7 +2,7 @@ let express = require("express");
|
||||
const apicache = require("../modules/apicache");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const StatusPage = require("../model/status_page");
|
||||
const { allowDevAllOrigin, sendHttpError } = require("../util-server");
|
||||
const { allowDevAllOrigin, sendHttpError } = require("../util");
|
||||
const { R } = require("redbean-node");
|
||||
const { badgeConstants } = require("../../src/util");
|
||||
const { makeBadge } = require("badge-maker");
|
||||
|
||||
+11
-14
@@ -3,6 +3,16 @@
|
||||
* node "server/server.js"
|
||||
* DO NOT require("./server") in other modules, it likely creates circular dependency!
|
||||
*/
|
||||
import {
|
||||
initDayjs,
|
||||
getSettings,
|
||||
setSettings,
|
||||
setting,
|
||||
checkLogin,
|
||||
allowDevAllOrigin,
|
||||
printServerUrls,
|
||||
allowDevOrigin,
|
||||
} from "./util";
|
||||
import { getRandomInt, isDevEnv, log, sleep } from "../src/util";
|
||||
import { auth, createHeaders, doubleCheckPassword, getDisableAuthSession, getSessionFromHeaders } from "./better-auth";
|
||||
import { createBetterAuthRouter, needSetup } from "./routers/better-auth-router";
|
||||
@@ -14,11 +24,7 @@ import * as fs from "node:fs";
|
||||
|
||||
console.log("Welcome to Uptime Kuma");
|
||||
|
||||
// As the log function need to use dayjs, it should be very top
|
||||
const dayjs = require("dayjs");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("./modules/dayjs/plugin/timezone"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
initDayjs();
|
||||
|
||||
// Load environment variables from `.env`
|
||||
try {
|
||||
@@ -119,15 +125,6 @@ log.debug("server", "Importing Monitor");
|
||||
const Monitor = require("./model/monitor");
|
||||
|
||||
log.debug("server", "Importing Settings");
|
||||
const {
|
||||
getSettings,
|
||||
setSettings,
|
||||
setting,
|
||||
checkLogin,
|
||||
allowDevAllOrigin,
|
||||
printServerUrls,
|
||||
allowDevOrigin,
|
||||
} = require("./util-server");
|
||||
|
||||
log.debug("server", "Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
|
||||
@@ -4,7 +4,7 @@ const expressStaticGzip = require("express-static-gzip");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const Database = require("./database");
|
||||
const { allowDevAllOrigin, printServerUrls } = require("./util-server");
|
||||
const { allowDevAllOrigin, printServerUrls } = require("./util");
|
||||
const mysql = require("mysql2/promise");
|
||||
const { isSSL, sslKey, sslCert, sslKeyPassphrase } = require("./config");
|
||||
const https = require("https");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const { UptimeCalculator } = require("../uptime-calculator");
|
||||
const { log } = require("../../src/util");
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { checkLogin, setSetting, setting } = require("../util-server");
|
||||
const { checkLogin, setSetting, setting } = require("../util");
|
||||
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { log } = require("../../src/util");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const Database = require("../database");
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { sendDockerHostList } = require("../client");
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const { DockerHost } = require("../docker");
|
||||
const { log } = require("../../src/util");
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { log } = require("../../src/util");
|
||||
const { Settings } = require("../settings");
|
||||
const { sendInfo } = require("../client");
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const { games } = require("gamedig");
|
||||
const { testChrome } = require("../monitor-types/real-browser-monitor-type");
|
||||
const { getPM2ProcessList } = require("../util/pm2");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const { log } = require("../../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const apicache = require("../modules/apicache");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const { Proxy } = require("../proxy");
|
||||
const { sendProxyList } = require("../client");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { sendRemoteBrowserList } = require("../client");
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const { RemoteBrowser } = require("../remote-browser");
|
||||
|
||||
const { log } = require("../../src/util");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { R } = require("redbean-node");
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { checkLogin } = require("../util");
|
||||
const dayjs = require("dayjs");
|
||||
const { log } = require("../../src/util");
|
||||
const ImageDataURI = require("../image-data-uri");
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import dayjs from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { ZodError } from "zod";
|
||||
import { checkLogin as betterAuthCheckLogin } from "./better-auth";
|
||||
import type { Request, Response } from "express";
|
||||
import type { SASLOptions } from "kafkajs";
|
||||
import type { TokenSet } from "openid-client";
|
||||
import type { Bean } from "redbean-node";
|
||||
import type { Server, Socket } from "socket.io";
|
||||
import type { TLSSocket } from "tls";
|
||||
|
||||
const { R } = require("redbean-node");
|
||||
const { log, badgeConstants } = require("../src/util");
|
||||
@@ -16,32 +27,28 @@ const {
|
||||
rfc2865: { file, attributes },
|
||||
},
|
||||
} = require("node-radius-utils");
|
||||
const dayjs = require("dayjs");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
|
||||
// SASLOptions used in JSDoc
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { Kafka, SASLOptions } = require("kafkajs");
|
||||
const { Kafka } = require("kafkajs");
|
||||
const crypto = require("crypto");
|
||||
|
||||
/**
|
||||
* Gets an Access Token from an oidc/oauth2 provider
|
||||
* @param {string} tokenEndpoint The token URI from the auth service provider
|
||||
* @param {string} clientId The oidc/oauth application client id
|
||||
* @param {string} clientSecret The oidc/oauth application client secret
|
||||
* @param {string} scope The scope(s) for which the token should be issued for
|
||||
* @param {string} audience The audience for which the token should be issued for
|
||||
* @param {string} authMethod The method used to send the credentials. Default client_secret_basic
|
||||
* @returns {Promise<oidc.TokenSet>} TokenSet promise if the token request was successful
|
||||
* @param tokenEndpoint The token URI from the auth service provider
|
||||
* @param clientId The oidc/oauth application client id
|
||||
* @param clientSecret The oidc/oauth application client secret
|
||||
* @param scope The scope(s) for which the token should be issued for
|
||||
* @param audience The audience for which the token should be issued for
|
||||
* @param authMethod The method used to send the credentials. Default client_secret_basic
|
||||
* @returns TokenSet promise if the token request was successful
|
||||
*/
|
||||
exports.getOidcTokenClientCredentials = async (
|
||||
tokenEndpoint,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
authMethod = "client_secret_basic"
|
||||
) => {
|
||||
export async function getOidcTokenClientCredentials(
|
||||
tokenEndpoint: string,
|
||||
clientId: string,
|
||||
clientSecret: string,
|
||||
scope: string,
|
||||
audience: string,
|
||||
authMethod: string = "client_secret_basic"
|
||||
): Promise<TokenSet> {
|
||||
const oauthProvider = new oidc.Issuer({ token_endpoint: tokenEndpoint });
|
||||
let client = new oauthProvider.Client({
|
||||
client_id: clientId,
|
||||
@@ -62,23 +69,29 @@ exports.getOidcTokenClientCredentials = async (
|
||||
grantParams.audience = audience;
|
||||
}
|
||||
return await client.grant(grantParams);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor Kafka using Producer
|
||||
* @param {string[]} brokers List of kafka brokers to connect, host and
|
||||
* @param brokers List of kafka brokers to connect, host and
|
||||
* port joined by ':'
|
||||
* @param {string} topic Topic name to produce into
|
||||
* @param {string} message Message to produce
|
||||
* @param {object} options Kafka client options. Contains ssl, clientId,
|
||||
* @param topic Topic name to produce into
|
||||
* @param message Message to produce
|
||||
* @param options Kafka client options. Contains ssl, clientId,
|
||||
* allowAutoTopicCreation and interval (interval defaults to 20,
|
||||
* allowAutoTopicCreation defaults to false, clientId defaults to
|
||||
* "Uptime-Kuma" and ssl defaults to false)
|
||||
* @param {SASLOptions} saslOptions Options for kafka client
|
||||
* @param saslOptions Options for kafka client
|
||||
* Authentication (SASL) (defaults to {})
|
||||
* @returns {Promise<string>} Status message
|
||||
* @returns Status message
|
||||
*/
|
||||
exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) {
|
||||
export function kafkaProducerAsync(
|
||||
brokers: string[],
|
||||
topic: string,
|
||||
message: string,
|
||||
options: object = {},
|
||||
saslOptions: SASLOptions = {}
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {
|
||||
interval = 20,
|
||||
@@ -168,15 +181,15 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Use NTLM Auth for a http request.
|
||||
* @param {object} options The http request options
|
||||
* @param {object} ntlmOptions The auth options
|
||||
* @returns {Promise<(string[] | object[] | object)>} NTLM response
|
||||
* @param options The http request options
|
||||
* @param ntlmOptions The auth options
|
||||
* @returns NTLM response
|
||||
*/
|
||||
exports.httpNtlm = function (options, ntlmOptions) {
|
||||
export function httpNtlm(options: object, ntlmOptions: object): Promise<string[] | object[] | object> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let client = NtlmClient(ntlmOptions);
|
||||
|
||||
@@ -188,30 +201,30 @@ exports.httpNtlm = function (options, ntlmOptions) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Query radius server
|
||||
* @param {string} hostname Hostname of radius server
|
||||
* @param {string} username Username to use
|
||||
* @param {string} password Password to use
|
||||
* @param {string} calledStationId ID of called station
|
||||
* @param {string} callingStationId ID of calling station
|
||||
* @param {string} secret Secret to use
|
||||
* @param {number} port Port to contact radius server on
|
||||
* @param {number} timeout Timeout for connection to use
|
||||
* @returns {Promise<any>} Response from server
|
||||
* @param hostname Hostname of radius server
|
||||
* @param username Username to use
|
||||
* @param password Password to use
|
||||
* @param calledStationId ID of called station
|
||||
* @param callingStationId ID of calling station
|
||||
* @param secret Secret to use
|
||||
* @param port Port to contact radius server on
|
||||
* @param timeout Timeout for connection to use
|
||||
* @returns Response from server
|
||||
*/
|
||||
exports.radius = function (
|
||||
hostname,
|
||||
username,
|
||||
password,
|
||||
calledStationId,
|
||||
callingStationId,
|
||||
secret,
|
||||
port = 1812,
|
||||
timeout = 2500
|
||||
) {
|
||||
export function radius(
|
||||
hostname: string,
|
||||
username: string,
|
||||
password: string,
|
||||
calledStationId: string,
|
||||
callingStationId: string,
|
||||
secret: string,
|
||||
port: number = 1812,
|
||||
timeout: number = 2500
|
||||
): Promise<{ code: string }> {
|
||||
const client = new RadiusClient({
|
||||
host: hostname,
|
||||
hostPort: port,
|
||||
@@ -247,47 +260,47 @@ exports.radius = function (
|
||||
throw enhancedError;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve value of setting based on key
|
||||
* @param {string} key Key of setting to retrieve
|
||||
* @returns {Promise<any>} Value
|
||||
* @param key Key of setting to retrieve
|
||||
* @returns Value
|
||||
* @deprecated Use await Settings.get(key)
|
||||
*/
|
||||
exports.setting = async function (key) {
|
||||
export async function setting(key: string): Promise<Bean | null> {
|
||||
return await Settings.get(key);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified setting to specified value
|
||||
* @param {string} key Key of setting to set
|
||||
* @param {any} value Value to set to
|
||||
* @param {?string} type Type of setting
|
||||
* @returns {Promise<void>}
|
||||
* @param key Key of setting to set
|
||||
* @param value Value to set to
|
||||
* @param type Type of setting
|
||||
* @returns
|
||||
*/
|
||||
exports.setSetting = async function (key, value, type = null) {
|
||||
export async function setSetting(key: string, value: unknown, type: string | null = null): Promise<void> {
|
||||
await Settings.set(key, value, type);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings based on type
|
||||
* @param {string} type The type of setting
|
||||
* @returns {Promise<Bean>} Settings of requested type
|
||||
* @param type The type of setting
|
||||
* @returns Settings of requested type
|
||||
*/
|
||||
exports.getSettings = async function (type) {
|
||||
export async function getSettings(type: string): Promise<Record<string, unknown>> {
|
||||
return await Settings.getSettings(type);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set settings based on type
|
||||
* @param {string} type Type of settings to set
|
||||
* @param {object} data Values of settings
|
||||
* @returns {Promise<void>}
|
||||
* @param type Type of settings to set
|
||||
* @param data Values of settings
|
||||
* @returns
|
||||
*/
|
||||
exports.setSettings = async function (type, data) {
|
||||
export async function setSettings(type: string, data: object): Promise<void> {
|
||||
await Settings.setSettings(type, data);
|
||||
};
|
||||
}
|
||||
|
||||
// ssl-checker by @dyaa
|
||||
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
||||
@@ -302,18 +315,17 @@ const getDaysBetween = (validFrom, validTo) => Math.round(Math.abs(+validFrom -
|
||||
|
||||
/**
|
||||
* Get days remaining from a time range
|
||||
* @param {Date} validFrom Start date
|
||||
* @param {Date} validTo End date
|
||||
* @returns {number} Number of days remaining
|
||||
* @param validFrom Start date
|
||||
* @param validTo End date
|
||||
* @returns Number of days remaining
|
||||
*/
|
||||
const getDaysRemaining = (validFrom, validTo) => {
|
||||
export function getDaysRemaining(validFrom: Date, validTo: Date): number {
|
||||
const daysRemaining = getDaysBetween(validFrom, validTo);
|
||||
if (new Date(validTo).getTime() < new Date().getTime()) {
|
||||
return -daysRemaining;
|
||||
}
|
||||
return daysRemaining;
|
||||
};
|
||||
module.exports.getDaysRemaining = getDaysRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix certificate info for display
|
||||
@@ -366,10 +378,10 @@ const parseCertificateInfo = function (info) {
|
||||
|
||||
/**
|
||||
* Check if certificate is valid
|
||||
* @param {tls.TLSSocket} socket TLSSocket, which may or may not be connected
|
||||
* @returns {null | {valid: boolean, certInfo: object}} Object containing certificate information
|
||||
* @param socket TLSSocket, which may or may not be connected
|
||||
* @returns} Object containing certificate information
|
||||
*/
|
||||
exports.checkCertificate = function (socket) {
|
||||
export function checkCertificate(socket: TLSSocket): null | { valid: boolean; certInfo: object } {
|
||||
let certInfoStartTime = dayjs().valueOf();
|
||||
|
||||
// Return null if there is no socket
|
||||
@@ -391,16 +403,16 @@ exports.checkCertificate = function (socket) {
|
||||
valid: valid,
|
||||
certInfo: parsedInfo,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the certificate is valid for the provided hostname.
|
||||
* Defaults to true if feature `X509Certificate` is not available, or input is not valid.
|
||||
* @param {Buffer} certBuffer - The certificate buffer.
|
||||
* @param {string} hostname - The hostname to compare against.
|
||||
* @returns {boolean} True if the certificate is valid for the provided hostname, false otherwise.
|
||||
* @param certBuffer - The certificate buffer.
|
||||
* @param hostname - The hostname to compare against.
|
||||
* @returns True if the certificate is valid for the provided hostname, false otherwise.
|
||||
*/
|
||||
exports.checkCertificateHostname = function (certBuffer, hostname) {
|
||||
export function checkCertificateHostname(certBuffer: Buffer, hostname: string): boolean {
|
||||
let X509Certificate;
|
||||
try {
|
||||
X509Certificate = require("node:crypto").X509Certificate;
|
||||
@@ -415,15 +427,15 @@ exports.checkCertificateHostname = function (certBuffer, hostname) {
|
||||
|
||||
let certObject = new X509Certificate(certBuffer);
|
||||
return certObject.checkHost(hostname) !== undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided status code is within the accepted ranges
|
||||
* @param {number} status The status code to check
|
||||
* @param {string[]} acceptedCodes An array of accepted status codes
|
||||
* @returns {boolean} True if status code within range, false otherwise
|
||||
* @param status The status code to check
|
||||
* @param acceptedCodes An array of accepted status codes
|
||||
* @returns True if status code within range, false otherwise
|
||||
*/
|
||||
exports.checkStatusCode = function (status, acceptedCodes) {
|
||||
export function checkStatusCode(status: number, acceptedCodes: string[]): boolean {
|
||||
if (acceptedCodes == null || acceptedCodes.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -450,15 +462,15 @@ exports.checkStatusCode = function (status, acceptedCodes) {
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total number of clients in room
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {string} roomName Name of room to check
|
||||
* @returns {number} Total clients in room
|
||||
* @param io Socket server instance
|
||||
* @param roomName Name of room to check
|
||||
* @returns Total clients in room
|
||||
*/
|
||||
exports.getTotalClientInRoom = (io, roomName) => {
|
||||
export function getTotalClientInRoom(io: Server, roomName: string): number {
|
||||
const sockets = io.sockets;
|
||||
|
||||
if (!sockets) {
|
||||
@@ -478,104 +490,104 @@ exports.getTotalClientInRoom = (io, roomName) => {
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use allowDevOrigin
|
||||
* Allow CORS all origins if development
|
||||
* @param {object} res Response object from axios
|
||||
* @returns {void}
|
||||
* @param res Response object from axios
|
||||
* @returns
|
||||
*/
|
||||
exports.allowDevAllOrigin = (res) => {
|
||||
export function allowDevAllOrigin(res: object): void {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
exports.allowAllOrigin(res);
|
||||
allowAllOrigin(res);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use allowOrigin
|
||||
* Allow CORS all origins
|
||||
* @param {object} res Response object from axios
|
||||
* @returns {void}
|
||||
* @param res Response object from axios
|
||||
* @returns
|
||||
*/
|
||||
exports.allowAllOrigin = (res) => {
|
||||
export function allowAllOrigin(res: object): void {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow CORS all origins if development
|
||||
* @param {Request} req Express request object
|
||||
* @param {Response} res Express response object
|
||||
* @returns {void}
|
||||
* @param req Express request object
|
||||
* @param res Express response object
|
||||
* @returns
|
||||
*/
|
||||
exports.allowDevOrigin = (req, res) => {
|
||||
export function allowDevOrigin(req: Request, res: Response): void {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
exports.allowOrigin(req, res);
|
||||
allowOrigin(req, res);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow CORS all origins
|
||||
* Since Allow-Credentials is set to true, the Access-Control-Allow-Origin cannot be *, so we will set it to the request origin.
|
||||
* @param {Request} req Express request object
|
||||
* @param {Response} res Response object
|
||||
* @returns {void}
|
||||
* @param req Express request object
|
||||
* @param res Response object
|
||||
* @returns
|
||||
*/
|
||||
exports.allowOrigin = (req, res) => {
|
||||
export function allowOrigin(req: Request, res: Response): void {
|
||||
res.header("Access-Control-Allow-Origin", req.get("origin"));
|
||||
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
res.header("Access-Control-Allow-Credentials", "true");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use better-auth's checkLogin
|
||||
* Check if a user is logged in
|
||||
* @param {Socket} socket Socket instance
|
||||
* @returns {void}
|
||||
* @param socket Socket instance
|
||||
* @returns
|
||||
* @throws The user is not logged in
|
||||
*/
|
||||
exports.checkLogin = (socket) => {
|
||||
export function checkLogin(socket: Socket): void {
|
||||
betterAuthCheckLogin(socket);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color code in hex format based on a given percentage:
|
||||
* 0% => hue = 10 => red
|
||||
* 100% => hue = 90 => green
|
||||
* @param {number} percentage float, 0 to 1
|
||||
* @param {number} maxHue Maximum hue - int
|
||||
* @param {number} minHue Minimum hue - int
|
||||
* @returns {string} Color in hex
|
||||
* @param percentage float, 0 to 1
|
||||
* @param maxHue Maximum hue - int
|
||||
* @param minHue Minimum hue - int
|
||||
* @returns Color in hex
|
||||
*/
|
||||
exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
|
||||
export function percentageToColor(percentage: number, maxHue: number = 90, minHue: number = 10): string {
|
||||
const hue = percentage * (maxHue - minHue) + minHue;
|
||||
try {
|
||||
return chroma(`hsl(${hue}, 90%, 40%)`).hex();
|
||||
} catch (err) {
|
||||
return badgeConstants.naColor;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins and array of string to one string after filtering out empty values
|
||||
* @param {string[]} parts Strings to join
|
||||
* @param {string} connector Separator for joined strings
|
||||
* @returns {string} Joined strings
|
||||
* @param parts Strings to join
|
||||
* @param connector Separator for joined strings
|
||||
* @returns Joined strings
|
||||
*/
|
||||
exports.filterAndJoin = (parts, connector = "") => {
|
||||
export function filterAndJoin(parts: string[], connector: string = ""): string {
|
||||
return parts.filter((part) => !!part && part !== "").join(connector);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an Error response
|
||||
* @param {object} res Express response object
|
||||
* @param {string} msg Message to send
|
||||
* @returns {void}
|
||||
* @param res Express response object
|
||||
* @param msg Message to send
|
||||
* @returns
|
||||
*/
|
||||
module.exports.sendHttpError = (res, msg = "") => {
|
||||
export function sendHttpError(res: object, msg: string = ""): void {
|
||||
if (msg.includes("SQLITE_BUSY") || msg.includes("SQLITE_LOCKED")) {
|
||||
res.status(503).json({
|
||||
status: "fail",
|
||||
@@ -592,16 +604,16 @@ module.exports.sendHttpError = (res, msg = "") => {
|
||||
msg: msg,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert timezone of time object
|
||||
* @param {object} obj Time object to update
|
||||
* @param {string} timezone New timezone to set
|
||||
* @param {boolean} timeObjectToUTC Convert time object to UTC
|
||||
* @returns {object} Time object with updated timezone
|
||||
* @param obj Time object to update
|
||||
* @param timezone New timezone to set
|
||||
* @param timeObjectToUTC Convert time object to UTC
|
||||
* @returns Time object with updated timezone
|
||||
*/
|
||||
function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
|
||||
function timeObjectConvertTimezone(obj: object, timezone: string, timeObjectToUTC: boolean = true): object {
|
||||
let offsetString;
|
||||
|
||||
if (timezone) {
|
||||
@@ -641,29 +653,29 @@ function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
|
||||
|
||||
/**
|
||||
* Convert time object to UTC
|
||||
* @param {object} obj Object to convert
|
||||
* @param {string} timezone Timezone of time object
|
||||
* @returns {object} Updated time object
|
||||
* @param obj Object to convert
|
||||
* @param timezone Timezone of time object
|
||||
* @returns Updated time object
|
||||
*/
|
||||
module.exports.timeObjectToUTC = (obj, timezone = undefined) => {
|
||||
export function timeObjectToUTC(obj: object, timezone: string = undefined): object {
|
||||
return timeObjectConvertTimezone(obj, timezone, true);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert time object to local time
|
||||
* @param {object} obj Object to convert
|
||||
* @param {string} timezone Timezone to convert to
|
||||
* @returns {object} Updated object
|
||||
* @param obj Object to convert
|
||||
* @param timezone Timezone to convert to
|
||||
* @returns Updated object
|
||||
*/
|
||||
module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
|
||||
export function timeObjectToLocal(obj: object, timezone: string = undefined): object {
|
||||
return timeObjectConvertTimezone(obj, timezone, false);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of SHA256 fingerprints for all known root certificates.
|
||||
* @returns {Set} A set of SHA256 fingerprints.
|
||||
* @returns A set of SHA256 fingerprints.
|
||||
*/
|
||||
module.exports.rootCertificatesFingerprints = () => {
|
||||
export function rootCertificatesFingerprints(): Set<unknown> {
|
||||
let fingerprints = tls.rootCertificates.map((cert) => {
|
||||
let certLines = cert.split("\n");
|
||||
certLines.shift();
|
||||
@@ -688,48 +700,44 @@ module.exports.rootCertificatesFingerprints = () => {
|
||||
); // ISRG X2 cross-signed with ISRG X1
|
||||
|
||||
return new Set(fingerprints);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.SHAKE256_LENGTH = 16;
|
||||
export const SHAKE256_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* @param {string} data The data to be hashed
|
||||
* @param {number} len Output length of the hash
|
||||
* @returns {string} The hashed data in hex format
|
||||
* @param data The data to be hashed
|
||||
* @param len Output length of the hash
|
||||
* @returns The hashed data in hex format
|
||||
*/
|
||||
module.exports.shake256 = (data, len) => {
|
||||
export function shake256(data: string, len: number): string {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
return crypto.createHash("shake256", { outputLength: len }).update(data).digest("hex");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Non await sleep
|
||||
* Source: https://stackoverflow.com/questions/59099454/is-there-a-way-to-call-sleep-without-await-keyword
|
||||
* @param {number} n Milliseconds to wait
|
||||
* @returns {void}
|
||||
* @param n Milliseconds to wait
|
||||
* @returns
|
||||
*/
|
||||
module.exports.wait = (n) => {
|
||||
export function wait(n: number): void {
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
|
||||
};
|
||||
}
|
||||
|
||||
// For unit test, export functions
|
||||
if (process.env.TEST_BACKEND) {
|
||||
module.exports.__test = {
|
||||
parseCertificateInfo,
|
||||
};
|
||||
module.exports.__getPrivateFunction = (functionName) => {
|
||||
return module.exports.__test[functionName];
|
||||
};
|
||||
export const __test = process.env.TEST_BACKEND ? { parseCertificateInfo } : undefined;
|
||||
export function __getPrivateFunction(functionName) {
|
||||
return __test?.[functionName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an abort signal with the specified timeout.
|
||||
* @param {number} timeoutMs - The timeout in milliseconds.
|
||||
* @returns {AbortSignal | null} - The generated abort signal, or null if not supported.
|
||||
* @param timeoutMs - The timeout in milliseconds.
|
||||
* @returns - The generated abort signal, or null if not supported.
|
||||
*/
|
||||
module.exports.axiosAbortSignal = (timeoutMs) => {
|
||||
export function axiosAbortSignal(timeoutMs: number): AbortSignal | null {
|
||||
try {
|
||||
// Just in case, as 0 timeout here will cause the request to be aborted immediately
|
||||
if (!timeoutMs || timeoutMs <= 0) {
|
||||
@@ -748,41 +756,39 @@ module.exports.axiosAbortSignal = (timeoutMs) => {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Async version of fs.existsSync
|
||||
* @param {PathLike} path File path
|
||||
* @returns {Promise<boolean>} True if file exists, false otherwise
|
||||
* @param path File path
|
||||
* @returns True if file exists, false otherwise
|
||||
*/
|
||||
function fsExists(path) {
|
||||
export function fsExists(path: string | Buffer): Promise<boolean> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
exists(path, function (exists) {
|
||||
resolve(exists);
|
||||
});
|
||||
});
|
||||
}
|
||||
module.exports.fsExists = fsExists;
|
||||
|
||||
/**
|
||||
* Encode user and password to Base64 encoding
|
||||
* for HTTP "basic" auth, as per RFC-7617
|
||||
* @param {string|null} user - The username (defaults to empty string if null/undefined)
|
||||
* @param {string|null} pass - The password (defaults to empty string if null/undefined)
|
||||
* @returns {string} Encoded Base64 string
|
||||
* @param user - The username (defaults to empty string if null/undefined)
|
||||
* @param pass - The password (defaults to empty string if null/undefined)
|
||||
* @returns Encoded Base64 string
|
||||
*/
|
||||
function encodeBase64(user, pass) {
|
||||
export function encodeBase64(user: string | null, pass: string | null): string {
|
||||
return Buffer.from(`${user || ""}:${pass || ""}`).toString("base64");
|
||||
}
|
||||
module.exports.encodeBase64 = encodeBase64;
|
||||
|
||||
/**
|
||||
* checks certificate chain for expiring certificates
|
||||
* @param {object} monitor - The monitor object
|
||||
* @param {object} tlsInfoObject Information about certificate
|
||||
* @returns {Promise<void>}
|
||||
* @param monitor - The monitor object
|
||||
* @param tlsInfoObject Information about certificate
|
||||
* @returns
|
||||
*/
|
||||
async function checkCertExpiryNotifications(monitor, tlsInfoObject) {
|
||||
export async function checkCertExpiryNotifications(monitor: object, tlsInfoObject: object): Promise<void> {
|
||||
if (!tlsInfoObject || !tlsInfoObject.certInfo || !tlsInfoObject.certInfo.daysRemaining) {
|
||||
return;
|
||||
}
|
||||
@@ -837,15 +843,14 @@ async function checkCertExpiryNotifications(monitor, tlsInfoObject) {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports.checkCertExpiryNotifications = checkCertExpiryNotifications;
|
||||
|
||||
/**
|
||||
* By default, command-exists will throw a null error if the command does not exist, which is ugly. The function makes it better.
|
||||
* Read more: https://github.com/mathisonian/command-exists/issues/22
|
||||
* @param {string} command Command to check
|
||||
* @returns {Promise<boolean>} True if command exists, false otherwise
|
||||
* @param command Command to check
|
||||
* @returns True if command exists, false otherwise
|
||||
*/
|
||||
async function commandExists(command) {
|
||||
export async function commandExists(command: string): Promise<boolean> {
|
||||
try {
|
||||
await require("command-exists")(command);
|
||||
return true;
|
||||
@@ -853,19 +858,18 @@ async function commandExists(command) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
module.exports.commandExists = commandExists;
|
||||
|
||||
/**
|
||||
* Log the server's listening URLs, similar to Vite's dev server output.
|
||||
* When no hostname is specified (bound to all interfaces), it prints
|
||||
* localhost plus every non-internal network address.
|
||||
* @param {string} tag Log tag (e.g. "server", "setup-database")
|
||||
* @param {number} port Port number
|
||||
* @param {string} hostname Bound hostname, if any
|
||||
* @param {boolean} isHTTPS Whether the server is using HTTPS
|
||||
* @returns {void}
|
||||
* @param tag Log tag (e.g. "server", "setup-database")
|
||||
* @param port Port number
|
||||
* @param hostname Bound hostname, if any
|
||||
* @param isHTTPS Whether the server is using HTTPS
|
||||
* @returns
|
||||
*/
|
||||
module.exports.printServerUrls = (tag, port, hostname, isHTTPS = false) => {
|
||||
export function printServerUrls(tag: string, port: number, hostname: string, isHTTPS: boolean = false): void {
|
||||
try {
|
||||
// If hostname is specified, just print that one.
|
||||
if (hostname) {
|
||||
@@ -909,19 +913,58 @@ module.exports.printServerUrls = (tag, port, hostname, isHTTPS = false) => {
|
||||
} catch (e) {
|
||||
log.error(tag, "Error printing server URLs: " + e.message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a URL a bit more safely
|
||||
* @param {boolean} isHTTPS Whether the URL should use HTTPS protocol
|
||||
* @param {string} hostname The hostname to use in the URL
|
||||
* @param {number} port The port
|
||||
* @returns {string} The constructed URL as a string
|
||||
* @param isHTTPS Whether the URL should use HTTPS protocol
|
||||
* @param hostname The hostname to use in the URL
|
||||
* @param port The port
|
||||
* @returns The constructed URL as a string
|
||||
*/
|
||||
function createURL(isHTTPS, hostname, port = 80) {
|
||||
function createURL(isHTTPS: boolean, hostname: string, port: number = 80): string {
|
||||
const url = new URL((isHTTPS ? "https" : "http") + `://` + hostname);
|
||||
url.port = String(port);
|
||||
|
||||
// Prefer origin if available, it doesn't contain the trailing slash
|
||||
return url.origin || url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend dayjs with the plugins the backend uses
|
||||
* @returns The shared dayjs instance
|
||||
*/
|
||||
export function initDayjs(): object {
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(customParseFormat);
|
||||
return dayjs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param res Express response
|
||||
* @param e Any error
|
||||
* @returns
|
||||
*/
|
||||
export function generalErrorResponse(res: Response, e: unknown): void {
|
||||
if (e instanceof ZodError) {
|
||||
let message = "";
|
||||
for (const issue of e.issues) {
|
||||
message += `${issue.path.join(".")}: ${issue.message}\n`;
|
||||
}
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
msg: message,
|
||||
});
|
||||
} else if (e instanceof Error) {
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
msg: "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ZodError } from "zod";
|
||||
import { Response } from "express";
|
||||
|
||||
/**
|
||||
* @param res Express response
|
||||
* @param e Any error
|
||||
*/
|
||||
export function generalErrorResponse(res: Response, e: unknown) {
|
||||
if (e instanceof ZodError) {
|
||||
let message = "";
|
||||
for (const issue of e.issues) {
|
||||
message += `${issue.path.join(".")}: ${issue.message}\n`;
|
||||
}
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
msg: message,
|
||||
});
|
||||
} else if (e instanceof Error) {
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
msg: "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require("express");
|
||||
const http = require("node:http");
|
||||
const { printServerUrls } = require("../util-server");
|
||||
const { printServerUrls } = require("../util");
|
||||
|
||||
/**
|
||||
* SimpleMigrationServer
|
||||
|
||||
+3
-8
@@ -16,14 +16,9 @@ import theme from "./mixins/theme";
|
||||
import lang from "./mixins/lang";
|
||||
import { router } from "./router";
|
||||
import { appName } from "./util.ts";
|
||||
import dayjs from "dayjs";
|
||||
import timezone from "./modules/dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import { loadToastSettings } from "./util-frontend";
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(relativeTime);
|
||||
import { initDayjs, loadToastSettings } from "./util-frontend";
|
||||
|
||||
initDayjs();
|
||||
|
||||
const app = createApp({
|
||||
mixins: [socket, theme, mobile, datetime, publicMixin, lang],
|
||||
|
||||
@@ -6,7 +6,7 @@ import dayjs from "dayjs";
|
||||
import mitt from "mitt";
|
||||
|
||||
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
|
||||
import { getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend.js";
|
||||
import { getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend";
|
||||
import { BeatVersionThrottle } from "../beat-version-throttle";
|
||||
import { applyBeat, applyLatestBeats, beatStatusEntry } from "../beat-state";
|
||||
import { logout as betterAuthLogout } from "../auth-client";
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export let SECONDS_A_MINUTE = 60;
|
||||
export let SECONDS_A_HOUR = SECONDS_A_MINUTE * 60;
|
||||
export let SECONDS_A_DAY = SECONDS_A_HOUR * 24;
|
||||
export let SECONDS_A_WEEK = SECONDS_A_DAY * 7;
|
||||
export let MILLISECONDS_A_SECOND = 1e3;
|
||||
export let MILLISECONDS_A_MINUTE = SECONDS_A_MINUTE * MILLISECONDS_A_SECOND;
|
||||
export let MILLISECONDS_A_HOUR = SECONDS_A_HOUR * MILLISECONDS_A_SECOND;
|
||||
export let MILLISECONDS_A_DAY = SECONDS_A_DAY * MILLISECONDS_A_SECOND;
|
||||
export let MILLISECONDS_A_WEEK = SECONDS_A_WEEK * MILLISECONDS_A_SECOND; // English locales
|
||||
|
||||
export let MS = "millisecond";
|
||||
export let S = "second";
|
||||
export let MIN = "minute";
|
||||
export let H = "hour";
|
||||
export let D = "day";
|
||||
export let W = "week";
|
||||
export let M = "month";
|
||||
export let Q = "quarter";
|
||||
export let Y = "year";
|
||||
export let DATE = "date";
|
||||
export let FORMAT_DEFAULT = "YYYY-MM-DDTHH:mm:ssZ";
|
||||
export let INVALID_DATE_STRING = "Invalid Date"; // regex
|
||||
|
||||
export let REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/;
|
||||
export let REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { PluginFunc, ConfigType } from "dayjs/esm";
|
||||
|
||||
declare const plugin: PluginFunc;
|
||||
export = plugin;
|
||||
|
||||
declare module "dayjs/esm" {
|
||||
interface Dayjs {
|
||||
tz(timezone?: string, keepLocalTime?: boolean): Dayjs;
|
||||
offsetName(type?: "short" | "long"): string | undefined;
|
||||
}
|
||||
|
||||
interface DayjsTimezone {
|
||||
(date: ConfigType, timezone?: string): Dayjs;
|
||||
(date: ConfigType, format: string, timezone?: string): Dayjs;
|
||||
guess(): string;
|
||||
setDefault(timezone?: string): void;
|
||||
}
|
||||
|
||||
const tz: DayjsTimezone;
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/**
|
||||
* Copy from node_modules/dayjs/plugin/timezone.js
|
||||
* Try to fix https://github.com/louislam/uptime-kuma/issues/2318
|
||||
* Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc
|
||||
* License: MIT
|
||||
*/
|
||||
import { MIN, MS } from "../../constant";
|
||||
let typeToPos = {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 2,
|
||||
hour: 3,
|
||||
minute: 4,
|
||||
second: 5,
|
||||
}; // Cache time-zone lookups from Intl.DateTimeFormat,
|
||||
// as it is a *very* slow method.
|
||||
|
||||
let dtfCache = {};
|
||||
|
||||
let getDateTimeFormat = function getDateTimeFormat(timezone, options) {
|
||||
if (options === void 0) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
let timeZoneName = options.timeZoneName || "short";
|
||||
let key = timezone + "|" + timeZoneName;
|
||||
let dtf = dtfCache[key];
|
||||
|
||||
if (!dtf) {
|
||||
dtf = new Intl.DateTimeFormat("en-US", {
|
||||
hour12: false,
|
||||
timeZone: timezone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZoneName: timeZoneName,
|
||||
});
|
||||
dtfCache[key] = dtf;
|
||||
}
|
||||
|
||||
return dtf;
|
||||
};
|
||||
|
||||
export default (function (o, c, d) {
|
||||
let defaultTimezone;
|
||||
|
||||
let makeFormatParts = function makeFormatParts(timestamp, timezone, options) {
|
||||
if (options === void 0) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
let date = new Date(timestamp);
|
||||
let dtf = getDateTimeFormat(timezone, options);
|
||||
return dtf.formatToParts(date);
|
||||
};
|
||||
|
||||
let tzOffset = function tzOffset(timestamp, timezone) {
|
||||
let formatResult = makeFormatParts(timestamp, timezone);
|
||||
let filled = [];
|
||||
|
||||
for (let i = 0; i < formatResult.length; i += 1) {
|
||||
let _formatResult$i = formatResult[i];
|
||||
let type = _formatResult$i.type;
|
||||
let value = _formatResult$i.value;
|
||||
let pos = typeToPos[type];
|
||||
|
||||
if (pos >= 0) {
|
||||
filled[pos] = parseInt(value, 10);
|
||||
}
|
||||
}
|
||||
|
||||
let hour = filled[3]; // Workaround for the same behavior in different node version
|
||||
// https://github.com/nodejs/node/issues/33027
|
||||
|
||||
/* istanbul ignore next */
|
||||
|
||||
let fixedHour = hour === 24 ? 0 : hour;
|
||||
let utcString =
|
||||
filled[0] +
|
||||
"-" +
|
||||
filled[1] +
|
||||
"-" +
|
||||
filled[2] +
|
||||
" " +
|
||||
fixedHour +
|
||||
":" +
|
||||
filled[4] +
|
||||
":" +
|
||||
filled[5] +
|
||||
":000";
|
||||
let utcTs = d.utc(utcString).valueOf();
|
||||
let asTS = +timestamp;
|
||||
let over = asTS % 1000;
|
||||
asTS -= over;
|
||||
return (utcTs - asTS) / (60 * 1000);
|
||||
}; // find the right offset a given local time. The o input is our guess, which determines which
|
||||
// offset we'll pick in ambiguous cases (e.g. there are two 3 AMs b/c Fallback DST)
|
||||
// https://github.com/moment/luxon/blob/master/src/datetime.js#L76
|
||||
|
||||
let fixOffset = function fixOffset(localTS, o0, tz) {
|
||||
// Our UTC time is just a guess because our offset is just a guess
|
||||
let utcGuess = localTS - o0 * 60 * 1000; // Test whether the zone matches the offset for this ts
|
||||
|
||||
let o2 = tzOffset(utcGuess, tz); // If so, offset didn't change and we're done
|
||||
|
||||
if (o0 === o2) {
|
||||
return [utcGuess, o0];
|
||||
} // If not, change the ts by the difference in the offset
|
||||
|
||||
utcGuess -= (o2 - o0) * 60 * 1000; // If that gives us the local time we want, we're done
|
||||
|
||||
let o3 = tzOffset(utcGuess, tz);
|
||||
|
||||
if (o2 === o3) {
|
||||
return [utcGuess, o2];
|
||||
} // If it's different, we're in a hole time.
|
||||
// The offset has changed, but the we don't adjust the time
|
||||
|
||||
return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3)];
|
||||
};
|
||||
|
||||
let proto = c.prototype;
|
||||
|
||||
proto.tz = function (timezone, keepLocalTime) {
|
||||
if (timezone === void 0) {
|
||||
timezone = defaultTimezone;
|
||||
}
|
||||
|
||||
let oldOffset = this.utcOffset();
|
||||
let date = this.toDate();
|
||||
let target = date
|
||||
.toLocaleString("en-US", {
|
||||
timeZone: timezone,
|
||||
})
|
||||
.replace("\u202f", " ");
|
||||
let diff = Math.round((date - new Date(target)) / 1000 / 60);
|
||||
let ins = d(target)
|
||||
.$set(MS, this.$ms)
|
||||
.utcOffset(-Math.round(date.getTimezoneOffset() / 15) * 15 - diff, true);
|
||||
|
||||
if (keepLocalTime) {
|
||||
let newOffset = ins.utcOffset();
|
||||
ins = ins.add(oldOffset - newOffset, MIN);
|
||||
}
|
||||
|
||||
ins.$x.$timezone = timezone;
|
||||
return ins;
|
||||
};
|
||||
|
||||
proto.offsetName = function (type) {
|
||||
// type: short(default) / long
|
||||
let zone = this.$x.$timezone || d.tz.guess();
|
||||
let result = makeFormatParts(this.valueOf(), zone, {
|
||||
timeZoneName: type,
|
||||
}).find(function (m) {
|
||||
return m.type.toLowerCase() === "timezonename";
|
||||
});
|
||||
return result && result.value;
|
||||
};
|
||||
|
||||
let oldStartOf = proto.startOf;
|
||||
|
||||
proto.startOf = function (units, startOf) {
|
||||
if (!this.$x || !this.$x.$timezone) {
|
||||
return oldStartOf.call(this, units, startOf);
|
||||
}
|
||||
|
||||
let withoutTz = d(this.format("YYYY-MM-DD HH:mm:ss:SSS"));
|
||||
let startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf);
|
||||
return startOfWithoutTz.tz(this.$x.$timezone, true);
|
||||
};
|
||||
|
||||
d.tz = function (input, arg1, arg2) {
|
||||
let parseFormat = arg2 && arg1;
|
||||
let timezone = arg2 || arg1 || defaultTimezone;
|
||||
let previousOffset = tzOffset(+d(), timezone);
|
||||
|
||||
if (typeof input !== "string") {
|
||||
// timestamp number || js Date || Day.js
|
||||
return d(input).tz(timezone);
|
||||
}
|
||||
|
||||
let localTs = d.utc(input, parseFormat).valueOf();
|
||||
|
||||
let _fixOffset = fixOffset(localTs, previousOffset, timezone);
|
||||
let targetTs = _fixOffset[0];
|
||||
let targetOffset = _fixOffset[1];
|
||||
|
||||
let ins = d(targetTs).utcOffset(targetOffset);
|
||||
ins.$x.$timezone = timezone;
|
||||
return ins;
|
||||
};
|
||||
|
||||
d.tz.guess = function () {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
};
|
||||
|
||||
d.tz.setDefault = function (timezone) {
|
||||
defaultTimezone = timezone;
|
||||
};
|
||||
});
|
||||
@@ -1,17 +1,43 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { getTimeZones } from "@vvo/tzdb";
|
||||
import { localeDirection, currentLocale } from "./i18n";
|
||||
import { POSITION } from "vue-toastification";
|
||||
import { baseURL } from "./auth-client";
|
||||
|
||||
interface TimezoneOption {
|
||||
name: string;
|
||||
value: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface ToastPluginOptions {
|
||||
position: string;
|
||||
containerClassName: string;
|
||||
showCloseButtonOnHover: boolean;
|
||||
filterBeforeCreate: (toast: { timeout?: number }, toasts: unknown[]) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend dayjs with the plugins the frontend uses
|
||||
* @returns The shared dayjs instance
|
||||
*/
|
||||
export function initDayjs(): Dayjs {
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(relativeTime);
|
||||
return dayjs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset from UTC in hours for the current locale.
|
||||
* @param {string} timeZone Timezone to get offset for
|
||||
* @returns {number} The offset from UTC in hours.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* @param timeZone Timezone to get offset for
|
||||
* @returns The offset from UTC in hours.
|
||||
*/
|
||||
function getTimezoneOffset(timeZone) {
|
||||
function getTimezoneOffset(timeZone: string): number {
|
||||
const now = new Date();
|
||||
const tzString = now.toLocaleString("en-US", {
|
||||
timeZone,
|
||||
@@ -24,24 +50,22 @@ function getTimezoneOffset(timeZone) {
|
||||
|
||||
/**
|
||||
* Returns a list of timezones sorted by their offset from UTC.
|
||||
* @returns {object[]} A list of the given timezones sorted by their offset from UTC.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* @returns A list of the given timezones sorted by their offset from UTC.
|
||||
*/
|
||||
export function timezoneList() {
|
||||
let result = [];
|
||||
export function timezoneList(): TimezoneOption[] {
|
||||
const result: TimezoneOption[] = [];
|
||||
const timeZones = getTimeZones();
|
||||
|
||||
for (let timezone of timeZones) {
|
||||
for (const timezone of timeZones) {
|
||||
try {
|
||||
let display = dayjs().tz(timezone.name).format("Z");
|
||||
const display = dayjs().tz(timezone.name).format("Z");
|
||||
|
||||
result.push({
|
||||
name: `(UTC${display}) ${timezone.name}`,
|
||||
value: timezone.name,
|
||||
time: getTimezoneOffset(timezone.name),
|
||||
});
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Skipping not supported timezone.name by dayjs
|
||||
}
|
||||
}
|
||||
@@ -63,9 +87,9 @@ export function timezoneList() {
|
||||
|
||||
/**
|
||||
* Set the locale of the HTML page
|
||||
* @returns {void}
|
||||
* @returns No return value.
|
||||
*/
|
||||
export function setPageLocale() {
|
||||
export function setPageLocale(): void {
|
||||
const html = document.documentElement;
|
||||
html.setAttribute("lang", currentLocale());
|
||||
html.setAttribute("dir", localeDirection());
|
||||
@@ -74,19 +98,19 @@ export function setPageLocale() {
|
||||
/**
|
||||
* Get the base URL
|
||||
* Mainly used for dev, because the backend and the frontend are in different ports.
|
||||
* @returns {string} Base URL
|
||||
* @returns Base URL
|
||||
*/
|
||||
export function getResBaseURL() {
|
||||
export function getResBaseURL(): string {
|
||||
return baseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tag color options
|
||||
* Shared between components
|
||||
* @param {any} self Component
|
||||
* @returns {object[]} Colour options
|
||||
* @param self Component
|
||||
* @returns Colour options
|
||||
*/
|
||||
export function colorOptions(self) {
|
||||
export function colorOptions(self: any): { name: string; color: string }[] {
|
||||
return [
|
||||
{ name: self.$t("Gray"), color: "#4B5563" },
|
||||
{ name: self.$t("Red"), color: "#DC2626" },
|
||||
@@ -101,9 +125,9 @@ export function colorOptions(self) {
|
||||
|
||||
/**
|
||||
* Loads the toast timeout settings from storage.
|
||||
* @returns {object} The toast plugin options object.
|
||||
* @returns The toast plugin options object.
|
||||
*/
|
||||
export function loadToastSettings() {
|
||||
export function loadToastSettings(): ToastPluginOptions {
|
||||
return {
|
||||
position: POSITION.BOTTOM_RIGHT,
|
||||
containerClassName: "toast-container mb-5",
|
||||
@@ -121,13 +145,14 @@ export function loadToastSettings() {
|
||||
|
||||
/**
|
||||
* Get timeout for success toasts
|
||||
* @returns {(number|boolean)} Timeout in ms. If false timeout disabled.
|
||||
* @returns Timeout in ms. If false timeout disabled.
|
||||
*/
|
||||
export function getToastSuccessTimeout() {
|
||||
let successTimeout = 20000;
|
||||
export function getToastSuccessTimeout(): number | false {
|
||||
let successTimeout: number | false = 20000;
|
||||
const storedTimeout = (localStorage as any).toastSuccessTimeout;
|
||||
|
||||
if (localStorage.toastSuccessTimeout !== undefined) {
|
||||
const parsedTimeout = parseInt(localStorage.toastSuccessTimeout);
|
||||
if (storedTimeout !== undefined) {
|
||||
const parsedTimeout = parseInt(storedTimeout);
|
||||
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||
successTimeout = parsedTimeout;
|
||||
}
|
||||
@@ -142,13 +167,14 @@ export function getToastSuccessTimeout() {
|
||||
|
||||
/**
|
||||
* Get timeout for error toasts
|
||||
* @returns {(number|boolean)} Timeout in ms. If false timeout disabled.
|
||||
* @returns Timeout in ms. If false timeout disabled.
|
||||
*/
|
||||
export function getToastErrorTimeout() {
|
||||
let errorTimeout = -1;
|
||||
export function getToastErrorTimeout(): number | false {
|
||||
let errorTimeout: number | false = -1;
|
||||
const storedTimeout = (localStorage as any).toastErrorTimeout;
|
||||
|
||||
if (localStorage.toastErrorTimeout !== undefined) {
|
||||
const parsedTimeout = parseInt(localStorage.toastErrorTimeout);
|
||||
if (storedTimeout !== undefined) {
|
||||
const parsedTimeout = parseInt(storedTimeout);
|
||||
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||
errorTimeout = parsedTimeout;
|
||||
}
|
||||
@@ -162,14 +188,22 @@ export function getToastErrorTimeout() {
|
||||
}
|
||||
|
||||
class TimeDurationFormatter {
|
||||
private durationFormatOptions: { style: string };
|
||||
private relativeTimeFormatOptions: { numeric: string };
|
||||
private durationFormatInstance?: { format: (duration: object) => string };
|
||||
private relativeTimeFormatInstance?: Intl.RelativeTimeFormat;
|
||||
|
||||
/**
|
||||
* Default locale and options for Time Duration Formatter (supports both DurationFormat and RelativeTimeFormat)
|
||||
*/
|
||||
constructor() {
|
||||
this.durationFormatOptions = { style: "long" };
|
||||
this.relativeTimeFormatOptions = { numeric: "always" };
|
||||
if (Intl.DurationFormat !== undefined) {
|
||||
this.durationFormatInstance = new Intl.DurationFormat(currentLocale(), this.durationFormatOptions);
|
||||
|
||||
const DurationFormat = (Intl as any).DurationFormat;
|
||||
|
||||
if (DurationFormat !== undefined) {
|
||||
this.durationFormatInstance = new DurationFormat(currentLocale(), this.durationFormatOptions);
|
||||
} else {
|
||||
this.relativeTimeFormatInstance = new Intl.RelativeTimeFormat(
|
||||
currentLocale(),
|
||||
@@ -180,12 +214,14 @@ class TimeDurationFormatter {
|
||||
|
||||
/**
|
||||
* Method to update the instance locale and options
|
||||
* @param {string} locale Localization identifier (e.g., "en", "ar-sy") to update the instance with.
|
||||
* @returns {void} No return value.
|
||||
* @param locale Localization identifier (e.g., "en", "ar-sy") to update the instance with.
|
||||
* @returns No return value.
|
||||
*/
|
||||
updateLocale(locale) {
|
||||
if (Intl.DurationFormat !== undefined) {
|
||||
this.durationFormatInstance = new Intl.DurationFormat(locale, this.durationFormatOptions);
|
||||
updateLocale(locale: string): void {
|
||||
const DurationFormat = (Intl as any).DurationFormat;
|
||||
|
||||
if (DurationFormat !== undefined) {
|
||||
this.durationFormatInstance = new DurationFormat(locale, this.durationFormatOptions);
|
||||
} else {
|
||||
this.relativeTimeFormatInstance = new Intl.RelativeTimeFormat(locale, this.relativeTimeFormatOptions);
|
||||
}
|
||||
@@ -193,10 +229,10 @@ class TimeDurationFormatter {
|
||||
|
||||
/**
|
||||
* Method to convert seconds into Human readable format
|
||||
* @param {number} seconds Receive value in seconds.
|
||||
* @returns {string} String converted to Days Mins Seconds Format
|
||||
* @param seconds Receive value in seconds.
|
||||
* @returns String converted to Days Mins Seconds Format
|
||||
*/
|
||||
secondsToHumanReadableFormat(seconds) {
|
||||
secondsToHumanReadableFormat(seconds: number): string {
|
||||
const days = Math.floor(seconds / 86400);
|
||||
const hours = Math.floor((seconds % 86400) / 3600);
|
||||
const minutes = Math.floor(((seconds % 86400) % 3600) / 60);
|
||||
@@ -212,18 +248,18 @@ class TimeDurationFormatter {
|
||||
});
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
const parts: string[] = [];
|
||||
/**
|
||||
* Build the formatted string from parts
|
||||
* 1. Get the relative time formatted parts from the instance.
|
||||
* 2. Filter out the relevant parts literal (unit of time) or integer (value).
|
||||
* 3. Map out the required values.
|
||||
* @param {number} value Receives value in seconds.
|
||||
* @param {string} unitOfTime Expected unit of time after conversion.
|
||||
* @returns {void}
|
||||
* @param value Receives value in seconds.
|
||||
* @param unitOfTime Expected unit of time after conversion.
|
||||
* @returns No return value.
|
||||
*/
|
||||
const toFormattedPart = (value, unitOfTime) => {
|
||||
const partsArray = this.relativeTimeFormatInstance.formatToParts(value, unitOfTime);
|
||||
const toFormattedPart = (value: number, unitOfTime: Intl.RelativeTimeFormatUnit) => {
|
||||
const partsArray = this.relativeTimeFormatInstance!.formatToParts(value, unitOfTime);
|
||||
const filteredParts = partsArray
|
||||
.filter((part, index) => part.type === "integer" || (part.type === "literal" && index > 0))
|
||||
.map((part) => part.value);
|
||||
@@ -248,7 +284,7 @@ class TimeDurationFormatter {
|
||||
if (parts.length > 0) {
|
||||
return `${parts.join(" ")}`;
|
||||
}
|
||||
return this.relativeTimeFormatInstance.format(0, "second"); // Handle case for 0 seconds
|
||||
return this.relativeTimeFormatInstance!.format(0, "second"); // Handle case for 0 seconds
|
||||
}
|
||||
}
|
||||
|
||||
+1
-14
@@ -3,14 +3,7 @@
|
||||
// Common Util for frontend and backend
|
||||
*/
|
||||
|
||||
import dayjsFrontend from "dayjs";
|
||||
|
||||
// For dayjs plugins' type checking, don't remove event though it is not used in this file
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import * as timezone from "dayjs/plugin/timezone";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import * as utc from "dayjs/plugin/utc";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import jsonata from "jsonata";
|
||||
|
||||
/**
|
||||
@@ -23,12 +16,6 @@ export function isDevEnv() {
|
||||
|
||||
export const isNode = typeof process !== "undefined" && process?.versions?.node;
|
||||
|
||||
/**
|
||||
* Smarter dayjs import that supports both frontend and backend
|
||||
* @returns {dayjs.Dayjs} dayjs instance
|
||||
*/
|
||||
const dayjs = isNode ? require("dayjs") : dayjsFrontend;
|
||||
|
||||
export const devOriginList = [
|
||||
"http://127.0.0.1:3000",
|
||||
"http://127.0.0.1:3001",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { kafkaProducerAsync } = require("../../../server/util-server");
|
||||
const { kafkaProducerAsync } = require("../../../server/util");
|
||||
|
||||
describe("Kafka Producer", () => {
|
||||
test("rejects when broker is not reachable", async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ const { describe, test } = require("node:test");
|
||||
|
||||
const assert = require("node:assert");
|
||||
|
||||
const { checkCertificateHostname } = require("../../server/util-server");
|
||||
const { checkCertificateHostname } = require("../../server/util");
|
||||
|
||||
const testCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
|
||||
@@ -8,10 +8,9 @@ const TestDB = require("../mock-testdb");
|
||||
const { R } = require("redbean-node");
|
||||
const { Notification } = require("../../server/notification");
|
||||
const { Settings } = require("../../server/settings");
|
||||
const { setSetting } = require("../../server/util-server");
|
||||
const { setSetting } = require("../../server/util");
|
||||
const dayjs = require("dayjs");
|
||||
const { retryExternalService } = require("./test-util");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
|
||||
const testDb = new TestDB("./data/test-domain");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { describe, test, mock } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { encodeBase64 } = require("../../server/util-server");
|
||||
const { encodeBase64 } = require("../../server/util");
|
||||
const { UP, PENDING } = require("../../src/util");
|
||||
|
||||
describe("GlobalpingMonitorType", () => {
|
||||
|
||||
@@ -4,9 +4,6 @@ import { createRequire } from "node:module";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
dayjs.extend(require("../../server/modules/dayjs/plugin/timezone"));
|
||||
|
||||
const Maintenance = require("../../server/model/maintenance");
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const dgram = require("dgram");
|
||||
const radius = require("radius");
|
||||
const { radius: radiusCheck } = require("../../server/util-server");
|
||||
const { radius: radiusCheck } = require("../../server/util");
|
||||
|
||||
/**
|
||||
* Start a UDP RADIUS server that replies to every Access-Request with the given response code.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const { describe, test, mock } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const StatusPage = require("../../server/model/status_page");
|
||||
const dayjs = require("dayjs");
|
||||
const utc = require("dayjs/plugin/utc");
|
||||
const {
|
||||
STATUS_PAGE_ALL_UP,
|
||||
STATUS_PAGE_ALL_DOWN,
|
||||
@@ -10,8 +8,6 @@ const {
|
||||
STATUS_PAGE_MAINTENANCE,
|
||||
} = require("../../src/util");
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
describe("StatusPage", () => {
|
||||
describe("getStatusDescription()", () => {
|
||||
test("returns 'No Services' when status is -1", () => {
|
||||
|
||||
@@ -7,7 +7,6 @@ const { UptimeCalculator } = require("../../server/uptime-calculator");
|
||||
const { R } = require("redbean-node");
|
||||
const dayjs = require("dayjs");
|
||||
const { UP } = require("../../src/util");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
|
||||
const originals = {
|
||||
getAll: R.getAll,
|
||||
|
||||
@@ -2,10 +2,8 @@ const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { UptimeCalculator } = require("../../server/uptime-calculator");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
const { UP, DOWN, PENDING, MAINTENANCE } = require("../../src/util");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("../../server/modules/dayjs/plugin/timezone"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
|
||||
/**
|
||||
* Create a calculator whose bucket loading is stubbed, so update() does not query the database
|
||||
|
||||
@@ -33,9 +33,6 @@ async function retryExternalService(testFn, maxAttempts = 5) {
|
||||
|
||||
module.exports = { retryExternalService };
|
||||
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
|
||||
describe("Server Utilities", () => {
|
||||
test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => {
|
||||
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { initDayjs } from "../server/util";
|
||||
|
||||
initDayjs();
|
||||
Reference in new issue
Block a user