mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 18:06:14 +03:00
Merge remote-tracking branch 'origin/master' into feature-google-analytics
This commit is contained in:
commit
10c6f3b688
14 changed files with 342 additions and 19 deletions
5
.github/workflows/auto-test.yml
vendored
5
.github/workflows/auto-test.yml
vendored
|
@ -6,8 +6,12 @@ name: Auto Test
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-test:
|
auto-test:
|
||||||
|
@ -36,6 +40,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
HEADLESS_TEST: 1
|
HEADLESS_TEST: 1
|
||||||
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
||||||
|
|
||||||
check-linters:
|
check-linters:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|
7
.github/workflows/close-incorrect-issue.yml
vendored
7
.github/workflows/close-incorrect-issue.yml
vendored
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
name: Close Incorrect Issue
|
name: Close Incorrect Issue
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
@ -12,13 +11,13 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
node-version: [16.x]
|
node-version: [16]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
4
.github/workflows/stale-bot.yml
vendored
4
.github/workflows/stale-bot.yml
vendored
|
@ -3,13 +3,13 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 */6 * * *'
|
- cron: '0 */6 * * *'
|
||||||
#Run every 6 hours
|
#Run every 6 hours
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v7
|
||||||
with:
|
with:
|
||||||
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
||||||
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.'
|
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Uptime Kuma
|
# Uptime Kuma
|
||||||
|
|
||||||
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Open%20Collective%20Backers&color=brightgreen" /></a>
|
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Open%20Collective%20Backers&color=brightgreen" /></a>
|
||||||
[![GitHub Sponsors](https://img.shields.io/github/sponsors/louislam?label=GitHub%20Sponsors)](https://github.com/sponsors/louislam) <a href="https://weblate.kuma.pet/engage/uptime-kuma/">
|
[![GitHub Sponsors](https://img.shields.io/github/sponsors/louislam?label=GitHub%20Sponsors)](https://github.com/sponsors/louislam) <a href="https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/">
|
||||||
<img src="https://weblate.kuma.pet/widgets/uptime-kuma/-/svg-badge.svg" alt="Translation status" />
|
<img src="https://weblate.kuma.pet/widgets/uptime-kuma/-/svg-badge.svg" alt="Translation status" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ Uptime Kuma is an easy-to-use self-hosted monitoring tool.
|
||||||
Try it!
|
Try it!
|
||||||
|
|
||||||
- Tokyo Demo Server: https://demo.uptime.kuma.pet (Sponsored by [Uptime Kuma Sponsors](https://github.com/louislam/uptime-kuma#%EF%B8%8F-sponsors))
|
- Tokyo Demo Server: https://demo.uptime.kuma.pet (Sponsored by [Uptime Kuma Sponsors](https://github.com/louislam/uptime-kuma#%EF%B8%8F-sponsors))
|
||||||
- Europe Demo Server: https://demo.uptime-kuma.karimi.dev:27000 (Provided by [@mhkarimi1383](https://github.com/mhkarimi1383))
|
|
||||||
|
|
||||||
It is a temporary live demo, all data will be deleted after 10 minutes. Use the one that is closer to you, but I suggest that you should install and try it out for the best demo experience.
|
It is a temporary live demo, all data will be deleted after 10 minutes. Use the one that is closer to you, but I suggest that you should install and try it out for the best demo experience.
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
const pkg = require("../package.json");
|
const pkg = require("../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const util = require("../src/util");
|
const util = require("../src/util");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
util.polyfill();
|
util.polyfill();
|
||||||
|
|
||||||
const oldVersion = pkg.version;
|
const oldVersion = pkg.version;
|
||||||
const newVersion = oldVersion + "-nightly-" + util.genSecret(8);
|
const newVersion = oldVersion + "-nightly-" + dayjs().format("YYYYMMDDHHmmss");
|
||||||
|
|
||||||
console.log("Old Version: " + oldVersion);
|
console.log("Old Version: " + oldVersion);
|
||||||
console.log("New Version: " + newVersion);
|
console.log("New Version: " + newVersion);
|
||||||
|
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -89,10 +89,12 @@
|
||||||
"cypress": "^10.1.0",
|
"cypress": "^10.1.0",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
|
"dompurify": "~2.4.3",
|
||||||
"eslint": "~8.14.0",
|
"eslint": "~8.14.0",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
"favico.js": "~0.3.10",
|
"favico.js": "~0.3.10",
|
||||||
"jest": "~27.2.5",
|
"jest": "~27.2.5",
|
||||||
|
"marked": "~4.2.5",
|
||||||
"postcss-html": "~1.5.0",
|
"postcss-html": "~1.5.0",
|
||||||
"postcss-rtlcss": "~3.7.2",
|
"postcss-rtlcss": "~3.7.2",
|
||||||
"postcss-scss": "~4.0.4",
|
"postcss-scss": "~4.0.4",
|
||||||
|
@ -7801,6 +7803,12 @@
|
||||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/domutils": {
|
"node_modules/domutils": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
|
||||||
|
@ -13622,6 +13630,18 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "4.2.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
|
||||||
|
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mathml-tag-names": {
|
"node_modules/mathml-tag-names": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
|
||||||
|
@ -24880,6 +24900,12 @@
|
||||||
"domelementtype": "^2.3.0"
|
"domelementtype": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dompurify": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
|
||||||
|
@ -29114,6 +29140,12 @@
|
||||||
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
|
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"marked": {
|
||||||
|
"version": "4.2.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
|
||||||
|
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"mathml-tag-names": {
|
"mathml-tag-names": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.19.6",
|
"version": "1.20.0-beta.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -146,9 +146,11 @@
|
||||||
"cypress": "^10.1.0",
|
"cypress": "^10.1.0",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
|
"dompurify": "~2.4.3",
|
||||||
"eslint": "~8.14.0",
|
"eslint": "~8.14.0",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
"favico.js": "~0.3.10",
|
"favico.js": "~0.3.10",
|
||||||
|
"marked": "~4.2.5",
|
||||||
"jest": "~27.2.5",
|
"jest": "~27.2.5",
|
||||||
"postcss-html": "~1.5.0",
|
"postcss-html": "~1.5.0",
|
||||||
"postcss-rtlcss": "~3.7.2",
|
"postcss-rtlcss": "~3.7.2",
|
||||||
|
|
|
@ -4,6 +4,7 @@ const demoMode = args["demo"] || false;
|
||||||
const badgeConstants = {
|
const badgeConstants = {
|
||||||
naColor: "#999",
|
naColor: "#999",
|
||||||
defaultUpColor: "#66c20a",
|
defaultUpColor: "#66c20a",
|
||||||
|
defaultWarnColor: "#eed202",
|
||||||
defaultDownColor: "#c2290a",
|
defaultDownColor: "#c2290a",
|
||||||
defaultPendingColor: "#f8a306",
|
defaultPendingColor: "#f8a306",
|
||||||
defaultMaintenanceColor: "#1747f5",
|
defaultMaintenanceColor: "#1747f5",
|
||||||
|
@ -13,6 +14,11 @@ const badgeConstants = {
|
||||||
defaultPingLabelSuffix: "h",
|
defaultPingLabelSuffix: "h",
|
||||||
defaultUptimeValueSuffix: "%",
|
defaultUptimeValueSuffix: "%",
|
||||||
defaultUptimeLabelSuffix: "h",
|
defaultUptimeLabelSuffix: "h",
|
||||||
|
defaultCertExpValueSuffix: " days",
|
||||||
|
defaultCertExpLabelSuffix: "h",
|
||||||
|
// Values Come From Default Notification Times
|
||||||
|
defaultCertExpireWarnDays: "14",
|
||||||
|
defaultCertExpireDownDays: "7"
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -1255,7 +1255,7 @@ class Monitor extends BeanModel {
|
||||||
*/
|
*/
|
||||||
static async getPreviousHeartbeat(monitorID) {
|
static async getPreviousHeartbeat(monitorID) {
|
||||||
return await R.getRow(`
|
return await R.getRow(`
|
||||||
SELECT status, time FROM heartbeat
|
SELECT ping, status, time FROM heartbeat
|
||||||
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
|
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
|
||||||
`, [
|
`, [
|
||||||
monitorID
|
monitorID
|
||||||
|
|
|
@ -145,7 +145,7 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
|
||||||
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
||||||
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
|
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
|
||||||
|
|
||||||
badgeValues.label = label ?? "";
|
badgeValues.label = label ?? "Status";
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DOWN:
|
case DOWN:
|
||||||
badgeValues.color = downColor;
|
badgeValues.color = downColor;
|
||||||
|
@ -212,7 +212,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
||||||
const badgeValues = { style };
|
const badgeValues = { style };
|
||||||
|
|
||||||
if (!publicMonitor) {
|
if (!publicMonitor) {
|
||||||
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non exsitant
|
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent
|
||||||
badgeValues.message = "N/A";
|
badgeValues.message = "N/A";
|
||||||
badgeValues.color = badgeConstants.naColor;
|
badgeValues.color = badgeConstants.naColor;
|
||||||
} else {
|
} else {
|
||||||
|
@ -228,8 +228,11 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
||||||
badgeValues.color = color ?? percentageToColor(uptime);
|
badgeValues.color = color ?? percentageToColor(uptime);
|
||||||
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||||
badgeValues.labelColor = labelColor ?? "";
|
badgeValues.labelColor = labelColor ?? "";
|
||||||
// build a lable string. If a custom label is given, override the default one (requestedDuration)
|
// build a label string. If a custom label is given, override the default one (requestedDuration)
|
||||||
badgeValues.label = filterAndJoin([ labelPrefix, label ?? requestedDuration, labelSuffix ]);
|
badgeValues.label = filterAndJoin([
|
||||||
|
labelPrefix,
|
||||||
|
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
|
||||||
|
]);
|
||||||
badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
|
badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +293,7 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
|
||||||
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||||
badgeValues.labelColor = labelColor ?? "";
|
badgeValues.labelColor = labelColor ?? "";
|
||||||
// build a lable string. If a custom label is given, override the default one (requestedDuration)
|
// build a lable string. If a custom label is given, override the default one (requestedDuration)
|
||||||
badgeValues.label = filterAndJoin([ labelPrefix, label ?? requestedDuration, labelSuffix ]);
|
badgeValues.label = filterAndJoin([ labelPrefix, label ?? `Avg. Ping (${requestedDuration}${labelSuffix})` ]);
|
||||||
badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]);
|
badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,4 +307,237 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (request, response) => {
|
||||||
|
allowAllOrigin(response);
|
||||||
|
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
labelPrefix,
|
||||||
|
labelSuffix,
|
||||||
|
prefix,
|
||||||
|
suffix = badgeConstants.defaultPingValueSuffix,
|
||||||
|
color = badgeConstants.defaultPingColor,
|
||||||
|
labelColor,
|
||||||
|
style = badgeConstants.defaultStyle,
|
||||||
|
value, // for demo purpose only
|
||||||
|
} = request.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestedMonitorId = parseInt(request.params.id, 10);
|
||||||
|
|
||||||
|
// Default duration is 24 (h) if not defined in queryParam, limited to 720h (30d)
|
||||||
|
const requestedDuration = Math.min(
|
||||||
|
request.params.duration
|
||||||
|
? parseInt(request.params.duration, 10)
|
||||||
|
: 24,
|
||||||
|
720
|
||||||
|
);
|
||||||
|
const overrideValue = value && parseFloat(value);
|
||||||
|
|
||||||
|
const publicAvgPing = parseInt(await R.getCell(`
|
||||||
|
SELECT AVG(ping) FROM monitor_group, \`group\`, heartbeat
|
||||||
|
WHERE monitor_group.group_id = \`group\`.id
|
||||||
|
AND heartbeat.time > DATETIME('now', ? || ' hours')
|
||||||
|
AND heartbeat.ping IS NOT NULL
|
||||||
|
AND public = 1
|
||||||
|
AND heartbeat.monitor_id = ?
|
||||||
|
`,
|
||||||
|
[ -requestedDuration, requestedMonitorId ]
|
||||||
|
));
|
||||||
|
|
||||||
|
const badgeValues = { style };
|
||||||
|
|
||||||
|
if (!publicAvgPing) {
|
||||||
|
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent
|
||||||
|
|
||||||
|
badgeValues.message = "N/A";
|
||||||
|
badgeValues.color = badgeConstants.naColor;
|
||||||
|
} else {
|
||||||
|
const avgPing = parseInt(overrideValue ?? publicAvgPing);
|
||||||
|
|
||||||
|
badgeValues.color = color;
|
||||||
|
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||||
|
badgeValues.labelColor = labelColor ?? "";
|
||||||
|
// build a label string. If a custom label is given, override the default one (requestedDuration)
|
||||||
|
badgeValues.label = filterAndJoin([
|
||||||
|
labelPrefix,
|
||||||
|
label ?? `Avg. Response (${requestedDuration}h)`,
|
||||||
|
labelSuffix,
|
||||||
|
]);
|
||||||
|
badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the SVG based on given values
|
||||||
|
const svg = makeBadge(badgeValues);
|
||||||
|
|
||||||
|
response.type("image/svg+xml");
|
||||||
|
response.send(svg);
|
||||||
|
} catch (error) {
|
||||||
|
send403(response, error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, response) => {
|
||||||
|
allowAllOrigin(response);
|
||||||
|
|
||||||
|
const date = request.query.date;
|
||||||
|
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
labelPrefix,
|
||||||
|
labelSuffix,
|
||||||
|
prefix,
|
||||||
|
suffix = date ? "" : badgeConstants.defaultCertExpValueSuffix,
|
||||||
|
upColor = badgeConstants.defaultUpColor,
|
||||||
|
warnColor = badgeConstants.defaultWarnColor,
|
||||||
|
downColor = badgeConstants.defaultDownColor,
|
||||||
|
warnDays = badgeConstants.defaultCertExpireWarnDays,
|
||||||
|
downDays = badgeConstants.defaultCertExpireDownDays,
|
||||||
|
labelColor,
|
||||||
|
style = badgeConstants.defaultStyle,
|
||||||
|
value, // for demo purpose only
|
||||||
|
} = request.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestedMonitorId = parseInt(request.params.id, 10);
|
||||||
|
|
||||||
|
const overrideValue = value && parseFloat(value);
|
||||||
|
|
||||||
|
let publicMonitor = await R.getRow(`
|
||||||
|
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
|
||||||
|
WHERE monitor_group.group_id = \`group\`.id
|
||||||
|
AND monitor_group.monitor_id = ?
|
||||||
|
AND public = 1
|
||||||
|
`,
|
||||||
|
[ requestedMonitorId ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const badgeValues = { style };
|
||||||
|
|
||||||
|
if (!publicMonitor) {
|
||||||
|
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent
|
||||||
|
|
||||||
|
badgeValues.message = "N/A";
|
||||||
|
badgeValues.color = badgeConstants.naColor;
|
||||||
|
} else {
|
||||||
|
const tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||||
|
requestedMonitorId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!tlsInfoBean) {
|
||||||
|
// return a "No/Bad Cert" badge in naColor (grey), if no cert saved (does not save bad certs?)
|
||||||
|
badgeValues.message = "No/Bad Cert";
|
||||||
|
badgeValues.color = badgeConstants.naColor;
|
||||||
|
} else {
|
||||||
|
const tlsInfo = JSON.parse(tlsInfoBean.info_json);
|
||||||
|
|
||||||
|
if (!tlsInfo.valid) {
|
||||||
|
// return a "Bad Cert" badge in naColor (grey), when cert is not valid
|
||||||
|
badgeValues.message = "Bad Cert";
|
||||||
|
badgeValues.color = badgeConstants.downColor;
|
||||||
|
} else {
|
||||||
|
const daysRemaining = parseInt(overrideValue ?? tlsInfo.certInfo.daysRemaining);
|
||||||
|
|
||||||
|
if (daysRemaining > warnDays) {
|
||||||
|
badgeValues.color = upColor;
|
||||||
|
} else if (daysRemaining > downDays) {
|
||||||
|
badgeValues.color = warnColor;
|
||||||
|
} else {
|
||||||
|
badgeValues.color = downColor;
|
||||||
|
}
|
||||||
|
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||||
|
badgeValues.labelColor = labelColor ?? "";
|
||||||
|
// build a label string. If a custom label is given, override the default one
|
||||||
|
badgeValues.label = filterAndJoin([
|
||||||
|
labelPrefix,
|
||||||
|
label ?? "Cert Exp.",
|
||||||
|
labelSuffix,
|
||||||
|
]);
|
||||||
|
badgeValues.message = filterAndJoin([ prefix, date ? tlsInfo.certInfo.validTo : daysRemaining, suffix ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the SVG based on given values
|
||||||
|
const svg = makeBadge(badgeValues);
|
||||||
|
|
||||||
|
response.type("image/svg+xml");
|
||||||
|
response.send(svg);
|
||||||
|
} catch (error) {
|
||||||
|
send403(response, error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/api/badge/:id/response", cache("5 minutes"), async (request, response) => {
|
||||||
|
allowAllOrigin(response);
|
||||||
|
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
labelPrefix,
|
||||||
|
labelSuffix,
|
||||||
|
prefix,
|
||||||
|
suffix = badgeConstants.defaultPingValueSuffix,
|
||||||
|
color = badgeConstants.defaultPingColor,
|
||||||
|
labelColor,
|
||||||
|
style = badgeConstants.defaultStyle,
|
||||||
|
value, // for demo purpose only
|
||||||
|
} = request.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestedMonitorId = parseInt(request.params.id, 10);
|
||||||
|
|
||||||
|
const overrideValue = value && parseFloat(value);
|
||||||
|
|
||||||
|
let publicMonitor = await R.getRow(`
|
||||||
|
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
|
||||||
|
WHERE monitor_group.group_id = \`group\`.id
|
||||||
|
AND monitor_group.monitor_id = ?
|
||||||
|
AND public = 1
|
||||||
|
`,
|
||||||
|
[ requestedMonitorId ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const badgeValues = { style };
|
||||||
|
|
||||||
|
if (!publicMonitor) {
|
||||||
|
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent
|
||||||
|
|
||||||
|
badgeValues.message = "N/A";
|
||||||
|
badgeValues.color = badgeConstants.naColor;
|
||||||
|
} else {
|
||||||
|
const heartbeat = await Monitor.getPreviousHeartbeat(
|
||||||
|
requestedMonitorId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!heartbeat.ping) {
|
||||||
|
// return a "N/A" badge in naColor (grey), if previous heartbeat has no ping
|
||||||
|
|
||||||
|
badgeValues.message = "N/A";
|
||||||
|
badgeValues.color = badgeConstants.naColor;
|
||||||
|
} else {
|
||||||
|
const ping = parseInt(overrideValue ?? heartbeat.ping);
|
||||||
|
|
||||||
|
badgeValues.color = color;
|
||||||
|
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||||
|
badgeValues.labelColor = labelColor ?? "";
|
||||||
|
// build a label string. If a custom label is given, override the default one
|
||||||
|
badgeValues.label = filterAndJoin([
|
||||||
|
labelPrefix,
|
||||||
|
label ?? "Response",
|
||||||
|
labelSuffix,
|
||||||
|
]);
|
||||||
|
badgeValues.message = filterAndJoin([ prefix, ping, suffix ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the SVG based on given values
|
||||||
|
const svg = makeBadge(badgeValues);
|
||||||
|
|
||||||
|
response.type("image/svg+xml");
|
||||||
|
response.send(svg);
|
||||||
|
} catch (error) {
|
||||||
|
send403(response, error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
@ -35,6 +35,11 @@ textarea.form-control {
|
||||||
color: $maintenance !important;
|
color: $maintenance !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.incident a,
|
||||||
|
.bg-maintenance a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.list-group {
|
.list-group {
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
|
|
||||||
|
@ -248,6 +253,11 @@ optgroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.incident a,
|
||||||
|
.bg-maintenance a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.form-control,
|
.form-control,
|
||||||
.form-control:focus,
|
.form-control:focus,
|
||||||
.form-select,
|
.form-select,
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"General Monitor Type": "General Monitor Type",
|
"General Monitor Type": "General Monitor Type",
|
||||||
"Passive Monitor Type": "Passive Monitor Type",
|
"Passive Monitor Type": "Passive Monitor Type",
|
||||||
"Specific Monitor Type": "Specific Monitor Type",
|
"Specific Monitor Type": "Specific Monitor Type",
|
||||||
|
"markdownSupported": "Markdown syntax supported",
|
||||||
"pauseDashboardHome": "Pause",
|
"pauseDashboardHome": "Pause",
|
||||||
"Pause": "Pause",
|
"Pause": "Pause",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
|
|
|
@ -21,6 +21,9 @@
|
||||||
<textarea
|
<textarea
|
||||||
id="description" v-model="maintenance.description" class="form-control"
|
id="description" v-model="maintenance.description" class="form-control"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("markdownSupported") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Affected Monitors -->
|
<!-- Affected Monitors -->
|
||||||
|
|
|
@ -26,6 +26,9 @@
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label>
|
<label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label>
|
||||||
<textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea>
|
<textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("markdownSupported") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-3 form-check form-switch">
|
<div class="my-3 form-check form-switch">
|
||||||
|
@ -154,7 +157,12 @@
|
||||||
<Editable v-model="incident.title" tag="h4" :contenteditable="editIncidentMode" :noNL="true" class="alert-heading" />
|
<Editable v-model="incident.title" tag="h4" :contenteditable="editIncidentMode" :noNL="true" class="alert-heading" />
|
||||||
|
|
||||||
<strong v-if="editIncidentMode">{{ $t("Content") }}:</strong>
|
<strong v-if="editIncidentMode">{{ $t("Content") }}:</strong>
|
||||||
<Editable v-model="incident.content" tag="div" :contenteditable="editIncidentMode" class="content" />
|
<Editable v-if="editIncidentMode" v-model="incident.content" tag="div" :contenteditable="editIncidentMode" class="content" />
|
||||||
|
<div v-if="editIncidentMode" class="form-text">
|
||||||
|
{{ $t("markdownSupported") }}
|
||||||
|
</div>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html-->
|
||||||
|
<div v-if="! editIncidentMode" class="content" v-html="incidentHTML"></div>
|
||||||
|
|
||||||
<!-- Incident Date -->
|
<!-- Incident Date -->
|
||||||
<div class="date mt-3">
|
<div class="date mt-3">
|
||||||
|
@ -242,7 +250,8 @@
|
||||||
class="shadow-box alert mb-4 p-3 bg-maintenance mt-4 position-relative" role="alert"
|
class="shadow-box alert mb-4 p-3 bg-maintenance mt-4 position-relative" role="alert"
|
||||||
>
|
>
|
||||||
<h4 class="alert-heading">{{ maintenance.title }}</h4>
|
<h4 class="alert-heading">{{ maintenance.title }}</h4>
|
||||||
<div class="content">{{ maintenance.description }}</div>
|
<!-- eslint-disable-next-line vue/no-v-html-->
|
||||||
|
<div class="content" v-html="maintenanceHTML(maintenance.description)"></div>
|
||||||
<MaintenanceTime :maintenance="maintenance" />
|
<MaintenanceTime :maintenance="maintenance" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -285,7 +294,9 @@
|
||||||
<div class="custom-footer-text text-start">
|
<div class="custom-footer-text text-start">
|
||||||
<strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong>
|
<strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong>
|
||||||
</div>
|
</div>
|
||||||
<Editable v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" />
|
<Editable v-if="enableEditMode" v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" />
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html-->
|
||||||
|
<div v-if="! enableEditMode" class="alert-heading p-2" v-html="footerHTML"></div>
|
||||||
|
|
||||||
<p v-if="config.showPoweredBy">
|
<p v-if="config.showPoweredBy">
|
||||||
{{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
{{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
||||||
|
@ -316,6 +327,8 @@ import ImageCropUpload from "vue-image-crop-upload";
|
||||||
import { PrismEditor } from "vue-prism-editor";
|
import { PrismEditor } from "vue-prism-editor";
|
||||||
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
|
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
import { marked } from "marked";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
import Confirm from "../components/Confirm.vue";
|
import Confirm from "../components/Confirm.vue";
|
||||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||||
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
||||||
|
@ -483,6 +496,13 @@ export default {
|
||||||
return this.overallStatus === STATUS_PAGE_MAINTENANCE;
|
return this.overallStatus === STATUS_PAGE_MAINTENANCE;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
incidentHTML() {
|
||||||
|
return DOMPurify.sanitize(marked(this.incident.content));
|
||||||
|
},
|
||||||
|
|
||||||
|
footerHTML() {
|
||||||
|
return DOMPurify.sanitize(marked(this.config.footerText));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
|
@ -842,6 +862,15 @@ export default {
|
||||||
this.config.domainNameList.splice(index, 1);
|
this.config.domainNameList.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate sanitized HTML from maintenance description
|
||||||
|
* @param {string} description
|
||||||
|
* @returns {string} Sanitized HTML
|
||||||
|
*/
|
||||||
|
maintenanceHTML(description) {
|
||||||
|
return DOMPurify.sanitize(marked(description));
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue