chore: cleanup messy code (#8085)

This commit is contained in:
Louis Lam authored and GitHub committed 2026-10-31 01:22:53 +08:00
1 parent 73a50f9ce0
commit 9bdac74a88
61 files changed
+406 -786

No files matched your search

+7
View File
@@ -119,5 +119,12 @@ module.exports = {
eqeqeq: "off",
},
},
// Backend: no `any`
{
files: ["server/**/*.ts"],
rules: {
"@typescript-eslint/no-explicit-any": "error",
},
},
],
};
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import { getTotalClientInRoom } from "./util-server";
import { getTotalClientInRoom } from "./util";
/**
* Room every logged-in socket joins; v3 has a single dashboard.
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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
View File
@@ -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;
}
-130
View File
@@ -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 -1
View File
@@ -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 {
+1 -1
View File
@@ -8,7 +8,7 @@ const {
encodeBase64,
getDaysRemaining,
checkCertExpiryNotifications,
} = require("../util-server");
} = require("../util");
const { R } = require("redbean-node");
/**
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 {
+1 -1
View File
@@ -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 -7
View File
@@ -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");
+1 -2
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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");
+1 -1
View File
@@ -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");
+250 -207
View File
@@ -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",
});
}
}
-29
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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],
+1 -1
View File
@@ -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";
-25
View File
@@ -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
View File
@@ -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;
}
-204
View File
@@ -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;
};
});
+85 -49
View File
@@ -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
View File
@@ -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-----
+1 -2
View File
@@ -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 -1
View File
@@ -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", () => {
-3
View File
@@ -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");
+1 -1
View File
@@ -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.
-4
View File
@@ -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,
+1 -3
View File
@@ -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
-3
View File
@@ -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");
+3
View File
@@ -0,0 +1,3 @@
import { initDayjs } from "../server/util";
initDayjs();