Merge branch 'louislam:master' into master

This commit is contained in:
Mohit Nagaraj 2024-10-11 14:02:19 +05:30 committed by GitHub
commit e868ebb68e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 423 additions and 337 deletions

View file

@ -1,6 +1,6 @@
# Project Info # Project Info
First of all, I want to thank everyone who have wrote issues or shared pull requests for Uptime Kuma. First of all, I want to thank everyone who has submitted issues or shared pull requests for Uptime Kuma.
I never thought the GitHub community would be so nice! I never thought the GitHub community would be so nice!
Because of this, I also never thought that other people would actually read and edit my code. Because of this, I also never thought that other people would actually read and edit my code.
Parts of the code are not very well-structured or commented, sorry about that. Parts of the code are not very well-structured or commented, sorry about that.
@ -9,7 +9,7 @@ The project was created with `vite.js` and is written in `vue3`.
Our backend lives in the `server`-directory and mostly communicates via websockets. Our backend lives in the `server`-directory and mostly communicates via websockets.
Both frontend and backend share the same `package.json`. Both frontend and backend share the same `package.json`.
For production, the frontend is build into `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint. For production, the frontend is built into the `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
For development, we run vite in development mode on another port. For development, we run vite in development mode on another port.
## Directories ## Directories
@ -28,7 +28,7 @@ For development, we run vite in development mode on another port.
## Can I create a pull request for Uptime Kuma? ## Can I create a pull request for Uptime Kuma?
Yes or no, it depends on what you will try to do. Yes or no, it depends on what you will try to do.
Both your and our maintainers time is precious, and we don't want to waste both time. Both yours and our maintainers' time is precious, and we don't want to waste either.
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^ If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
@ -49,11 +49,11 @@ Different guidelines exist for different types of pull requests (PRs):
<p> <p>
If you come across a bug and think you can solve, we appreciate your work. If you come across a bug and think you can solve, we appreciate your work.
Please make sure that you follow by these rules: Please make sure that you follow these rules:
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable - keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
- test that your code does what you came it does. - test that your code does what you claim it does.
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub> <sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
</p> </p>
</details> </details>
- <details><summary><b>translations / internationalisation (i18n)</b></summary> - <details><summary><b>translations / internationalisation (i18n)</b></summary>
@ -68,7 +68,7 @@ Different guidelines exist for different types of pull requests (PRs):
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated. - language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md) - **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub> <sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
</p> </p>
</details> </details>
- <details><summary><b>new notification providers</b></summary> - <details><summary><b>new notification providers</b></summary>
@ -102,7 +102,7 @@ Different guidelines exist for different types of pull requests (PRs):
Therefore, making sure that they work is also really important. Therefore, making sure that they work is also really important.
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider. Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
To make shure you have tested the notification provider, please include screenshots of the following events in the pull-request description: To make sure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
- `UP`/`DOWN` - `UP`/`DOWN`
- Certificate Expiry via https://expired.badssl.com/ - Certificate Expiry via https://expired.badssl.com/
- Testing (the test button on the notification provider setup page) - Testing (the test button on the notification provider setup page)
@ -117,7 +117,7 @@ Different guidelines exist for different types of pull requests (PRs):
| Testing | paste-image-here | paste-image-here | | Testing | paste-image-here | paste-image-here |
``` ```
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub> <sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
</p> </p>
</details> </details>
- <details><summary><b>new monitoring types</b></summary> - <details><summary><b>new monitoring types</b></summary>
@ -138,14 +138,14 @@ Different guidelines exist for different types of pull requests (PRs):
- -
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub> <sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
</p> </p>
</details> </details>
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary> - <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
<p> <p>
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**. be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
This is especially important for a large pull request or you don't know if it will be merged or not. This is especially important for a large pull request or when you don't know if it will be merged or not.
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub> <sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
</p> </p>
@ -201,7 +201,7 @@ The rationale behind this is that we can align the direction and scope of the fe
## Project Styles ## Project Styles
I personally do not like something that requires so many configurations before you can finally start the app. I personally do not like something that requires a lot of configuration before you can finally start the app.
The goal is to make the Uptime Kuma installation as easy as installing a mobile app. The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
- Easy to install for non-Docker users - Easy to install for non-Docker users
@ -260,7 +260,7 @@ Port `3000` and port `3001` will be used.
npm run dev npm run dev
``` ```
But sometimes, you would like to restart the server, but not the frontend, you can run these commands in two terminals: But sometimes you may want to restart the server without restarting the frontend. In that case, you can run these commands in two terminals:
```bash ```bash
npm run start-frontend-dev npm run start-frontend-dev
@ -409,7 +409,7 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
### What is a maintainer and what are their roles? ### What is a maintainer and what are their roles?
This project has multiple maintainers which specialise in different areas. This project has multiple maintainers who specialise in different areas.
Currently, there are 3 maintainers: Currently, there are 3 maintainers:
| Person | Role | Main Area | | Person | Role | Main Area |

View file

@ -1,6 +1,7 @@
const basicAuth = require("express-basic-auth"); const basicAuth = require("express-basic-auth");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setting } = require("./util-server");
const { log } = require("../src/util"); const { log } = require("../src/util");
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter"); const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
const { Settings } = require("./settings"); const { Settings } = require("./settings");
@ -138,7 +139,7 @@ exports.basicAuth = async function (req, res, next) {
challenge: true, challenge: true,
}); });
const disabledAuth = await Settings.get("disableAuth"); const disabledAuth = await setting("disableAuth");
if (!disabledAuth) { if (!disabledAuth) {
middleware(req, res, next); middleware(req, res, next);

View file

@ -1,7 +1,7 @@
const { setSetting, setting } = require("./util-server");
const axios = require("axios"); const axios = require("axios");
const compareVersions = require("compare-versions"); const compareVersions = require("compare-versions");
const { log } = require("../src/util"); const { log } = require("../src/util");
const { Settings } = require("./settings");
exports.version = require("../package.json").version; exports.version = require("../package.json").version;
exports.latestVersion = null; exports.latestVersion = null;
@ -14,7 +14,7 @@ let interval;
exports.startInterval = () => { exports.startInterval = () => {
let check = async () => { let check = async () => {
if (await Settings.get("checkUpdate") === false) { if (await setting("checkUpdate") === false) {
return; return;
} }
@ -28,7 +28,7 @@ exports.startInterval = () => {
res.data.slow = "1000.0.0"; res.data.slow = "1000.0.0";
} }
let checkBeta = await Settings.get("checkBeta"); let checkBeta = await setting("checkBeta");
if (checkBeta && res.data.beta) { if (checkBeta && res.data.beta) {
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) { if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
@ -57,7 +57,7 @@ exports.startInterval = () => {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
exports.enableCheckUpdate = async (value) => { exports.enableCheckUpdate = async (value) => {
await Settings.set("checkUpdate", value); await setSetting("checkUpdate", value);
clearInterval(interval); clearInterval(interval);

View file

@ -6,8 +6,8 @@ const { R } = require("redbean-node");
const { UptimeKumaServer } = require("./uptime-kuma-server"); const { UptimeKumaServer } = require("./uptime-kuma-server");
const server = UptimeKumaServer.getInstance(); const server = UptimeKumaServer.getInstance();
const io = server.io; const io = server.io;
const { setting } = require("./util-server");
const checkVersion = require("./check-version"); const checkVersion = require("./check-version");
const { Settings } = require("./settings");
const Database = require("./database"); const Database = require("./database");
/** /**
@ -158,8 +158,8 @@ async function sendInfo(socket, hideVersion = false) {
version, version,
latestVersion, latestVersion,
isContainer, isContainer,
primaryBaseURL: await Settings.get("primaryBaseURL"),
dbType, dbType,
primaryBaseURL: await setting("primaryBaseURL"),
serverTimezone: await server.getTimezone(), serverTimezone: await server.getTimezone(),
serverTimezoneOffset: server.getTimezoneOffset(), serverTimezoneOffset: server.getTimezoneOffset(),
}); });

View file

@ -1,11 +1,11 @@
const fs = require("fs"); const fs = require("fs");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setSetting, setting } = require("./util-server");
const { log, sleep } = require("../src/util"); const { log, sleep } = require("../src/util");
const knex = require("knex"); const knex = require("knex");
const path = require("path"); const path = require("path");
const { EmbeddedMariaDB } = require("./embedded-mariadb"); const { EmbeddedMariaDB } = require("./embedded-mariadb");
const mysql = require("mysql2/promise"); const mysql = require("mysql2/promise");
const { Settings } = require("./settings");
/** /**
* Database & App Data Folder * Database & App Data Folder
@ -420,7 +420,7 @@ class Database {
* @deprecated * @deprecated
*/ */
static async patchSqlite() { static async patchSqlite() {
let version = parseInt(await Settings.get("database_version")); let version = parseInt(await setting("database_version"));
if (! version) { if (! version) {
version = 0; version = 0;
@ -445,7 +445,7 @@ class Database {
log.info("db", `Patching ${sqlFile}`); log.info("db", `Patching ${sqlFile}`);
await Database.importSQLFile(sqlFile); await Database.importSQLFile(sqlFile);
log.info("db", `Patched ${sqlFile}`); log.info("db", `Patched ${sqlFile}`);
await Settings.set("database_version", i); await setSetting("database_version", i);
} }
} catch (ex) { } catch (ex) {
await Database.close(); await Database.close();
@ -471,7 +471,7 @@ class Database {
*/ */
static async patchSqlite2() { static async patchSqlite2() {
log.debug("db", "Database Patch 2.0 Process"); log.debug("db", "Database Patch 2.0 Process");
let databasePatchedFiles = await Settings.get("databasePatchedFiles"); let databasePatchedFiles = await setting("databasePatchedFiles");
if (! databasePatchedFiles) { if (! databasePatchedFiles) {
databasePatchedFiles = {}; databasePatchedFiles = {};
@ -499,7 +499,7 @@ class Database {
process.exit(1); process.exit(1);
} }
await Settings.set("databasePatchedFiles", databasePatchedFiles); await setSetting("databasePatchedFiles", databasePatchedFiles);
} }
/** /**
@ -512,27 +512,27 @@ class Database {
// Fix 1.13.0 empty slug bug // Fix 1.13.0 empty slug bug
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''"); await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
let title = await Settings.get("title"); let title = await setting("title");
if (title) { if (title) {
log.info("database", "Migrating Status Page"); console.log("Migrating Status Page");
let statusPageCheck = await R.findOne("status_page", " slug = 'default' "); let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
if (statusPageCheck !== null) { if (statusPageCheck !== null) {
log.info("database", "Migrating Status Page - Skip, default slug record is already existing"); console.log("Migrating Status Page - Skip, default slug record is already existing");
return; return;
} }
let statusPage = R.dispense("status_page"); let statusPage = R.dispense("status_page");
statusPage.slug = "default"; statusPage.slug = "default";
statusPage.title = title; statusPage.title = title;
statusPage.description = await Settings.get("description"); statusPage.description = await setting("description");
statusPage.icon = await Settings.get("icon"); statusPage.icon = await setting("icon");
statusPage.theme = await Settings.get("statusPageTheme"); statusPage.theme = await setting("statusPageTheme");
statusPage.published = !!await Settings.get("statusPagePublished"); statusPage.published = !!await setting("statusPagePublished");
statusPage.search_engine_index = !!await Settings.get("searchEngineIndex"); statusPage.search_engine_index = !!await setting("searchEngineIndex");
statusPage.show_tags = !!await Settings.get("statusPageTags"); statusPage.show_tags = !!await setting("statusPageTags");
statusPage.password = null; statusPage.password = null;
if (!statusPage.title) { if (!statusPage.title) {
@ -560,13 +560,13 @@ class Database {
await R.exec("DELETE FROM setting WHERE type = 'statusPage'"); await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
// Migrate Entry Page if it is status page // Migrate Entry Page if it is status page
let entryPage = await Settings.get("entryPage"); let entryPage = await setting("entryPage");
if (entryPage === "statusPage") { if (entryPage === "statusPage") {
await Settings.set("entryPage", "statusPage-default", "general"); await setSetting("entryPage", "statusPage-default", "general");
} }
log.info("database", "Migrating Status Page - Done"); console.log("Migrating Status Page - Done");
} }
} }

View file

@ -1,7 +1,7 @@
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { log } = require("../../src/util"); const { log } = require("../../src/util");
const { setSetting, setting } = require("../util-server");
const Database = require("../database"); const Database = require("../database");
const { Settings } = require("../settings");
const DEFAULT_KEEP_PERIOD = 180; const DEFAULT_KEEP_PERIOD = 180;
@ -11,11 +11,11 @@ const DEFAULT_KEEP_PERIOD = 180;
*/ */
const clearOldData = async () => { const clearOldData = async () => {
let period = await Settings.get("keepDataPeriodDays"); let period = await setting("keepDataPeriodDays");
// Set Default Period // Set Default Period
if (period == null) { if (period == null) {
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general"); await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
period = DEFAULT_KEEP_PERIOD; period = DEFAULT_KEEP_PERIOD;
} }
@ -25,7 +25,7 @@ const clearOldData = async () => {
parsedPeriod = parseInt(period); parsedPeriod = parseInt(period);
} catch (_) { } catch (_) {
log.warn("clearOldData", "Failed to parse setting, resetting to default.."); log.warn("clearOldData", "Failed to parse setting, resetting to default..");
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general"); await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
parsedPeriod = DEFAULT_KEEP_PERIOD; parsedPeriod = DEFAULT_KEEP_PERIOD;
} }

View file

@ -4,7 +4,7 @@ const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT, evaluateJsonQuery SQL_DATETIME_FORMAT, evaluateJsonQuery
} = require("../../src/util"); } = require("../../src/util");
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, mssqlQuery, postgresQuery, mysqlQuery, httpNtlm, radius, grpcQuery, const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
} = require("../util-server"); } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
@ -24,7 +24,6 @@ const { CookieJar } = require("tough-cookie");
const { HttpsCookieAgent } = require("http-cookie-agent/http"); const { HttpsCookieAgent } = require("http-cookie-agent/http");
const https = require("https"); const https = require("https");
const http = require("http"); const http = require("http");
const { Settings } = require("../settings");
const rootCertificates = rootCertificatesFingerprints(); const rootCertificates = rootCertificatesFingerprints();
@ -326,7 +325,7 @@ class Monitor extends BeanModel {
let previousBeat = null; let previousBeat = null;
let retries = 0; let retries = 0;
this.prometheus = await Prometheus.createAndInitMetrics(this); this.prometheus = new Prometheus(this);
const beat = async () => { const beat = async () => {
@ -652,7 +651,7 @@ class Monitor extends BeanModel {
} else if (this.type === "steam") { } else if (this.type === "steam") {
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/"; const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
const steamAPIKey = await Settings.get("steamAPIKey"); const steamAPIKey = await setting("steamAPIKey");
const filter = `addr\\${this.hostname}:${this.port}`; const filter = `addr\\${this.hostname}:${this.port}`;
if (!steamAPIKey) { if (!steamAPIKey) {
@ -978,7 +977,7 @@ class Monitor extends BeanModel {
await R.store(bean); await R.store(bean);
log.debug("monitor", `[${this.name}] prometheus.update`); log.debug("monitor", `[${this.name}] prometheus.update`);
await this.prometheus?.update(bean, tlsInfo); this.prometheus?.update(bean, tlsInfo);
previousBeat = bean; previousBeat = bean;
@ -1374,12 +1373,11 @@ class Monitor extends BeanModel {
return; return;
} }
let notifyDays = await Settings.get("tlsExpiryNotifyDays"); let notifyDays = await setting("tlsExpiryNotifyDays");
if (notifyDays == null || !Array.isArray(notifyDays)) { if (notifyDays == null || !Array.isArray(notifyDays)) {
// Reset Default // Reset Default
await Settings.set("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general"); await setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
notifyDays = [ 7, 14, 21 ]; notifyDays = [ 7, 14, 21 ];
await Settings.set("tlsExpiryNotifyDays", notifyDays, "general");
} }
if (Array.isArray(notifyDays)) { if (Array.isArray(notifyDays)) {

View file

@ -1,6 +1,5 @@
let url = require("url"); let url = require("url");
let MemoryCache = require("./memory-cache"); let MemoryCache = require("./memory-cache");
const { log } = require("../../../src/util");
let t = { let t = {
ms: 1, ms: 1,
@ -91,6 +90,24 @@ function ApiCache() {
instances.push(this); instances.push(this);
this.id = instances.length; this.id = instances.length;
/**
* Logs a message to the console if the `DEBUG` environment variable is set.
* @param {string} a The first argument to log.
* @param {string} b The second argument to log.
* @param {string} c The third argument to log.
* @param {string} d The fourth argument to log, and so on... (optional)
*
* Generated by Trelent
*/
function debug(a, b, c, d) {
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
return arg !== undefined;
});
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
}
/** /**
* Returns true if the given request and response should be logged. * Returns true if the given request and response should be logged.
* @param {Object} request The HTTP request object. * @param {Object} request The HTTP request object.
@ -129,7 +146,7 @@ function ApiCache() {
let groupName = req.apicacheGroup; let groupName = req.apicacheGroup;
if (groupName) { if (groupName) {
log.debug("apicache", `group detected "${groupName}"`); debug("group detected \"" + groupName + "\"");
let group = (index.groups[groupName] = index.groups[groupName] || []); let group = (index.groups[groupName] = index.groups[groupName] || []);
group.unshift(key); group.unshift(key);
} }
@ -195,7 +212,7 @@ function ApiCache() {
redis.hset(key, "duration", duration); redis.hset(key, "duration", duration);
redis.expire(key, duration / 1000, expireCallback || function () {}); redis.expire(key, duration / 1000, expireCallback || function () {});
} catch (err) { } catch (err) {
log.debug("apicache", `error in redis.hset(): ${err}`); debug("[apicache] error in redis.hset()");
} }
} else { } else {
memCache.add(key, value, duration, expireCallback); memCache.add(key, value, duration, expireCallback);
@ -303,10 +320,10 @@ function ApiCache() {
// display log entry // display log entry
let elapsed = new Date() - req.apicacheTimer; let elapsed = new Date() - req.apicacheTimer;
log.debug("apicache", `adding cache entry for "${key}" @ ${strDuration} ${logDuration(elapsed)}`); debug("adding cache entry for \"" + key + "\" @ " + strDuration, logDuration(elapsed));
log.debug("apicache", `_apicache.headers: ${JSON.stringify(res._apicache.headers)}`); debug("_apicache.headers: ", res._apicache.headers);
log.debug("apicache", `res.getHeaders(): ${JSON.stringify(getSafeHeaders(res))}`); debug("res.getHeaders(): ", getSafeHeaders(res));
log.debug("apicache", `cacheObject: ${JSON.stringify(cacheObject)}`); debug("cacheObject: ", cacheObject);
} }
} }
@ -385,10 +402,10 @@ function ApiCache() {
let redis = globalOptions.redisClient; let redis = globalOptions.redisClient;
if (group) { if (group) {
log.debug("apicache", `clearing group "${target}"`); debug("clearing group \"" + target + "\"");
group.forEach(function (key) { group.forEach(function (key) {
log.debug("apicache", `clearing cached entry for "${key}"`); debug("clearing cached entry for \"" + key + "\"");
clearTimeout(timers[key]); clearTimeout(timers[key]);
delete timers[key]; delete timers[key];
if (!globalOptions.redisClient) { if (!globalOptions.redisClient) {
@ -397,7 +414,7 @@ function ApiCache() {
try { try {
redis.del(key); redis.del(key);
} catch (err) { } catch (err) {
log.info("apicache", "error in redis.del(\"" + key + "\")"); console.log("[apicache] error in redis.del(\"" + key + "\")");
} }
} }
index.all = index.all.filter(doesntMatch(key)); index.all = index.all.filter(doesntMatch(key));
@ -405,7 +422,7 @@ function ApiCache() {
delete index.groups[target]; delete index.groups[target];
} else if (target) { } else if (target) {
log.debug("apicache", `clearing ${isAutomatic ? "expired" : "cached"} entry for "${target}"`); debug("clearing " + (isAutomatic ? "expired" : "cached") + " entry for \"" + target + "\"");
clearTimeout(timers[target]); clearTimeout(timers[target]);
delete timers[target]; delete timers[target];
// clear actual cached entry // clear actual cached entry
@ -415,7 +432,7 @@ function ApiCache() {
try { try {
redis.del(target); redis.del(target);
} catch (err) { } catch (err) {
log.error("apicache", "error in redis.del(\"" + target + "\")"); console.log("[apicache] error in redis.del(\"" + target + "\")");
} }
} }
@ -432,7 +449,7 @@ function ApiCache() {
} }
}); });
} else { } else {
log.debug("apicache", "clearing entire index"); debug("clearing entire index");
if (!redis) { if (!redis) {
memCache.clear(); memCache.clear();
@ -444,7 +461,7 @@ function ApiCache() {
try { try {
redis.del(key); redis.del(key);
} catch (err) { } catch (err) {
log.error("apicache", `error in redis.del("${key}"): ${err}`); console.log("[apicache] error in redis.del(\"" + key + "\")");
} }
}); });
} }
@ -735,7 +752,7 @@ function ApiCache() {
*/ */
let cache = function (req, res, next) { let cache = function (req, res, next) {
function bypass() { function bypass() {
log.debug("apicache", "bypass detected, skipping cache."); debug("bypass detected, skipping cache.");
return next(); return next();
} }
@ -788,7 +805,7 @@ function ApiCache() {
// send if cache hit from memory-cache // send if cache hit from memory-cache
if (cached) { if (cached) {
let elapsed = new Date() - req.apicacheTimer; let elapsed = new Date() - req.apicacheTimer;
log.debug("apicache", `sending cached (memory-cache) version of ${key} ${logDuration(elapsed)}`); debug("sending cached (memory-cache) version of", key, logDuration(elapsed));
perf.hit(key); perf.hit(key);
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration); return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
@ -800,7 +817,7 @@ function ApiCache() {
redis.hgetall(key, function (err, obj) { redis.hgetall(key, function (err, obj) {
if (!err && obj && obj.response) { if (!err && obj && obj.response) {
let elapsed = new Date() - req.apicacheTimer; let elapsed = new Date() - req.apicacheTimer;
log.debug("apicache", "sending cached (redis) version of "+ key+" "+ logDuration(elapsed)); debug("sending cached (redis) version of", key, logDuration(elapsed));
perf.hit(key); perf.hit(key);
return sendCachedResponse( return sendCachedResponse(
@ -856,7 +873,7 @@ function ApiCache() {
} }
if (globalOptions.trackPerformance) { if (globalOptions.trackPerformance) {
log.debug("apicache", "WARNING: using trackPerformance flag can cause high memory usage!"); debug("WARNING: using trackPerformance flag can cause high memory usage!");
} }
return this; return this;

View file

@ -63,7 +63,7 @@ if (process.platform === "win32") {
* @returns {Promise<boolean>} The executable is allowed? * @returns {Promise<boolean>} The executable is allowed?
*/ */
async function isAllowedChromeExecutable(executablePath) { async function isAllowedChromeExecutable(executablePath) {
log.info("Chromium", config.args); console.log(config.args);
if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") { if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
return true; return true;
} }
@ -102,8 +102,7 @@ async function getBrowser() {
*/ */
async function getRemoteBrowser(remoteBrowserID, userId) { async function getRemoteBrowser(remoteBrowserID, userId) {
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId); let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
log.debug("Chromium", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`); log.debug("MONITOR", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
browser = chromium.connect(remoteBrowser.url);
browser = await chromium.connect(remoteBrowser.url); browser = await chromium.connect(remoteBrowser.url);
return browser; return browser;
} }

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util"); const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
const { Settings } = require("../settings");
class AlertNow extends NotificationProvider { class AlertNow extends NotificationProvider {
name = "AlertNow"; name = "AlertNow";
@ -29,7 +29,7 @@ class AlertNow extends NotificationProvider {
textMsg += ` - ${msg}`; textMsg += ` - ${msg}`;
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorJSON) { if (baseURL && monitorJSON) {
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`; textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
} }

View file

@ -87,7 +87,6 @@ class DingDing extends NotificationProvider {
* @returns {string} Status * @returns {string} Status
*/ */
statusToString(status) { statusToString(status) {
// TODO: Move to notification-provider.js to avoid repetition in classes
switch (status) { switch (status) {
case DOWN: case DOWN:
return "DOWN"; return "DOWN";

View file

@ -48,7 +48,7 @@ class Discord extends NotificationProvider {
}, },
{ {
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL", name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: this.extractAdress(monitorJSON), value: this.extractAddress(monitorJSON),
}, },
{ {
name: `Time (${heartbeatJSON["timezone"]})`, name: `Time (${heartbeatJSON["timezone"]})`,
@ -85,7 +85,7 @@ class Discord extends NotificationProvider {
}, },
{ {
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL", name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: this.extractAdress(monitorJSON), value: this.extractAddress(monitorJSON),
}, },
{ {
name: `Time (${heartbeatJSON["timezone"]})`, name: `Time (${heartbeatJSON["timezone"]})`,

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings"); const { setting } = require("../util-server");
const successMessage = "Sent Successfully."; const successMessage = "Sent Successfully.";
class FlashDuty extends NotificationProvider { class FlashDuty extends NotificationProvider {
@ -84,7 +84,7 @@ class FlashDuty extends NotificationProvider {
} }
}; };
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorInfo) { if (baseURL && monitorInfo) {
options.client = "Uptime Kuma"; options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id); options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util"); const { getMonitorRelativeURL, UP } = require("../../src/util");
const { Settings } = require("../settings");
class GoogleChat extends NotificationProvider { class GoogleChat extends NotificationProvider {
name = "GoogleChat"; name = "GoogleChat";
@ -45,7 +45,7 @@ class GoogleChat extends NotificationProvider {
} }
// add button for monitor link if available // add button for monitor link if available
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL) { if (baseURL) {
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/"; const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
sectionWidgets.push({ sectionWidgets.push({

View file

@ -24,7 +24,7 @@ class NotificationProvider {
* @param {?object} monitorJSON Monitor details (For Up/Down only) * @param {?object} monitorJSON Monitor details (For Up/Down only)
* @returns {string} The extracted address based on the monitor type. * @returns {string} The extracted address based on the monitor type.
*/ */
extractAdress(monitorJSON) { extractAddress(monitorJSON) {
if (!monitorJSON) { if (!monitorJSON) {
return ""; return "";
} }

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings"); const { setting } = require("../util-server");
let successMessage = "Sent Successfully."; let successMessage = "Sent Successfully.";
class PagerDuty extends NotificationProvider { class PagerDuty extends NotificationProvider {
@ -95,7 +95,7 @@ class PagerDuty extends NotificationProvider {
} }
}; };
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorInfo) { if (baseURL && monitorInfo) {
options.client = "Uptime Kuma"; options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id); options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings"); const { setting } = require("../util-server");
let successMessage = "Sent Successfully."; let successMessage = "Sent Successfully.";
class PagerTree extends NotificationProvider { class PagerTree extends NotificationProvider {
@ -74,7 +74,7 @@ class PagerTree extends NotificationProvider {
} }
}; };
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorJSON) { if (baseURL && monitorJSON) {
options.client = "Uptime Kuma"; options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id); options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);

View file

@ -1,8 +1,8 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const Slack = require("./slack"); const Slack = require("./slack");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, DOWN } = require("../../src/util"); const { getMonitorRelativeURL, DOWN } = require("../../src/util");
const { Settings } = require("../settings");
class RocketChat extends NotificationProvider { class RocketChat extends NotificationProvider {
name = "rocket.chat"; name = "rocket.chat";
@ -49,7 +49,7 @@ class RocketChat extends NotificationProvider {
await Slack.deprecateURL(notification.rocketbutton); await Slack.deprecateURL(notification.rocketbutton);
} }
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL) { if (baseURL) {
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id); data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);

View file

@ -32,7 +32,7 @@ class SevenIO extends NotificationProvider {
return okMsg; return okMsg;
} }
let address = this.extractAdress(monitorJSON); let address = this.extractAddress(monitorJSON);
if (address !== "") { if (address !== "") {
address = `(${address}) `; address = `(${address}) `;
} }

View file

@ -18,7 +18,7 @@ class SIGNL4 extends NotificationProvider {
msg, msg,
// Source system // Source system
"X-S4-SourceSystem": "UptimeKuma", "X-S4-SourceSystem": "UptimeKuma",
monitorUrl: this.extractAdress(monitorJSON), monitorUrl: this.extractAddress(monitorJSON),
}; };
const config = { const config = {

View file

@ -1,8 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { setSettings, setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util"); const { getMonitorRelativeURL, UP } = require("../../src/util");
const { Settings } = require("../settings");
const { log } = require("../../src/util");
class Slack extends NotificationProvider { class Slack extends NotificationProvider {
name = "slack"; name = "slack";
@ -15,13 +14,15 @@ class Slack extends NotificationProvider {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async deprecateURL(url) { static async deprecateURL(url) {
let currentPrimaryBaseURL = await Settings.get("primaryBaseURL"); let currentPrimaryBaseURL = await setting("primaryBaseURL");
if (!currentPrimaryBaseURL) { if (!currentPrimaryBaseURL) {
log.error("notification", "Move the url to be the primary base URL"); console.log("Move the url to be the primary base URL");
await Settings.set("primaryBaseURL", url, "general"); await setSettings("general", {
primaryBaseURL: url,
});
} else { } else {
log.debug("notification", "Already there, no need to move the primary base URL"); console.log("Already there, no need to move the primary base URL");
} }
} }
@ -47,7 +48,7 @@ class Slack extends NotificationProvider {
} }
const address = this.extractAdress(monitorJSON); const address = this.extractAddress(monitorJSON);
if (address) { if (address) {
actions.push({ actions.push({
"type": "button", "type": "button",
@ -135,21 +136,26 @@ class Slack extends NotificationProvider {
return okMsg; return okMsg;
} }
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
const title = "Uptime Kuma Alert"; const title = "Uptime Kuma Alert";
let data = { let data = {
"text": `${title}\n${msg}`,
"channel": notification.slackchannel, "channel": notification.slackchannel,
"username": notification.slackusername, "username": notification.slackusername,
"icon_emoji": notification.slackiconemo, "icon_emoji": notification.slackiconemo,
"attachments": [ "attachments": [],
};
if (notification.slackrichmessage) {
data.attachments.push(
{ {
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a", "color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
"blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg), "blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
} }
] );
}; } else {
data.text = `${title}\n${msg}`;
}
if (notification.slackbutton) { if (notification.slackbutton) {
await Slack.deprecateURL(notification.slackbutton); await Slack.deprecateURL(notification.slackbutton);

View file

@ -93,7 +93,7 @@ class SMTP extends NotificationProvider {
if (monitorJSON !== null) { if (monitorJSON !== null) {
monitorName = monitorJSON["name"]; monitorName = monitorJSON["name"];
monitorHostnameOrURL = this.extractAdress(monitorJSON); monitorHostnameOrURL = this.extractAddress(monitorJSON);
} }
let serviceStatus = "⚠️ Test"; let serviceStatus = "⚠️ Test";

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings"); const { setting } = require("../util-server");
let successMessage = "Sent Successfully."; let successMessage = "Sent Successfully.";
class Splunk extends NotificationProvider { class Splunk extends NotificationProvider {
@ -95,7 +95,7 @@ class Splunk extends NotificationProvider {
} }
}; };
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorInfo) { if (baseURL && monitorInfo) {
options.client = "Uptime Kuma"; options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id); options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View file

@ -34,7 +34,7 @@ class Squadcast extends NotificationProvider {
data.status = "resolve"; data.status = "resolve";
} }
data.tags["AlertAddress"] = this.extractAdress(monitorJSON); data.tags["AlertAddress"] = this.extractAddress(monitorJSON);
monitorJSON["tags"].forEach(tag => { monitorJSON["tags"].forEach(tag => {
data.tags[tag["name"]] = { data.tags[tag["name"]] = {

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL } = require("../../src/util"); const { getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings");
class Stackfield extends NotificationProvider { class Stackfield extends NotificationProvider {
name = "stackfield"; name = "stackfield";
@ -23,7 +23,7 @@ class Stackfield extends NotificationProvider {
textMsg += `\n${msg}`; textMsg += `\n${msg}`;
const baseURL = await Settings.get("primaryBaseURL"); const baseURL = await setting("primaryBaseURL");
if (baseURL) { if (baseURL) {
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`; textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
} }

View file

@ -225,7 +225,7 @@ class Teams extends NotificationProvider {
const payload = this._notificationPayloadFactory({ const payload = this._notificationPayloadFactory({
heartbeatJSON: heartbeatJSON, heartbeatJSON: heartbeatJSON,
monitorName: monitorJSON.name, monitorName: monitorJSON.name,
monitorUrl: this.extractAdress(monitorJSON), monitorUrl: this.extractAddress(monitorJSON),
dashboardUrl: dashboardUrl, dashboardUrl: dashboardUrl,
}); });

View file

@ -10,11 +10,22 @@ class TechulusPush extends NotificationProvider {
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully."; const okMsg = "Sent Successfully.";
try { let data = {
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, { "title": notification?.pushTitle?.length ? notification.pushTitle : "Uptime-Kuma",
"title": "Uptime-Kuma",
"body": msg, "body": msg,
}); "timeSensitive": notification.pushTimeSensitive ?? true,
};
if (notification.pushChannel) {
data.channel = notification.pushChannel;
}
if (notification.pushSound) {
data.sound = notification.pushSound;
}
try {
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, data);
return okMsg; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error); this.throwGeneralAxiosError(error);

View file

@ -32,20 +32,17 @@ class WeCom extends NotificationProvider {
* @returns {object} Message * @returns {object} Message
*/ */
composeMessage(heartbeatJSON, msg) { composeMessage(heartbeatJSON, msg) {
let title; let title = "UptimeKuma Message";
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
title = "UptimeKuma Monitor Up"; title = "UptimeKuma Monitor Up";
} }
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
title = "UptimeKuma Monitor Down"; title = "UptimeKuma Monitor Down";
} }
if (msg != null) {
title = "UptimeKuma Message";
}
return { return {
msgtype: "text", msgtype: "text",
text: { text: {
content: title + msg content: title + "\n" + msg
} }
}; };
} }

View file

@ -85,7 +85,7 @@ class ZohoCliq extends NotificationProvider {
const payload = this._notificationPayloadFactory({ const payload = this._notificationPayloadFactory({
monitorMessage: heartbeatJSON.msg, monitorMessage: heartbeatJSON.msg,
monitorName: monitorJSON.name, monitorName: monitorJSON.name,
monitorUrl: this.extractAdress(monitorJSON), monitorUrl: this.extractAddress(monitorJSON),
status: heartbeatJSON.status status: heartbeatJSON.status
}); });

View file

@ -1,4 +1,3 @@
const { R } = require("redbean-node");
const PrometheusClient = require("prom-client"); const PrometheusClient = require("prom-client");
const { log } = require("../src/util"); const { log } = require("../src/util");
@ -10,102 +9,36 @@ const commonLabels = [
"monitor_port", "monitor_port",
]; ];
class Prometheus { const monitorCertDaysRemaining = new PrometheusClient.Gauge({
/**
* Metric: monitor_cert_days_remaining
* @type {PrometheusClient.Gauge<string> | null}
*/
static monitorCertDaysRemaining = null;
/**
* Metric: monitor_cert_is_valid
* @type {PrometheusClient.Gauge<string> | null}
*/
static monitorCertIsValid = null;
/**
* Metric: monitor_response_time
* @type {PrometheusClient.Gauge<string> | null}
*/
static monitorResponseTime = null;
/**
* Metric: monitor_status
* @type {PrometheusClient.Gauge<string> | null}
*/
static monitorStatus = null;
/**
* All registered metric labels.
* @type {string[] | null}
*/
static monitorLabelNames = null;
/**
* Monitor labels/values combination.
* @type {{}}
*/
monitorLabelValues;
/**
* Initialize metrics and get all label names the first time called.
* @returns {void}
*/
static async initMetrics() {
if (!this.monitorLabelNames) {
let labelNames = await R.getCol("SELECT name FROM tag");
this.monitorLabelNames = [ ...commonLabels, ...labelNames ];
}
if (!this.monitorCertDaysRemaining) {
this.monitorCertDaysRemaining = new PrometheusClient.Gauge({
name: "monitor_cert_days_remaining", name: "monitor_cert_days_remaining",
help: "The number of days remaining until the certificate expires", help: "The number of days remaining until the certificate expires",
labelNames: this.monitorLabelNames labelNames: commonLabels
}); });
}
if (!this.monitorCertIsValid) { const monitorCertIsValid = new PrometheusClient.Gauge({
this.monitorCertIsValid = new PrometheusClient.Gauge({
name: "monitor_cert_is_valid", name: "monitor_cert_is_valid",
help: "Is the certificate still valid? (1 = Yes, 0= No)", help: "Is the certificate still valid? (1 = Yes, 0= No)",
labelNames: this.monitorLabelNames labelNames: commonLabels
}); });
} const monitorResponseTime = new PrometheusClient.Gauge({
if (!this.monitorResponseTime) {
this.monitorResponseTime = new PrometheusClient.Gauge({
name: "monitor_response_time", name: "monitor_response_time",
help: "Monitor Response Time (ms)", help: "Monitor Response Time (ms)",
labelNames: this.monitorLabelNames labelNames: commonLabels
}); });
}
if (!this.monitorStatus) { const monitorStatus = new PrometheusClient.Gauge({
this.monitorStatus = new PrometheusClient.Gauge({
name: "monitor_status", name: "monitor_status",
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)", help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
labelNames: this.monitorLabelNames labelNames: commonLabels
}); });
}
} class Prometheus {
monitorLabelValues = {};
/** /**
* Wrapper to create a `Prometheus` instance and ensure metrics are initialized. * @param {object} monitor Monitor object to monitor
* @param {Monitor} monitor Monitor object to monitor
* @returns {Promise<Prometheus>} `Prometheus` instance
*/ */
static async createAndInitMetrics(monitor) { constructor(monitor) {
await Prometheus.initMetrics();
let tags = await monitor.getTags();
return new Prometheus(monitor, tags);
}
/**
* Creates a prometheus metric instance.
*
* Note: Make sure to call `Prometheus.initMetrics()` once prior creating Prometheus instances.
* @param {Monitor} monitor Monitor object to monitor
* @param {Promise<LooseObject<any>[]>} tags Tags of the monitor
*/
constructor(monitor, tags) {
this.monitorLabelValues = { this.monitorLabelValues = {
monitor_name: monitor.name, monitor_name: monitor.name,
monitor_type: monitor.type, monitor_type: monitor.type,
@ -113,12 +46,6 @@ class Prometheus {
monitor_hostname: monitor.hostname, monitor_hostname: monitor.hostname,
monitor_port: monitor.port monitor_port: monitor.port
}; };
Object.values(tags)
// only label names that were known at first metric creation.
.filter(tag => Prometheus.monitorLabelNames.includes(tag.name))
.forEach(tag => {
this.monitorLabelValues[tag.name] = tag.value;
});
} }
/** /**
@ -128,6 +55,7 @@ class Prometheus {
* @returns {void} * @returns {void}
*/ */
update(heartbeat, tlsInfo) { update(heartbeat, tlsInfo) {
if (typeof tlsInfo !== "undefined") { if (typeof tlsInfo !== "undefined") {
try { try {
let isValid; let isValid;
@ -136,7 +64,7 @@ class Prometheus {
} else { } else {
isValid = 0; isValid = 0;
} }
Prometheus.monitorCertIsValid.set(this.monitorLabelValues, isValid); monitorCertIsValid.set(this.monitorLabelValues, isValid);
} catch (e) { } catch (e) {
log.error("prometheus", "Caught error"); log.error("prometheus", "Caught error");
log.error("prometheus", e); log.error("prometheus", e);
@ -144,7 +72,7 @@ class Prometheus {
try { try {
if (tlsInfo.certInfo != null) { if (tlsInfo.certInfo != null) {
Prometheus.monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
} }
} catch (e) { } catch (e) {
log.error("prometheus", "Caught error"); log.error("prometheus", "Caught error");
@ -154,7 +82,7 @@ class Prometheus {
if (heartbeat) { if (heartbeat) {
try { try {
Prometheus.monitorStatus.set(this.monitorLabelValues, heartbeat.status); monitorStatus.set(this.monitorLabelValues, heartbeat.status);
} catch (e) { } catch (e) {
log.error("prometheus", "Caught error"); log.error("prometheus", "Caught error");
log.error("prometheus", e); log.error("prometheus", e);
@ -162,10 +90,10 @@ class Prometheus {
try { try {
if (typeof heartbeat.ping === "number") { if (typeof heartbeat.ping === "number") {
Prometheus.monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping); monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
} else { } else {
// Is it good? // Is it good?
Prometheus.monitorResponseTime.set(this.monitorLabelValues, -1); monitorResponseTime.set(this.monitorLabelValues, -1);
} }
} catch (e) { } catch (e) {
log.error("prometheus", "Caught error"); log.error("prometheus", "Caught error");
@ -180,10 +108,10 @@ class Prometheus {
*/ */
remove() { remove() {
try { try {
Prometheus.monitorCertDaysRemaining?.remove(this.monitorLabelValues); monitorCertDaysRemaining.remove(this.monitorLabelValues);
Prometheus.monitorCertIsValid?.remove(this.monitorLabelValues); monitorCertIsValid.remove(this.monitorLabelValues);
Prometheus.monitorResponseTime?.remove(this.monitorLabelValues); monitorResponseTime.remove(this.monitorLabelValues);
Prometheus.monitorStatus?.remove(this.monitorLabelValues); monitorStatus.remove(this.monitorLabelValues);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View file

@ -2,7 +2,7 @@ const { R } = require("redbean-node");
const HttpProxyAgent = require("http-proxy-agent"); const HttpProxyAgent = require("http-proxy-agent");
const HttpsProxyAgent = require("https-proxy-agent"); const HttpsProxyAgent = require("https-proxy-agent");
const SocksProxyAgent = require("socks-proxy-agent"); const SocksProxyAgent = require("socks-proxy-agent");
const { log } = require("../src/util"); const { debug } = require("../src/util");
const { UptimeKumaServer } = require("./uptime-kuma-server"); const { UptimeKumaServer } = require("./uptime-kuma-server");
const { CookieJar } = require("tough-cookie"); const { CookieJar } = require("tough-cookie");
const { createCookieAgent } = require("http-cookie-agent/http"); const { createCookieAgent } = require("http-cookie-agent/http");
@ -110,9 +110,9 @@ class Proxy {
proxyOptions.auth = `${proxy.username}:${proxy.password}`; proxyOptions.auth = `${proxy.username}:${proxy.password}`;
} }
log.debug("update-proxy", `Proxy Options: ${JSON.stringify(proxyOptions)}`); debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
log.debug("update-proxy", `HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`); debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
log.debug("update-proxy", `HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`); debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
switch (proxy.protocol) { switch (proxy.protocol) {
case "http": case "http":

View file

@ -1,5 +1,6 @@
let express = require("express"); let express = require("express");
const { const {
setting,
allowDevAllOrigin, allowDevAllOrigin,
allowAllOrigin, allowAllOrigin,
percentageToColor, percentageToColor,
@ -17,7 +18,6 @@ const { makeBadge } = require("badge-maker");
const { Prometheus } = require("../prometheus"); const { Prometheus } = require("../prometheus");
const Database = require("../database"); const Database = require("../database");
const { UptimeCalculator } = require("../uptime-calculator"); const { UptimeCalculator } = require("../uptime-calculator");
const { Settings } = require("../settings");
let router = express.Router(); let router = express.Router();
@ -30,7 +30,7 @@ router.get("/api/entry-page", async (request, response) => {
let result = { }; let result = { };
let hostname = request.hostname; let hostname = request.hostname;
if ((await Settings.get("trustProxy")) && request.headers["x-forwarded-host"]) { if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
hostname = request.headers["x-forwarded-host"]; hostname = request.headers["x-forwarded-host"];
} }

View file

@ -90,7 +90,8 @@ const Monitor = require("./model/monitor");
const User = require("./model/user"); const User = require("./model/user");
log.debug("server", "Importing Settings"); log.debug("server", "Importing Settings");
const { initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin } = require("./util-server"); const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
} = require("./util-server");
log.debug("server", "Importing Notification"); log.debug("server", "Importing Notification");
const { Notification } = require("./notification"); const { Notification } = require("./notification");
@ -200,7 +201,7 @@ let needSetup = false;
// Entry Page // Entry Page
app.get("/", async (request, response) => { app.get("/", async (request, response) => {
let hostname = request.hostname; let hostname = request.hostname;
if (await Settings.get("trustProxy")) { if (await setting("trustProxy")) {
const proxy = request.headers["x-forwarded-host"]; const proxy = request.headers["x-forwarded-host"];
if (proxy) { if (proxy) {
hostname = proxy; hostname = proxy;
@ -280,7 +281,7 @@ let needSetup = false;
// Robots.txt // Robots.txt
app.get("/robots.txt", async (_request, response) => { app.get("/robots.txt", async (_request, response) => {
let txt = "User-agent: *\nDisallow:"; let txt = "User-agent: *\nDisallow:";
if (!await Settings.get("searchEngineIndex")) { if (!await setting("searchEngineIndex")) {
txt += " /"; txt += " /";
} }
response.setHeader("Content-Type", "text/plain"); response.setHeader("Content-Type", "text/plain");
@ -1326,7 +1327,7 @@ let needSetup = false;
socket.on("getSettings", async (callback) => { socket.on("getSettings", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
const data = await Settings.getSettings("general"); const data = await getSettings("general");
if (!data.serverTimezone) { if (!data.serverTimezone) {
data.serverTimezone = await server.getTimezone(); data.serverTimezone = await server.getTimezone();
@ -1354,7 +1355,7 @@ let needSetup = false;
// Disabled Auth + Want to Enable Auth => No Check // Disabled Auth + Want to Enable Auth => No Check
// Enabled Auth + Want to Disable Auth => Check!! // Enabled Auth + Want to Disable Auth => Check!!
// Enabled Auth + Want to Enable Auth => No Check // Enabled Auth + Want to Enable Auth => No Check
const currentDisabledAuth = await Settings.get("disableAuth"); const currentDisabledAuth = await setting("disableAuth");
if (!currentDisabledAuth && data.disableAuth) { if (!currentDisabledAuth && data.disableAuth) {
await doubleCheckPassword(socket, currentPassword); await doubleCheckPassword(socket, currentPassword);
} }
@ -1368,7 +1369,7 @@ let needSetup = false;
const previousChromeExecutable = await Settings.get("chromeExecutable"); const previousChromeExecutable = await Settings.get("chromeExecutable");
const previousNSCDStatus = await Settings.get("nscd"); const previousNSCDStatus = await Settings.get("nscd");
await Settings.setSettings("general", data); await setSettings("general", data);
server.entryPage = data.entryPage; server.entryPage = data.entryPage;
// Also need to apply timezone globally // Also need to apply timezone globally
@ -1464,7 +1465,7 @@ let needSetup = false;
}); });
} catch (e) { } catch (e) {
log.error("server", e); console.error(e);
callback({ callback({
ok: false, ok: false,
@ -1577,7 +1578,7 @@ let needSetup = false;
// *************************** // ***************************
log.debug("auth", "check auto login"); log.debug("auth", "check auto login");
if (await Settings.get("disableAuth")) { if (await setting("disableAuth")) {
log.info("auth", "Disabled Auth: auto login to admin"); log.info("auth", "Disabled Auth: auto login to admin");
await afterLogin(socket, await R.findOne("user")); await afterLogin(socket, await R.findOne("user"));
socket.emit("autoLogin"); socket.emit("autoLogin");

View file

@ -60,7 +60,7 @@ module.exports.apiKeySocketHandler = (socket) => {
ok: true, ok: true,
}); });
} catch (e) { } catch (e) {
log.error("apikeys", e); console.error(e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,

View file

@ -1,8 +1,7 @@
const { checkLogin, doubleCheckPassword } = require("../util-server"); const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
const { CloudflaredTunnel } = require("node-cloudflared-tunnel"); const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
const { UptimeKumaServer } = require("../uptime-kuma-server"); const { UptimeKumaServer } = require("../uptime-kuma-server");
const { log } = require("../../src/util"); const { log } = require("../../src/util");
const { Settings } = require("../settings");
const io = UptimeKumaServer.getInstance().io; const io = UptimeKumaServer.getInstance().io;
const prefix = "cloudflared_"; const prefix = "cloudflared_";
@ -41,7 +40,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.join("cloudflared"); socket.join("cloudflared");
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled()); io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
io.to(socket.userID).emit(prefix + "running", cloudflared.running); io.to(socket.userID).emit(prefix + "running", cloudflared.running);
io.to(socket.userID).emit(prefix + "token", await Settings.get("cloudflaredTunnelToken")); io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
} catch (error) { } } catch (error) { }
}); });
@ -56,7 +55,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
try { try {
checkLogin(socket); checkLogin(socket);
if (token && typeof token === "string") { if (token && typeof token === "string") {
await Settings.set("cloudflaredTunnelToken", token); await setSetting("cloudflaredTunnelToken", token);
cloudflared.token = token; cloudflared.token = token;
} else { } else {
cloudflared.token = null; cloudflared.token = null;
@ -68,7 +67,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "stop", async (currentPassword, callback) => { socket.on(prefix + "stop", async (currentPassword, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
const disabledAuth = await Settings.get("disableAuth"); const disabledAuth = await setting("disableAuth");
if (!disabledAuth) { if (!disabledAuth) {
await doubleCheckPassword(socket, currentPassword); await doubleCheckPassword(socket, currentPassword);
} }
@ -84,7 +83,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "removeToken", async () => { socket.on(prefix + "removeToken", async () => {
try { try {
checkLogin(socket); checkLogin(socket);
await Settings.set("cloudflaredTunnelToken", ""); await setSetting("cloudflaredTunnelToken", "");
} catch (error) { } } catch (error) { }
}); });
@ -97,15 +96,15 @@ module.exports.cloudflaredSocketHandler = (socket) => {
*/ */
module.exports.autoStart = async (token) => { module.exports.autoStart = async (token) => {
if (!token) { if (!token) {
token = await Settings.get("cloudflaredTunnelToken"); token = await setting("cloudflaredTunnelToken");
} else { } else {
// Override the current token via args or env var // Override the current token via args or env var
await Settings.set("cloudflaredTunnelToken", token); await setSetting("cloudflaredTunnelToken", token);
log.info("cloudflare", "Use cloudflared token from args or env var"); console.log("Use cloudflared token from args or env var");
} }
if (token) { if (token) {
log.info("cloudflare", "Start cloudflared"); console.log("Start cloudflared");
cloudflared.token = token; cloudflared.token = token;
cloudflared.start(); cloudflared.start();
} }

View file

@ -67,7 +67,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
}); });
} catch (e) { } catch (e) {
log.error("maintenance", e); console.error(e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
@ -177,7 +177,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
ok: true, ok: true,
}); });
} catch (e) { } catch (e) {
log.error("maintenance", e); console.error(e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
@ -201,7 +201,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
}); });
} catch (e) { } catch (e) {
log.error("maintenance", e); console.error(e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
@ -225,7 +225,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
}); });
} catch (e) { } catch (e) {
log.error("maintenance", e); console.error(e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,

View file

@ -1,5 +1,5 @@
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { checkLogin } = require("../util-server"); const { checkLogin, setSetting } = require("../util-server");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { log } = require("../../src/util"); const { log } = require("../../src/util");
const ImageDataURI = require("../image-data-uri"); const ImageDataURI = require("../image-data-uri");
@ -7,7 +7,6 @@ const Database = require("../database");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page"); const StatusPage = require("../model/status_page");
const { UptimeKumaServer } = require("../uptime-kuma-server"); const { UptimeKumaServer } = require("../uptime-kuma-server");
const { Settings } = require("../settings");
/** /**
* Socket handlers for status page * Socket handlers for status page
@ -234,7 +233,7 @@ module.exports.statusPageSocketHandler = (socket) => {
// Also change entry page to new slug if it is the default one, and slug is changed. // Also change entry page to new slug if it is the default one, and slug is changed.
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) { if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
server.entryPage = "statusPage-" + statusPage.slug; server.entryPage = "statusPage-" + statusPage.slug;
await Settings.set("entryPage", server.entryPage, "general"); await setSetting("entryPage", server.entryPage, "general");
} }
apicache.clear(); apicache.clear();
@ -292,7 +291,7 @@ module.exports.statusPageSocketHandler = (socket) => {
}); });
} catch (error) { } catch (error) {
log.error("socket", error); console.error(error);
callback({ callback({
ok: false, ok: false,
msg: error.message, msg: error.message,
@ -314,7 +313,7 @@ module.exports.statusPageSocketHandler = (socket) => {
// Reset entry page if it is the default one. // Reset entry page if it is the default one.
if (server.entryPage === "statusPage-" + slug) { if (server.entryPage === "statusPage-" + slug) {
server.entryPage = "dashboard"; server.entryPage = "dashboard";
await Settings.set("entryPage", server.entryPage, "general"); await setSetting("entryPage", server.entryPage, "general");
} }
// No need to delete records from `status_page_cname`, because it has cascade foreign key. // No need to delete records from `status_page_cname`, because it has cascade foreign key.

View file

@ -12,6 +12,7 @@ const { Client } = require("pg");
const postgresConParse = require("pg-connection-string").parse; const postgresConParse = require("pg-connection-string").parse;
const mysql = require("mysql2"); const mysql = require("mysql2");
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js"); const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
const { Settings } = require("./settings");
const grpc = require("@grpc/grpc-js"); const grpc = require("@grpc/grpc-js");
const protojs = require("protobufjs"); const protojs = require("protobufjs");
const radiusClient = require("node-radius-client"); const radiusClient = require("node-radius-client");
@ -520,6 +521,46 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
}); });
}; };
/**
* Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve
* @returns {Promise<any>} Value
* @deprecated Use await Settings.get(key)
*/
exports.setting = async function (key) {
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>}
*/
exports.setSetting = async function (key, value, type = null) {
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
*/
exports.getSettings = async function (type) {
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>}
*/
exports.setSettings = async function (type, data) {
await Settings.setSettings(type, data);
};
// ssl-checker by @dyaa // ssl-checker by @dyaa
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts //https://github.com/dyaa/ssl-checker/blob/master/src/index.ts

View file

@ -9,6 +9,12 @@
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label> <label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label>
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control"> <input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control">
<label class="form-label">{{ $t("Message format") }}</label>
<div class="form-check form-switch">
<input id="slack-text-message" v-model="$parent.notification.slackrichmessage" type="checkbox" class="form-check-input">
<label for="slack-text-message" class="form-label">{{ $t("Send rich messages") }}</label>
</div>
<div class="form-text"> <div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;"> <i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">

View file

@ -4,6 +4,53 @@
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput> <HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput>
</div> </div>
<div class="mb-3">
<label for="push-api-title" class="form-label">{{ $t("Title") }}</label>
<input id="push-api-title" v-model="$parent.notification.pushTitle" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="push-api-channel" class="form-label">{{ $t("Notification Channel") }}</label>
<input id="push-api-channel" v-model="$parent.notification.pushChannel" type="text" class="form-control" patttern="[A-Za-z0-9-]+">
<div class="form-text">
{{ $t("Alphanumerical string and hyphens only") }}
</div>
</div>
<div class="mb-3">
<label for="push-api-sound" class="form-label">{{ $t("Sound") }}</label>
<select id="push-api-sound" v-model="$parent.notification.pushSound" class="form-select">
<option value="default">{{ $t("Default") }}</option>
<option value="arcade">{{ $t("Arcade") }}</option>
<option value="correct">{{ $t("Correct") }}</option>
<option value="fail">{{ $t("Fail") }}</option>
<option value="harp">{{ $t("Harp") }}</option>
<option value="reveal">{{ $t("Reveal") }}</option>
<option value="bubble">{{ $t("Bubble") }}</option>
<option value="doorbell">{{ $t("Doorbell") }}</option>
<option value="flute">{{ $t("Flute") }}</option>
<option value="money">{{ $t("Money") }}</option>
<option value="scifi">{{ $t("Scifi") }}</option>
<option value="clear">{{ $t("Clear") }}</option>
<option value="elevator">{{ $t("Elevator") }}</option>
<option value="guitar">{{ $t("Guitar") }}</option>
<option value="pop">{{ $t("Pop") }}</option>
</select>
<div class="form-text">
{{ $t("Custom sound to override default notification sound") }}
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.pushTimeSensitive" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Time Sensitive (iOS Only)") }}</label>
</div>
<div class="form-text">
{{ $t("Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.") }}
</div>
</div>
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> <i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a> <a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a>
</i18n-t> </i18n-t>
@ -16,5 +63,19 @@ export default {
components: { components: {
HiddenInput, HiddenInput,
}, },
mounted() {
if (typeof this.$parent.notification.pushTitle === "undefined") {
this.$parent.notification.pushTitle = "Uptime-Kuma";
}
if (typeof this.$parent.notification.pushChannel === "undefined") {
this.$parent.notification.pushChannel = "uptime-kuma";
}
if (typeof this.$parent.notification.pushSound === "undefined") {
this.$parent.notification.pushSound = "default";
}
if (typeof this.$parent.notification.pushTimeSensitive === "undefined") {
this.$parent.notification.pushTimeSensitive = true;
}
},
}; };
</script> </script>

View file

@ -895,6 +895,8 @@
"cacheBusterParamDescription": "Randomly generated parameter to skip caches.", "cacheBusterParamDescription": "Randomly generated parameter to skip caches.",
"gamedigGuessPort": "Gamedig: Guess Port", "gamedigGuessPort": "Gamedig: Guess Port",
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.", "gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
"Message format": "Message format",
"Send rich messages": "Send rich messages",
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL", "Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
"wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}", "wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}",
"bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.", "bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.",
@ -1025,5 +1027,25 @@
"greater than": "greater than", "greater than": "greater than",
"less than or equal to": "less than or equal to", "less than or equal to": "less than or equal to",
"greater than or equal to": "greater than or equal to", "greater than or equal to": "greater than or equal to",
"record": "record" "record": "record",
"Notification Channel": "Notification Channel",
"Sound": "Sound",
"Alphanumerical string and hyphens only": "Alphanumerical string and hyphens only",
"Arcade": "Arcade",
"Correct": "Correct",
"Fail":"Fail",
"Harp":"Harp",
"Reveal":"Reveal",
"Bubble":"Bubble",
"Doorbell":"Doorbell",
"Flute":"Flute",
"Money":"Money",
"Scifi":"Scifi",
"Clear":"Clear",
"Elevator":"Elevator",
"Guitar":"Guitar",
"Pop":"Pop",
"Custom sound to override default notification sound": "Custom sound to override default notification sound",
"Time Sensitive (iOS Only)": "Time Sensitive (iOS Only)",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode."
} }

View file

@ -8,34 +8,17 @@
// Backend uses the compiled file util.js // Backend uses the compiled file util.js
// Frontend uses util.ts // Frontend uses util.ts
*/ */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
var _a; var _a;
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0; exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.ucfirst = void 0; exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
const dayjs_1 = __importDefault(require("dayjs")); const dayjs_1 = __importDefault(require("dayjs"));
const jsonata = __importStar(require("jsonata")); const dayjs = require("dayjs");
const jsonata = require("jsonata");
exports.isDev = process.env.NODE_ENV === "development"; exports.isDev = process.env.NODE_ENV === "development";
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node); exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
exports.appName = "Uptime Kuma"; exports.appName = "Uptime Kuma";
@ -142,6 +125,10 @@ function ucfirst(str) {
return firstLetter.toUpperCase() + str.substr(1); return firstLetter.toUpperCase() + str.substr(1);
} }
exports.ucfirst = ucfirst; exports.ucfirst = ucfirst;
function debug(msg) {
exports.log.log("", msg, "debug");
}
exports.debug = debug;
class Logger { class Logger {
constructor() { constructor() {
this.hideLog = { this.hideLog = {
@ -169,6 +156,8 @@ class Logger {
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) { if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
return; return;
} }
module = module.toUpperCase();
level = level.toUpperCase();
let now; let now;
if (dayjs_1.default.tz) { if (dayjs_1.default.tz) {
now = dayjs_1.default.tz(new Date()).format(); now = dayjs_1.default.tz(new Date()).format();
@ -178,20 +167,10 @@ class Logger {
} }
const levelColor = consoleLevelColors[level]; const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)]; const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart = now; let timePart;
let modulePart = module; let modulePart;
let levelPart = level; let levelPart;
let msgPart = msg; let msgPart;
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
console.log(JSON.stringify({
time: timePart,
module: modulePart,
level: levelPart,
msg: typeof msg === "string" ? msg : JSON.stringify(msg),
}));
return;
}
module = module.toUpperCase();
if (exports.isNode) { if (exports.isNode) {
switch (level) { switch (level) {
case "DEBUG": case "DEBUG":
@ -208,17 +187,28 @@ class Logger {
if (typeof msg === "string") { if (typeof msg === "string") {
msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset; msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
} }
else {
msgPart = msg;
}
break; break;
case "DEBUG": case "DEBUG":
if (typeof msg === "string") { if (typeof msg === "string") {
msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset; msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
} }
else {
msgPart = msg;
}
break;
default:
msgPart = msg;
break; break;
} }
} }
else { else {
timePart = now;
modulePart = `[${module}]`; modulePart = `[${module}]`;
levelPart = `${level}:`; levelPart = `${level}:`;
msgPart = msg;
} }
switch (level) { switch (level) {
case "ERROR": case "ERROR":
@ -241,23 +231,23 @@ class Logger {
} }
} }
info(module, msg) { info(module, msg) {
this.log(module, msg, "INFO"); this.log(module, msg, "info");
} }
warn(module, msg) { warn(module, msg) {
this.log(module, msg, "WARN"); this.log(module, msg, "warn");
} }
error(module, msg) { error(module, msg) {
this.log(module, msg, "ERROR"); this.log(module, msg, "error");
} }
debug(module, msg) { debug(module, msg) {
this.log(module, msg, "DEBUG"); this.log(module, msg, "debug");
} }
exception(module, exception, msg) { exception(module, exception, msg) {
let finalMessage = exception; let finalMessage = exception;
if (msg) { if (msg) {
finalMessage = `${msg}: ${exception}`; finalMessage = `${msg}: ${exception}`;
} }
this.log(module, finalMessage, "ERROR"); this.log(module, finalMessage, "error");
} }
} }
exports.log = new Logger(); exports.log = new Logger();

View file

@ -120,7 +120,11 @@ export const badgeConstants = {
defaultCertExpireDownDays: "7" defaultCertExpireDownDays: "7"
}; };
/** Flip the status of s */ /**
* Flip the status of s between UP and DOWN if this is possible
* @param s {number} status
* @returns {number} flipped status
*/
export function flipStatus(s: number) { export function flipStatus(s: number) {
if (s === UP) { if (s === UP) {
return DOWN; return DOWN;
@ -156,6 +160,15 @@ export function ucfirst(str: string) {
return firstLetter.toUpperCase() + str.substr(1); return firstLetter.toUpperCase() + str.substr(1);
} }
/**
* @deprecated Use log.debug (https://github.com/louislam/uptime-kuma/pull/910)
* @param msg Message to write
* @returns {void}
*/
export function debug(msg: unknown) {
log.log("", msg, "debug");
}
class Logger { class Logger {
/** /**
@ -197,13 +210,12 @@ class Logger {
/** /**
* Write a message to the log * Write a message to the log
* @private
* @param module The module the log comes from * @param module The module the log comes from
* @param msg Message to write * @param msg Message to write
* @param level {"INFO"|"WARN"|"ERROR"|"DEBUG"} Log level * @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
* @returns {void} * @returns {void}
*/ */
log(module: string, msg: unknown, level: "INFO"|"WARN"|"ERROR"|"DEBUG"): void { log(module: string, msg: any, level: string) {
if (level === "DEBUG" && !isDev) { if (level === "DEBUG" && !isDev) {
return; return;
} }
@ -212,6 +224,9 @@ class Logger {
return; return;
} }
module = module.toUpperCase();
level = level.toUpperCase();
let now; let now;
if (dayjs.tz) { if (dayjs.tz) {
now = dayjs.tz(new Date()).format(); now = dayjs.tz(new Date()).format();
@ -222,23 +237,10 @@ class Logger {
const levelColor = consoleLevelColors[level]; const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)]; const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart: string = now; let timePart: string;
let modulePart: string = module; let modulePart: string;
let levelPart: string = level; let levelPart: string;
let msgPart: unknown = msg; let msgPart: string;
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
console.log(JSON.stringify({
time: timePart,
module: modulePart,
level: levelPart,
msg: typeof msg === "string" ? msg : JSON.stringify(msg),
}));
return;
}
// Console rendering:
module = module.toUpperCase();
if (isNode) { if (isNode) {
// Add console colors // Add console colors
@ -259,18 +261,27 @@ class Logger {
case "ERROR": case "ERROR":
if (typeof msg === "string") { if (typeof msg === "string") {
msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset; msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
} else {
msgPart = msg;
} }
break; break;
case "DEBUG": case "DEBUG":
if (typeof msg === "string") { if (typeof msg === "string") {
msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset; msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
} else {
msgPart = msg;
} }
break; break;
default:
msgPart = msg;
break;
} }
} else { } else {
// No console colors // No console colors
timePart = now;
modulePart = `[${module}]`; modulePart = `[${module}]`;
levelPart = `${level}:`; levelPart = `${level}:`;
msgPart = msg;
} }
// Write to console // Write to console
@ -301,8 +312,8 @@ class Logger {
* @param msg Message to write * @param msg Message to write
* @returns {void} * @returns {void}
*/ */
info(module: string, msg: string): void { info(module: string, msg: unknown) {
this.log(module, msg, "INFO"); this.log(module, msg, "info");
} }
/** /**
@ -311,8 +322,8 @@ class Logger {
* @param msg Message to write * @param msg Message to write
* @returns {void} * @returns {void}
*/ */
warn(module: string, msg: string): void { warn(module: string, msg: unknown) {
this.log(module, msg, "WARN"); this.log(module, msg, "warn");
} }
/** /**
@ -321,8 +332,8 @@ class Logger {
* @param msg Message to write * @param msg Message to write
* @returns {void} * @returns {void}
*/ */
error(module: string, msg: string): void { error(module: string, msg: unknown) {
this.log(module, msg, "ERROR"); this.log(module, msg, "error");
} }
/** /**
@ -331,8 +342,8 @@ class Logger {
* @param msg Message to write * @param msg Message to write
* @returns {void} * @returns {void}
*/ */
debug(module: string, msg: string): void { debug(module: string, msg: unknown) {
this.log(module, msg, "DEBUG"); this.log(module, msg, "debug");
} }
/** /**
@ -349,7 +360,7 @@ class Logger {
finalMessage = `${msg}: ${exception}`; finalMessage = `${msg}: ${exception}`;
} }
this.log(module, finalMessage, "ERROR"); this.log(module, finalMessage, "error");
} }
} }
@ -393,7 +404,7 @@ export class TimeLogger {
* @param name Name of monitor * @param name Name of monitor
* @returns {void} * @returns {void}
*/ */
print(name: string): void { print(name: string) {
if (isDev && process.env.TIMELOGGER === "1") { if (isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
} }