mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-15 16:07:03 +08:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c1aaad0d85 | ||
|
954e05b72f | ||
|
6d4a45f18c | ||
|
f0975cd929 | ||
|
40d6a21453 | ||
|
b383392e8f | ||
|
9964b6c4d8 | ||
|
d56bf08cd7 | ||
|
291d5d7c55 | ||
|
8e3ff25f7b | ||
|
6e80c850f4 | ||
|
0608881954 | ||
|
38efd97b28 | ||
|
ce0ba6c0ca | ||
|
c43223a16d | ||
|
9f170a68d7 | ||
|
1a862e47ab | ||
|
e64bf0e3fe | ||
|
523d137e2b | ||
|
18169c59a1 | ||
|
4ccf263481 | ||
|
1c13a75970 |
34
db/patch-fix-kafka-producer-booleans.sql
Normal file
34
db/patch-fix-kafka-producer-booleans.sql
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- Rename COLUMNs to another one (suffixed by `_old`)
|
||||||
|
ALTER TABLE monitor
|
||||||
|
RENAME COLUMN kafka_producer_ssl TO kafka_producer_ssl_old;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
RENAME COLUMN kafka_producer_allow_auto_topic_creation TO kafka_producer_allow_auto_topic_creation_old;
|
||||||
|
|
||||||
|
-- Add correct COLUMNs
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD COLUMN kafka_producer_ssl BOOLEAN default 0 NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD COLUMN kafka_producer_allow_auto_topic_creation BOOLEAN default 0 NOT NULL;
|
||||||
|
|
||||||
|
-- These SQL is still not fully safe. See https://github.com/louislam/uptime-kuma/issues/4039.
|
||||||
|
|
||||||
|
-- Set bring old values from `_old` COLUMNs to correct ones
|
||||||
|
-- UPDATE monitor SET kafka_producer_allow_auto_topic_creation = monitor.kafka_producer_allow_auto_topic_creation_old
|
||||||
|
-- WHERE monitor.kafka_producer_allow_auto_topic_creation_old IS NOT NULL;
|
||||||
|
|
||||||
|
-- UPDATE monitor SET kafka_producer_ssl = monitor.kafka_producer_ssl_old
|
||||||
|
-- WHERE monitor.kafka_producer_ssl_old IS NOT NULL;
|
||||||
|
|
||||||
|
-- Remove old COLUMNs
|
||||||
|
ALTER TABLE monitor
|
||||||
|
DROP COLUMN kafka_producer_allow_auto_topic_creation_old;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
DROP COLUMN kafka_producer_ssl_old;
|
||||||
|
|
||||||
|
COMMIT;
|
7
db/patch-timeout.sql
Normal file
7
db/patch-timeout.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
UPDATE monitor SET timeout = (interval * 0.8)
|
||||||
|
WHERE timeout IS NULL OR timeout <= 0;
|
||||||
|
|
||||||
|
COMMIT;
|
@@ -1,6 +1,6 @@
|
|||||||
# DON'T UPDATE TO node:14-bullseye-slim, see #372.
|
# DON'T UPDATE TO bullseye-slim, see #372.
|
||||||
# If the image changed, the second stage image should be changed too
|
# There is no 20-buster-slim for armv7 unfortunately, 18-buster-slim is the last one for Uptime Kuma v1.
|
||||||
FROM node:16-buster-slim
|
FROM node:18-buster-slim
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
2406
package-lock.json
generated
2406
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.23.3",
|
"version": "1.23.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
||||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||||
"setup": "git checkout 1.23.3 && npm ci --production && npm run download-dist",
|
"setup": "git checkout 1.23.6 && npm ci --production && npm run download-dist",
|
||||||
"download-dist": "node extra/download-dist.js",
|
"download-dist": "node extra/download-dist.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
@@ -57,6 +57,8 @@
|
|||||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||||
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
||||||
"simple-mongo": "docker run --rm -p 27017:27017 mongo",
|
"simple-mongo": "docker run --rm -p 27017:27017 mongo",
|
||||||
|
"simple-postgres": "docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres",
|
||||||
|
"simple-mariadb": "docker run --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mariadb# mariadb",
|
||||||
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
||||||
"ncu-patch": "npm-check-updates -u -t patch",
|
"ncu-patch": "npm-check-updates -u -t patch",
|
||||||
"release-final": "node ./extra/test-docker.js && node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
"release-final": "node ./extra/test-docker.js && node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||||
@@ -70,7 +72,8 @@
|
|||||||
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
||||||
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
|
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
|
||||||
"deploy-demo-server": "node extra/deploy-demo-server.js",
|
"deploy-demo-server": "node extra/deploy-demo-server.js",
|
||||||
"sort-contributors": "node extra/sort-contributors.js"
|
"sort-contributors": "node extra/sort-contributors.js",
|
||||||
|
"start-server-node14-win": "private\\node14\\node.exe server/server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.7.3",
|
"@grpc/grpc-js": "~1.7.3",
|
||||||
@@ -112,7 +115,7 @@
|
|||||||
"mongodb": "~4.17.1",
|
"mongodb": "~4.17.1",
|
||||||
"mqtt": "~4.3.7",
|
"mqtt": "~4.3.7",
|
||||||
"mssql": "~8.1.4",
|
"mssql": "~8.1.4",
|
||||||
"mysql2": "~2.3.3",
|
"mysql2": "~3.6.2",
|
||||||
"nanoid": "~3.3.4",
|
"nanoid": "~3.3.4",
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
"node-radius-client": "~1.0.0",
|
"node-radius-client": "~1.0.0",
|
||||||
@@ -121,8 +124,8 @@
|
|||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
"openid-client": "^5.4.2",
|
"openid-client": "^5.4.2",
|
||||||
"password-hash": "~1.2.2",
|
"password-hash": "~1.2.2",
|
||||||
"pg": "~8.8.0",
|
"pg": "~8.11.3",
|
||||||
"pg-connection-string": "~2.5.0",
|
"pg-connection-string": "~2.6.2",
|
||||||
"playwright-core": "~1.35.1",
|
"playwright-core": "~1.35.1",
|
||||||
"prom-client": "~13.2.0",
|
"prom-client": "~13.2.0",
|
||||||
"prometheus-api-metrics": "~3.2.1",
|
"prometheus-api-metrics": "~3.2.1",
|
||||||
|
@@ -82,6 +82,8 @@ class Database {
|
|||||||
"patch-add-timeout-monitor.sql": true,
|
"patch-add-timeout-monitor.sql": true,
|
||||||
"patch-add-gamedig-given-port.sql": true,
|
"patch-add-gamedig-given-port.sql": true,
|
||||||
"patch-notification-config.sql": true,
|
"patch-notification-config.sql": true,
|
||||||
|
"patch-fix-kafka-producer-booleans.sql": true,
|
||||||
|
"patch-timeout.sql": true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,10 +3,10 @@ const dayjs = require("dayjs");
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||||
SQL_DATETIME_FORMAT
|
SQL_DATETIME_FORMAT, isDev, sleep, getRandomInt
|
||||||
} = require("../../src/util");
|
} = require("../../src/util");
|
||||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||||
redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials,
|
redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
@@ -22,6 +22,9 @@ const { UptimeCacheList } = require("../uptime-cache-list");
|
|||||||
const Gamedig = require("gamedig");
|
const Gamedig = require("gamedig");
|
||||||
const jsonata = require("jsonata");
|
const jsonata = require("jsonata");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
const rootCertificates = rootCertificatesFingerprints();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status:
|
* status:
|
||||||
@@ -141,8 +144,8 @@ class Monitor extends BeanModel {
|
|||||||
expectedValue: this.expectedValue,
|
expectedValue: this.expectedValue,
|
||||||
kafkaProducerTopic: this.kafkaProducerTopic,
|
kafkaProducerTopic: this.kafkaProducerTopic,
|
||||||
kafkaProducerBrokers: JSON.parse(this.kafkaProducerBrokers),
|
kafkaProducerBrokers: JSON.parse(this.kafkaProducerBrokers),
|
||||||
kafkaProducerSsl: this.kafkaProducerSsl === "1" && true || false,
|
kafkaProducerSsl: this.getKafkaProducerSsl(),
|
||||||
kafkaProducerAllowAutoTopicCreation: this.kafkaProducerAllowAutoTopicCreation === "1" && true || false,
|
kafkaProducerAllowAutoTopicCreation: this.getKafkaProducerAllowAutoTopicCreation(),
|
||||||
kafkaProducerMessage: this.kafkaProducerMessage,
|
kafkaProducerMessage: this.kafkaProducerMessage,
|
||||||
screenshot,
|
screenshot,
|
||||||
};
|
};
|
||||||
@@ -285,6 +288,22 @@ class Monitor extends BeanModel {
|
|||||||
return Boolean(this.gamedigGivenPortOnly);
|
return Boolean(this.gamedigGivenPortOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse to boolean
|
||||||
|
* @returns {boolean} Kafka Producer Ssl enabled?
|
||||||
|
*/
|
||||||
|
getKafkaProducerSsl() {
|
||||||
|
return Boolean(this.kafkaProducerSsl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse to boolean
|
||||||
|
* @returns {boolean} Kafka Producer Allow Auto Topic Creation Enabled?
|
||||||
|
*/
|
||||||
|
getKafkaProducerAllowAutoTopicCreation() {
|
||||||
|
return Boolean(this.kafkaProducerAllowAutoTopicCreation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start monitor
|
* Start monitor
|
||||||
* @param {Server} io Socket server instance
|
* @param {Server} io Socket server instance
|
||||||
@@ -310,6 +329,16 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evil
|
||||||
|
if (isDev) {
|
||||||
|
if (process.env.EVIL_RANDOM_MONITOR_SLEEP === "SURE") {
|
||||||
|
if (getRandomInt(0, 100) === 0) {
|
||||||
|
log.debug("evil", `[${this.name}] Evil mode: Random sleep: ` + beatInterval * 10000);
|
||||||
|
await sleep(beatInterval * 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Expose here for prometheus update
|
// Expose here for prometheus update
|
||||||
// undefined if not https
|
// undefined if not https
|
||||||
let tlsInfo = undefined;
|
let tlsInfo = undefined;
|
||||||
@@ -339,6 +368,12 @@ class Monitor extends BeanModel {
|
|||||||
bean.duration = 0;
|
bean.duration = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runtime patch timeout if it is 0
|
||||||
|
// See https://github.com/louislam/uptime-kuma/pull/3961#issuecomment-1804149144
|
||||||
|
if (!this.timeout || this.timeout <= 0) {
|
||||||
|
this.timeout = this.interval * 1000 * 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||||
bean.msg = "Monitor under maintenance";
|
bean.msg = "Monitor under maintenance";
|
||||||
@@ -409,6 +444,7 @@ class Monitor extends BeanModel {
|
|||||||
const httpsAgentOptions = {
|
const httpsAgentOptions = {
|
||||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||||
rejectUnauthorized: !this.getIgnoreTls(),
|
rejectUnauthorized: !this.getIgnoreTls(),
|
||||||
|
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||||
};
|
};
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
||||||
@@ -447,6 +483,7 @@ class Monitor extends BeanModel {
|
|||||||
validateStatus: (status) => {
|
validateStatus: (status) => {
|
||||||
return checkStatusCode(status, this.getAcceptedStatuscodes());
|
return checkStatusCode(status, this.getAcceptedStatuscodes());
|
||||||
},
|
},
|
||||||
|
signal: axiosAbortSignal(this.timeout * 1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (bodyValue) {
|
if (bodyValue) {
|
||||||
@@ -662,6 +699,7 @@ class Monitor extends BeanModel {
|
|||||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||||
rejectUnauthorized: !this.getIgnoreTls(),
|
rejectUnauthorized: !this.getIgnoreTls(),
|
||||||
|
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||||
}),
|
}),
|
||||||
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
||||||
maxCachedSessions: 0,
|
maxCachedSessions: 0,
|
||||||
@@ -704,8 +742,6 @@ class Monitor extends BeanModel {
|
|||||||
} else if (this.type === "docker") {
|
} else if (this.type === "docker") {
|
||||||
log.debug("monitor", `[${this.name}] Prepare Options for Axios`);
|
log.debug("monitor", `[${this.name}] Prepare Options for Axios`);
|
||||||
|
|
||||||
const dockerHost = await R.load("docker_host", this.docker_host);
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
url: `/containers/${this.docker_container}/json`,
|
url: `/containers/${this.docker_container}/json`,
|
||||||
timeout: this.interval * 1000 * 0.8,
|
timeout: this.interval * 1000 * 0.8,
|
||||||
@@ -716,12 +752,19 @@ class Monitor extends BeanModel {
|
|||||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||||
rejectUnauthorized: !this.getIgnoreTls(),
|
rejectUnauthorized: !this.getIgnoreTls(),
|
||||||
|
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||||
}),
|
}),
|
||||||
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
||||||
maxCachedSessions: 0,
|
maxCachedSessions: 0,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dockerHost = await R.load("docker_host", this.docker_host);
|
||||||
|
|
||||||
|
if (!dockerHost) {
|
||||||
|
throw new Error("Failed to load docker host config");
|
||||||
|
}
|
||||||
|
|
||||||
if (dockerHost._dockerType === "socket") {
|
if (dockerHost._dockerType === "socket") {
|
||||||
options.socketPath = dockerHost._dockerDaemon;
|
options.socketPath = dockerHost._dockerDaemon;
|
||||||
} else if (dockerHost._dockerType === "tcp") {
|
} else if (dockerHost._dockerType === "tcp") {
|
||||||
@@ -756,7 +799,7 @@ class Monitor extends BeanModel {
|
|||||||
} else if (this.type === "sqlserver") {
|
} else if (this.type === "sqlserver") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
await mssqlQuery(this.databaseConnectionString, this.databaseQuery);
|
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
||||||
|
|
||||||
bean.msg = "";
|
bean.msg = "";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
@@ -795,7 +838,7 @@ class Monitor extends BeanModel {
|
|||||||
} else if (this.type === "postgres") {
|
} else if (this.type === "postgres") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
await postgresQuery(this.databaseConnectionString, this.databaseQuery);
|
await postgresQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
||||||
|
|
||||||
bean.msg = "";
|
bean.msg = "";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
@@ -803,7 +846,11 @@ class Monitor extends BeanModel {
|
|||||||
} else if (this.type === "mysql") {
|
} else if (this.type === "mysql") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
// Use `radius_password` as `password` field, since there are too many unnecessary fields
|
||||||
|
// TODO: rename `radius_password` to `password` later for general use
|
||||||
|
let mysqlPassword = this.radiusPassword;
|
||||||
|
|
||||||
|
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1", mysqlPassword);
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
} else if (this.type === "mongodb") {
|
} else if (this.type === "mongodb") {
|
||||||
@@ -973,6 +1020,7 @@ class Monitor extends BeanModel {
|
|||||||
if (! this.isStop) {
|
if (! this.isStop) {
|
||||||
log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
|
log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
|
||||||
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
|
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
|
||||||
|
this.lastScheduleBeatTime = dayjs();
|
||||||
} else {
|
} else {
|
||||||
log.info("monitor", `[${this.name}] isStop = true, no next check.`);
|
log.info("monitor", `[${this.name}] isStop = true, no next check.`);
|
||||||
}
|
}
|
||||||
@@ -982,7 +1030,9 @@ class Monitor extends BeanModel {
|
|||||||
/** Get a heartbeat and handle errors */
|
/** Get a heartbeat and handle errors */
|
||||||
const safeBeat = async () => {
|
const safeBeat = async () => {
|
||||||
try {
|
try {
|
||||||
|
this.lastStartBeatTime = dayjs();
|
||||||
await beat();
|
await beat();
|
||||||
|
this.lastEndBeatTime = dayjs();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.trace(e);
|
console.trace(e);
|
||||||
UptimeKumaServer.errorLog(e, false);
|
UptimeKumaServer.errorLog(e, false);
|
||||||
@@ -991,6 +1041,9 @@ class Monitor extends BeanModel {
|
|||||||
if (! this.isStop) {
|
if (! this.isStop) {
|
||||||
log.info("monitor", "Try to restart the monitor");
|
log.info("monitor", "Try to restart the monitor");
|
||||||
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
|
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
|
||||||
|
this.lastScheduleBeatTime = dayjs();
|
||||||
|
} else {
|
||||||
|
log.info("monitor", "isStop = true, no next check.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1424,7 +1477,10 @@ class Monitor extends BeanModel {
|
|||||||
let certInfo = tlsInfoObject.certInfo;
|
let certInfo = tlsInfoObject.certInfo;
|
||||||
while (certInfo) {
|
while (certInfo) {
|
||||||
let subjectCN = certInfo.subject["CN"];
|
let subjectCN = certInfo.subject["CN"];
|
||||||
if (certInfo.daysRemaining > targetDays) {
|
if (rootCertificates.has(certInfo.fingerprint256)) {
|
||||||
|
log.debug("monitor", `Known root cert: ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
||||||
|
break;
|
||||||
|
} else if (certInfo.daysRemaining > targetDays) {
|
||||||
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
|
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
|
||||||
|
@@ -49,7 +49,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
|||||||
|
|
||||||
let pushToken = request.params.pushToken;
|
let pushToken = request.params.pushToken;
|
||||||
let msg = request.query.msg || "OK";
|
let msg = request.query.msg || "OK";
|
||||||
let ping = parseInt(request.query.ping) || null;
|
let ping = parseFloat(request.query.ping) || null;
|
||||||
let statusString = request.query.status || "up";
|
let statusString = request.query.status || "up";
|
||||||
let status = (statusString === "up") ? UP : DOWN;
|
let status = (statusString === "up") ? UP : DOWN;
|
||||||
|
|
||||||
|
@@ -299,12 +299,12 @@ let needSetup = false;
|
|||||||
decoded.username,
|
decoded.username,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Check if the password changed
|
|
||||||
if (decoded.h !== shake256(user.password, SHAKE256_LENGTH)) {
|
|
||||||
throw new Error("The token is invalid due to password change or old token");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
|
// Check if the password changed
|
||||||
|
if (decoded.h !== shake256(user.password, SHAKE256_LENGTH)) {
|
||||||
|
throw new Error("The token is invalid due to password change or old token");
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("auth", "afterLogin");
|
log.debug("auth", "afterLogin");
|
||||||
afterLogin(socket, user);
|
afterLogin(socket, user);
|
||||||
log.debug("auth", "afterLogin ok");
|
log.debug("auth", "afterLogin ok");
|
||||||
@@ -789,6 +789,9 @@ let needSetup = false;
|
|||||||
bean.kafkaProducerAllowAutoTopicCreation = monitor.kafkaProducerAllowAutoTopicCreation;
|
bean.kafkaProducerAllowAutoTopicCreation = monitor.kafkaProducerAllowAutoTopicCreation;
|
||||||
bean.kafkaProducerSaslOptions = JSON.stringify(monitor.kafkaProducerSaslOptions);
|
bean.kafkaProducerSaslOptions = JSON.stringify(monitor.kafkaProducerSaslOptions);
|
||||||
bean.kafkaProducerMessage = monitor.kafkaProducerMessage;
|
bean.kafkaProducerMessage = monitor.kafkaProducerMessage;
|
||||||
|
bean.kafkaProducerSsl = monitor.kafkaProducerSsl;
|
||||||
|
bean.kafkaProducerAllowAutoTopicCreation =
|
||||||
|
monitor.kafkaProducerAllowAutoTopicCreation;
|
||||||
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
|
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
|
||||||
|
|
||||||
bean.validate();
|
bean.validate();
|
||||||
@@ -1829,6 +1832,7 @@ async function pauseMonitor(userID, monitorID) {
|
|||||||
|
|
||||||
if (monitorID in server.monitorList) {
|
if (monitorID in server.monitorList) {
|
||||||
server.monitorList[monitorID].stop();
|
server.monitorList[monitorID].stop();
|
||||||
|
server.monitorList[monitorID].active = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1887,8 +1891,10 @@ gracefulShutdown(server.httpServer, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Catch unexpected errors here
|
// Catch unexpected errors here
|
||||||
process.addListener("unhandledRejection", (error, promise) => {
|
let unexpectedErrorHandler = (error, promise) => {
|
||||||
console.trace(error);
|
console.trace(error);
|
||||||
UptimeKumaServer.errorLog(error, false);
|
UptimeKumaServer.errorLog(error, false);
|
||||||
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
|
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
|
||||||
});
|
};
|
||||||
|
process.addListener("unhandledRejection", unexpectedErrorHandler);
|
||||||
|
process.addListener("uncaughtException", unexpectedErrorHandler);
|
||||||
|
@@ -12,6 +12,7 @@ const { Settings } = require("./settings");
|
|||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const childProcess = require("child_process");
|
const childProcess = require("child_process");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const axios = require("axios");
|
||||||
// DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`, put at the bottom of this file instead.
|
// DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`, put at the bottom of this file instead.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +63,8 @@ class UptimeKumaServer {
|
|||||||
*/
|
*/
|
||||||
jwtSecret = null;
|
jwtSecret = null;
|
||||||
|
|
||||||
|
checkMonitorsInterval = null;
|
||||||
|
|
||||||
static getInstance(args) {
|
static getInstance(args) {
|
||||||
if (UptimeKumaServer.instance == null) {
|
if (UptimeKumaServer.instance == null) {
|
||||||
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
||||||
@@ -75,6 +78,9 @@ class UptimeKumaServer {
|
|||||||
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||||
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;
|
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;
|
||||||
|
|
||||||
|
// Set default axios timeout to 5 minutes instead of infinity
|
||||||
|
axios.defaults.timeout = 300 * 1000;
|
||||||
|
|
||||||
log.info("server", "Creating express and socket.io instance");
|
log.info("server", "Creating express and socket.io instance");
|
||||||
this.app = express();
|
this.app = express();
|
||||||
if (sslKey && sslCert) {
|
if (sslKey && sslCert) {
|
||||||
@@ -346,6 +352,10 @@ class UptimeKumaServer {
|
|||||||
if (enable || enable === null) {
|
if (enable || enable === null) {
|
||||||
this.startNSCDServices();
|
this.startNSCDServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkMonitorsInterval = setInterval(() => {
|
||||||
|
this.checkMonitors();
|
||||||
|
}, 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -358,6 +368,8 @@ class UptimeKumaServer {
|
|||||||
if (enable || enable === null) {
|
if (enable || enable === null) {
|
||||||
this.stopNSCDServices();
|
this.stopNSCDServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearInterval(this.checkMonitorsInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -388,6 +400,83 @@ class UptimeKumaServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the specified monitor
|
||||||
|
* @param {number} monitorID ID of monitor to start
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async startMonitor(monitorID) {
|
||||||
|
log.info("manage", `Resume Monitor: ${monitorID} by server`);
|
||||||
|
|
||||||
|
await R.exec("UPDATE monitor SET active = 1 WHERE id = ?", [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let monitor = await R.findOne("monitor", " id = ? ", [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (monitor.id in this.monitorList) {
|
||||||
|
this.monitorList[monitor.id].stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.monitorList[monitor.id] = monitor;
|
||||||
|
monitor.start(this.io);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restart a given monitor
|
||||||
|
* @param {number} monitorID ID of monitor to start
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async restartMonitor(monitorID) {
|
||||||
|
return await this.startMonitor(monitorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if monitors are running properly
|
||||||
|
*/
|
||||||
|
async checkMonitors() {
|
||||||
|
log.debug("monitor_checker", "Checking monitors");
|
||||||
|
|
||||||
|
for (let monitorID in this.monitorList) {
|
||||||
|
let monitor = this.monitorList[monitorID];
|
||||||
|
|
||||||
|
// Not for push monitor
|
||||||
|
if (monitor.type === "push") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitor.active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the lastStartBeatTime, if it is too long, then restart
|
||||||
|
if (monitor.lastScheduleBeatTime ) {
|
||||||
|
let diff = dayjs().diff(monitor.lastStartBeatTime, "second");
|
||||||
|
|
||||||
|
if (diff > monitor.interval * 1.5) {
|
||||||
|
log.error("monitor_checker", `Monitor Interval: ${monitor.interval} Monitor ` + monitorID + " lastStartBeatTime diff: " + diff);
|
||||||
|
log.error("monitor_checker", "Unexpected error: Monitor " + monitorID + " is struck for unknown reason");
|
||||||
|
log.error("monitor_checker", "Last start beat time: " + R.isoDateTime(monitor.lastStartBeatTime));
|
||||||
|
log.error("monitor_checker", "Last end beat time: " + R.isoDateTime(monitor.lastEndBeatTime));
|
||||||
|
log.error("monitor_checker", "Last ScheduleBeatTime: " + R.isoDateTime(monitor.lastScheduleBeatTime));
|
||||||
|
|
||||||
|
// Restart
|
||||||
|
log.error("monitor_checker", `Restarting monitor ${monitorID} automatically now`);
|
||||||
|
this.restartMonitor(monitorID);
|
||||||
|
} else {
|
||||||
|
//log.debug("monitor_checker", "Monitor " + monitorID + " is running normally");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//log.debug("monitor_checker", "Monitor " + monitorID + " is not started yet, skipp");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("monitor_checker", "Checking monitors end");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@@ -22,6 +22,7 @@ const protojs = require("protobufjs");
|
|||||||
const radiusClient = require("node-radius-client");
|
const radiusClient = require("node-radius-client");
|
||||||
const redis = require("redis");
|
const redis = require("redis");
|
||||||
const oidc = require("openid-client");
|
const oidc = require("openid-client");
|
||||||
|
const tls = require("tls");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dictionaries: {
|
dictionaries: {
|
||||||
@@ -395,6 +396,9 @@ exports.mssqlQuery = async function (connectionString, query) {
|
|||||||
try {
|
try {
|
||||||
pool = new mssql.ConnectionPool(connectionString);
|
pool = new mssql.ConnectionPool(connectionString);
|
||||||
await pool.connect();
|
await pool.connect();
|
||||||
|
if (!query) {
|
||||||
|
query = "SELECT 1";
|
||||||
|
}
|
||||||
await pool.request().query(query);
|
await pool.request().query(query);
|
||||||
pool.close();
|
pool.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -415,12 +419,22 @@ exports.postgresQuery = function (connectionString, query) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const config = postgresConParse(connectionString);
|
const config = postgresConParse(connectionString);
|
||||||
|
|
||||||
if (config.password === "") {
|
// Fix #3868, which true/false is not parsed to boolean
|
||||||
// See https://github.com/brianc/node-postgres/issues/1927
|
if (typeof config.ssl === "string") {
|
||||||
return reject(new Error("Password is undefined."));
|
config.ssl = config.ssl === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new Client({ connectionString });
|
if (config.password === "") {
|
||||||
|
// See https://github.com/brianc/node-postgres/issues/1927
|
||||||
|
reject(new Error("Password is undefined."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const client = new Client(config);
|
||||||
|
|
||||||
|
client.on("error", (error) => {
|
||||||
|
log.debug("postgres", "Error caught in the error event handler.");
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
client.connect((err) => {
|
client.connect((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -455,11 +469,15 @@ exports.postgresQuery = function (connectionString, query) {
|
|||||||
* Run a query on MySQL/MariaDB
|
* Run a query on MySQL/MariaDB
|
||||||
* @param {string} connectionString The database connection string
|
* @param {string} connectionString The database connection string
|
||||||
* @param {string} query The query to validate the database with
|
* @param {string} query The query to validate the database with
|
||||||
|
* @param {?string} password The password to use
|
||||||
* @returns {Promise<(string)>}
|
* @returns {Promise<(string)>}
|
||||||
*/
|
*/
|
||||||
exports.mysqlQuery = function (connectionString, query) {
|
exports.mysqlQuery = function (connectionString, query, password = undefined) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const connection = mysql.createConnection(connectionString);
|
const connection = mysql.createConnection({
|
||||||
|
uri: connectionString,
|
||||||
|
password
|
||||||
|
});
|
||||||
|
|
||||||
connection.on("error", (err) => {
|
connection.on("error", (err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -1056,6 +1074,30 @@ module.exports.grpcQuery = async (options) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of SHA256 fingerprints for all known root certificates.
|
||||||
|
* @returns {Set} A set of SHA256 fingerprints.
|
||||||
|
*/
|
||||||
|
module.exports.rootCertificatesFingerprints = () => {
|
||||||
|
let fingerprints = tls.rootCertificates.map(cert => {
|
||||||
|
let certLines = cert.split("\n");
|
||||||
|
certLines.shift();
|
||||||
|
certLines.pop();
|
||||||
|
let certBody = certLines.join("");
|
||||||
|
let buf = Buffer.from(certBody, "base64");
|
||||||
|
|
||||||
|
const shasum = crypto.createHash("sha256");
|
||||||
|
shasum.update(buf);
|
||||||
|
|
||||||
|
return shasum.digest("hex").toUpperCase().replace(/(.{2})(?!$)/g, "$1:");
|
||||||
|
});
|
||||||
|
|
||||||
|
fingerprints.push("6D:99:FB:26:5E:B1:C5:B3:74:47:65:FC:BC:64:8F:3C:D8:E1:BF:FA:FD:C4:C2:F9:9B:9D:47:CF:7F:F1:C2:4F"); // ISRG X1 cross-signed with DST X3
|
||||||
|
fingerprints.push("8B:05:B6:8C:C6:59:E5:ED:0F:CB:38:F2:C9:42:FB:FD:20:0E:6F:2F:F9:F8:5D:63:C6:99:4E:F5:E0:B0:27:01"); // ISRG X2 cross-signed with ISRG X1
|
||||||
|
|
||||||
|
return new Set(fingerprints);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.SHAKE256_LENGTH = 16;
|
module.exports.SHAKE256_LENGTH = 16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1082,3 +1124,30 @@ if (process.env.TEST_BACKEND) {
|
|||||||
return module.exports.__test[functionName];
|
return module.exports.__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.
|
||||||
|
*/
|
||||||
|
module.exports.axiosAbortSignal = (timeoutMs) => {
|
||||||
|
try {
|
||||||
|
// Just in case, as 0 timeout here will cause the request to be aborted immediately
|
||||||
|
if (!timeoutMs || timeoutMs <= 0) {
|
||||||
|
timeoutMs = 5000;
|
||||||
|
}
|
||||||
|
return AbortSignal.timeout(timeoutMs);
|
||||||
|
} catch (_) {
|
||||||
|
// v16-: AbortSignal.timeout is not supported
|
||||||
|
try {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
setTimeout(() => abortController.abort(), timeoutMs);
|
||||||
|
|
||||||
|
return abortController.signal;
|
||||||
|
} catch (_) {
|
||||||
|
// v15-: AbortController is not supported
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<select ref="select" v-model="model" class="form-select" :disabled="disabled">
|
<select ref="select" v-model="model" class="form-select" :disabled="disabled" :required="required">
|
||||||
<option v-for="option in options" :key="option" :value="option.value">{{ option.label }}</option>
|
<option v-for="option in options" :key="option" :value="option.value" :disabled="option.disabled">{{ option.label }}</option>
|
||||||
</select>
|
</select>
|
||||||
<a class="btn btn-outline-primary" @click="action()">
|
<a class="btn btn-outline-primary" :class="{ disabled: actionDisabled }" @click="action()">
|
||||||
<font-awesome-icon :icon="icon" />
|
<font-awesome-icon :icon="icon" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +50,22 @@ export default {
|
|||||||
action: {
|
action: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Whether the action button is disabled.
|
||||||
|
* @example true
|
||||||
|
*/
|
||||||
|
actionDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Whether the select field is required.
|
||||||
|
* @example true
|
||||||
|
*/
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: [ "update:modelValue" ],
|
emits: [ "update:modelValue" ],
|
||||||
|
@@ -70,7 +70,7 @@ export default {
|
|||||||
Confirm,
|
Confirm,
|
||||||
},
|
},
|
||||||
props: {},
|
props: {},
|
||||||
emits: [ "added" ],
|
emits: [ "added", "deleted" ],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
modal: null,
|
modal: null,
|
||||||
@@ -167,6 +167,7 @@ export default {
|
|||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
this.$emit("deleted", this.id);
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -369,6 +369,8 @@
|
|||||||
"Setup Docker Host": "Setup Docker Host",
|
"Setup Docker Host": "Setup Docker Host",
|
||||||
"Connection Type": "Connection Type",
|
"Connection Type": "Connection Type",
|
||||||
"Docker Daemon": "Docker Daemon",
|
"Docker Daemon": "Docker Daemon",
|
||||||
|
"noDockerHostMsg": "Not Available. Setup a Docker Host First.",
|
||||||
|
"DockerHostRequired": "Please set the Docker Host for this monitor.",
|
||||||
"deleteDockerHostMsg": "Are you sure want to delete this docker host for all monitors?",
|
"deleteDockerHostMsg": "Are you sure want to delete this docker host for all monitors?",
|
||||||
"socket": "Socket",
|
"socket": "Socket",
|
||||||
"tcp": "TCP / HTTP",
|
"tcp": "TCP / HTTP",
|
||||||
|
@@ -285,22 +285,17 @@
|
|||||||
<!-- Docker Host -->
|
<!-- Docker Host -->
|
||||||
<!-- For Docker Type -->
|
<!-- For Docker Type -->
|
||||||
<div v-if="monitor.type === 'docker'" class="my-3">
|
<div v-if="monitor.type === 'docker'" class="my-3">
|
||||||
<h2 class="mb-2">{{ $t("Docker Host") }}</h2>
|
<div class="mb-3">
|
||||||
<p v-if="$root.dockerHostList.length === 0">
|
|
||||||
{{ $t("Not available, please setup.") }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div v-else class="mb-3">
|
|
||||||
<label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
|
<label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
|
||||||
<select id="docket-host" v-model="monitor.docker_host" class="form-select">
|
<ActionSelect
|
||||||
<option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option>
|
v-model="monitor.docker_host"
|
||||||
</select>
|
:options="dockerHostOptionsList"
|
||||||
<a href="#" @click="$refs.dockerHostDialog.show(monitor.docker_host)">{{ $t("Edit") }}</a>
|
:disabled="$root.dockerHostList == null || $root.dockerHostList.length === 0"
|
||||||
|
:icon="'plus'"
|
||||||
|
:action="() => $refs.dockerHostDialog.show()"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()">
|
|
||||||
{{ $t("Setup Docker Host") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MQTT -->
|
<!-- MQTT -->
|
||||||
@@ -370,11 +365,20 @@
|
|||||||
<input id="connectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" required>
|
<input id="connectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="monitor.type === 'mysql'">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="mysql-password" class="form-label">{{ $t("Password") }}</label>
|
||||||
|
<!-- TODO: Rename monitor.radiusPassword to monitor.password for general use -->
|
||||||
|
<HiddenInput id="mysql-password" v-model="monitor.radiusPassword" autocomplete="false"></HiddenInput>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- SQL Server / PostgreSQL / MySQL -->
|
<!-- SQL Server / PostgreSQL / MySQL -->
|
||||||
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
||||||
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" :placeholder="$t('Example:', [ 'select getdate()' ])" required></textarea>
|
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" :placeholder="$t('Example:', [ 'SELECT 1' ])"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -843,6 +847,7 @@ import TagsManager from "../components/TagsManager.vue";
|
|||||||
import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts";
|
import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts";
|
||||||
import { hostNameRegexPattern } from "../util-frontend";
|
import { hostNameRegexPattern } from "../util-frontend";
|
||||||
import { sleep } from "../util";
|
import { sleep } from "../util";
|
||||||
|
import HiddenInput from "../components/HiddenInput.vue";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -881,11 +886,13 @@ const monitorDefaults = {
|
|||||||
mechanism: "None",
|
mechanism: "None",
|
||||||
},
|
},
|
||||||
kafkaProducerSsl: false,
|
kafkaProducerSsl: false,
|
||||||
|
kafkaProducerAllowAutoTopicCreation: false,
|
||||||
gamedigGivenPortOnly: true,
|
gamedigGivenPortOnly: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
HiddenInput,
|
||||||
ActionSelect,
|
ActionSelect,
|
||||||
ProxyDialog,
|
ProxyDialog,
|
||||||
CopyableInput,
|
CopyableInput,
|
||||||
@@ -1107,6 +1114,21 @@ message HealthCheckResponse {
|
|||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dockerHostOptionsList() {
|
||||||
|
if (this.$root.dockerHostList && this.$root.dockerHostList.length > 0) {
|
||||||
|
return this.$root.dockerHostList.map((host) => {
|
||||||
|
return {
|
||||||
|
label: host.name,
|
||||||
|
value: host.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return [{
|
||||||
|
label: this.$t("noDockerHostMsg"),
|
||||||
|
value: null,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"$root.proxyList"() {
|
"$root.proxyList"() {
|
||||||
@@ -1339,6 +1361,12 @@ message HealthCheckResponse {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.monitor.type === "docker") {
|
||||||
|
if (this.monitor.docker_host == null) {
|
||||||
|
toast.error(this.$t("DockerHostRequired"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user