Merge branch 'master' into mqtt2

# Conflicts:
#	server/database.js
#	server/util-server.js
pull/875/head
Louis Lam 3 years ago
commit 5fa62a888c

@ -22,14 +22,16 @@ module.exports = {
requireConfigFile: false, requireConfigFile: false,
}, },
rules: { rules: {
"yoda": "error",
eqeqeq: [ "warn", "smart" ],
"linebreak-style": [ "error", "unix" ], "linebreak-style": [ "error", "unix" ],
"camelcase": [ "warn", { "camelcase": [ "warn", {
"properties": "never", "properties": "never",
"ignoreImports": true "ignoreImports": true
}], }],
// override/add rules settings here, such as: "no-unused-vars": [ "warn", {
// 'vue/no-unused-vars': 'error' "args": "none"
"no-unused-vars": "warn", }],
indent: [ indent: [
"error", "error",
4, 4,
@ -44,10 +46,16 @@ module.exports = {
"vue/max-attributes-per-line": "off", "vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off", "vue/singleline-html-element-content-newline": "off",
"vue/html-self-closing": "off", "vue/html-self-closing": "off",
"vue/require-component-is": "off", // not allow is="style" https://github.com/vuejs/eslint-plugin-vue/issues/462#issuecomment-430234675
"vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly "vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly
"no-multi-spaces": [ "error", { "no-multi-spaces": [ "error", {
ignoreEOLComments: true, ignoreEOLComments: true,
}], }],
"array-bracket-spacing": [ "warn", "always", {
"singleValue": true,
"objectsInArrays": false,
"arraysInArrays": false
}],
"space-before-function-paren": [ "error", { "space-before-function-paren": [ "error", {
"anonymous": "always", "anonymous": "always",
"named": "never", "named": "never",

@ -0,0 +1,6 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
ALTER TABLE status_page ADD footer_text TEXT;
ALTER TABLE status_page ADD custom_css TEXT;
ALTER TABLE status_page ADD show_powered_by BOOLEAN NOT NULL DEFAULT 1;
COMMIT;

@ -5,7 +5,6 @@ const util = require("../../src/util");
util.polyfill(); util.polyfill();
const oldVersion = pkg.version;
const version = process.env.VERSION; const version = process.env.VERSION;
console.log("Beta Version: " + version); console.log("Beta Version: " + version);
@ -61,11 +60,3 @@ function tagExists(version) {
return res.stdout.toString().trim() === version; return res.stdout.toString().trim() === version;
} }
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

@ -1,7 +1,6 @@
const pkg = require("../package.json"); const pkg = require("../package.json");
const fs = require("fs"); const fs = require("fs");
const rmSync = require("./fs-rmSync.js"); const childProcess = require("child_process");
const child_process = require("child_process");
const util = require("../src/util"); const util = require("../src/util");
util.polyfill(); util.polyfill();
@ -42,7 +41,7 @@ if (! exists) {
function commit(version) { function commit(version) {
let msg = "Update to " + version; let msg = "Update to " + version;
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]); let res = childProcess.spawnSync("git", [ "commit", "-m", msg, "-a" ]);
let stdout = res.stdout.toString().trim(); let stdout = res.stdout.toString().trim();
console.log(stdout); console.log(stdout);
@ -52,7 +51,7 @@ function commit(version) {
} }
function tag(version) { function tag(version) {
let res = child_process.spawnSync("git", ["tag", version]); let res = childProcess.spawnSync("git", [ "tag", version ]);
console.log(res.stdout.toString().trim()); console.log(res.stdout.toString().trim());
} }
@ -67,7 +66,7 @@ function tagExists(version) {
throw new Error("invalid version"); throw new Error("invalid version");
} }
let res = child_process.spawnSync("git", ["tag", "-l", version]); let res = childProcess.spawnSync("git", [ "tag", "-l", version ]);
return res.stdout.toString().trim() === version; return res.stdout.toString().trim() === version;
} }

@ -1,4 +1,4 @@
const child_process = require("child_process"); const childProcess = require("child_process");
const fs = require("fs"); const fs = require("fs");
const newVersion = process.env.VERSION; const newVersion = process.env.VERSION;
@ -16,23 +16,23 @@ function updateWiki(newVersion) {
safeDelete(wikiDir); safeDelete(wikiDir);
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]); childProcess.spawnSync("git", [ "clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir ]);
let content = fs.readFileSync(howToUpdateFilename).toString(); let content = fs.readFileSync(howToUpdateFilename).toString();
// Replace the version: https://regex101.com/r/hmj2Bc/1 // Replace the version: https://regex101.com/r/hmj2Bc/1
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`); content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content); fs.writeFileSync(howToUpdateFilename, content);
child_process.spawnSync("git", ["add", "-A"], { childProcess.spawnSync("git", [ "add", "-A" ], {
cwd: wikiDir, cwd: wikiDir,
}); });
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], { childProcess.spawnSync("git", [ "commit", "-m", `Update to ${newVersion}` ], {
cwd: wikiDir, cwd: wikiDir,
}); });
console.log("Pushing to Github"); console.log("Pushing to Github");
child_process.spawnSync("git", ["push"], { childProcess.spawnSync("git", [ "push" ], {
cwd: wikiDir, cwd: wikiDir,
}); });

31
package-lock.json generated

@ -45,6 +45,7 @@
"password-hash": "~1.2.2", "password-hash": "~1.2.2",
"postcss-rtlcss": "~3.4.1", "postcss-rtlcss": "~3.4.1",
"postcss-scss": "~4.0.3", "postcss-scss": "~4.0.3",
"prismjs": "^1.27.0",
"prom-client": "~13.2.0", "prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.1", "prometheus-api-metrics": "~3.2.1",
"qrcode": "~1.5.0", "qrcode": "~1.5.0",
@ -64,6 +65,7 @@
"vue-i18n": "~9.1.9", "vue-i18n": "~9.1.9",
"vue-image-crop-upload": "~3.0.3", "vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2", "vue-multiselect": "~3.0.0-alpha.2",
"vue-prism-editor": "^2.0.0-alpha.2",
"vue-qrcode": "~1.0.0", "vue-qrcode": "~1.0.0",
"vue-router": "~4.0.14", "vue-router": "~4.0.14",
"vue-toastification": "~2.0.0-rc.5", "vue-toastification": "~2.0.0-rc.5",
@ -13012,6 +13014,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"engines": {
"node": ">=6"
}
},
"node_modules/proc-log": { "node_modules/proc-log": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz",
@ -15790,6 +15800,17 @@
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
} }
}, },
"node_modules/vue-prism-editor": {
"version": "2.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz",
"integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-qrcode": { "node_modules/vue-qrcode": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-1.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-1.0.0.tgz",
@ -26033,6 +26054,11 @@
} }
} }
}, },
"prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="
},
"proc-log": { "proc-log": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz",
@ -28156,6 +28182,11 @@
"resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0-alpha.2.tgz", "resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0-alpha.2.tgz",
"integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q==" "integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q=="
}, },
"vue-prism-editor": {
"version": "2.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz",
"integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w=="
},
"vue-qrcode": { "vue-qrcode": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-1.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-1.0.0.tgz",

@ -13,6 +13,7 @@
"install-legacy": "npm install --legacy-peer-deps", "install-legacy": "npm install --legacy-peer-deps",
"update-legacy": "npm update --legacy-peer-deps", "update-legacy": "npm update --legacy-peer-deps",
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .", "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
"lint-fix:js": "eslint --ext \".js,.vue\" --fix --ignore-path .gitignore .",
"lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore", "lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore",
"lint": "npm run lint:js && npm run lint:style", "lint": "npm run lint:js && npm run lint:style",
"dev": "vite --host --config ./config/vite.config.js", "dev": "vite --host --config ./config/vite.config.js",
@ -93,6 +94,7 @@
"password-hash": "~1.2.2", "password-hash": "~1.2.2",
"postcss-rtlcss": "~3.4.1", "postcss-rtlcss": "~3.4.1",
"postcss-scss": "~4.0.3", "postcss-scss": "~4.0.3",
"prismjs": "^1.27.0",
"prom-client": "~13.2.0", "prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.1", "prometheus-api-metrics": "~3.2.1",
"qrcode": "~1.5.0", "qrcode": "~1.5.0",
@ -112,6 +114,7 @@
"vue-i18n": "~9.1.9", "vue-i18n": "~9.1.9",
"vue-image-crop-upload": "~3.0.3", "vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2", "vue-multiselect": "~3.0.0-alpha.2",
"vue-prism-editor": "^2.0.0-alpha.2",
"vue-qrcode": "~1.0.0", "vue-qrcode": "~1.0.0",
"vue-router": "~4.0.14", "vue-router": "~4.0.14",
"vue-toastification": "~2.0.0-rc.5", "vue-toastification": "~2.0.0-rc.5",

@ -1,4 +1,3 @@
const { checkLogin } = require("./util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
class TwoFA { class TwoFA {

@ -56,6 +56,7 @@ class Database {
"patch-status-page.sql": true, "patch-status-page.sql": true,
"patch-proxy.sql": true, "patch-proxy.sql": true,
"patch-monitor-expiry-notification.sql": true, "patch-monitor-expiry-notification.sql": true,
"patch-status-page-footer-css.sql": true,
"patch-added-mqtt-monitor.sql": true, "patch-added-mqtt-monitor.sql": true,
} }

@ -9,7 +9,7 @@ const log = function (any) {
}; };
const exit = function (error) { const exit = function (error) {
if (error && error != 0) { if (error && error !== 0) {
process.exit(error); process.exit(error);
} else { } else {
if (parentPort) { if (parentPort) {

@ -42,7 +42,7 @@ class Monitor extends BeanModel {
/** /**
* Return an object that ready to parse to JSON * Return an object that ready to parse to JSON
*/ */
async toJSON() { async toJSON(includeSensitiveData = true) {
let notificationIDList = {}; let notificationIDList = {};
@ -56,15 +56,11 @@ class Monitor extends BeanModel {
const tags = await this.getTags(); const tags = await this.getTags();
return { let data = {
id: this.id, id: this.id,
name: this.name, name: this.name,
url: this.url, url: this.url,
method: this.method, method: this.method,
body: this.body,
headers: this.headers,
basic_auth_user: this.basic_auth_user,
basic_auth_pass: this.basic_auth_pass,
hostname: this.hostname, hostname: this.hostname,
port: this.port, port: this.port,
maxretries: this.maxretries, maxretries: this.maxretries,
@ -82,7 +78,6 @@ class Monitor extends BeanModel {
dns_resolve_type: this.dns_resolve_type, dns_resolve_type: this.dns_resolve_type,
dns_resolve_server: this.dns_resolve_server, dns_resolve_server: this.dns_resolve_server,
dns_last_result: this.dns_last_result, dns_last_result: this.dns_last_result,
pushToken: this.pushToken,
proxyId: this.proxy_id, proxyId: this.proxy_id,
notificationIDList, notificationIDList,
tags: tags, tags: tags,
@ -91,6 +86,19 @@ class Monitor extends BeanModel {
mqttTopic: this.mqttTopic, mqttTopic: this.mqttTopic,
mqttSuccessMessage: this.mqttSuccessMessage mqttSuccessMessage: this.mqttSuccessMessage
}; };
if (includeSensitiveData) {
data = {
...data,
headers: this.headers,
body: this.body,
basic_auth_user: this.basic_auth_user,
basic_auth_pass: this.basic_auth_pass,
pushToken: this.pushToken,
};
}
return data;
} }
async getTags() { async getTags() {
@ -266,7 +274,7 @@ class Monitor extends BeanModel {
log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
} }
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) { if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID === this.id) {
log.info("monitor", res.data); log.info("monitor", res.data);
} }
@ -306,24 +314,24 @@ class Monitor extends BeanModel {
let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.dns_resolve_type); let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.dns_resolve_type);
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;
if (this.dns_resolve_type == "A" || this.dns_resolve_type == "AAAA" || this.dns_resolve_type == "TXT") { if (this.dns_resolve_type === "A" || this.dns_resolve_type === "AAAA" || this.dns_resolve_type === "TXT") {
dnsMessage += "Records: "; dnsMessage += "Records: ";
dnsMessage += dnsRes.join(" | "); dnsMessage += dnsRes.join(" | ");
} else if (this.dns_resolve_type == "CNAME" || this.dns_resolve_type == "PTR") { } else if (this.dns_resolve_type === "CNAME" || this.dns_resolve_type === "PTR") {
dnsMessage = dnsRes[0]; dnsMessage = dnsRes[0];
} else if (this.dns_resolve_type == "CAA") { } else if (this.dns_resolve_type === "CAA") {
dnsMessage = dnsRes[0].issue; dnsMessage = dnsRes[0].issue;
} else if (this.dns_resolve_type == "MX") { } else if (this.dns_resolve_type === "MX") {
dnsRes.forEach(record => { dnsRes.forEach(record => {
dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `; dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `;
}); });
dnsMessage = dnsMessage.slice(0, -2); dnsMessage = dnsMessage.slice(0, -2);
} else if (this.dns_resolve_type == "NS") { } else if (this.dns_resolve_type === "NS") {
dnsMessage += "Servers: "; dnsMessage += "Servers: ";
dnsMessage += dnsRes.join(" | "); dnsMessage += dnsRes.join(" | ");
} else if (this.dns_resolve_type == "SOA") { } else if (this.dns_resolve_type === "SOA") {
dnsMessage += `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`; dnsMessage += `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`;
} else if (this.dns_resolve_type == "SRV") { } else if (this.dns_resolve_type === "SRV") {
dnsRes.forEach(record => { dnsRes.forEach(record => {
dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `; dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `;
}); });
@ -620,11 +628,11 @@ class Monitor extends BeanModel {
} }
static async sendCertInfo(io, monitorID, userID) { static async sendCertInfo(io, monitorID, userID) {
let tls_info = await R.findOne("monitor_tls_info", "monitor_id = ?", [ let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
monitorID, monitorID,
]); ]);
if (tls_info != null) { if (tlsInfo != null) {
io.to(userID).emit("certInfo", monitorID, tls_info.info_json); io.to(userID).emit("certInfo", monitorID, tlsInfo.info_json);
} }
} }
@ -738,7 +746,7 @@ class Monitor extends BeanModel {
for (let notification of notificationList) { for (let notification of notificationList) {
try { try {
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON()); await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), bean.toJSON());
} catch (e) { } catch (e) {
log.error("monitor", "Cannot send notification to " + notification.name); log.error("monitor", "Cannot send notification to " + notification.name);
log.error("monitor", e); log.error("monitor", e);

@ -92,6 +92,9 @@ class StatusPage extends BeanModel {
published: !!this.published, published: !!this.published,
showTags: !!this.show_tags, showTags: !!this.show_tags,
domainNameList: this.getDomainNameList(), domainNameList: this.getDomainNameList(),
customCSS: this.custom_css,
footerText: this.footer_text,
showPoweredBy: !!this.show_powered_by,
}; };
} }
@ -104,6 +107,9 @@ class StatusPage extends BeanModel {
theme: this.theme, theme: this.theme,
published: !!this.published, published: !!this.published,
showTags: !!this.show_tags, showTags: !!this.show_tags,
customCSS: this.custom_css,
footerText: this.footer_text,
showPoweredBy: !!this.show_powered_by,
}; };
} }

@ -46,11 +46,11 @@ class Alerta extends NotificationProvider {
resource: monitorJSON["name"], resource: monitorJSON["name"],
}, data ); }, data );
if (heartbeatJSON["status"] == DOWN) { if (heartbeatJSON["status"] === DOWN) {
datadup.severity = notification.alertaAlertState; // critical datadup.severity = notification.alertaAlertState; // critical
datadup.text = "Service " + monitorJSON["type"] + " is down."; datadup.text = "Service " + monitorJSON["type"] + " is down.";
await axios.post(alertaUrl, datadup, config); await axios.post(alertaUrl, datadup, config);
} else if (heartbeatJSON["status"] == UP) { } else if (heartbeatJSON["status"] === UP) {
datadup.severity = notification.alertaRecoverState; // cleaned datadup.severity = notification.alertaRecoverState; // cleaned
datadup.text = "Service " + monitorJSON["type"] + " is up."; datadup.text = "Service " + monitorJSON["type"] + " is up.";
await axios.post(alertaUrl, datadup, config); await axios.post(alertaUrl, datadup, config);

@ -64,7 +64,7 @@ class AliyunSMS extends NotificationProvider {
}; };
let result = await axios(config); let result = await axios(config);
if (result.data.Message == "OK") { if (result.data.Message === "OK") {
return true; return true;
} }
return false; return false;

@ -1,12 +1,12 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const child_process = require("child_process"); const childProcess = require("child_process");
class Apprise extends NotificationProvider { class Apprise extends NotificationProvider {
name = "apprise"; name = "apprise";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]); let s = childProcess.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL ]);
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";

@ -28,12 +28,12 @@ class Bark extends NotificationProvider {
barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1); barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1);
} }
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
let title = "UptimeKuma Monitor Up"; let title = "UptimeKuma Monitor Up";
return await this.postNotification(title, msg, barkEndpoint); return await this.postNotification(title, msg, barkEndpoint);
} }
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
let title = "UptimeKuma Monitor Down"; let title = "UptimeKuma Monitor Down";
return await this.postNotification(title, msg, barkEndpoint); return await this.postNotification(title, msg, barkEndpoint);
} }

@ -50,7 +50,7 @@ class DingDing extends NotificationProvider {
}; };
let result = await axios(config); let result = await axios(config);
if (result.data.errmsg == "ok") { if (result.data.errmsg === "ok") {
return true; return true;
} }
return false; return false;

@ -35,7 +35,7 @@ class Discord extends NotificationProvider {
} }
// If heartbeatJSON is not null, we go into the normal alerting loop. // If heartbeatJSON is not null, we go into the normal alerting loop.
if (heartbeatJSON["status"] == DOWN) { if (heartbeatJSON["status"] === DOWN) {
let discorddowndata = { let discorddowndata = {
username: discordDisplayName, username: discordDisplayName,
embeds: [{ embeds: [{
@ -70,7 +70,7 @@ class Discord extends NotificationProvider {
await axios.post(notification.discordWebhookUrl, discorddowndata); await axios.post(notification.discordWebhookUrl, discorddowndata);
return okMsg; return okMsg;
} else if (heartbeatJSON["status"] == UP) { } else if (heartbeatJSON["status"] === UP) {
let discordupdata = { let discordupdata = {
username: discordDisplayName, username: discordDisplayName,
embeds: [{ embeds: [{

@ -21,7 +21,7 @@ class Feishu extends NotificationProvider {
return okMsg; return okMsg;
} }
if (heartbeatJSON["status"] == DOWN) { if (heartbeatJSON["status"] === DOWN) {
let downdata = { let downdata = {
msg_type: "post", msg_type: "post",
content: { content: {
@ -48,7 +48,7 @@ class Feishu extends NotificationProvider {
return okMsg; return okMsg;
} }
if (heartbeatJSON["status"] == UP) { if (heartbeatJSON["status"] === UP) {
let updata = { let updata = {
msg_type: "post", msg_type: "post",
content: { content: {

@ -27,7 +27,7 @@ class Line extends NotificationProvider {
] ]
}; };
await axios.post(lineAPIUrl, testMessage, config); await axios.post(lineAPIUrl, testMessage, config);
} else if (heartbeatJSON["status"] == DOWN) { } else if (heartbeatJSON["status"] === DOWN) {
let downMessage = { let downMessage = {
"to": notification.lineUserID, "to": notification.lineUserID,
"messages": [ "messages": [
@ -38,7 +38,7 @@ class Line extends NotificationProvider {
] ]
}; };
await axios.post(lineAPIUrl, downMessage, config); await axios.post(lineAPIUrl, downMessage, config);
} else if (heartbeatJSON["status"] == UP) { } else if (heartbeatJSON["status"] === UP) {
let upMessage = { let upMessage = {
"to": notification.lineUserID, "to": notification.lineUserID,
"messages": [ "messages": [

@ -20,7 +20,7 @@ class LunaSea extends NotificationProvider {
return okMsg; return okMsg;
} }
if (heartbeatJSON["status"] == DOWN) { if (heartbeatJSON["status"] === DOWN) {
let downdata = { let downdata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
@ -29,7 +29,7 @@ class LunaSea extends NotificationProvider {
return okMsg; return okMsg;
} }
if (heartbeatJSON["status"] == UP) { if (heartbeatJSON["status"] === UP) {
let updata = { let updata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],

@ -29,7 +29,7 @@ class Mattermost extends NotificationProvider {
const mattermostIconEmoji = notification.mattermosticonemo; const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl; const mattermostIconUrl = notification.mattermosticonurl;
if (heartbeatJSON["status"] == DOWN) { if (heartbeatJSON["status"] === DOWN) {
let mattermostdowndata = { let mattermostdowndata = {
username: mattermostUserName, username: mattermostUserName,
text: "Uptime Kuma Alert", text: "Uptime Kuma Alert",
@ -73,7 +73,7 @@ class Mattermost extends NotificationProvider {
mattermostdowndata mattermostdowndata
); );
return okMsg; return okMsg;
} else if (heartbeatJSON["status"] == UP) { } else if (heartbeatJSON["status"] === UP) {
let mattermostupdata = { let mattermostupdata = {
username: mattermostUserName, username: mattermostUserName,
text: "Uptime Kuma Alert", text: "Uptime Kuma Alert",

@ -10,7 +10,7 @@ class Octopush extends NotificationProvider {
try { try {
// Default - V2 // Default - V2
if (notification.octopushVersion == 2 || !notification.octopushVersion) { if (notification.octopushVersion === 2 || !notification.octopushVersion) {
let config = { let config = {
headers: { headers: {
"api-key": notification.octopushAPIKey, "api-key": notification.octopushAPIKey,
@ -31,13 +31,13 @@ class Octopush extends NotificationProvider {
"sender": notification.octopushSenderName "sender": notification.octopushSenderName
}; };
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config); await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config);
} else if (notification.octopushVersion == 1) { } else if (notification.octopushVersion === 1) {
let data = { let data = {
"user_login": notification.octopushDMLogin, "user_login": notification.octopushDMLogin,
"api_key": notification.octopushDMAPIKey, "api_key": notification.octopushDMAPIKey,
"sms_recipients": notification.octopushDMPhoneNumber, "sms_recipients": notification.octopushDMPhoneNumber,
"sms_sender": notification.octopushDMSenderName, "sms_sender": notification.octopushDMSenderName,
"sms_type": (notification.octopushDMSMSType == "sms_premium") ? "FR" : "XXX", "sms_type": (notification.octopushDMSMSType === "sms_premium") ? "FR" : "XXX",
"transactional": "1", "transactional": "1",
//octopush not supporting non ascii char //octopush not supporting non ascii char
"sms_text": msg.replace(/[^\x00-\x7F]/g, ""), "sms_text": msg.replace(/[^\x00-\x7F]/g, ""),

@ -27,7 +27,7 @@ class OneBot extends NotificationProvider {
"auto_escape": true, "auto_escape": true,
"message": pushText, "message": pushText,
}; };
if (notification.msgType == "group") { if (notification.msgType === "group") {
data["message_type"] = "group"; data["message_type"] = "group";
data["group_id"] = notification.recieverId; data["group_id"] = notification.recieverId;
} else { } else {

@ -25,14 +25,14 @@ class Pushbullet extends NotificationProvider {
"body": "Testing Successful.", "body": "Testing Successful.",
}; };
await axios.post(pushbulletUrl, testdata, config); await axios.post(pushbulletUrl, testdata, config);
} else if (heartbeatJSON["status"] == DOWN) { } else if (heartbeatJSON["status"] === DOWN) {
let downdata = { let downdata = {
"type": "note", "type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
}; };
await axios.post(pushbulletUrl, downdata, config); await axios.post(pushbulletUrl, downdata, config);
} else if (heartbeatJSON["status"] == UP) { } else if (heartbeatJSON["status"] === UP) {
let updata = { let updata = {
"type": "note", "type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],

@ -0,0 +1,52 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class PushDeer extends NotificationProvider {
name = "PushDeer";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let pushdeerlink = "https://api2.pushdeer.com/message/push";
let valid = msg != null && monitorJSON != null && heartbeatJSON != null;
let title;
if (valid && heartbeatJSON.status === UP) {
title = "## Uptime Kuma: " + monitorJSON.name + " up";
} else if (valid && heartbeatJSON.status === DOWN) {
title = "## Uptime Kuma: " + monitorJSON.name + " down";
} else {
title = "## Uptime Kuma Message";
}
let data = {
"pushkey": notification.pushdeerKey,
"text": title,
"desp": msg.replace(/\n/g, "\n\n"),
"type": "markdown",
};
try {
let res = await axios.post(pushdeerlink, data);
if ("error" in res.data) {
let error = res.data.error;
this.throwGeneralAxiosError(error);
}
if (res.data.content.result.length === 0) {
let error = "Invalid PushDeer key";
this.throwGeneralAxiosError(error);
} else if (JSON.parse(res.data.content.result[0]).success !== "ok") {
let error = "Unknown error";
this.throwGeneralAxiosError(error);
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = PushDeer;

@ -1,6 +1,6 @@
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util"); const { DOWN } = require("../../src/util");
class SMTP extends NotificationProvider { class SMTP extends NotificationProvider {

@ -26,10 +26,10 @@ class WeCom extends NotificationProvider {
composeMessage(heartbeatJSON, msg) { composeMessage(heartbeatJSON, msg) {
let title; let title;
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) { if (msg != null) {

@ -32,6 +32,7 @@ const GoogleChat = require("./notification-providers/google-chat");
const Gorush = require("./notification-providers/gorush"); const Gorush = require("./notification-providers/gorush");
const Alerta = require("./notification-providers/alerta"); const Alerta = require("./notification-providers/alerta");
const OneBot = require("./notification-providers/onebot"); const OneBot = require("./notification-providers/onebot");
const PushDeer = require("./notification-providers/pushdeer");
class Notification { class Notification {
@ -75,6 +76,7 @@ class Notification {
new Gorush(), new Gorush(),
new Alerta(), new Alerta(),
new OneBot(), new OneBot(),
new PushDeer(),
]; ];
for (let item of list) { for (let item of list) {

@ -9,24 +9,24 @@ const commonLabels = [
"monitor_port", "monitor_port",
]; ];
const monitor_cert_days_remaining = new PrometheusClient.Gauge({ const 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: commonLabels labelNames: commonLabels
}); });
const monitor_cert_is_valid = new PrometheusClient.Gauge({ const 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: commonLabels labelNames: commonLabels
}); });
const monitor_response_time = new PrometheusClient.Gauge({ const monitorResponseTime = new PrometheusClient.Gauge({
name: "monitor_response_time", name: "monitor_response_time",
help: "Monitor Response Time (ms)", help: "Monitor Response Time (ms)",
labelNames: commonLabels labelNames: commonLabels
}); });
const monitor_status = new PrometheusClient.Gauge({ const monitorStatus = new PrometheusClient.Gauge({
name: "monitor_status", name: "monitor_status",
help: "Monitor Status (1 = UP, 0= DOWN)", help: "Monitor Status (1 = UP, 0= DOWN)",
labelNames: commonLabels labelNames: commonLabels
@ -49,13 +49,13 @@ class Prometheus {
if (typeof tlsInfo !== "undefined") { if (typeof tlsInfo !== "undefined") {
try { try {
let isValid = 0; let isValid;
if (tlsInfo.valid == true) { if (tlsInfo.valid === true) {
isValid = 1; isValid = 1;
} else { } else {
isValid = 0; isValid = 0;
} }
monitor_cert_is_valid.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);
@ -63,7 +63,7 @@ class Prometheus {
try { try {
if (tlsInfo.certInfo != null) { if (tlsInfo.certInfo != null) {
monitor_cert_days_remaining.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");
@ -72,7 +72,7 @@ class Prometheus {
} }
try { try {
monitor_status.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);
@ -80,10 +80,10 @@ class Prometheus {
try { try {
if (typeof heartbeat.ping === "number") { if (typeof heartbeat.ping === "number") {
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping); monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
} else { } else {
// Is it good? // Is it good?
monitor_response_time.set(this.monitorLabelValues, -1); monitorResponseTime.set(this.monitorLabelValues, -1);
} }
} catch (e) { } catch (e) {
log.error("prometheus", "Caught error"); log.error("prometheus", "Caught error");
@ -93,10 +93,10 @@ class Prometheus {
remove() { remove() {
try { try {
monitor_cert_days_remaining.remove(this.monitorLabelValues); monitorCertDaysRemaining.remove(this.monitorLabelValues);
monitor_cert_is_valid.remove(this.monitorLabelValues); monitorCertIsValid.remove(this.monitorLabelValues);
monitor_response_time.remove(this.monitorLabelValues); monitorResponseTime.remove(this.monitorLabelValues);
monitor_status.remove(this.monitorLabelValues); monitorStatus.remove(this.monitorLabelValues);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

@ -1,5 +1,5 @@
let express = require("express"); let express = require("express");
const { allowDevAllOrigin, getSettings, setting } = require("../util-server"); const { allowDevAllOrigin } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const server = require("../server"); const server = require("../server");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
@ -195,14 +195,6 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
} }
}); });
/**
* Default is published
* @returns {Promise<boolean>}
*/
async function isPublished() {
return true;
}
function send403(res, msg = "") { function send403(res, msg = "") {
res.status(403).json({ res.status(403).json({
"status": "fail", "status": "fail",

@ -11,7 +11,7 @@ if (nodeVersion < requiredVersion) {
} }
const args = require("args-parser")(process.argv); const args = require("args-parser")(process.argv);
const { sleep, log, getRandomInt, genSecret, debug } = require("../src/util"); const { sleep, log, getRandomInt, genSecret, debug, isDev } = require("../src/util");
const config = require("./config"); const config = require("./config");
log.info("server", "Welcome to Uptime Kuma"); log.info("server", "Welcome to Uptime Kuma");
@ -119,7 +119,7 @@ const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined; const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
// 2FA / notp verification defaults // 2FA / notp verification defaults
const twofa_verification_opts = { const twoFAVerifyOptions = {
"window": 1, "window": 1,
"time": 30 "time": 30
}; };
@ -175,6 +175,7 @@ app.use(function (req, res, next) {
/** /**
* Total WebSocket client connected to server currently, no actual use * Total WebSocket client connected to server currently, no actual use
*
* @type {number} * @type {number}
*/ */
let totalClient = 0; let totalClient = 0;
@ -234,6 +235,13 @@ try {
} }
}); });
if (isDev) {
app.post("/test-webhook", async (request, response) => {
log.debug("test", request.body);
response.send("OK");
});
}
// 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:";
@ -379,7 +387,7 @@ try {
} }
if (data.token) { if (data.token) {
let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts); let verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
if (user.twofa_last_token !== data.token && verify) { if (user.twofa_last_token !== data.token && verify) {
afterLogin(socket, user); afterLogin(socket, user);
@ -546,7 +554,7 @@ try {
socket.userID, socket.userID,
]); ]);
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts); let verify = notp.totp.verify(token, user.twofa_secret, twoFAVerifyOptions);
if (user.twofa_last_token !== token && verify) { if (user.twofa_last_token !== token && verify) {
callback({ callback({

@ -155,6 +155,9 @@ module.exports.statusPageSocketHandler = (socket) => {
//statusPage.search_engine_index = ; //statusPage.search_engine_index = ;
statusPage.show_tags = config.showTags; statusPage.show_tags = config.showTags;
//statusPage.password = null; //statusPage.password = null;
statusPage.footer_text = config.footerText;
statusPage.custom_css = config.customCSS;
statusPage.show_powered_by = config.showPoweredBy;
statusPage.modified_date = R.isoDateTime(); statusPage.modified_date = R.isoDateTime();
await R.store(statusPage); await R.store(statusPage);

@ -146,11 +146,11 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
}); });
}; };
exports.dnsResolve = function (hostname, resolver_server, rrtype) { exports.dnsResolve = function (hostname, resolverServer, rrtype) {
const resolver = new Resolver(); const resolver = new Resolver();
resolver.setServers([resolver_server]); resolver.setServers([ resolverServer ]);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (rrtype == "PTR") { if (rrtype === "PTR") {
resolver.reverse(hostname, (err, records) => { resolver.reverse(hostname, (err, records) => {
if (err) { if (err) {
reject(err); reject(err);
@ -315,19 +315,19 @@ exports.checkCertificate = function (res) {
// Return: true if the status code is within the accepted ranges, false otherwise // Return: true if the status code is within the accepted ranges, false otherwise
// Will throw an error if the provided status code is not a valid range string or code string // Will throw an error if the provided status code is not a valid range string or code string
exports.checkStatusCode = function (status, accepted_codes) { exports.checkStatusCode = function (status, acceptedCodes) {
if (accepted_codes == null || accepted_codes.length === 0) { if (acceptedCodes == null || acceptedCodes.length === 0) {
return false; return false;
} }
for (const code_range of accepted_codes) { for (const codeRange of acceptedCodes) {
const code_range_split = code_range.split("-").map(string => parseInt(string)); const codeRangeSplit = codeRange.split("-").map(string => parseInt(string));
if (code_range_split.length === 1) { if (codeRangeSplit.length === 1) {
if (status === code_range_split[0]) { if (status === codeRangeSplit[0]) {
return true; return true;
} }
} else if (code_range_split.length === 2) { } else if (codeRangeSplit.length === 2) {
if (status >= code_range_split[0] && status <= code_range_split[1]) { if (status >= codeRangeSplit[0] && status <= codeRangeSplit[1]) {
return true; return true;
} }
} else { } else {

@ -469,6 +469,10 @@ textarea.form-control {
color: $primary; color: $primary;
} }
.prism-editor__textarea {
outline: none !important;
}
// Localization // Localization
@import "localization.scss"; @import "localization.scss";

@ -42,6 +42,7 @@ export default {
default: "No", default: "No",
}, },
}, },
emits: [ "yes" ],
data: () => ({ data: () => ({
modal: null, modal: null,
}), }),

@ -57,6 +57,7 @@ export default {
default: undefined, default: undefined,
}, },
}, },
emits: [ "update:modelValue" ],
data() { data() {
return { return {
visibility: "password", visibility: "password",

@ -48,6 +48,7 @@ export default {
default: undefined, default: undefined,
}, },
}, },
emits: [ "update:modelValue" ],
data() { data() {
return { return {
visibility: "password", visibility: "password",

@ -220,6 +220,7 @@ export default {
if (newPeriod == "0") { if (newPeriod == "0") {
newPeriod = null; newPeriod = null;
this.heartbeatList = null; this.heartbeatList = null;
this.$root.storage().removeItem(`chart-period-${this.monitorId}`);
} else { } else {
this.loading = true; this.loading = true;
@ -228,6 +229,7 @@ export default {
toast.error(res.msg); toast.error(res.msg);
} else { } else {
this.heartbeatList = res.data; this.heartbeatList = res.data;
this.$root.storage()[`chart-period-${this.monitorId}`] = newPeriod;
} }
this.loading = false; this.loading = false;
}); });
@ -248,6 +250,12 @@ export default {
}, },
{ deep: true } { deep: true }
); );
// Load chart period from storage if saved
let period = this.$root.storage()[`chart-period-${this.monitorId}`];
if (period != null) {
this.chartPeriodHrs = Math.min(period, 6);
}
} }
}; };
</script> </script>

@ -0,0 +1,19 @@
<template>
<div class="mb-3">
<label for="pushdeer-key" class="form-label">{{ $t("PushDeer Key") }}</label>
<HiddenInput id="pushdeer-key" v-model="$parent.notification.pushdeerKey" :required="true" autocomplete="one-time-code" placeholder="PDUxxxx"></HiddenInput>
</div>
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="http://www.pushdeer.com/" rel="noopener noreferrer" target="_blank">http://www.pushdeer.com/</a>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

@ -30,6 +30,7 @@ import GoogleChat from "./GoogleChat.vue";
import Gorush from "./Gorush.vue"; import Gorush from "./Gorush.vue";
import Alerta from "./Alerta.vue"; import Alerta from "./Alerta.vue";
import OneBot from "./OneBot.vue"; import OneBot from "./OneBot.vue";
import PushDeer from "./PushDeer.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -69,6 +70,7 @@ const NotificationFormList = {
"gorush": Gorush, "gorush": Gorush,
"alerta": Alerta, "alerta": Alerta,
"OneBot": OneBot, "OneBot": OneBot,
"PushDeer": PushDeer,
}; };
export default NotificationFormList; export default NotificationFormList;

@ -197,7 +197,7 @@ export default {
line: "Line Messenger", line: "Line Messenger",
mattermost: "Mattermost", mattermost: "Mattermost",
"Status Page": "Статус страница", "Status Page": "Статус страница",
"Status Pages": "Статус страница", "Status Pages": "Статус страници",
"Primary Base URL": "Основен базов URL адрес", "Primary Base URL": "Основен базов URL адрес",
"Push URL": "Генериран Push URL адрес", "Push URL": "Генериран Push URL адрес",
needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди", needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди",
@ -371,4 +371,75 @@ export default {
alertaAlertState: "Състояние на тревога", alertaAlertState: "Състояние на тревога",
alertaRecoverState: "Състояние на възстановяване", alertaRecoverState: "Състояние на възстановяване",
deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?", deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?",
Proxies: "Проксита",
default: "По подразбиране",
enabled: "Включено",
setAsDefault: "Зададен по подразбиране",
deleteProxyMsg: "Сигурни ли сте, че желаете да изтриете това прокси за всички монитори?",
proxyDescription: "Прокситата трябва да бъдат зададени към монитор за да функционират.",
enableProxyDescription: "Това прокси няма да има ефект върху заявките за мониторинг, докато не бъде активирано. Може да контролирате временното деактивиране на проксито от всички монитори чрез статуса на активиране.",
setAsDefaultProxyDescription: "Това проки ще бъде включено по подразбиране за новите монитори. Може да го изключите по отделно за всеки един монитор.",
"Certificate Chain": "Верига на сертификата",
Valid: "Валиден",
Invalid: "Невалиден",
AccessKeyId: "ID на ключ за достъп",
SecretAccessKey: "Тайна на ключа за достъп",
PhoneNumbers: "Телефонни номера",
TemplateCode: "Шаблон Код",
SignName: "Знак име",
"Sms template must contain parameters: ": "SMS шаблонът трябва да съдържа следните параметри: ",
"Bark Endpoint": "Bark крайна точка",
WebHookUrl: "URL адрес на уеб кука",
SecretKey: "Таен ключ",
"For safety, must use secret key": "За сигурност, трябва да се използва таен ключ",
"Device Token": "Токен за устройство",
Platform: "Платформа",
iOS: "iOS",
Android: "Android",
Huawei: "Huawei",
High: "Висок",
Retry: "Повтори",
Topic: "Тема",
"WeCom Bot Key": "WeCom бот ключ",
"Setup Proxy": "Настройка за прокси",
"Proxy Protocol": "Прокси протокол",
"Proxy Server": "Прокси сървър",
"Proxy server has authentication": "Прокси сървърът е с удостоверяване",
User: "Потребител",
Installed: "Инсталиран",
"Not installed": "Не е инсталиран",
Running: "Работи",
"Not running": "Не работи",
"Remove Token": "Премахни токен",
Start: "Старт",
Stop: "Стоп",
"Uptime Kuma": "Uptime Kuma",
"Add New Status Page": "Добави нова статус страница",
Slug: "Слъг",
"Accept characters:": "Приеми символи:",
startOrEndWithOnly: "Започва или завършва само с {0}",
"No consecutive dashes": "Без последователни тирета",
Next: "Следващ",
"The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
"No Proxy": "Без прокси",
"HTTP Basic Auth": "HTTP основно удостоверяване",
"New Status Page": "Нова статус страница",
"Page Not Found": "Страницата не е открита",
"Reverse Proxy": "Ревърс прокси",
Backup: "Архивиране",
About: "Относно",
wayToGetCloudflaredURL: "(Свалете \"cloudflared\" от {0})",
cloudflareWebsite: "Cloudflare уебсайт",
"Message:": "Съобщение:",
"Don't know how to get the token? Please read the guide:": "Не знаете как да вземете токен? Моля, прочетете ръководството:",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Текущата връзка може да прекъсне ако в момента сте свързани чрез \"Cloudflare Tunnel\". Сигурни ли сте, че желаете да го спрете? Въведете Вашата текуща парола за да потвърдите.",
"Other Software": "Друг софтуер",
"For example: nginx, Apache and Traefik.": "Например: Nginx, Apache и Traefik.",
"Please read": "Моля, прочетете",
"Subject:": "Тема:",
"Valid To:": "Валиден до:",
"Days Remaining:": "Оставащи дни:",
"Issuer:": "Издател:",
"Fingerprint:": "Пръстов отпечатък:",
"No status pages": "Няма статус страници",
}; };

@ -337,7 +337,7 @@ export default {
"Hide Tags": "Tags ausblenden", "Hide Tags": "Tags ausblenden",
Description: "Beschreibung", Description: "Beschreibung",
"No monitors available.": "Keine Monitore verfügbar.", "No monitors available.": "Keine Monitore verfügbar.",
"Add one": "Füge eins hinzu", "Add one": "Hinzufügen",
"No Monitors": "Keine Monitore", "No Monitors": "Keine Monitore",
"Untitled Group": "Gruppe ohne Titel", "Untitled Group": "Gruppe ohne Titel",
Services: "Dienste", Services: "Dienste",
@ -442,4 +442,7 @@ export default {
"Issuer:": "Aussteller:", "Issuer:": "Aussteller:",
"Fingerprint:": "Fingerabdruck:", "Fingerprint:": "Fingerabdruck:",
"No status pages": "Keine Status-Seiten", "No status pages": "Keine Status-Seiten",
Customize: "Anpassen",
"Custom Footer": "Eigener Footer",
"Custom CSS": "Eigenes CSS",
}; };

@ -358,6 +358,9 @@ export default {
serwersmsPhoneNumber: "Phone number", serwersmsPhoneNumber: "Phone number",
serwersmsSenderName: "SMS Sender Name (registered via customer portal)", serwersmsSenderName: "SMS Sender Name (registered via customer portal)",
stackfield: "Stackfield", stackfield: "Stackfield",
Customize: "Customize",
"Custom Footer": "Custom Footer",
"Custom CSS": "Custom CSS",
smtpDkimSettings: "DKIM Settings", smtpDkimSettings: "DKIM Settings",
smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.",
documentation: "documentation", documentation: "documentation",
@ -455,4 +458,5 @@ export default {
onebotPrivateMessage: "Private", onebotPrivateMessage: "Private",
onebotUserOrGroupId: "Group/User ID", onebotUserOrGroupId: "Group/User ID",
onebotSafetyTips: "For safety, must set access token", onebotSafetyTips: "For safety, must set access token",
"PushDeer Key": "PushDeer Key",
}; };

@ -1,6 +1,6 @@
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import jwt_decode from "jwt-decode"; import jwtDecode from "jwt-decode";
import Favico from "favico.js"; import Favico from "favico.js";
const toast = useToast(); const toast = useToast();
@ -266,7 +266,7 @@ export default {
const jwtToken = this.$root.storage().token; const jwtToken = this.$root.storage().token;
if (jwtToken && jwtToken !== "autoLogin") { if (jwtToken && jwtToken !== "autoLogin") {
return jwt_decode(jwtToken); return jwtDecode(jwtToken);
} }
return undefined; return undefined;
}, },

@ -16,11 +16,18 @@
<input id="title" v-model="config.title" type="text" class="form-control"> <input id="title" v-model="config.title" type="text" class="form-control">
</div> </div>
<!-- Description -->
<div class="my-3"> <div class="my-3">
<label for="description" class="form-label">{{ $t("Description") }}</label> <label for="description" class="form-label">{{ $t("Description") }}</label>
<textarea id="description" v-model="config.description" class="form-control"></textarea> <textarea id="description" v-model="config.description" class="form-control"></textarea>
</div> </div>
<!-- Footer Text -->
<div class="my-3">
<label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label>
<textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea>
</div>
<div class="my-3 form-check form-switch"> <div class="my-3 form-check form-switch">
<input id="switch-theme" v-model="config.theme" class="form-check-input" type="checkbox" true-value="dark" false-value="light"> <input id="switch-theme" v-model="config.theme" class="form-check-input" type="checkbox" true-value="dark" false-value="light">
<label class="form-check-label" for="switch-theme">{{ $t("Switch to Dark Theme") }}</label> <label class="form-check-label" for="switch-theme">{{ $t("Switch to Dark Theme") }}</label>
@ -31,6 +38,12 @@
<label class="form-check-label" for="showTags">{{ $t("Show Tags") }}</label> <label class="form-check-label" for="showTags">{{ $t("Show Tags") }}</label>
</div> </div>
<!-- Show Powered By -->
<div class="my-3 form-check form-switch">
<input id="show-powered-by" v-model="config.showPoweredBy" class="form-check-input" type="checkbox">
<label class="form-check-label" for="show-powered-by">{{ $t("Show Powered By") }}</label>
</div>
<div v-if="false" class="my-3"> <div v-if="false" class="my-3">
<label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label> <label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label>
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control"> <input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
@ -51,6 +64,12 @@
</ul> </ul>
</div> </div>
<!-- Custom CSS -->
<div class="my-3">
<div class="mb-1">{{ $t("Custom CSS") }}</div>
<prism-editor v-model="config.customCSS" class="css-editor" :highlight="highlighter" line-numbers></prism-editor>
</div>
<div class="danger-zone"> <div class="danger-zone">
<button class="btn btn-danger me-2" @click="deleteDialog"> <button class="btn btn-danger me-2" @click="deleteDialog">
<font-awesome-icon icon="trash" /> <font-awesome-icon icon="trash" />
@ -239,13 +258,24 @@
</div> </div>
<footer class="mt-5 mb-4"> <footer class="mt-5 mb-4">
<div class="custom-footer-text text-start">
<strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong>
</div>
<Editable v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" />
<p v-if="config.showPoweredBy">
{{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a> {{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
</p>
</footer> </footer>
</div> </div>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteStatusPage"> <Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteStatusPage">
{{ $t("deleteStatusPageMsg") }} {{ $t("deleteStatusPageMsg") }}
</Confirm> </Confirm>
<component is="style" v-if="config.customCSS" type="text/css">
{{ config.customCSS }}
</component>
</div> </div>
</template> </template>
@ -259,11 +289,20 @@ import dayjs from "dayjs";
import Favico from "favico.js"; import Favico from "favico.js";
import { getResBaseURL } from "../util-frontend"; import { getResBaseURL } from "../util-frontend";
import Confirm from "../components/Confirm.vue"; import Confirm from "../components/Confirm.vue";
// import Prism Editor
import { PrismEditor } from "vue-prism-editor";
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
// import highlighting library (you can use any library you want just return html string)
import { highlight, languages } from "prismjs/components/prism-core";
import "prismjs/components/prism-css";
import "prismjs/themes/prism-tomorrow.css"; // import syntax highlighting styles
const toast = useToast(); const toast = useToast();
const leavePageMsg = "Do you really want to leave? you have unsaved changes!"; const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
// eslint-disable-next-line no-unused-vars
let feedInterval; let feedInterval;
const favicon = new Favico({ const favicon = new Favico({
@ -276,6 +315,7 @@ export default {
PublicGroupList, PublicGroupList,
ImageCropUpload, ImageCropUpload,
Confirm, Confirm,
PrismEditor,
}, },
// Leave Page for vue route change // Leave Page for vue route change
@ -418,6 +458,13 @@ export default {
this.$root.getSocket().emit("getStatusPage", this.slug, (res) => { this.$root.getSocket().emit("getStatusPage", this.slug, (res) => {
if (res.ok) { if (res.ok) {
this.config = res.config; this.config = res.config;
if (!this.config.customCSS) {
this.config.customCSS = "body {\n" +
" \n" +
"}\n";
}
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
@ -520,6 +567,10 @@ export default {
}, },
methods: { methods: {
highlighter(code) {
return highlight(code, languages.css);
},
updateHeartbeatList() { updateHeartbeatList() {
// If editMode, it will use the data from websocket. // If editMode, it will use the data from websocket.
if (! this.editMode) { if (! this.editMode) {
@ -893,4 +944,18 @@ footer {
} }
} }
/* required class */
.css-editor {
/* we dont use `language-` classes anymore so thats why we need to add background and text color manually */
border-radius: 1rem;
padding: 10px 5px;
border: 1px solid #ced4da;
.dark & {
background: $dark-bg;
border: 1px solid $dark-border-color;
}
}
</style> </style>

Loading…
Cancel
Save