mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-13 23:17:00 +08:00
Compare commits
115 Commits
1.16.1
...
1.17.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
dd09351c8e | ||
|
ffad990ca4 | ||
|
42848bcd2e | ||
|
a3b94aa532 | ||
|
fdbdf83a0d | ||
|
81d5360520 | ||
|
8f1e193de3 | ||
|
da91317760 | ||
|
bef0febede | ||
|
7d63b700e1 | ||
|
0223f86a2a | ||
|
c170b1edd0 | ||
|
5943514a92 | ||
|
62acd2edb1 | ||
|
483cbfb636 | ||
|
660005b143 | ||
|
98f3c126e5 | ||
|
6995a29980 | ||
|
cf2ca71dee | ||
|
0bd1c42080 | ||
|
9b21b86e70 | ||
|
f723930d11 | ||
|
e425e408a2 | ||
|
c690d1c3a1 | ||
|
8abbc9fd15 | ||
|
af7c905b44 | ||
|
0e8f6d2f85 | ||
|
d16be6fb7d | ||
|
f25ca96308 | ||
|
6682839ec8 | ||
|
817f6db4fd | ||
|
dc2302244f | ||
|
7a27d3752a | ||
|
f0e8f34aeb | ||
|
69273a6c41 | ||
|
6424fe77ab | ||
|
fa60672cce | ||
|
6e43ef1dd3 | ||
|
f4f2b8ddb8 | ||
|
436bc13aeb | ||
|
b72a279361 | ||
|
a28ef56553 | ||
|
7f432bd916 | ||
|
f570d41142 | ||
|
d4485fe62f | ||
|
e1681ce370 | ||
|
69d6633e6d | ||
|
04e22f17a9 | ||
|
11243a6ca1 | ||
|
87428231ad | ||
|
a68d945cdc | ||
|
2c0180f323 | ||
|
4fdaa1abb6 | ||
|
6ee7b3696a | ||
|
cc258dce14 | ||
|
fb420fa1b1 | ||
|
a707b51053 | ||
|
a927f5cd15 | ||
|
0e28707307 | ||
|
c94dcf1533 | ||
|
b0476cfb5b | ||
|
2170229031 | ||
|
213aca4fc3 | ||
|
2b42c3c828 | ||
|
d939d03690 | ||
|
07888e43f1 | ||
|
c6c1bb5b5c | ||
|
3210264e28 | ||
|
54e948c2ca | ||
|
80094ec4e1 | ||
|
091158cfe7 | ||
|
abb6ce2366 | ||
|
e4ad8cbfc8 | ||
|
a674caa520 | ||
|
179e3569b5 | ||
|
43527f2f40 | ||
|
26ff6f45a0 | ||
|
c095767f4a | ||
|
ffb7ba176c | ||
|
cfa5b551a5 | ||
|
46ee149b70 | ||
|
54184350a4 | ||
|
14dbe7c334 | ||
|
122e6a842b | ||
|
77ef22bdb4 | ||
|
59f983d506 | ||
|
71f031c14e | ||
|
73b965c867 | ||
|
b7ba6330db | ||
|
ef73af391f | ||
|
44f6fca945 | ||
|
23ce7c6623 | ||
|
c346ea7864 | ||
|
f0ad32a252 | ||
|
5720017fb4 | ||
|
b7dc8e3ef8 | ||
|
5bba19f866 | ||
|
e198f2f1ab | ||
|
f91e5b98f9 | ||
|
87f933df4f | ||
|
332b9ab248 | ||
|
7cc89979f0 | ||
|
668e97c5a9 | ||
|
fdd781b081 | ||
|
373bd9b962 | ||
|
59be9bb971 | ||
|
201a25c659 | ||
|
cbfecab850 | ||
|
25cc54bf72 | ||
|
3700b16c5b | ||
|
d0546afe71 | ||
|
f4515ad8c5 | ||
|
a4be651118 | ||
|
244a7b3671 | ||
|
ee90d2713f |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
👉 Delete this line if you have read and agree our pull request rules and guidelines: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
Fixes #(issue)
|
Fixes #(issue)
|
||||||
|
@@ -27,17 +27,30 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
|||||||
|
|
||||||
## Can I create a pull request for Uptime Kuma?
|
## Can I create a pull request for Uptime Kuma?
|
||||||
|
|
||||||
(Updated 2022-04-24) Since I don't want to waste your time, be sure to create empty draft pull request, so we can discuss first.
|
Yes, you can. However, since I don't want to waste your time, be sure to **create empty draft pull request, so we can discuss first** if it is a large pull request or you don't know it will be merged or not.
|
||||||
|
|
||||||
|
Also, please don't rush or ask for ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
|
||||||
|
|
||||||
|
I will mark your pull request in the [milestones](https://github.com/louislam/uptime-kuma/milestones), if I am plan to review and merge it.
|
||||||
|
|
||||||
✅ Accept:
|
✅ Accept:
|
||||||
- Bug/Security fix
|
- Bug/Security fix
|
||||||
- Translations
|
- Translations
|
||||||
- Adding notification providers
|
- Adding notification providers
|
||||||
|
|
||||||
⚠️ Discuss First
|
⚠️ Discussion First
|
||||||
- Large pull requests
|
- Large pull requests
|
||||||
- New features
|
- New features
|
||||||
|
|
||||||
|
❌ Won't Merge
|
||||||
|
- Do not pass auto test
|
||||||
|
- Any breaking changes
|
||||||
|
- Duplicated pull request
|
||||||
|
- Buggy
|
||||||
|
- Existing logic is completely modified or deleted for no reason
|
||||||
|
- A function that is completely out of scope
|
||||||
|
|
||||||
|
|
||||||
### Recommended Pull Request Guideline
|
### Recommended Pull Request Guideline
|
||||||
|
|
||||||
Before deep into coding, discussion first is preferred. Creating an empty pull request for discussion would be recommended.
|
Before deep into coding, discussion first is preferred. Creating an empty pull request for discussion would be recommended.
|
||||||
@@ -53,22 +66,15 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
|
|||||||
1. Click "Change to draft"
|
1. Click "Change to draft"
|
||||||
1. Discussion
|
1. Discussion
|
||||||
|
|
||||||
#### ❌ Won't Merge
|
|
||||||
|
|
||||||
- Any breaking changes
|
|
||||||
- Duplicated pull request
|
|
||||||
- Buggy
|
|
||||||
- Existing logic is completely modified or deleted
|
|
||||||
- A function that is completely out of scope
|
|
||||||
|
|
||||||
## Project Styles
|
## Project Styles
|
||||||
|
|
||||||
I personally do not like something need to learn so much and need to config so much before you can finally start the app.
|
I personally do not like something need to learn so much and need to config so much before you can finally start the app.
|
||||||
|
|
||||||
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort to get it run
|
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort to get it run
|
||||||
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
||||||
- Settings should be configurable in the frontend. Env var is not encouraged.
|
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`.
|
||||||
- Easy to use
|
- Easy to use
|
||||||
|
- The web UI styling should be consistent and nice.
|
||||||
|
|
||||||
## Coding Styles
|
## Coding Styles
|
||||||
|
|
||||||
|
@@ -8,9 +8,6 @@ Do not use the issue tracker or discuss it in the public as it will cause more d
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Use this section to tell people about which versions of your project are
|
|
||||||
currently being supported with security updates.
|
|
||||||
|
|
||||||
### Uptime Kuma Versions
|
### Uptime Kuma Versions
|
||||||
|
|
||||||
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` versions are upgradable to the lastest version.
|
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` versions are upgradable to the lastest version.
|
||||||
|
@@ -1,18 +1,32 @@
|
|||||||
import legacy from "@vitejs/plugin-legacy";
|
import legacy from "@vitejs/plugin-legacy";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import visualizer from "rollup-plugin-visualizer";
|
||||||
|
import viteCompression from "vite-plugin-compression";
|
||||||
|
|
||||||
const postCssScss = require("postcss-scss");
|
const postCssScss = require("postcss-scss");
|
||||||
const postcssRTLCSS = require("postcss-rtlcss");
|
const postcssRTLCSS = require("postcss-rtlcss");
|
||||||
|
|
||||||
|
const viteCompressionFilter = /\.(js|mjs|json|css|html|svg)$/i;
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
legacy({
|
legacy({
|
||||||
targets: [ "ie > 11" ],
|
targets: [ "since 2015" ],
|
||||||
additionalLegacyPolyfills: [ "regenerator-runtime/runtime" ]
|
}),
|
||||||
})
|
visualizer({
|
||||||
|
filename: "tmp/dist-stats.html"
|
||||||
|
}),
|
||||||
|
viteCompression({
|
||||||
|
algorithm: "gzip",
|
||||||
|
filter: viteCompressionFilter,
|
||||||
|
}),
|
||||||
|
viteCompression({
|
||||||
|
algorithm: "brotliCompress",
|
||||||
|
filter: viteCompressionFilter,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
css: {
|
css: {
|
||||||
postcss: {
|
postcss: {
|
||||||
@@ -21,4 +35,13 @@ export default defineConfig({
|
|||||||
"plugins": [ postcssRTLCSS ]
|
"plugins": [ postcssRTLCSS ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks(id, { getModuleInfo, getModuleIds }) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
18
db/patch-add-other-auth.sql
Normal file
18
db/patch-add-other-auth.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD auth_method VARCHAR(250);
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD auth_domain TEXT;
|
||||||
|
ALTER TABLE monitor
|
||||||
|
|
||||||
|
ADD auth_workstation TEXT;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
UPDATE monitor
|
||||||
|
SET auth_method = 'basic'
|
||||||
|
WHERE basic_auth_user is not null;
|
||||||
|
COMMIT;
|
10
db/patch-add-sqlserver-monitor.sql
Normal file
10
db/patch-add-sqlserver-monitor.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD database_connection_string VARCHAR(2000);
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD database_query TEXT;
|
||||||
|
|
||||||
|
|
||||||
|
COMMIT
|
@@ -12,7 +12,8 @@ RUN apt update && \
|
|||||||
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||||
sqlite3 iputils-ping util-linux dumb-init && \
|
sqlite3 iputils-ping util-linux dumb-init && \
|
||||||
pip3 --no-cache-dir install apprise==0.9.8.3 && \
|
pip3 --no-cache-dir install apprise==0.9.8.3 && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
apt --yes autoremove
|
||||||
|
|
||||||
# Install cloudflared
|
# Install cloudflared
|
||||||
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
|
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
|
||||||
@@ -22,5 +23,6 @@ RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
|
|||||||
apt update && \
|
apt update && \
|
||||||
apt --yes --no-install-recommends install ./cloudflared.deb && \
|
apt --yes --no-install-recommends install ./cloudflared.deb && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
rm -f cloudflared.deb
|
rm -f cloudflared.deb && \
|
||||||
|
apt --yes autoremove
|
||||||
|
|
||||||
|
4256
package-lock.json
generated
4256
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.16.1",
|
"version": "1.17.0-beta.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -57,7 +57,8 @@
|
|||||||
"ncu-patch": "npm-check-updates -u -t patch",
|
"ncu-patch": "npm-check-updates -u -t patch",
|
||||||
"release-final": "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/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||||
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
||||||
"git-remove-tag": "git tag -d"
|
"git-remove-tag": "git tag -d",
|
||||||
|
"build-dist-and-restart": "npm run build && npm run start-server-dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
||||||
@@ -68,6 +69,8 @@
|
|||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
"args-parser": "~1.3.0",
|
"args-parser": "~1.3.0",
|
||||||
"axios": "~0.26.1",
|
"axios": "~0.26.1",
|
||||||
|
"axios-cached-dns-resolve": "^3.0.6",
|
||||||
|
"axios-ntlm": "^1.3.0",
|
||||||
"badge-maker": "^3.3.1",
|
"badge-maker": "^3.3.1",
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"bootstrap": "5.1.3",
|
"bootstrap": "5.1.3",
|
||||||
@@ -76,12 +79,16 @@
|
|||||||
"chart.js": "~3.6.2",
|
"chart.js": "~3.6.2",
|
||||||
"chartjs-adapter-dayjs": "~1.0.0",
|
"chartjs-adapter-dayjs": "~1.0.0",
|
||||||
"check-password-strength": "^2.0.5",
|
"check-password-strength": "^2.0.5",
|
||||||
|
"cheerio": "^1.0.0-rc.10",
|
||||||
"chroma-js": "^2.1.2",
|
"chroma-js": "^2.1.2",
|
||||||
"command-exists": "~1.2.9",
|
"command-exists": "~1.2.9",
|
||||||
"compare-versions": "~3.6.0",
|
"compare-versions": "~3.6.0",
|
||||||
|
"compression": "^1.7.4",
|
||||||
"dayjs": "^1.11.0",
|
"dayjs": "^1.11.0",
|
||||||
|
"esm-wallaby": "^3.2.26",
|
||||||
"express": "~4.17.3",
|
"express": "~4.17.3",
|
||||||
"express-basic-auth": "~1.2.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
|
"express-static-gzip": "^2.1.7",
|
||||||
"favico.js": "^0.3.10",
|
"favico.js": "^0.3.10",
|
||||||
"form-data": "~4.0.0",
|
"form-data": "~4.0.0",
|
||||||
"http-graceful-shutdown": "~3.1.7",
|
"http-graceful-shutdown": "~3.1.7",
|
||||||
@@ -92,6 +99,7 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"limiter": "^2.1.0",
|
"limiter": "^2.1.0",
|
||||||
"mqtt": "^4.2.8",
|
"mqtt": "^4.2.8",
|
||||||
|
"mssql": "^8.1.0",
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
"nodemailer": "~6.6.5",
|
"nodemailer": "~6.6.5",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
@@ -129,27 +137,31 @@
|
|||||||
"@babel/eslint-parser": "~7.17.0",
|
"@babel/eslint-parser": "~7.17.0",
|
||||||
"@babel/preset-env": "^7.15.8",
|
"@babel/preset-env": "^7.15.8",
|
||||||
"@types/bootstrap": "~5.1.9",
|
"@types/bootstrap": "~5.1.9",
|
||||||
"@vitejs/plugin-legacy": "~1.6.4",
|
"@vitejs/plugin-legacy": "~1.8.2",
|
||||||
"@vitejs/plugin-vue": "~1.9.4",
|
"@vitejs/plugin-vue": "~2.3.3",
|
||||||
"@vue/compiler-sfc": "~3.2.31",
|
"@vue/compiler-sfc": "~3.2.36",
|
||||||
"aedes": "^0.46.3",
|
"aedes": "^0.46.3",
|
||||||
"babel-plugin-rewire": "~1.2.0",
|
"babel-plugin-rewire": "~1.2.0",
|
||||||
"concurrently": "^7.1.0",
|
"concurrently": "^7.1.0",
|
||||||
"core-js": "~3.18.3",
|
"core-js": "~3.18.3",
|
||||||
"cross-env": "~7.0.3",
|
"cross-env": "~7.0.3",
|
||||||
|
"delay": "^5.0.0",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
"eslint": "~8.14.0",
|
"eslint": "~8.14.0",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
"jest": "~27.2.5",
|
"jest": "~27.2.5",
|
||||||
"jest-puppeteer": "~6.0.3",
|
"jest-puppeteer": "~6.0.3",
|
||||||
|
"lru-cache": "^7.7.1",
|
||||||
"npm-check-updates": "^12.5.9",
|
"npm-check-updates": "^12.5.9",
|
||||||
"postcss-html": "^1.3.1",
|
"postcss-html": "^1.3.1",
|
||||||
"puppeteer": "~13.1.3",
|
"puppeteer": "~13.1.3",
|
||||||
|
"rollup-plugin-visualizer": "^5.6.0",
|
||||||
"sass": "~1.42.1",
|
"sass": "~1.42.1",
|
||||||
"stylelint": "~14.7.1",
|
"stylelint": "~14.7.1",
|
||||||
"stylelint-config-standard": "~25.0.0",
|
"stylelint-config-standard": "~25.0.0",
|
||||||
"typescript": "~4.4.4",
|
"typescript": "~4.4.4",
|
||||||
"vite": "~2.6.14",
|
"vite": "~2.9.9",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"wait-on": "^6.0.1"
|
"wait-on": "^6.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,10 @@ async function sendNotificationList(socket) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
for (let bean of list) {
|
for (let bean of list) {
|
||||||
result.push(bean.export());
|
let notificationObject = bean.export();
|
||||||
|
notificationObject.isDefault = (notificationObject.isDefault === 1);
|
||||||
|
notificationObject.active = (notificationObject.active === 1);
|
||||||
|
result.push(notificationObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
io.to(socket.userID).emit("notificationList", result);
|
io.to(socket.userID).emit("notificationList", result);
|
||||||
|
@@ -58,6 +58,8 @@ class Database {
|
|||||||
"patch-monitor-expiry-notification.sql": true,
|
"patch-monitor-expiry-notification.sql": true,
|
||||||
"patch-status-page-footer-css.sql": true,
|
"patch-status-page-footer-css.sql": true,
|
||||||
"patch-added-mqtt-monitor.sql": true,
|
"patch-added-mqtt-monitor.sql": true,
|
||||||
|
"patch-add-sqlserver-monitor.sql": true,
|
||||||
|
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -7,7 +7,7 @@ dayjs.extend(timezone);
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
||||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mqttAsync } = require("../util-server");
|
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mqttAsync, setSetting, httpNtlm } = 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");
|
||||||
const { Notification } = require("../notification");
|
const { Notification } = require("../notification");
|
||||||
@@ -17,6 +17,12 @@ const version = require("../../package.json").version;
|
|||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
|
|
||||||
|
const axiosCachedDnsResolve = require("esm-wallaby")(module)("axios-cached-dns-resolve");
|
||||||
|
|
||||||
|
// create an axios client instance with the cached DNS resolve interceptor
|
||||||
|
const axiosClient = axios.create();
|
||||||
|
axiosCachedDnsResolve.registerInterceptor(axiosClient);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status:
|
* status:
|
||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
@@ -87,7 +93,12 @@ class Monitor extends BeanModel {
|
|||||||
mqttUsername: this.mqttUsername,
|
mqttUsername: this.mqttUsername,
|
||||||
mqttPassword: this.mqttPassword,
|
mqttPassword: this.mqttPassword,
|
||||||
mqttTopic: this.mqttTopic,
|
mqttTopic: this.mqttTopic,
|
||||||
mqttSuccessMessage: this.mqttSuccessMessage
|
mqttSuccessMessage: this.mqttSuccessMessage,
|
||||||
|
databaseConnectionString: this.databaseConnectionString,
|
||||||
|
databaseQuery: this.databaseQuery,
|
||||||
|
authMethod: this.authMethod,
|
||||||
|
authWorkstation: this.authWorkstation,
|
||||||
|
authDomain: this.authDomain,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (includeSensitiveData) {
|
if (includeSensitiveData) {
|
||||||
@@ -213,7 +224,7 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
// HTTP basic auth
|
// HTTP basic auth
|
||||||
let basicAuthHeader = {};
|
let basicAuthHeader = {};
|
||||||
if (this.basic_auth_user) {
|
if (this.auth_method === "basic") {
|
||||||
basicAuthHeader = {
|
basicAuthHeader = {
|
||||||
"Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass),
|
"Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass),
|
||||||
};
|
};
|
||||||
@@ -264,7 +275,21 @@ class Monitor extends BeanModel {
|
|||||||
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
||||||
log.debug("monitor", `[${this.name}] Axios Request`);
|
log.debug("monitor", `[${this.name}] Axios Request`);
|
||||||
|
|
||||||
let res = await axios.request(options);
|
let res;
|
||||||
|
if (this.auth_method === "ntlm") {
|
||||||
|
options.httpsAgent.keepAlive = true;
|
||||||
|
|
||||||
|
res = await httpNtlm(options, {
|
||||||
|
username: this.basic_auth_user,
|
||||||
|
password: this.basic_auth_pass,
|
||||||
|
domain: this.authDomain,
|
||||||
|
workstation: this.authWorkstation ? this.authWorkstation : undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res = await axiosClient.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
bean.msg = `${res.status} - ${res.statusText}`;
|
bean.msg = `${res.status} - ${res.statusText}`;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
|
||||||
@@ -312,7 +337,11 @@ class Monitor extends BeanModel {
|
|||||||
bean.msg += ", keyword is found";
|
bean.msg += ", keyword is found";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(bean.msg + ", but keyword is not found");
|
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ");
|
||||||
|
if (data.length > 50) {
|
||||||
|
data = data.substring(0, 47) + "...";
|
||||||
|
}
|
||||||
|
throw new Error(bean.msg + ", but keyword is not in [" + data + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -377,7 +406,7 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
// If the previous beat was down or pending we use the regular
|
// If the previous beat was down or pending we use the regular
|
||||||
// beatInterval/retryInterval in the setTimeout further below
|
// beatInterval/retryInterval in the setTimeout further below
|
||||||
if (previousBeat.status !== UP || msSinceLastBeat > beatInterval * 1000 + bufferTime) {
|
if (previousBeat.status !== (this.isUpsideDown() ? DOWN : UP) || msSinceLastBeat > beatInterval * 1000 + bufferTime) {
|
||||||
throw new Error("No heartbeat in the time window");
|
throw new Error("No heartbeat in the time window");
|
||||||
} else {
|
} else {
|
||||||
let timeout = beatInterval * 1000 - msSinceLastBeat;
|
let timeout = beatInterval * 1000 - msSinceLastBeat;
|
||||||
@@ -405,7 +434,7 @@ class Monitor extends BeanModel {
|
|||||||
throw new Error("Steam API Key not found");
|
throw new Error("Steam API Key not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await axios.get(steamApiUrl, {
|
let res = await axiosClient.get(steamApiUrl, {
|
||||||
timeout: this.interval * 1000 * 0.8,
|
timeout: this.interval * 1000 * 0.8,
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
@@ -443,6 +472,14 @@ class Monitor extends BeanModel {
|
|||||||
interval: this.interval,
|
interval: this.interval,
|
||||||
});
|
});
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
|
} else if (this.type === "sqlserver") {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
|
await mssqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||||
|
|
||||||
|
bean.msg = "";
|
||||||
|
bean.status = UP;
|
||||||
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
} else {
|
} else {
|
||||||
bean.msg = "Unknown Monitor Type";
|
bean.msg = "Unknown Monitor Type";
|
||||||
bean.status = PENDING;
|
bean.status = PENDING;
|
||||||
@@ -493,7 +530,7 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bean.status === UP) {
|
if (bean.status === UP) {
|
||||||
log.info("monitor", `Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
log.debug("monitor", `Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||||
} else if (bean.status === PENDING) {
|
} else if (bean.status === PENDING) {
|
||||||
if (this.retryInterval > 0) {
|
if (this.retryInterval > 0) {
|
||||||
beatInterval = this.retryInterval;
|
beatInterval = this.retryInterval;
|
||||||
@@ -837,10 +874,19 @@ class Monitor extends BeanModel {
|
|||||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||||
const notificationList = await Monitor.getNotificationList(this);
|
const notificationList = await Monitor.getNotificationList(this);
|
||||||
|
|
||||||
log.debug("monitor", "call sendCertNotificationByTargetDays");
|
let notifyDays = await setting("tlsExpiryNotifyDays");
|
||||||
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList);
|
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
||||||
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList);
|
// Reset Default
|
||||||
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList);
|
setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
|
||||||
|
notifyDays = [ 7, 14, 21 ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyDays != null && Array.isArray(notifyDays)) {
|
||||||
|
for (const day of notifyDays) {
|
||||||
|
log.debug("monitor", "call sendCertNotificationByTargetDays", day);
|
||||||
|
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,104 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
const cheerio = require("cheerio");
|
||||||
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
|
|
||||||
class StatusPage extends BeanModel {
|
class StatusPage extends BeanModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like this: { "test-uptime.kuma.pet": "default" }
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
static domainMappingList = { };
|
static domainMappingList = { };
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Response} response
|
||||||
|
* @param {string} indexHTML
|
||||||
|
* @param {string} slug
|
||||||
|
*/
|
||||||
|
static async handleStatusPageResponse(response, indexHTML, slug) {
|
||||||
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
slug
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (statusPage) {
|
||||||
|
response.send(await StatusPage.renderHTML(indexHTML, statusPage));
|
||||||
|
} else {
|
||||||
|
response.status(404).send(UptimeKumaServer.getInstance().indexHTML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSR for status pages
|
||||||
|
* @param {string} indexHTML
|
||||||
|
* @param {StatusPage} statusPage
|
||||||
|
*/
|
||||||
|
static async renderHTML(indexHTML, statusPage) {
|
||||||
|
const $ = cheerio.load(indexHTML);
|
||||||
|
const description155 = statusPage.description?.substring(0, 155);
|
||||||
|
|
||||||
|
$("title").text(statusPage.title);
|
||||||
|
$("meta[name=description]").attr("content", description155);
|
||||||
|
|
||||||
|
if (statusPage.icon) {
|
||||||
|
$("link[rel=icon]")
|
||||||
|
.attr("href", statusPage.icon)
|
||||||
|
.removeAttr("type");
|
||||||
|
}
|
||||||
|
|
||||||
|
const head = $("head");
|
||||||
|
|
||||||
|
// OG Meta Tags
|
||||||
|
head.append(`<meta property="og:title" content="${statusPage.title}" />`);
|
||||||
|
head.append(`<meta property="og:description" content="${description155}" />`);
|
||||||
|
|
||||||
|
// Preload data
|
||||||
|
const json = JSON.stringify(await StatusPage.getStatusPageData(statusPage));
|
||||||
|
head.append(`
|
||||||
|
<script>
|
||||||
|
window.preloadData = ${json}
|
||||||
|
</script>
|
||||||
|
`);
|
||||||
|
|
||||||
|
return $.root().html();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all status page data in one call
|
||||||
|
* @param {StatusPage} statusPage
|
||||||
|
*/
|
||||||
|
static async getStatusPageData(statusPage) {
|
||||||
|
// Incident
|
||||||
|
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
|
||||||
|
statusPage.id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (incident) {
|
||||||
|
incident = incident.toPublicJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public Group List
|
||||||
|
const publicGroupList = [];
|
||||||
|
const showTags = !!statusPage.show_tags;
|
||||||
|
|
||||||
|
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
||||||
|
statusPage.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (let groupBean of list) {
|
||||||
|
let monitorGroup = await groupBean.toPublicJSON(showTags);
|
||||||
|
publicGroupList.push(monitorGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
return {
|
||||||
|
config: await statusPage.toPublicJSON(),
|
||||||
|
incident,
|
||||||
|
publicGroupList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads domain mapping from DB
|
* Loads domain mapping from DB
|
||||||
* Return object like this: { "test-uptime.kuma.pet": "default" }
|
* Return object like this: { "test-uptime.kuma.pet": "default" }
|
||||||
|
26
server/notification-providers/ntfy.js
Normal file
26
server/notification-providers/ntfy.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class Ntfy extends NotificationProvider {
|
||||||
|
|
||||||
|
name = "ntfy";
|
||||||
|
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
let okMsg = "Sent Successfully.";
|
||||||
|
try {
|
||||||
|
await axios.post(`${notification.ntfyserverurl}`, {
|
||||||
|
"topic": notification.ntfytopic,
|
||||||
|
"message": msg,
|
||||||
|
"priority": notification.ntfyPriority || 4,
|
||||||
|
"title": "Uptime-Kuma",
|
||||||
|
});
|
||||||
|
|
||||||
|
return okMsg;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Ntfy;
|
@@ -2,6 +2,7 @@ const { R } = require("redbean-node");
|
|||||||
const Apprise = require("./notification-providers/apprise");
|
const Apprise = require("./notification-providers/apprise");
|
||||||
const Discord = require("./notification-providers/discord");
|
const Discord = require("./notification-providers/discord");
|
||||||
const Gotify = require("./notification-providers/gotify");
|
const Gotify = require("./notification-providers/gotify");
|
||||||
|
const Ntfy = require("./notification-providers/ntfy");
|
||||||
const Line = require("./notification-providers/line");
|
const Line = require("./notification-providers/line");
|
||||||
const LunaSea = require("./notification-providers/lunasea");
|
const LunaSea = require("./notification-providers/lunasea");
|
||||||
const Mattermost = require("./notification-providers/mattermost");
|
const Mattermost = require("./notification-providers/mattermost");
|
||||||
@@ -52,6 +53,7 @@ class Notification {
|
|||||||
new Discord(),
|
new Discord(),
|
||||||
new Teams(),
|
new Teams(),
|
||||||
new Gotify(),
|
new Gotify(),
|
||||||
|
new Ntfy(),
|
||||||
new Line(),
|
new Line(),
|
||||||
new LunaSea(),
|
new LunaSea(),
|
||||||
new Feishu(),
|
new Feishu(),
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
let express = require("express");
|
let express = require("express");
|
||||||
const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin } = require("../util-server");
|
const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, send403 } = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const Monitor = require("../model/monitor");
|
const Monitor = require("../model/monitor");
|
||||||
@@ -92,115 +92,13 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
response.json({
|
response.status(404).json({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message
|
msg: e.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Status page config, incident, monitor list
|
|
||||||
router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
|
|
||||||
allowDevAllOrigin(response);
|
|
||||||
let slug = request.params.slug;
|
|
||||||
|
|
||||||
// Get Status Page
|
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
|
||||||
slug
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!statusPage) {
|
|
||||||
response.statusCode = 404;
|
|
||||||
response.json({
|
|
||||||
msg: "Not Found"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Incident
|
|
||||||
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
|
|
||||||
statusPage.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (incident) {
|
|
||||||
incident = incident.toPublicJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public Group List
|
|
||||||
const publicGroupList = [];
|
|
||||||
const showTags = !!statusPage.show_tags;
|
|
||||||
|
|
||||||
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
|
||||||
statusPage.id
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let groupBean of list) {
|
|
||||||
let monitorGroup = await groupBean.toPublicJSON(showTags);
|
|
||||||
publicGroupList.push(monitorGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response
|
|
||||||
response.json({
|
|
||||||
config: await statusPage.toPublicJSON(),
|
|
||||||
incident,
|
|
||||||
publicGroupList
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
send403(response, error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Status Page Polling Data
|
|
||||||
// Can fetch only if published
|
|
||||||
router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => {
|
|
||||||
allowDevAllOrigin(response);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let heartbeatList = {};
|
|
||||||
let uptimeList = {};
|
|
||||||
|
|
||||||
let slug = request.params.slug;
|
|
||||||
let statusPageID = await StatusPage.slugToID(slug);
|
|
||||||
|
|
||||||
let monitorIDList = await R.getCol(`
|
|
||||||
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
|
|
||||||
WHERE monitor_group.group_id = \`group\`.id
|
|
||||||
AND public = 1
|
|
||||||
AND \`group\`.status_page_id = ?
|
|
||||||
`, [
|
|
||||||
statusPageID
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let monitorID of monitorIDList) {
|
|
||||||
let list = await R.getAll(`
|
|
||||||
SELECT * FROM heartbeat
|
|
||||||
WHERE monitor_id = ?
|
|
||||||
ORDER BY time DESC
|
|
||||||
LIMIT 50
|
|
||||||
`, [
|
|
||||||
monitorID,
|
|
||||||
]);
|
|
||||||
|
|
||||||
list = R.convertToBeans("heartbeat", list);
|
|
||||||
heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON());
|
|
||||||
|
|
||||||
const type = 24;
|
|
||||||
uptimeList[`${monitorID}_${type}`] = await Monitor.calcUptime(type, monitorID);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.json({
|
|
||||||
heartbeatList,
|
|
||||||
uptimeList
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
send403(response, error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response) => {
|
router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response) => {
|
||||||
allowAllOrigin(response);
|
allowAllOrigin(response);
|
||||||
|
|
||||||
@@ -377,16 +275,4 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a 403 response
|
|
||||||
* @param {Object} res Express response object
|
|
||||||
* @param {string} [msg=""] Message to send
|
|
||||||
*/
|
|
||||||
function send403(res, msg = "") {
|
|
||||||
res.status(403).json({
|
|
||||||
"status": "fail",
|
|
||||||
"msg": msg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
110
server/routers/status-page-router.js
Normal file
110
server/routers/status-page-router.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
let express = require("express");
|
||||||
|
const apicache = require("../modules/apicache");
|
||||||
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
|
const StatusPage = require("../model/status_page");
|
||||||
|
const { allowDevAllOrigin, send403 } = require("../util-server");
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
const Monitor = require("../model/monitor");
|
||||||
|
|
||||||
|
let router = express.Router();
|
||||||
|
|
||||||
|
let cache = apicache.middleware;
|
||||||
|
const server = UptimeKumaServer.getInstance();
|
||||||
|
|
||||||
|
router.get("/status/:slug", cache("5 minutes"), async (request, response) => {
|
||||||
|
let slug = request.params.slug;
|
||||||
|
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/status", cache("5 minutes"), async (request, response) => {
|
||||||
|
let slug = "default";
|
||||||
|
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/status-page", cache("5 minutes"), async (request, response) => {
|
||||||
|
let slug = "default";
|
||||||
|
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Status page config, incident, monitor list
|
||||||
|
router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
|
||||||
|
allowDevAllOrigin(response);
|
||||||
|
let slug = request.params.slug;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get Status Page
|
||||||
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
slug
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!statusPage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusPageData = await StatusPage.getStatusPageData(statusPage);
|
||||||
|
|
||||||
|
if (!statusPageData) {
|
||||||
|
response.statusCode = 404;
|
||||||
|
response.json({
|
||||||
|
msg: "Not Found"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
response.json(statusPageData);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
send403(response, error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Status Page Polling Data
|
||||||
|
// Can fetch only if published
|
||||||
|
router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => {
|
||||||
|
allowDevAllOrigin(response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let heartbeatList = {};
|
||||||
|
let uptimeList = {};
|
||||||
|
|
||||||
|
let slug = request.params.slug;
|
||||||
|
let statusPageID = await StatusPage.slugToID(slug);
|
||||||
|
|
||||||
|
let monitorIDList = await R.getCol(`
|
||||||
|
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
|
||||||
|
WHERE monitor_group.group_id = \`group\`.id
|
||||||
|
AND public = 1
|
||||||
|
AND \`group\`.status_page_id = ?
|
||||||
|
`, [
|
||||||
|
statusPageID
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (let monitorID of monitorIDList) {
|
||||||
|
let list = await R.getAll(`
|
||||||
|
SELECT * FROM heartbeat
|
||||||
|
WHERE monitor_id = ?
|
||||||
|
ORDER BY time DESC
|
||||||
|
LIMIT 50
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
list = R.convertToBeans("heartbeat", list);
|
||||||
|
heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON());
|
||||||
|
|
||||||
|
const type = 24;
|
||||||
|
uptimeList[`${monitorID}_${type}`] = await Monitor.calcUptime(type, monitorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.json({
|
||||||
|
heartbeatList,
|
||||||
|
uptimeList
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
send403(response, error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
@@ -16,7 +16,7 @@ if (nodeVersion < requiredVersion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const args = require("args-parser")(process.argv);
|
const args = require("args-parser")(process.argv);
|
||||||
const { sleep, log, getRandomInt, genSecret, debug, isDev } = require("../src/util");
|
const { sleep, log, getRandomInt, genSecret, 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");
|
||||||
@@ -35,6 +35,7 @@ const fs = require("fs");
|
|||||||
log.info("server", "Importing 3rd-party libraries");
|
log.info("server", "Importing 3rd-party libraries");
|
||||||
log.debug("server", "Importing express");
|
log.debug("server", "Importing express");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
|
const expressStaticGzip = require("express-static-gzip");
|
||||||
log.debug("server", "Importing redbean-node");
|
log.debug("server", "Importing redbean-node");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
log.debug("server", "Importing jsonwebtoken");
|
log.debug("server", "Importing jsonwebtoken");
|
||||||
@@ -148,22 +149,6 @@ let jwtSecret = null;
|
|||||||
*/
|
*/
|
||||||
let needSetup = false;
|
let needSetup = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache Index HTML
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
let indexHTML = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|
||||||
} catch (e) {
|
|
||||||
// "dist/index.html" is not necessary for development
|
|
||||||
if (process.env.NODE_ENV !== "development") {
|
|
||||||
log.error("server", "Error: Cannot find 'dist/index.html', did you install correctly?");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
Database.init(args);
|
Database.init(args);
|
||||||
await initDatabase(testMode);
|
await initDatabase(testMode);
|
||||||
@@ -179,13 +164,17 @@ try {
|
|||||||
|
|
||||||
// Entry Page
|
// Entry Page
|
||||||
app.get("/", async (request, response) => {
|
app.get("/", async (request, response) => {
|
||||||
debug(`Request Domain: ${request.hostname}`);
|
log.debug("entry", `Request Domain: ${request.hostname}`);
|
||||||
|
|
||||||
if (request.hostname in StatusPage.domainMappingList) {
|
if (request.hostname in StatusPage.domainMappingList) {
|
||||||
debug("This is a status page domain");
|
log.debug("entry", "This is a status page domain");
|
||||||
response.send(indexHTML);
|
|
||||||
|
let slug = StatusPage.domainMappingList[request.hostname];
|
||||||
|
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||||
|
|
||||||
} else if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
|
} else if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
|
||||||
response.redirect("/status/" + exports.entryPage.replace("statusPage-", ""));
|
response.redirect("/status/" + exports.entryPage.replace("statusPage-", ""));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
response.redirect("/dashboard");
|
response.redirect("/dashboard");
|
||||||
}
|
}
|
||||||
@@ -214,7 +203,9 @@ try {
|
|||||||
// With Basic Auth using the first user's username/password
|
// With Basic Auth using the first user's username/password
|
||||||
app.get("/metrics", basicAuth, prometheusAPIMetrics());
|
app.get("/metrics", basicAuth, prometheusAPIMetrics());
|
||||||
|
|
||||||
app.use("/", express.static("dist"));
|
app.use("/", expressStaticGzip("dist", {
|
||||||
|
enableBrotli: true,
|
||||||
|
}));
|
||||||
|
|
||||||
// ./data/upload
|
// ./data/upload
|
||||||
app.use("/upload", express.static(Database.uploadDir));
|
app.use("/upload", express.static(Database.uploadDir));
|
||||||
@@ -227,12 +218,16 @@ try {
|
|||||||
const apiRouter = require("./routers/api-router");
|
const apiRouter = require("./routers/api-router");
|
||||||
app.use(apiRouter);
|
app.use(apiRouter);
|
||||||
|
|
||||||
|
// Status Page Router
|
||||||
|
const statusPageRouter = require("./routers/status-page-router");
|
||||||
|
app.use(statusPageRouter);
|
||||||
|
|
||||||
// Universal Route Handler, must be at the end of all express routes.
|
// Universal Route Handler, must be at the end of all express routes.
|
||||||
app.get("*", async (_request, response) => {
|
app.get("*", async (_request, response) => {
|
||||||
if (_request.originalUrl.startsWith("/upload/")) {
|
if (_request.originalUrl.startsWith("/upload/")) {
|
||||||
response.status(404).send("File not found.");
|
response.status(404).send("File not found.");
|
||||||
} else {
|
} else {
|
||||||
response.send(indexHTML);
|
response.send(server.indexHTML);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -674,6 +669,11 @@ try {
|
|||||||
bean.mqttPassword = monitor.mqttPassword;
|
bean.mqttPassword = monitor.mqttPassword;
|
||||||
bean.mqttTopic = monitor.mqttTopic;
|
bean.mqttTopic = monitor.mqttTopic;
|
||||||
bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
|
bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
|
||||||
|
bean.databaseConnectionString = monitor.databaseConnectionString;
|
||||||
|
bean.databaseQuery = monitor.databaseQuery;
|
||||||
|
bean.authMethod = monitor.authMethod;
|
||||||
|
bean.authWorkstation = monitor.authWorkstation;
|
||||||
|
bean.authDomain = monitor.authDomain;
|
||||||
|
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
@@ -1247,8 +1247,11 @@ try {
|
|||||||
method: monitorListData[i].method || "GET",
|
method: monitorListData[i].method || "GET",
|
||||||
body: monitorListData[i].body,
|
body: monitorListData[i].body,
|
||||||
headers: monitorListData[i].headers,
|
headers: monitorListData[i].headers,
|
||||||
|
authMethod: monitorListData[i].authMethod,
|
||||||
basic_auth_user: monitorListData[i].basic_auth_user,
|
basic_auth_user: monitorListData[i].basic_auth_user,
|
||||||
basic_auth_pass: monitorListData[i].basic_auth_pass,
|
basic_auth_pass: monitorListData[i].basic_auth_pass,
|
||||||
|
authWorkstation: monitorListData[i].authWorkstation,
|
||||||
|
authDomain: monitorListData[i].authDomain,
|
||||||
interval: monitorListData[i].interval,
|
interval: monitorListData[i].interval,
|
||||||
retryInterval: retryInterval,
|
retryInterval: retryInterval,
|
||||||
hostname: monitorListData[i].hostname,
|
hostname: monitorListData[i].hostname,
|
||||||
|
@@ -29,6 +29,12 @@ class UptimeKumaServer {
|
|||||||
httpServer = undefined;
|
httpServer = undefined;
|
||||||
io = undefined;
|
io = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache Index HTML
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
indexHTML = "";
|
||||||
|
|
||||||
static getInstance(args) {
|
static getInstance(args) {
|
||||||
if (UptimeKumaServer.instance == null) {
|
if (UptimeKumaServer.instance == null) {
|
||||||
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
||||||
@@ -55,6 +61,16 @@ class UptimeKumaServer {
|
|||||||
this.httpServer = http.createServer(this.app);
|
this.httpServer = http.createServer(this.app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||||
|
} catch (e) {
|
||||||
|
// "dist/index.html" is not necessary for development
|
||||||
|
if (process.env.NODE_ENV !== "development") {
|
||||||
|
log.error("server", "Error: Cannot find 'dist/index.html', did you install correctly?");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.io = new Server(this.httpServer);
|
this.io = new Server(this.httpServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,6 +10,8 @@ const chardet = require("chardet");
|
|||||||
const mqtt = require("mqtt");
|
const mqtt = require("mqtt");
|
||||||
const chroma = require("chroma-js");
|
const chroma = require("chroma-js");
|
||||||
const { badgeConstants } = require("./config");
|
const { badgeConstants } = require("./config");
|
||||||
|
const mssql = require("mssql");
|
||||||
|
const { NtlmClient } = require("axios-ntlm");
|
||||||
|
|
||||||
// From ping-lite
|
// From ping-lite
|
||||||
exports.WIN = /^win/.test(process.platform);
|
exports.WIN = /^win/.test(process.platform);
|
||||||
@@ -172,6 +174,26 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use NTLM Auth for a http request.
|
||||||
|
* @param {Object} options The http request options
|
||||||
|
* @param {Object} ntlmOptions The auth options
|
||||||
|
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||||
|
*/
|
||||||
|
exports.httpNtlm = function (options, ntlmOptions) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let client = NtlmClient(ntlmOptions);
|
||||||
|
|
||||||
|
client(options)
|
||||||
|
.then((resp) => {
|
||||||
|
resolve(resp);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a given record using the specified DNS server
|
* Resolves a given record using the specified DNS server
|
||||||
* @param {string} hostname The hostname of the record to lookup
|
* @param {string} hostname The hostname of the record to lookup
|
||||||
@@ -185,7 +207,7 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
|
|||||||
// Remove brackets from IPv6 addresses so we can re-add them to
|
// Remove brackets from IPv6 addresses so we can re-add them to
|
||||||
// prevent issues with ::1:5300 (::1 port 5300)
|
// prevent issues with ::1:5300 (::1 port 5300)
|
||||||
resolverServer = resolverServer.replace("[", "").replace("]", "");
|
resolverServer = resolverServer.replace("[", "").replace("]", "");
|
||||||
resolver.setServers([`[${resolverServer}]:${resolverPort}`]);
|
resolver.setServers([ `[${resolverServer}]:${resolverPort}` ]);
|
||||||
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) => {
|
||||||
@@ -207,6 +229,31 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a query on SQL Server
|
||||||
|
* @param {string} connectionString The database connection string
|
||||||
|
* @param {string} query The query to validate the database with
|
||||||
|
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||||
|
*/
|
||||||
|
exports.mssqlQuery = function (connectionString, query) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
mssql.on("error", err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
mssql.connect(connectionString).then(pool => {
|
||||||
|
return pool.request()
|
||||||
|
.query(query);
|
||||||
|
}).then(result => {
|
||||||
|
resolve(result);
|
||||||
|
}).catch(err => {
|
||||||
|
reject(err);
|
||||||
|
}).finally(() => {
|
||||||
|
mssql.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve value of setting based on key
|
* Retrieve value of setting based on key
|
||||||
* @param {string} key Key of setting to retrieve
|
* @param {string} key Key of setting to retrieve
|
||||||
@@ -558,3 +605,15 @@ exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
|
|||||||
exports.filterAndJoin = (parts, connector = "") => {
|
exports.filterAndJoin = (parts, connector = "") => {
|
||||||
return parts.filter((part) => !!part && part !== "").join(connector);
|
return parts.filter((part) => !!part && part !== "").join(connector);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a 403 response
|
||||||
|
* @param {Object} res Express response object
|
||||||
|
* @param {string} [msg=""] Message to send
|
||||||
|
*/
|
||||||
|
module.exports.send403 = (res, msg = "") => {
|
||||||
|
res.status(403).json({
|
||||||
|
"status": "fail",
|
||||||
|
"msg": msg,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@@ -34,6 +34,25 @@ textarea.form-control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// optgroup
|
||||||
|
optgroup {
|
||||||
|
color: #b1b1b1;
|
||||||
|
option {
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
optgroup {
|
||||||
|
color: #535864;
|
||||||
|
option {
|
||||||
|
color: $dark-font-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrollbar
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -364,6 +383,12 @@ textarea.form-control {
|
|||||||
height: calc(100% - 65px);
|
height: calc(100% - 65px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 770px) {
|
||||||
|
&.scrollbar {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
display: block;
|
display: block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -473,6 +498,14 @@ textarea.form-control {
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h5.settings-subheading::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 50%;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-bottom: 1px solid $dark-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
|
|
||||||
@import "localization.scss";
|
@import "localization.scss";
|
||||||
|
86
src/components/ActionInput.vue
Normal file
86
src/components/ActionInput.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input
|
||||||
|
ref="input"
|
||||||
|
v-model="model"
|
||||||
|
class="form-control"
|
||||||
|
:type="type"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="!enabled"
|
||||||
|
>
|
||||||
|
<a class="btn btn-outline-primary" @click="action()">
|
||||||
|
<font-awesome-icon :icon="icon" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Generic input field with a customizable action on the right.
|
||||||
|
* Action is passed in as a function.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* The value of the input field.
|
||||||
|
*/
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Whether the input field is enabled / disabled.
|
||||||
|
*/
|
||||||
|
enabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Placeholder text for the input field.
|
||||||
|
*/
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The icon displayed in the right button of the input field.
|
||||||
|
* Accepts a Font Awesome icon string identifier.
|
||||||
|
* @example "plus"
|
||||||
|
*/
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The input type of the input field.
|
||||||
|
* @example "email"
|
||||||
|
*/
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: "text",
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The action to be performed when the button is clicked.
|
||||||
|
* Action is passed in as a function.
|
||||||
|
*/
|
||||||
|
action: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: [ "update:modelValue" ],
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* Send value update to parent on change.
|
||||||
|
*/
|
||||||
|
model: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit("update:modelValue", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@@ -25,10 +25,12 @@ export default {
|
|||||||
CertificateInfoRow,
|
CertificateInfoRow,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
/** Object representing certificate */
|
||||||
certInfo: {
|
certInfo: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
/** Is the TLS certificate valid? */
|
||||||
valid: {
|
valid: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
|
@@ -56,12 +56,19 @@ export default {
|
|||||||
Datetime,
|
Datetime,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
/** Object representing certificate */
|
||||||
cert: {
|
cert: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Format the subject of the certificate
|
||||||
|
* @param {Object} subject Object representing the certificates
|
||||||
|
* subject
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
formatSubject(subject) {
|
formatSubject(subject) {
|
||||||
if (subject.O && subject.CN && subject.C) {
|
if (subject.O && subject.CN && subject.C) {
|
||||||
return `${subject.CN} - ${subject.O} (${subject.C})`;
|
return `${subject.CN} - ${subject.O} (${subject.C})`;
|
||||||
|
@@ -29,14 +29,17 @@ import { Modal } from "bootstrap";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Style of button */
|
||||||
btnStyle: {
|
btnStyle: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "btn-primary",
|
default: "btn-primary",
|
||||||
},
|
},
|
||||||
|
/** Text to use as yes */
|
||||||
yesText: {
|
yesText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "Yes", // TODO: No idea what to translate this
|
default: "Yes", // TODO: No idea what to translate this
|
||||||
},
|
},
|
||||||
|
/** Text to use as no */
|
||||||
noText: {
|
noText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "No",
|
default: "No",
|
||||||
@@ -50,9 +53,13 @@ export default {
|
|||||||
this.modal = new Modal(this.$refs.modal);
|
this.modal = new Modal(this.$refs.modal);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Show the confirm dialog */
|
||||||
show() {
|
show() {
|
||||||
this.modal.show();
|
this.modal.show();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @emits string "yes" Notify the parent when Yes is pressed
|
||||||
|
*/
|
||||||
yes() {
|
yes() {
|
||||||
this.$emit("yes");
|
this.$emit("yes");
|
||||||
},
|
},
|
||||||
|
@@ -25,33 +25,41 @@ let timeout;
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** ID of this input */
|
||||||
id: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
|
/** Type of input */
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "text"
|
default: "text"
|
||||||
},
|
},
|
||||||
|
/** The value of the input */
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
|
/** A placeholder to use */
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
|
/** Should the field auto complete */
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
|
/** Is the input required? */
|
||||||
required: {
|
required: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
|
/** Should the input be read only? */
|
||||||
readonly: {
|
readonly: {
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
|
/** Is the input disabled? */
|
||||||
disabled: {
|
disabled: {
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
@@ -79,14 +87,21 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
/** Show the input */
|
||||||
showInput() {
|
showInput() {
|
||||||
this.visibility = "text";
|
this.visibility = "text";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Hide the input */
|
||||||
hideInput() {
|
hideInput() {
|
||||||
this.visibility = "password";
|
this.visibility = "password";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the provided text to the users clipboard
|
||||||
|
* @param {string} textToCopy
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
copyToClipboard(textToCopy) {
|
copyToClipboard(textToCopy) {
|
||||||
this.icon = "check";
|
this.icon = "check";
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import { sleep } from "../util.ts";
|
|||||||
export default {
|
export default {
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
/** Value to count */
|
||||||
value: {
|
value: {
|
||||||
type: [ String, Number ],
|
type: [ String, Number ],
|
||||||
default: 0,
|
default: 0,
|
||||||
@@ -18,6 +19,7 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 0.3,
|
default: 0.3,
|
||||||
},
|
},
|
||||||
|
/** Unit of the value */
|
||||||
unit: {
|
unit: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "ms",
|
default: "ms",
|
||||||
@@ -43,9 +45,7 @@ export default {
|
|||||||
let frames = 12;
|
let frames = 12;
|
||||||
let step = Math.floor(diff / frames);
|
let step = Math.floor(diff / frames);
|
||||||
|
|
||||||
if (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0) {
|
if (! (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0)) {
|
||||||
// Lazy to NOT this condition, hahaha.
|
|
||||||
} else {
|
|
||||||
for (let i = 1; i < frames; i++) {
|
for (let i = 1; i < frames; i++) {
|
||||||
this.output += step;
|
this.output += step;
|
||||||
await sleep(15);
|
await sleep(15);
|
||||||
|
@@ -13,10 +13,12 @@ dayjs.extend(relativeTime);
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Value of date time */
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
/** Should only the date be displayed? */
|
||||||
dateOnly: {
|
dateOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@@ -17,14 +17,17 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Size of the heartbeat bar */
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "big",
|
default: "big",
|
||||||
},
|
},
|
||||||
|
/** ID of the monitor */
|
||||||
monitorId: {
|
monitorId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
/** Array of the monitors heartbeats */
|
||||||
heartbeatList: {
|
heartbeatList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null,
|
default: null,
|
||||||
@@ -160,12 +163,19 @@ export default {
|
|||||||
this.resize();
|
this.resize();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Resize the heartbeat bar */
|
||||||
resize() {
|
resize() {
|
||||||
if (this.$refs.wrap) {
|
if (this.$refs.wrap) {
|
||||||
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
|
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the beat.
|
||||||
|
* Used as the hover tooltip on the heartbeat bar.
|
||||||
|
* @param {Object} beat Beat to get title from
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
getBeatTitle(beat) {
|
getBeatTitle(beat) {
|
||||||
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
|
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
|
||||||
},
|
},
|
||||||
|
@@ -24,25 +24,31 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** The value of the input */
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
|
/** A placeholder to use */
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
|
/** Maximum length of the input */
|
||||||
maxlength: {
|
maxlength: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 255
|
default: 255
|
||||||
},
|
},
|
||||||
|
/** Should the field auto complete */
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
|
/** Is the input required? */
|
||||||
required: {
|
required: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
|
/** Should the input be read only? */
|
||||||
readonly: {
|
readonly: {
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
@@ -68,9 +74,11 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Show users input in plain text */
|
||||||
showInput() {
|
showInput() {
|
||||||
this.visibility = "text";
|
this.visibility = "text";
|
||||||
},
|
},
|
||||||
|
/** Censor users input */
|
||||||
hideInput() {
|
hideInput() {
|
||||||
this.visibility = "password";
|
this.visibility = "password";
|
||||||
},
|
},
|
||||||
|
@@ -55,6 +55,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Submit the user details and attempt to log in */
|
||||||
submit() {
|
submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
|
@@ -58,6 +58,7 @@ export default {
|
|||||||
Tag,
|
Tag,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
/** Should the scrollbar be shown */
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
@@ -69,10 +70,22 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
/**
|
||||||
|
* Improve the sticky appearance of the list by increasing its
|
||||||
|
* height as user scrolls down.
|
||||||
|
* Not used on mobile.
|
||||||
|
*/
|
||||||
boxStyle() {
|
boxStyle() {
|
||||||
return {
|
if (window.innerWidth > 550) {
|
||||||
height: `calc(100vh - 160px + ${this.windowTop}px)`,
|
return {
|
||||||
};
|
height: `calc(100vh - 160px + ${this.windowTop}px)`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
height: "calc(100vh - 160px)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sortedMonitorList() {
|
sortedMonitorList() {
|
||||||
@@ -124,6 +137,7 @@ export default {
|
|||||||
window.removeEventListener("scroll", this.onScroll);
|
window.removeEventListener("scroll", this.onScroll);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Handle user scroll */
|
||||||
onScroll() {
|
onScroll() {
|
||||||
if (window.top.scrollY <= 133) {
|
if (window.top.scrollY <= 133) {
|
||||||
this.windowTop = window.top.scrollY;
|
this.windowTop = window.top.scrollY;
|
||||||
@@ -131,9 +145,15 @@ export default {
|
|||||||
this.windowTop = 133;
|
this.windowTop = 133;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Get URL of monitor
|
||||||
|
* @param {number} id ID of monitor
|
||||||
|
* @returns {string} Relative URL of monitor
|
||||||
|
*/
|
||||||
monitorURL(id) {
|
monitorURL(id) {
|
||||||
return getMonitorRelativeURL(id);
|
return getMonitorRelativeURL(id);
|
||||||
},
|
},
|
||||||
|
/** Clear the search bar */
|
||||||
clearSearchText() {
|
clearSearchText() {
|
||||||
this.searchText = "";
|
this.searchText = "";
|
||||||
}
|
}
|
||||||
|
@@ -125,11 +125,16 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
/** Show dialog to confirm deletion */
|
||||||
deleteConfirm() {
|
deleteConfirm() {
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
this.$refs.confirmDelete.show();
|
this.$refs.confirmDelete.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show settings for specified notification
|
||||||
|
* @param {number} notificationID ID of notification to show
|
||||||
|
*/
|
||||||
show(notificationID) {
|
show(notificationID) {
|
||||||
if (notificationID) {
|
if (notificationID) {
|
||||||
this.id = notificationID;
|
this.id = notificationID;
|
||||||
@@ -152,6 +157,7 @@ export default {
|
|||||||
this.modal.show();
|
this.modal.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Submit the form to the server */
|
||||||
submit() {
|
submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => {
|
this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => {
|
||||||
@@ -170,6 +176,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Test the notification endpoint */
|
||||||
test() {
|
test() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("testNotification", this.notification, (res) => {
|
this.$root.getSocket().emit("testNotification", this.notification, (res) => {
|
||||||
@@ -178,6 +185,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Delete the notification endpoint */
|
||||||
deleteNotification() {
|
deleteNotification() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("deleteNotification", this.id, (res) => {
|
this.$root.getSocket().emit("deleteNotification", this.id, (res) => {
|
||||||
@@ -190,6 +198,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
* Get a unique default name for the notification
|
||||||
* @param {keyof NotificationFormList} notificationKey
|
* @param {keyof NotificationFormList} notificationKey
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
|
@@ -35,6 +35,7 @@ Chart.register(LineController, BarController, LineElement, PointElement, TimeSca
|
|||||||
export default {
|
export default {
|
||||||
components: { LineChart },
|
components: { LineChart },
|
||||||
props: {
|
props: {
|
||||||
|
/** ID of monitor */
|
||||||
monitorId: {
|
monitorId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
|
@@ -130,11 +130,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Show dialog to confirm deletion */
|
||||||
deleteConfirm() {
|
deleteConfirm() {
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
this.$refs.confirmDelete.show();
|
this.$refs.confirmDelete.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show settings for specified proxy
|
||||||
|
* @param {number} proxyID ID of proxy to show
|
||||||
|
*/
|
||||||
show(proxyID) {
|
show(proxyID) {
|
||||||
if (proxyID) {
|
if (proxyID) {
|
||||||
this.id = proxyID;
|
this.id = proxyID;
|
||||||
@@ -163,6 +168,7 @@ export default {
|
|||||||
this.modal.show();
|
this.modal.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Submit form data for saving */
|
||||||
submit() {
|
submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
|
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
|
||||||
@@ -180,6 +186,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Delete this proxy */
|
||||||
deleteProxy() {
|
deleteProxy() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("deleteProxy", this.id, (res) => {
|
this.$root.getSocket().emit("deleteProxy", this.id, (res) => {
|
||||||
|
@@ -72,10 +72,12 @@ export default {
|
|||||||
Tag,
|
Tag,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
/** Are we in edit mode? */
|
||||||
editMode: {
|
editMode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
/** Should tags be shown? */
|
||||||
showTags: {
|
showTags: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
}
|
}
|
||||||
@@ -94,10 +96,20 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Remove the specified group
|
||||||
|
* @param {number} index Index of group to remove
|
||||||
|
*/
|
||||||
removeGroup(index) {
|
removeGroup(index) {
|
||||||
this.$root.publicGroupList.splice(index, 1);
|
this.$root.publicGroupList.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a monitor from a group
|
||||||
|
* @param {number} groupIndex Index of group to remove monitor
|
||||||
|
* from
|
||||||
|
* @param {number} index Index of monitor to remove
|
||||||
|
*/
|
||||||
removeMonitor(groupIndex, index) {
|
removeMonitor(groupIndex, index) {
|
||||||
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
|
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Current status of monitor */
|
||||||
status: {
|
status: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
|
@@ -20,14 +20,20 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Object representing tag */
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
/** Function to remove tag */
|
||||||
remove: {
|
remove: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Size of tag
|
||||||
|
* @values normal, small
|
||||||
|
*/
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "normal",
|
default: "normal",
|
||||||
|
@@ -139,6 +139,7 @@ export default {
|
|||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
/** Array of tags to be pre-selected */
|
||||||
preSelectedTags: {
|
preSelectedTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -241,9 +242,11 @@ export default {
|
|||||||
this.getExistingTags();
|
this.getExistingTags();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Show the add tag dialog */
|
||||||
showAddDialog() {
|
showAddDialog() {
|
||||||
this.modal.show();
|
this.modal.show();
|
||||||
},
|
},
|
||||||
|
/** Get all existing tags */
|
||||||
getExistingTags() {
|
getExistingTags() {
|
||||||
this.$root.getSocket().emit("getTags", (res) => {
|
this.$root.getSocket().emit("getTags", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -253,6 +256,10 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Delete the specified tag
|
||||||
|
* @param {Object} tag Object representing tag to delete
|
||||||
|
*/
|
||||||
deleteTag(item) {
|
deleteTag(item) {
|
||||||
if (item.new) {
|
if (item.new) {
|
||||||
// Undo Adding a new Tag
|
// Undo Adding a new Tag
|
||||||
@@ -262,6 +269,13 @@ export default {
|
|||||||
this.deleteTags.push(item);
|
this.deleteTags.push(item);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Get colour of text inside the tag
|
||||||
|
* @param {Object} option The tag that needs to be displayed.
|
||||||
|
* Defaults to "white" unless the tag has no color, which will
|
||||||
|
* then return the body color (based on application theme)
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
textColor(option) {
|
textColor(option) {
|
||||||
if (option.color) {
|
if (option.color) {
|
||||||
return "white";
|
return "white";
|
||||||
@@ -269,6 +283,7 @@ export default {
|
|||||||
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
|
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/** Add a draft tag */
|
||||||
addDraftTag() {
|
addDraftTag() {
|
||||||
console.log("Adding Draft Tag: ", this.newDraftTag);
|
console.log("Adding Draft Tag: ", this.newDraftTag);
|
||||||
if (this.newDraftTag.select != null) {
|
if (this.newDraftTag.select != null) {
|
||||||
@@ -296,6 +311,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.clearDraftTag();
|
this.clearDraftTag();
|
||||||
},
|
},
|
||||||
|
/** Remove a draft tag */
|
||||||
clearDraftTag() {
|
clearDraftTag() {
|
||||||
this.newDraftTag = {
|
this.newDraftTag = {
|
||||||
name: null,
|
name: null,
|
||||||
@@ -307,26 +323,51 @@ export default {
|
|||||||
};
|
};
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Add a tag asynchronously
|
||||||
|
* @param {Object} newTag Object representing new tag to add
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
addTagAsync(newTag) {
|
addTagAsync(newTag) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.$root.getSocket().emit("addTag", newTag, resolve);
|
this.$root.getSocket().emit("addTag", newTag, resolve);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Add a tag to a monitor asynchronously
|
||||||
|
* @param {number} tagId ID of tag to add
|
||||||
|
* @param {number} monitorId ID of monitor to add tag to
|
||||||
|
* @param {string} value Value of tag
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
addMonitorTagAsync(tagId, monitorId, value) {
|
addMonitorTagAsync(tagId, monitorId, value) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve);
|
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Delete a tag from a monitor asynchronously
|
||||||
|
* @param {number} tagId ID of tag to remove
|
||||||
|
* @param {number} monitorId ID of monitor to remove tag from
|
||||||
|
* @param {string} value Value of tag
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
deleteMonitorTagAsync(tagId, monitorId, value) {
|
deleteMonitorTagAsync(tagId, monitorId, value) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
|
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/** Handle pressing Enter key when inside the modal */
|
||||||
onEnter() {
|
onEnter() {
|
||||||
if (!this.validateDraftTag.invalid) {
|
if (!this.validateDraftTag.invalid) {
|
||||||
this.addDraftTag();
|
this.addDraftTag();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Submit the form data
|
||||||
|
* @param {number} monitorId ID of monitor this change affects
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
async submit(monitorId) {
|
async submit(monitorId) {
|
||||||
console.log(`Submitting tag changes for monitor ${monitorId}...`);
|
console.log(`Submitting tag changes for monitor ${monitorId}...`);
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
@@ -29,10 +29,12 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Heading of the section */
|
||||||
heading: {
|
heading: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
/** Should the section be open by default? */
|
||||||
defaultOpen: {
|
defaultOpen: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@@ -100,18 +100,22 @@ export default {
|
|||||||
this.getStatus();
|
this.getStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Show the dialog */
|
||||||
show() {
|
show() {
|
||||||
this.modal.show();
|
this.modal.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog to confirm enabling 2FA */
|
||||||
confirmEnableTwoFA() {
|
confirmEnableTwoFA() {
|
||||||
this.$refs.confirmEnableTwoFA.show();
|
this.$refs.confirmEnableTwoFA.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog to confirm disabling 2FA */
|
||||||
confirmDisableTwoFA() {
|
confirmDisableTwoFA() {
|
||||||
this.$refs.confirmDisableTwoFA.show();
|
this.$refs.confirmDisableTwoFA.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Prepare 2FA configuration */
|
||||||
prepare2FA() {
|
prepare2FA() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
@@ -126,6 +130,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Save the current 2FA configuration */
|
||||||
save2FA() {
|
save2FA() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
@@ -143,6 +148,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Disable 2FA for this user */
|
||||||
disable2FA() {
|
disable2FA() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
@@ -160,6 +166,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Verify the token generated by the user */
|
||||||
verifyToken() {
|
verifyToken() {
|
||||||
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
|
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -170,6 +177,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Get current status of 2FA */
|
||||||
getStatus() {
|
getStatus() {
|
||||||
this.$root.getSocket().emit("twoFAStatus", (res) => {
|
this.$root.getSocket().emit("twoFAStatus", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
@@ -5,14 +5,17 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
/** Monitor this represents */
|
||||||
monitor: {
|
monitor: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
/** Type of monitor */
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
/** Is this a pill? */
|
||||||
pill: {
|
pill: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
30
src/components/notifications/Ntfy.vue
Normal file
30
src/components/notifications/Ntfy.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="ntfy-ntfytopic" class="form-label">{{ $t("ntfy Topic") }}</label>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input id="ntfy-ntfytopic" v-model="$parent.notification.ntfytopic" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="ntfy-server-url" class="form-label">{{ $t("Server URL") }}</label>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input id="ntfy-server-url" v-model="$parent.notification.ntfyserverurl" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="ntfy-priority" class="form-label">{{ $t("Priority") }}</label>
|
||||||
|
<input id="ntfy-priority" v-model="$parent.notification.ntfyPriority" type="number" class="form-control" required min="1" max="5" step="1">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
mounted() {
|
||||||
|
if (typeof this.$parent.notification.ntfyPriority === "undefined") {
|
||||||
|
this.$parent.notification.ntfyserverurl = "https://ntfy.sh";
|
||||||
|
this.$parent.notification.ntfyPriority = 5;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="promosms-login" class="form-label">{{ $("promosmsLogin") }}</label>
|
<label for="promosms-login" class="form-label">{{ $t("promosmsLogin") }}</label>
|
||||||
<input id="promosms-login" v-model="$parent.notification.promosmsLogin" type="text" class="form-control" required>
|
<input id="promosms-login" v-model="$parent.notification.promosmsLogin" type="text" class="form-control" required>
|
||||||
<label for="promosms-key" class="form-label">{{ $("promosmsPassword") }}</label>
|
<label for="promosms-key" class="form-label">{{ $t("promosmsPassword") }}</label>
|
||||||
<HiddenInput id="promosms-key" v-model="$parent.notification.promosmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
|
<HiddenInput id="promosms-key" v-model="$parent.notification.promosmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@@ -4,6 +4,7 @@ import Discord from "./Discord.vue";
|
|||||||
import Webhook from "./Webhook.vue";
|
import Webhook from "./Webhook.vue";
|
||||||
import Signal from "./Signal.vue";
|
import Signal from "./Signal.vue";
|
||||||
import Gotify from "./Gotify.vue";
|
import Gotify from "./Gotify.vue";
|
||||||
|
import Ntfy from "./Ntfy.vue";
|
||||||
import Slack from "./Slack.vue";
|
import Slack from "./Slack.vue";
|
||||||
import RocketChat from "./RocketChat.vue";
|
import RocketChat from "./RocketChat.vue";
|
||||||
import Teams from "./Teams.vue";
|
import Teams from "./Teams.vue";
|
||||||
@@ -46,6 +47,7 @@ const NotificationFormList = {
|
|||||||
"teams": Teams,
|
"teams": Teams,
|
||||||
"signal": Signal,
|
"signal": Signal,
|
||||||
"gotify": Gotify,
|
"gotify": Gotify,
|
||||||
|
"ntfy": Ntfy,
|
||||||
"slack": Slack,
|
"slack": Slack,
|
||||||
"rocket.chat": RocketChat,
|
"rocket.chat": RocketChat,
|
||||||
"pushover": Pushover,
|
"pushover": Pushover,
|
||||||
|
@@ -133,10 +133,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Show the confimation dialog confirming the configuration
|
||||||
|
* be imported
|
||||||
|
*/
|
||||||
confirmImport() {
|
confirmImport() {
|
||||||
this.$refs.confirmImport.show();
|
this.$refs.confirmImport.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Download a backup of the configuration */
|
||||||
downloadBackup() {
|
downloadBackup() {
|
||||||
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
|
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
|
||||||
let fileName = `Uptime_Kuma_Backup_${time}.json`;
|
let fileName = `Uptime_Kuma_Backup_${time}.json`;
|
||||||
@@ -157,6 +162,10 @@ export default {
|
|||||||
downloadItem.click();
|
downloadItem.click();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the specified backup file
|
||||||
|
* @returns {?string}
|
||||||
|
*/
|
||||||
importBackup() {
|
importBackup() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
let uploadItem = document.getElementById("import-backend").files;
|
let uploadItem = document.getElementById("import-backend").files;
|
||||||
|
@@ -178,10 +178,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Save the settings */
|
||||||
saveGeneral() {
|
saveGeneral() {
|
||||||
localStorage.timezone = this.$root.userTimezone;
|
localStorage.timezone = this.$root.userTimezone;
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
},
|
},
|
||||||
|
/** Get the base URL of the application */
|
||||||
autoGetPrimaryBaseURL() {
|
autoGetPrimaryBaseURL() {
|
||||||
this.settings.primaryBaseURL = location.protocol + "//" + location.host;
|
this.settings.primaryBaseURL = location.protocol + "//" + location.host;
|
||||||
},
|
},
|
||||||
|
@@ -90,6 +90,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Get the current size of the database */
|
||||||
loadDatabaseSize() {
|
loadDatabaseSize() {
|
||||||
log.debug("monitorhistory", "load database size");
|
log.debug("monitorhistory", "load database size");
|
||||||
this.$root.getSocket().emit("getDatabaseSize", (res) => {
|
this.$root.getSocket().emit("getDatabaseSize", (res) => {
|
||||||
@@ -102,6 +103,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Request that the database is shrunk */
|
||||||
shrinkDatabase() {
|
shrinkDatabase() {
|
||||||
this.$root.getSocket().emit("shrinkDatabase", (res) => {
|
this.$root.getSocket().emit("shrinkDatabase", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -113,10 +115,12 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show the dialog to confirm clearing stats */
|
||||||
confirmClearStatistics() {
|
confirmClearStatistics() {
|
||||||
this.$refs.confirmClearStatistics.show();
|
this.$refs.confirmClearStatistics.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Send the request to clear stats */
|
||||||
clearStatistics() {
|
clearStatistics() {
|
||||||
this.$root.clearStatistics((res) => {
|
this.$root.clearStatistics((res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
@@ -20,16 +20,91 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="my-4 pt-4">
|
||||||
|
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
|
||||||
|
<p>{{ $t("certificationExpiryDescription") }}</p>
|
||||||
|
<div class="mt-1 mb-3 ps-2 cert-exp-days col-12 col-xl-6">
|
||||||
|
<div v-for="day in settings.tlsExpiryNotifyDays" :key="day" class="d-flex align-items-center justify-content-between cert-exp-day-row py-2">
|
||||||
|
<span>{{ day }} {{ $tc("day", day) }}</span>
|
||||||
|
<button type="button" class="btn-rm-expiry btn btn-outline-danger ms-2 py-1" @click="removeExpiryNotifDay(day)">
|
||||||
|
<font-awesome-icon class="" icon="times" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-xl-6">
|
||||||
|
<ActionInput v-model="expiryNotifInput" :type="'number'" :placeholder="$t('day')" :icon="'plus'" :action="() => addExpiryNotifDay(expiryNotifInput)" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" type="button" @click="saveSettings()">
|
||||||
|
{{ $t("Save") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<NotificationDialog ref="notificationDialog" />
|
<NotificationDialog ref="notificationDialog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotificationDialog from "../../components/NotificationDialog.vue";
|
import NotificationDialog from "../../components/NotificationDialog.vue";
|
||||||
|
import ActionInput from "../ActionInput.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NotificationDialog
|
NotificationDialog,
|
||||||
|
ActionInput,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Variable to store the input for new certificate expiry day.
|
||||||
|
*/
|
||||||
|
expiryNotifInput: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
settings() {
|
||||||
|
return this.$parent.$parent.$parent.settings;
|
||||||
|
},
|
||||||
|
saveSettings() {
|
||||||
|
return this.$parent.$parent.$parent.saveSettings;
|
||||||
|
},
|
||||||
|
settingsLoaded() {
|
||||||
|
return this.$parent.$parent.$parent.settingsLoaded;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Remove a day from expiry notification days.
|
||||||
|
* @param {number} day The day to remove.
|
||||||
|
*/
|
||||||
|
removeExpiryNotifDay(day) {
|
||||||
|
this.settings.tlsExpiryNotifyDays = this.settings.tlsExpiryNotifyDays.filter(d => d !== day);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Add a new expiry notification day.
|
||||||
|
* Will verify:
|
||||||
|
* - day is not null or empty string.
|
||||||
|
* - day is a number.
|
||||||
|
* - day is > 0.
|
||||||
|
* - The day is not already in the list.
|
||||||
|
* @param {number} day The day number to add.
|
||||||
|
*/
|
||||||
|
addExpiryNotifDay(day) {
|
||||||
|
if (day != null && day !== "") {
|
||||||
|
const parsedDay = parseInt(day);
|
||||||
|
if (parsedDay != null && !isNaN(parsedDay) && parsedDay > 0) {
|
||||||
|
if (!this.settings.tlsExpiryNotifyDays.includes(parsedDay)) {
|
||||||
|
this.settings.tlsExpiryNotifyDays.push(parseInt(day));
|
||||||
|
this.settings.tlsExpiryNotifyDays.sort((a, b) => a - b);
|
||||||
|
this.expiryNotifInput = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -37,10 +112,27 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../assets/vars.scss";
|
@import "../../assets/vars.scss";
|
||||||
|
|
||||||
|
.btn-rm-expiry {
|
||||||
|
padding-left: 11px;
|
||||||
|
padding-right: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
background-color: $dark-bg2;
|
background-color: $dark-bg2;
|
||||||
color: $dark-font-color;
|
color: $dark-font-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cert-exp-days .cert-exp-day-row {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
border-bottom: 1px solid $dark-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-exp-days .cert-exp-day-row:last-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -120,14 +120,17 @@ export default {
|
|||||||
this.$root.getSocket().emit(prefix + "leave");
|
this.$root.getSocket().emit(prefix + "leave");
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Start the Cloudflare tunnel */
|
||||||
start() {
|
start() {
|
||||||
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
|
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
|
||||||
},
|
},
|
||||||
|
/** Stop the Cloudflare tunnel */
|
||||||
stop() {
|
stop() {
|
||||||
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
|
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/** Remove the token for the Cloudflare tunnel */
|
||||||
removeToken() {
|
removeToken() {
|
||||||
this.$root.getSocket().emit(prefix + "removeToken");
|
this.$root.getSocket().emit(prefix + "removeToken");
|
||||||
this.cloudflareTunnelToken = "";
|
this.cloudflareTunnelToken = "";
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<button v-if="! settings.disableAuth" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
|
<button v-if="! settings.disableAuth" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h5 class="my-4">{{ $t("Change Password") }}</h5>
|
<h5 class="my-4 settings-subheading">{{ $t("Change Password") }}</h5>
|
||||||
<form class="mb-3" @submit.prevent="savePassword">
|
<form class="mb-3" @submit.prevent="savePassword">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="current-password" class="form-label">
|
<label for="current-password" class="form-label">
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
|
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
|
||||||
<h5 class="my-4">
|
<h5 class="my-4 settings-subheading">
|
||||||
{{ $t("Two Factor Authentication") }}
|
{{ $t("Two Factor Authentication") }}
|
||||||
</h5>
|
</h5>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
<!-- Advanced -->
|
<!-- Advanced -->
|
||||||
<h5 class="my-4">{{ $t("Advanced") }}</h5>
|
<h5 class="my-4 settings-subheading">{{ $t("Advanced") }}</h5>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
|
<button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
|
||||||
@@ -303,6 +303,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Check new passwords match before saving them */
|
||||||
savePassword() {
|
savePassword() {
|
||||||
if (this.password.newPassword !== this.password.repeatNewPassword) {
|
if (this.password.newPassword !== this.password.repeatNewPassword) {
|
||||||
this.invalidPassword = true;
|
this.invalidPassword = true;
|
||||||
@@ -320,6 +321,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Disable authentication for web app access */
|
||||||
disableAuth() {
|
disableAuth() {
|
||||||
this.settings.disableAuth = true;
|
this.settings.disableAuth = true;
|
||||||
|
|
||||||
@@ -332,6 +334,7 @@ export default {
|
|||||||
}, this.password.currentPassword);
|
}, this.password.currentPassword);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Enable authentication for web app access */
|
||||||
enableAuth() {
|
enableAuth() {
|
||||||
this.settings.disableAuth = false;
|
this.settings.disableAuth = false;
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
@@ -346,15 +349,3 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import "../../assets/vars.scss";
|
|
||||||
|
|
||||||
h5::after {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: 50%;
|
|
||||||
padding-top: 8px;
|
|
||||||
border-bottom: 1px solid $dark-border-color;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -55,8 +55,7 @@ export default {
|
|||||||
Current: "Текущ",
|
Current: "Текущ",
|
||||||
Uptime: "Достъпност",
|
Uptime: "Достъпност",
|
||||||
"Cert Exp.": "Вал. сертификат",
|
"Cert Exp.": "Вал. сертификат",
|
||||||
days: "дни",
|
day: "ден | дни",
|
||||||
day: "ден",
|
|
||||||
"-day": "-дни",
|
"-day": "-дни",
|
||||||
hour: "час",
|
hour: "час",
|
||||||
"-hour": "-часa",
|
"-hour": "-часa",
|
||||||
@@ -422,6 +421,7 @@ export default {
|
|||||||
Next: "Следващ",
|
Next: "Следващ",
|
||||||
"The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
|
"The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
|
||||||
"No Proxy": "Без прокси",
|
"No Proxy": "Без прокси",
|
||||||
|
Authentication: "Удостоверяване",
|
||||||
"HTTP Basic Auth": "HTTP основно удостоверяване",
|
"HTTP Basic Auth": "HTTP основно удостоверяване",
|
||||||
"New Status Page": "Нова статус страница",
|
"New Status Page": "Нова статус страница",
|
||||||
"Page Not Found": "Страницата не е открита",
|
"Page Not Found": "Страницата не е открита",
|
||||||
@@ -515,4 +515,18 @@ export default {
|
|||||||
"Go back to the previous page.": "Да се върнете към предишната страница.",
|
"Go back to the previous page.": "Да се върнете към предишната страница.",
|
||||||
"Coming Soon": "Очаквайте скоро",
|
"Coming Soon": "Очаквайте скоро",
|
||||||
wayToGetClickSendSMSToken: "Може да получите API потребителско име и API ключ от {0} .",
|
wayToGetClickSendSMSToken: "Може да получите API потребителско име и API ключ от {0} .",
|
||||||
|
dnsPortDescription: "DNS порт на сървъра. По подразбиране е 53, но може да бъде променен по всяко време.",
|
||||||
|
error: "грешка",
|
||||||
|
critical: "критична",
|
||||||
|
wayToGetPagerDutyKey: "Може да го получите като посетите Service -> Service Directory -> (Select a service) -> Integrations -> Add integration. Тук може да потърсите \"Events API V2\". Повече информация {0}",
|
||||||
|
"Integration Key": "Ключ за интегриране",
|
||||||
|
"Integration URL": "URL адрес за интеграция",
|
||||||
|
"Auto resolve or acknowledged": "Автоматично разрешаване или потвърждаване",
|
||||||
|
"do nothing": "не прави нищо",
|
||||||
|
"auto acknowledged": "автоматично потвърждаване",
|
||||||
|
"auto resolve": "автоматично потвърждаване",
|
||||||
|
"Connection String": "Стринг за връзка",
|
||||||
|
Query: "Заявка",
|
||||||
|
settingsCertificateExpiry: "Изтичане валидността на TLS сертификата",
|
||||||
|
certificationExpiryDescription: "HTTPS мониторите задействат известие при изтичане на TLS сертификата в:",
|
||||||
};
|
};
|
||||||
|
@@ -56,8 +56,7 @@ export default {
|
|||||||
Current: "Aktuální",
|
Current: "Aktuální",
|
||||||
Uptime: "Doba provozu",
|
Uptime: "Doba provozu",
|
||||||
"Cert Exp.": "Platnost certifikátu",
|
"Cert Exp.": "Platnost certifikátu",
|
||||||
days: "dny/í",
|
day: "den | dny/í",
|
||||||
day: "den",
|
|
||||||
"-day": "-dní",
|
"-day": "-dní",
|
||||||
hour: "hodina",
|
hour: "hodina",
|
||||||
"-hour": "-hodin",
|
"-hour": "-hodin",
|
||||||
|
@@ -30,8 +30,7 @@ export default {
|
|||||||
Current: "Aktuelt",
|
Current: "Aktuelt",
|
||||||
Uptime: "Oppetid",
|
Uptime: "Oppetid",
|
||||||
"Cert Exp.": "Certifikatets udløb",
|
"Cert Exp.": "Certifikatets udløb",
|
||||||
days: "Dage",
|
day: "Dag | Dage",
|
||||||
day: "Dag",
|
|
||||||
"-day": "-Dage",
|
"-day": "-Dage",
|
||||||
hour: "Timer",
|
hour: "Timer",
|
||||||
"-hour": "-Timer",
|
"-hour": "-Timer",
|
||||||
|
@@ -30,8 +30,7 @@ export default {
|
|||||||
Current: "Aktuell",
|
Current: "Aktuell",
|
||||||
Uptime: "Verfügbarkeit",
|
Uptime: "Verfügbarkeit",
|
||||||
"Cert Exp.": "Zertifikatsablauf",
|
"Cert Exp.": "Zertifikatsablauf",
|
||||||
days: "Tage",
|
day: "Tag | Tage",
|
||||||
day: "Tag",
|
|
||||||
"-day": "-Tage",
|
"-day": "-Tage",
|
||||||
hour: "Stunde",
|
hour: "Stunde",
|
||||||
"-hour": "-Stunden",
|
"-hour": "-Stunden",
|
||||||
@@ -422,6 +421,7 @@ export default {
|
|||||||
Next: "Weiter",
|
Next: "Weiter",
|
||||||
"The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.",
|
"The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.",
|
||||||
"No Proxy": "Kein Proxy",
|
"No Proxy": "Kein Proxy",
|
||||||
|
Authentication: "Authentifizierung",
|
||||||
"HTTP Basic Auth": "HTTP Basisauthentifizierung",
|
"HTTP Basic Auth": "HTTP Basisauthentifizierung",
|
||||||
"New Status Page": "Neue Status-Seite",
|
"New Status Page": "Neue Status-Seite",
|
||||||
"Page Not Found": "Seite nicht gefunden",
|
"Page Not Found": "Seite nicht gefunden",
|
||||||
|
@@ -57,8 +57,7 @@ export default {
|
|||||||
Current: "Current",
|
Current: "Current",
|
||||||
Uptime: "Uptime",
|
Uptime: "Uptime",
|
||||||
"Cert Exp.": "Cert Exp.",
|
"Cert Exp.": "Cert Exp.",
|
||||||
days: "days",
|
day: "day | days",
|
||||||
day: "day",
|
|
||||||
"-day": "-day",
|
"-day": "-day",
|
||||||
hour: "hour",
|
hour: "hour",
|
||||||
"-hour": "-hour",
|
"-hour": "-hour",
|
||||||
@@ -439,6 +438,7 @@ export default {
|
|||||||
Next: "Next",
|
Next: "Next",
|
||||||
"The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.",
|
"The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.",
|
||||||
"No Proxy": "No Proxy",
|
"No Proxy": "No Proxy",
|
||||||
|
Authentication: "Authentication",
|
||||||
"HTTP Basic Auth": "HTTP Basic Auth",
|
"HTTP Basic Auth": "HTTP Basic Auth",
|
||||||
"New Status Page": "New Status Page",
|
"New Status Page": "New Status Page",
|
||||||
"Page Not Found": "Page Not Found",
|
"Page Not Found": "Page Not Found",
|
||||||
@@ -525,4 +525,8 @@ export default {
|
|||||||
"Go back to the previous page.": "Go back to the previous page.",
|
"Go back to the previous page.": "Go back to the previous page.",
|
||||||
"Coming Soon": "Coming Soon",
|
"Coming Soon": "Coming Soon",
|
||||||
wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .",
|
wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .",
|
||||||
|
"Connection String": "Connection String",
|
||||||
|
"Query": "Query",
|
||||||
|
settingsCertificateExpiry: "TLS Certificate Expiry",
|
||||||
|
certificationExpiryDescription: "HTTPS Monitors trigger notification when TLS certificate expires in:",
|
||||||
};
|
};
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "Actual",
|
Current: "Actual",
|
||||||
Uptime: "Tiempo activo",
|
Uptime: "Tiempo activo",
|
||||||
"Cert Exp.": "Caducidad cert.",
|
"Cert Exp.": "Caducidad cert.",
|
||||||
days: "días",
|
day: "día | días",
|
||||||
day: "día",
|
|
||||||
"-day": "-día",
|
"-day": "-día",
|
||||||
hour: "hora",
|
hour: "hora",
|
||||||
"-hour": "-hora",
|
"-hour": "-hora",
|
||||||
|
@@ -47,8 +47,7 @@ export default {
|
|||||||
Current: "Hetkeseisund",
|
Current: "Hetkeseisund",
|
||||||
Uptime: "Eluiga",
|
Uptime: "Eluiga",
|
||||||
"Cert Exp.": "Sert. aegumine",
|
"Cert Exp.": "Sert. aegumine",
|
||||||
days: "päeva",
|
day: "päev | päeva",
|
||||||
day: "päev",
|
|
||||||
"-day": "-päev",
|
"-day": "-päev",
|
||||||
hour: "tund",
|
hour: "tund",
|
||||||
"-hour": "-tund",
|
"-hour": "-tund",
|
||||||
|
@@ -55,7 +55,6 @@ export default {
|
|||||||
Current: "فعلی",
|
Current: "فعلی",
|
||||||
Uptime: "آپتایم",
|
Uptime: "آپتایم",
|
||||||
"Cert Exp.": "تاریخ انقضای SSL",
|
"Cert Exp.": "تاریخ انقضای SSL",
|
||||||
days: "روز",
|
|
||||||
day: "روز",
|
day: "روز",
|
||||||
"-day": "-روز",
|
"-day": "-روز",
|
||||||
hour: "ساعت",
|
hour: "ساعت",
|
||||||
|
@@ -55,8 +55,7 @@ export default {
|
|||||||
Current: "Actuellement",
|
Current: "Actuellement",
|
||||||
Uptime: "Uptime",
|
Uptime: "Uptime",
|
||||||
"Cert Exp.": "Expiration SSL",
|
"Cert Exp.": "Expiration SSL",
|
||||||
days: "jours",
|
day: "jour | jours",
|
||||||
day: "jour",
|
|
||||||
"-day": "-jours",
|
"-day": "-jours",
|
||||||
hour: "-heure",
|
hour: "-heure",
|
||||||
"-hour": "-heures",
|
"-hour": "-heures",
|
||||||
|
@@ -56,8 +56,7 @@ export default {
|
|||||||
Current: "Trenutno",
|
Current: "Trenutno",
|
||||||
Uptime: "Dostupnost",
|
Uptime: "Dostupnost",
|
||||||
"Cert Exp.": "Istek cert.",
|
"Cert Exp.": "Istek cert.",
|
||||||
days: "dana",
|
day: "dan | dana",
|
||||||
day: "dan",
|
|
||||||
"-day": "-dnevno",
|
"-day": "-dnevno",
|
||||||
hour: "sat",
|
hour: "sat",
|
||||||
"-hour": "-satno",
|
"-hour": "-satno",
|
||||||
|
@@ -55,7 +55,6 @@ export default {
|
|||||||
Current: "Aktuális",
|
Current: "Aktuális",
|
||||||
Uptime: "Uptime",
|
Uptime: "Uptime",
|
||||||
"Cert Exp.": "SSL lejárat",
|
"Cert Exp.": "SSL lejárat",
|
||||||
days: "nap",
|
|
||||||
day: "nap",
|
day: "nap",
|
||||||
"-day": " nap",
|
"-day": " nap",
|
||||||
hour: "óra",
|
hour: "óra",
|
||||||
|
@@ -55,8 +55,7 @@ export default {
|
|||||||
Current: "Saat ini",
|
Current: "Saat ini",
|
||||||
Uptime: "Waktu aktif",
|
Uptime: "Waktu aktif",
|
||||||
"Cert Exp.": "Cert Exp.",
|
"Cert Exp.": "Cert Exp.",
|
||||||
days: "hari-hari",
|
day: "hari | hari-hari",
|
||||||
day: "hari",
|
|
||||||
"-day": "-hari",
|
"-day": "-hari",
|
||||||
hour: "Jam",
|
hour: "Jam",
|
||||||
"-hour": "-Jam",
|
"-hour": "-Jam",
|
||||||
|
@@ -56,8 +56,7 @@ export default {
|
|||||||
Current: "Corrente",
|
Current: "Corrente",
|
||||||
Uptime: "Tempo di attività",
|
Uptime: "Tempo di attività",
|
||||||
"Cert Exp.": "Scadenza certificato",
|
"Cert Exp.": "Scadenza certificato",
|
||||||
days: "giorni",
|
day: "giorno | giorni",
|
||||||
day: "giorno",
|
|
||||||
"-day": "-giorni",
|
"-day": "-giorni",
|
||||||
hour: "ora",
|
hour: "ora",
|
||||||
"-hour": "-ore",
|
"-hour": "-ore",
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "現在",
|
Current: "現在",
|
||||||
Uptime: "起動時間",
|
Uptime: "起動時間",
|
||||||
"Cert Exp.": "証明書有効期限",
|
"Cert Exp.": "証明書有効期限",
|
||||||
days: "日間",
|
day: "日 | 日間",
|
||||||
day: "日",
|
|
||||||
"-day": "-日",
|
"-day": "-日",
|
||||||
hour: "時間",
|
hour: "時間",
|
||||||
"-hour": "-時間",
|
"-hour": "-時間",
|
||||||
|
@@ -55,7 +55,6 @@ export default {
|
|||||||
Current: "현재",
|
Current: "현재",
|
||||||
Uptime: "업타임",
|
Uptime: "업타임",
|
||||||
"Cert Exp.": "인증서 만료",
|
"Cert Exp.": "인증서 만료",
|
||||||
days: "일",
|
|
||||||
day: "일",
|
day: "일",
|
||||||
"-day": "-일",
|
"-day": "-일",
|
||||||
hour: "시간",
|
hour: "시간",
|
||||||
@@ -439,6 +438,7 @@ export default {
|
|||||||
Next: "다음",
|
Next: "다음",
|
||||||
"The slug is already taken. Please choose another slug.": "이미 존재하는 주소에요. 다른 주소를 사용해 주세요.",
|
"The slug is already taken. Please choose another slug.": "이미 존재하는 주소에요. 다른 주소를 사용해 주세요.",
|
||||||
"No Proxy": "프록시 없음",
|
"No Proxy": "프록시 없음",
|
||||||
|
Authentication: "인증",
|
||||||
"HTTP Basic Auth": "HTTP 인증",
|
"HTTP Basic Auth": "HTTP 인증",
|
||||||
"New Status Page": "새로운 상태 페이지",
|
"New Status Page": "새로운 상태 페이지",
|
||||||
"Page Not Found": "페이지를 찾을 수 없어요",
|
"Page Not Found": "페이지를 찾을 수 없어요",
|
||||||
|
@@ -55,8 +55,7 @@ export default {
|
|||||||
Current: "Nåværende",
|
Current: "Nåværende",
|
||||||
Uptime: "Oppetid",
|
Uptime: "Oppetid",
|
||||||
"Cert Exp.": "Sertifikat utløper",
|
"Cert Exp.": "Sertifikat utløper",
|
||||||
days: "dager",
|
day: "dag | dager",
|
||||||
day: "dag",
|
|
||||||
"-day": "-dag",
|
"-day": "-dag",
|
||||||
hour: "time",
|
hour: "time",
|
||||||
"-hour": "-time",
|
"-hour": "-time",
|
||||||
|
@@ -52,8 +52,7 @@ export default {
|
|||||||
Current: "Huidig",
|
Current: "Huidig",
|
||||||
Uptime: "Uptime",
|
Uptime: "Uptime",
|
||||||
"Cert Exp.": "Cert. verl.",
|
"Cert Exp.": "Cert. verl.",
|
||||||
days: "dagen",
|
day: "dag | dagen",
|
||||||
day: "dag",
|
|
||||||
"-day": "-dag",
|
"-day": "-dag",
|
||||||
hour: "uur",
|
hour: "uur",
|
||||||
"-hour": "-uur",
|
"-hour": "-uur",
|
||||||
|
@@ -55,8 +55,7 @@ export default {
|
|||||||
Current: "Aktualny",
|
Current: "Aktualny",
|
||||||
Uptime: "Czas pracy",
|
Uptime: "Czas pracy",
|
||||||
"Cert Exp.": "Certyfikat wygasa",
|
"Cert Exp.": "Certyfikat wygasa",
|
||||||
days: "dni",
|
day: "dzień | dni",
|
||||||
day: "dzień",
|
|
||||||
"-day": " dni",
|
"-day": " dni",
|
||||||
hour: "godzina",
|
hour: "godzina",
|
||||||
"-hour": " godzin",
|
"-hour": " godzin",
|
||||||
@@ -429,6 +428,7 @@ export default {
|
|||||||
Next: "Dalej",
|
Next: "Dalej",
|
||||||
"The slug is already taken. Please choose another slug.": "Ten symbol jest już zajęty. Proszę, wybierz inny.",
|
"The slug is already taken. Please choose another slug.": "Ten symbol jest już zajęty. Proszę, wybierz inny.",
|
||||||
"No Proxy": "Bez proxy",
|
"No Proxy": "Bez proxy",
|
||||||
|
Authentication: "Uwierzytelnianie",
|
||||||
"HTTP Basic Auth": "Podstawowa autoryzacja HTTP",
|
"HTTP Basic Auth": "Podstawowa autoryzacja HTTP",
|
||||||
"New Status Page": "Nowa strona statusu",
|
"New Status Page": "Nowa strona statusu",
|
||||||
"Page Not Found": "Strona nie została znaleziona",
|
"Page Not Found": "Strona nie została znaleziona",
|
||||||
|
@@ -55,8 +55,7 @@ export default {
|
|||||||
Current: "Atual",
|
Current: "Atual",
|
||||||
Uptime: "Tempo de atividade",
|
Uptime: "Tempo de atividade",
|
||||||
"Cert Exp.": "Cert Exp.",
|
"Cert Exp.": "Cert Exp.",
|
||||||
days: "dias",
|
day: "dia | dias",
|
||||||
day: "dia",
|
|
||||||
"-day": "-dia",
|
"-day": "-dia",
|
||||||
hour: "hora",
|
hour: "hora",
|
||||||
"-hour": "-hora",
|
"-hour": "-hora",
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "Текущий",
|
Current: "Текущий",
|
||||||
Uptime: "Аптайм",
|
Uptime: "Аптайм",
|
||||||
"Cert Exp.": "Сертификат истекает",
|
"Cert Exp.": "Сертификат истекает",
|
||||||
days: "дней",
|
day: "день | дней",
|
||||||
day: "день",
|
|
||||||
"-day": " дней",
|
"-day": " дней",
|
||||||
hour: "час",
|
hour: "час",
|
||||||
"-hour": " часа",
|
"-hour": " часа",
|
||||||
@@ -352,7 +351,8 @@ export default {
|
|||||||
"Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9",
|
"Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9",
|
||||||
"No consecutive dashes --": "Запрещено использовать тире --",
|
"No consecutive dashes --": "Запрещено использовать тире --",
|
||||||
"HTTP Options": "HTTP Опции",
|
"HTTP Options": "HTTP Опции",
|
||||||
"Basic Auth": "HTTP Авторизация",
|
Authentication: "Аутентификация",
|
||||||
|
"HTTP Basic Auth": "HTTP Авторизация",
|
||||||
PushByTechulus: "Push by Techulus",
|
PushByTechulus: "Push by Techulus",
|
||||||
clicksendsms: "ClickSend SMS",
|
clicksendsms: "ClickSend SMS",
|
||||||
GoogleChat: "Google Chat (только Google Workspace)",
|
GoogleChat: "Google Chat (только Google Workspace)",
|
||||||
|
@@ -56,8 +56,7 @@ export default {
|
|||||||
Current: "Trenutno",
|
Current: "Trenutno",
|
||||||
Uptime: "Uptime",
|
Uptime: "Uptime",
|
||||||
"Cert Exp.": "Potek certifikata",
|
"Cert Exp.": "Potek certifikata",
|
||||||
days: "dni",
|
day: "dan | dni",
|
||||||
day: "dan",
|
|
||||||
"-day": "-dni",
|
"-day": "-dni",
|
||||||
hour: "ura",
|
hour: "ura",
|
||||||
"-hour": "-ur",
|
"-hour": "-ur",
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "Trenutno",
|
Current: "Trenutno",
|
||||||
Uptime: "Vreme rada",
|
Uptime: "Vreme rada",
|
||||||
"Cert Exp.": "Istek sert.",
|
"Cert Exp.": "Istek sert.",
|
||||||
days: "dana",
|
day: "dan | dana",
|
||||||
day: "dan",
|
|
||||||
"-day": "-dana",
|
"-day": "-dana",
|
||||||
hour: "sat",
|
hour: "sat",
|
||||||
"-hour": "-sata",
|
"-hour": "-sata",
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "Тренутно",
|
Current: "Тренутно",
|
||||||
Uptime: "Време рада",
|
Uptime: "Време рада",
|
||||||
"Cert Exp.": "Истек серт.",
|
"Cert Exp.": "Истек серт.",
|
||||||
days: "дана",
|
day: "дан | дана",
|
||||||
day: "дан",
|
|
||||||
"-day": "-дана",
|
"-day": "-дана",
|
||||||
hour: "сат",
|
hour: "сат",
|
||||||
"-hour": "-сата",
|
"-hour": "-сата",
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "Nuvarande",
|
Current: "Nuvarande",
|
||||||
Uptime: "Drifttid",
|
Uptime: "Drifttid",
|
||||||
"Cert Exp.": "Certifikat utgår",
|
"Cert Exp.": "Certifikat utgår",
|
||||||
days: "dagar",
|
day: "dag | dagar",
|
||||||
day: "dag",
|
|
||||||
"-day": " dagar",
|
"-day": " dagar",
|
||||||
hour: "timme",
|
hour: "timme",
|
||||||
"-hour": " timmar",
|
"-hour": " timmar",
|
||||||
|
@@ -57,8 +57,7 @@ export default {
|
|||||||
Current: "Şu anda",
|
Current: "Şu anda",
|
||||||
Uptime: "Çalışma zamanı",
|
Uptime: "Çalışma zamanı",
|
||||||
"Cert Exp.": "Sertifika Süresi",
|
"Cert Exp.": "Sertifika Süresi",
|
||||||
days: "günler",
|
day: "gün | günler",
|
||||||
day: "gün",
|
|
||||||
"-day": "-gün",
|
"-day": "-gün",
|
||||||
hour: "saat",
|
hour: "saat",
|
||||||
"-hour": "-saat",
|
"-hour": "-saat",
|
||||||
|
@@ -44,8 +44,7 @@ export default {
|
|||||||
Current: "Поточний",
|
Current: "Поточний",
|
||||||
Uptime: "Аптайм",
|
Uptime: "Аптайм",
|
||||||
"Cert Exp.": "Сертифікат спливає",
|
"Cert Exp.": "Сертифікат спливає",
|
||||||
days: "днів",
|
day: "день | днів",
|
||||||
day: "день",
|
|
||||||
"-day": " днів",
|
"-day": " днів",
|
||||||
hour: "година",
|
hour: "година",
|
||||||
"-hour": " години",
|
"-hour": " години",
|
||||||
@@ -351,7 +350,8 @@ export default {
|
|||||||
"Start or end with a-z 0-9 only": "Початок та закінчення імені лише на символи: a-z 0-9",
|
"Start or end with a-z 0-9 only": "Початок та закінчення імені лише на символи: a-z 0-9",
|
||||||
"No consecutive dashes --": "Заборонено використовувати тире --",
|
"No consecutive dashes --": "Заборонено використовувати тире --",
|
||||||
"HTTP Options": "HTTP Опції",
|
"HTTP Options": "HTTP Опції",
|
||||||
"Basic Auth": "HTTP Авторизація",
|
Authentication: "Аутентифікація",
|
||||||
|
"HTTP Basic Auth": "HTTP Авторизація",
|
||||||
PushByTechulus: "Push by Techulus",
|
PushByTechulus: "Push by Techulus",
|
||||||
clicksendsms: "ClickSend SMS",
|
clicksendsms: "ClickSend SMS",
|
||||||
GoogleChat: "Google Chat (тільки Google Workspace)",
|
GoogleChat: "Google Chat (тільки Google Workspace)",
|
||||||
|
@@ -56,7 +56,6 @@ export default {
|
|||||||
Current: "Hiện tại",
|
Current: "Hiện tại",
|
||||||
Uptime: "Uptime",
|
Uptime: "Uptime",
|
||||||
"Cert Exp.": "Cert hết hạn",
|
"Cert Exp.": "Cert hết hạn",
|
||||||
days: "ngày",
|
|
||||||
day: "ngày",
|
day: "ngày",
|
||||||
"-day": "-ngày",
|
"-day": "-ngày",
|
||||||
hour: "giờ",
|
hour: "giờ",
|
||||||
|
@@ -57,7 +57,6 @@ export default {
|
|||||||
Current: "当前",
|
Current: "当前",
|
||||||
Uptime: "在线时间",
|
Uptime: "在线时间",
|
||||||
"Cert Exp.": "证书有效期",
|
"Cert Exp.": "证书有效期",
|
||||||
days: "天",
|
|
||||||
day: "天",
|
day: "天",
|
||||||
"-day": " 天",
|
"-day": " 天",
|
||||||
hour: "小时",
|
hour: "小时",
|
||||||
@@ -437,6 +436,7 @@ export default {
|
|||||||
Next: "下一步",
|
Next: "下一步",
|
||||||
"The slug is already taken. Please choose another slug.": "该路径已被使用。请选择其他路径。",
|
"The slug is already taken. Please choose another slug.": "该路径已被使用。请选择其他路径。",
|
||||||
"No Proxy": "无代理",
|
"No Proxy": "无代理",
|
||||||
|
Authentication: "验证",
|
||||||
"HTTP Basic Auth": "HTTP 基础身份验证",
|
"HTTP Basic Auth": "HTTP 基础身份验证",
|
||||||
"New Status Page": "新的状态页",
|
"New Status Page": "新的状态页",
|
||||||
"Page Not Found": "未找到该页面",
|
"Page Not Found": "未找到该页面",
|
||||||
@@ -520,4 +520,14 @@ export default {
|
|||||||
wayToGetClickSendSMSToken: "您可以从 {0} 获取 API 凭证 Username 和 凭证 Key。",
|
wayToGetClickSendSMSToken: "您可以从 {0} 获取 API 凭证 Username 和 凭证 Key。",
|
||||||
signedInDisp: "当前用户: {0}",
|
signedInDisp: "当前用户: {0}",
|
||||||
signedInDispDisabled: "已禁用身份验证",
|
signedInDispDisabled: "已禁用身份验证",
|
||||||
|
dnsPortDescription: "DNS 服务器端口,默认为 53,你可以在任何时候更改此端口.",
|
||||||
|
error: "错误",
|
||||||
|
critical: "关键",
|
||||||
|
wayToGetPagerDutyKey: "你可以在 Service -> Service Directory -> (Select a service) -> Integrations -> Add integration 页面中搜索 \"Events API V2\" 以获取此 Integration Key,更多信息请参见 {0}",
|
||||||
|
"Integration Key": "Integration Key",
|
||||||
|
"Integration URL": "Integration URL",
|
||||||
|
"Auto resolve or acknowledged": "自动标记为已解决或已读",
|
||||||
|
"do nothing": "不做任何操作",
|
||||||
|
"auto acknowledged": "自动标记为已读",
|
||||||
|
"auto resolve": "自动标记为已解决",
|
||||||
};
|
};
|
||||||
|
@@ -30,7 +30,6 @@ export default {
|
|||||||
Current: "目前",
|
Current: "目前",
|
||||||
Uptime: "上線率",
|
Uptime: "上線率",
|
||||||
"Cert Exp.": "証書期限",
|
"Cert Exp.": "証書期限",
|
||||||
days: "日",
|
|
||||||
day: "日",
|
day: "日",
|
||||||
"-day": "日",
|
"-day": "日",
|
||||||
hour: "小時",
|
hour: "小時",
|
||||||
|
@@ -56,7 +56,6 @@ export default {
|
|||||||
Current: "目前",
|
Current: "目前",
|
||||||
Uptime: "運作率",
|
Uptime: "運作率",
|
||||||
"Cert Exp.": "憑證期限",
|
"Cert Exp.": "憑證期限",
|
||||||
days: "天",
|
|
||||||
day: "天",
|
day: "天",
|
||||||
"-day": "天",
|
"-day": "天",
|
||||||
hour: "小時",
|
hour: "小時",
|
||||||
@@ -429,6 +428,7 @@ export default {
|
|||||||
Next: "下一步",
|
Next: "下一步",
|
||||||
"The slug is already taken. Please choose another slug.": "此 slug 已被使用。請選擇其他 slug。",
|
"The slug is already taken. Please choose another slug.": "此 slug 已被使用。請選擇其他 slug。",
|
||||||
"No Proxy": "無 Proxy",
|
"No Proxy": "無 Proxy",
|
||||||
|
Authentication: "驗證",
|
||||||
"HTTP Basic Auth": "HTTP 基本驗證",
|
"HTTP Basic Auth": "HTTP 基本驗證",
|
||||||
"New Status Page": "新狀態頁",
|
"New Status Page": "新狀態頁",
|
||||||
"Page Not Found": "找不到頁面",
|
"Page Not Found": "找不到頁面",
|
||||||
|
@@ -18,14 +18,31 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Return a given value in the format YYYY-MM-DD HH:mm:ss
|
||||||
|
* @param {any} value Value to format as date time
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
datetime(value) {
|
datetime(value) {
|
||||||
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss");
|
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a given value in the format YYYY-MM-DD
|
||||||
|
* @param {any} value Value to format as date
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
date(value) {
|
date(value) {
|
||||||
return this.datetimeFormat(value, "YYYY-MM-DD");
|
return this.datetimeFormat(value, "YYYY-MM-DD");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a given value in the format HH:mm or if second is set
|
||||||
|
* to true, HH:mm:ss
|
||||||
|
* @param {any} value Value to format
|
||||||
|
* @param {boolean} second Should seconds be included?
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
time(value, second = true) {
|
time(value, second = true) {
|
||||||
let secondString;
|
let secondString;
|
||||||
if (second) {
|
if (second) {
|
||||||
@@ -36,6 +53,12 @@ export default {
|
|||||||
return this.datetimeFormat(value, "HH:mm" + secondString);
|
return this.datetimeFormat(value, "HH:mm" + secondString);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a value in a custom format
|
||||||
|
* @param {any} value Value to format
|
||||||
|
* @param {any} format Format to return value in
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
datetimeFormat(value, format) {
|
datetimeFormat(value, format) {
|
||||||
if (value !== undefined && value !== "") {
|
if (value !== undefined && value !== "") {
|
||||||
return dayjs.utc(value).tz(this.timezone).format(format);
|
return dayjs.utc(value).tz(this.timezone).format(format);
|
||||||
|
@@ -22,6 +22,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Change the application language */
|
||||||
async changeLang(lang) {
|
async changeLang(lang) {
|
||||||
let message = (await langModules["../languages/" + lang + ".js"]()).default;
|
let message = (await langModules["../languages/" + lang + ".js"]()).default;
|
||||||
this.$i18n.setLocaleMessage(lang, message);
|
this.$i18n.setLocaleMessage(lang, message);
|
||||||
|
@@ -12,11 +12,13 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Handle screen resize */
|
||||||
onResize() {
|
onResize() {
|
||||||
this.windowWidth = window.innerWidth;
|
this.windowWidth = window.innerWidth;
|
||||||
this.updateBody();
|
this.updateBody();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Add css-class "mobile" to body if needed */
|
||||||
updateBody() {
|
updateBody() {
|
||||||
if (this.isMobile) {
|
if (this.isMobile) {
|
||||||
document.body.classList.add("mobile");
|
document.body.classList.add("mobile");
|
||||||
|
@@ -62,6 +62,12 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize connection to socket server
|
||||||
|
* @param {boolean} [bypass = false] Should the check for if we
|
||||||
|
* are on a status page be bypassed?
|
||||||
|
* @returns {(void|null)}
|
||||||
|
*/
|
||||||
initSocketIO(bypass = false) {
|
initSocketIO(bypass = false) {
|
||||||
// No need to re-init
|
// No need to re-init
|
||||||
if (this.socket.initedSocketIO) {
|
if (this.socket.initedSocketIO) {
|
||||||
@@ -258,10 +264,18 @@ export default {
|
|||||||
socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res);
|
socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The storage currently in use
|
||||||
|
* @returns {Storage}
|
||||||
|
*/
|
||||||
storage() {
|
storage() {
|
||||||
return (this.remember) ? localStorage : sessionStorage;
|
return (this.remember) ? localStorage : sessionStorage;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get payload of JWT cookie
|
||||||
|
* @returns {(Object|undefined)}
|
||||||
|
*/
|
||||||
getJWTPayload() {
|
getJWTPayload() {
|
||||||
const jwtToken = this.$root.storage().token;
|
const jwtToken = this.$root.storage().token;
|
||||||
|
|
||||||
@@ -271,10 +285,18 @@ export default {
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current socket
|
||||||
|
* @returns {Socket}
|
||||||
|
*/
|
||||||
getSocket() {
|
getSocket() {
|
||||||
return socket;
|
return socket;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show success or error toast dependant on response status code
|
||||||
|
* @param {Object} res Response object
|
||||||
|
*/
|
||||||
toastRes(res) {
|
toastRes(res) {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
toast.success(res.msg);
|
toast.success(res.msg);
|
||||||
@@ -283,14 +305,35 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a success toast
|
||||||
|
* @param {string} msg Message to show
|
||||||
|
*/
|
||||||
toastSuccess(msg) {
|
toastSuccess(msg) {
|
||||||
toast.success(msg);
|
toast.success(msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error toast
|
||||||
|
* @param {string} msg Message to show
|
||||||
|
*/
|
||||||
toastError(msg) {
|
toastError(msg) {
|
||||||
toast.error(msg);
|
toast.error(msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for login
|
||||||
|
* @callback loginCB
|
||||||
|
* @param {Object} res Response object
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send request to log user in
|
||||||
|
* @param {string} username Username to log in with
|
||||||
|
* @param {string} password Password to log in with
|
||||||
|
* @param {string} token User token
|
||||||
|
* @param {loginCB} callback Callback to call with result
|
||||||
|
*/
|
||||||
login(username, password, token, callback) {
|
login(username, password, token, callback) {
|
||||||
socket.emit("login", {
|
socket.emit("login", {
|
||||||
username,
|
username,
|
||||||
@@ -315,6 +358,10 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log in using a token
|
||||||
|
* @param {string} token Token to log in with
|
||||||
|
*/
|
||||||
loginByToken(token) {
|
loginByToken(token) {
|
||||||
socket.emit("loginByToken", token, (res) => {
|
socket.emit("loginByToken", token, (res) => {
|
||||||
this.allowLoginDialog = true;
|
this.allowLoginDialog = true;
|
||||||
@@ -328,6 +375,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Log out of the web application */
|
||||||
logout() {
|
logout() {
|
||||||
socket.emit("logout", () => { });
|
socket.emit("logout", () => { });
|
||||||
this.storage().removeItem("token");
|
this.storage().removeItem("token");
|
||||||
@@ -337,26 +385,54 @@ export default {
|
|||||||
this.clearData();
|
this.clearData();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for general socket requests
|
||||||
|
* @callback socketCB
|
||||||
|
* @param {Object} res Result of operation
|
||||||
|
*/
|
||||||
|
/** Prepare 2FA configuration */
|
||||||
prepare2FA(callback) {
|
prepare2FA(callback) {
|
||||||
socket.emit("prepare2FA", callback);
|
socket.emit("prepare2FA", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current 2FA configuration
|
||||||
|
* @param {any} secret Unused
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
save2FA(secret, callback) {
|
save2FA(secret, callback) {
|
||||||
socket.emit("save2FA", callback);
|
socket.emit("save2FA", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable 2FA for this user
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
disable2FA(callback) {
|
disable2FA(callback) {
|
||||||
socket.emit("disable2FA", callback);
|
socket.emit("disable2FA", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the provided 2FA token
|
||||||
|
* @param {string} token Token to verify
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
verifyToken(token, callback) {
|
verifyToken(token, callback) {
|
||||||
socket.emit("verifyToken", token, callback);
|
socket.emit("verifyToken", token, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current 2FA status
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
twoFAStatus(callback) {
|
twoFAStatus(callback) {
|
||||||
socket.emit("twoFAStatus", callback);
|
socket.emit("twoFAStatus", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of monitors
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
getMonitorList(callback) {
|
getMonitorList(callback) {
|
||||||
if (! callback) {
|
if (! callback) {
|
||||||
callback = () => { };
|
callback = () => { };
|
||||||
@@ -364,36 +440,74 @@ export default {
|
|||||||
socket.emit("getMonitorList", callback);
|
socket.emit("getMonitorList", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a monitor
|
||||||
|
* @param {Object} monitor Object representing monitor to add
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
add(monitor, callback) {
|
add(monitor, callback) {
|
||||||
socket.emit("add", monitor, callback);
|
socket.emit("add", monitor, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete monitor by ID
|
||||||
|
* @param {number} monitorID ID of monitor to delete
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
deleteMonitor(monitorID, callback) {
|
deleteMonitor(monitorID, callback) {
|
||||||
socket.emit("deleteMonitor", monitorID, callback);
|
socket.emit("deleteMonitor", monitorID, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Clear the hearbeat list */
|
||||||
clearData() {
|
clearData() {
|
||||||
console.log("reset heartbeat list");
|
console.log("reset heartbeat list");
|
||||||
this.heartbeatList = {};
|
this.heartbeatList = {};
|
||||||
this.importantHeartbeatList = {};
|
this.importantHeartbeatList = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload the provided backup
|
||||||
|
* @param {string} uploadedJSON JSON to upload
|
||||||
|
* @param {string} importHandle Type of import. If set to
|
||||||
|
* most data in database will be replaced
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
uploadBackup(uploadedJSON, importHandle, callback) {
|
uploadBackup(uploadedJSON, importHandle, callback) {
|
||||||
socket.emit("uploadBackup", uploadedJSON, importHandle, callback);
|
socket.emit("uploadBackup", uploadedJSON, importHandle, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear events for a specified monitor
|
||||||
|
* @param {number} monitorID ID of monitor to clear
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
clearEvents(monitorID, callback) {
|
clearEvents(monitorID, callback) {
|
||||||
socket.emit("clearEvents", monitorID, callback);
|
socket.emit("clearEvents", monitorID, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the heartbeats of a specified monitor
|
||||||
|
* @param {number} monitorID Id of monitor to clear
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
clearHeartbeats(monitorID, callback) {
|
clearHeartbeats(monitorID, callback) {
|
||||||
socket.emit("clearHeartbeats", monitorID, callback);
|
socket.emit("clearHeartbeats", monitorID, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all statistics
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
clearStatistics(callback) {
|
clearStatistics(callback) {
|
||||||
socket.emit("clearStatistics", callback);
|
socket.emit("clearStatistics", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get monitor beats for a specific monitor in a time range
|
||||||
|
* @param {number} monitorID ID of monitor to fetch
|
||||||
|
* @param {number} period Time in hours from now
|
||||||
|
* @param {socketCB} callback
|
||||||
|
*/
|
||||||
getMonitorBeats(monitorID, period, callback) {
|
getMonitorBeats(monitorID, period, callback) {
|
||||||
socket.emit("getMonitorBeats", monitorID, period, callback);
|
socket.emit("getMonitorBeats", monitorID, period, callback);
|
||||||
}
|
}
|
||||||
|
@@ -75,6 +75,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Update the theme color meta tag */
|
||||||
updateThemeColorMeta() {
|
updateThemeColorMeta() {
|
||||||
if (this.theme === "dark") {
|
if (this.theme === "dark") {
|
||||||
document.querySelector("#theme-color").setAttribute("content", "#161B22");
|
document.querySelector("#theme-color").setAttribute("content", "#161B22");
|
||||||
|
@@ -51,6 +51,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Submit form data to add new status page */
|
||||||
async submit() {
|
async submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
|
@@ -77,7 +77,7 @@
|
|||||||
<h4>{{ $t("Cert Exp.") }}</h4>
|
<h4>{{ $t("Cert Exp.") }}</h4>
|
||||||
<p>(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p>
|
<p>(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p>
|
||||||
<span class="num">
|
<span class="num">
|
||||||
<a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ tlsInfo.certInfo.daysRemaining }} {{ $t("days") }}</a>
|
<a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ tlsInfo.certInfo.daysRemaining }} {{ $tc("day", tlsInfo.certInfo.daysRemaining) }}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,39 +289,47 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Request a test notification be sent for this monitor */
|
||||||
testNotification() {
|
testNotification() {
|
||||||
this.$root.getSocket().emit("testNotification", this.monitor.id);
|
this.$root.getSocket().emit("testNotification", this.monitor.id);
|
||||||
toast.success("Test notification is requested.");
|
toast.success("Test notification is requested.");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog to confirm pause */
|
||||||
pauseDialog() {
|
pauseDialog() {
|
||||||
this.$refs.confirmPause.show();
|
this.$refs.confirmPause.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Resume this monitor */
|
||||||
resumeMonitor() {
|
resumeMonitor() {
|
||||||
this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => {
|
this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Request that this monitor is paused */
|
||||||
pauseMonitor() {
|
pauseMonitor() {
|
||||||
this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => {
|
this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog to confirm deletion */
|
||||||
deleteDialog() {
|
deleteDialog() {
|
||||||
this.$refs.confirmDelete.show();
|
this.$refs.confirmDelete.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog to confirm clearing events */
|
||||||
clearEventsDialog() {
|
clearEventsDialog() {
|
||||||
this.$refs.confirmClearEvents.show();
|
this.$refs.confirmClearEvents.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog to confirm clearing heartbeats */
|
||||||
clearHeartbeatsDialog() {
|
clearHeartbeatsDialog() {
|
||||||
this.$refs.confirmClearHeartbeats.show();
|
this.$refs.confirmClearHeartbeats.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Request that this monitor is deleted */
|
||||||
deleteMonitor() {
|
deleteMonitor() {
|
||||||
this.$root.deleteMonitor(this.monitor.id, (res) => {
|
this.$root.deleteMonitor(this.monitor.id, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -333,6 +341,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Request that this monitors events are cleared */
|
||||||
clearEvents() {
|
clearEvents() {
|
||||||
this.$root.clearEvents(this.monitor.id, (res) => {
|
this.$root.clearEvents(this.monitor.id, (res) => {
|
||||||
if (! res.ok) {
|
if (! res.ok) {
|
||||||
@@ -341,6 +350,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Request that this monitors heartbeats are cleared */
|
||||||
clearHeartbeats() {
|
clearHeartbeats() {
|
||||||
this.$root.clearHeartbeats(this.monitor.id, (res) => {
|
this.$root.clearHeartbeats(this.monitor.id, (res) => {
|
||||||
if (! res.ok) {
|
if (! res.ok) {
|
||||||
@@ -349,6 +359,11 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the correct title for the ping stat
|
||||||
|
* @param {boolean} [average=false] Is the statistic an average?
|
||||||
|
* @returns {string} Title formated dependant on monitor type
|
||||||
|
*/
|
||||||
pingTitle(average = false) {
|
pingTitle(average = false) {
|
||||||
let translationPrefix = "";
|
let translationPrefix = "";
|
||||||
if (average) {
|
if (average) {
|
||||||
|
@@ -11,30 +11,41 @@
|
|||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
|
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
|
||||||
<select id="type" v-model="monitor.type" class="form-select">
|
<select id="type" v-model="monitor.type" class="form-select">
|
||||||
<option value="http">
|
<optgroup label="General Monitor Type">
|
||||||
HTTP(s)
|
<option value="http">
|
||||||
</option>
|
HTTP(s)
|
||||||
<option value="port">
|
</option>
|
||||||
TCP Port
|
<option value="port">
|
||||||
</option>
|
TCP Port
|
||||||
<option value="ping">
|
</option>
|
||||||
Ping
|
<option value="ping">
|
||||||
</option>
|
Ping
|
||||||
<option value="keyword">
|
</option>
|
||||||
HTTP(s) - {{ $t("Keyword") }}
|
<option value="keyword">
|
||||||
</option>
|
HTTP(s) - {{ $t("Keyword") }}
|
||||||
<option value="dns">
|
</option>
|
||||||
DNS
|
<option value="dns">
|
||||||
</option>
|
DNS
|
||||||
<option value="push">
|
</option>
|
||||||
Push
|
</optgroup>
|
||||||
</option>
|
|
||||||
<option value="steam">
|
<optgroup label="Passive Monitor Type">
|
||||||
{{ $t("Steam Game Server") }}
|
<option value="push">
|
||||||
</option>
|
Push
|
||||||
<option value="mqtt">
|
</option>
|
||||||
MQTT
|
</optgroup>
|
||||||
</option>
|
|
||||||
|
<optgroup label="Specific Monitor Type">
|
||||||
|
<option value="steam">
|
||||||
|
{{ $t("Steam Game Server") }}
|
||||||
|
</option>
|
||||||
|
<option value="mqtt">
|
||||||
|
MQTT
|
||||||
|
</option>
|
||||||
|
<option value="sqlserver">
|
||||||
|
SQL Server
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -157,6 +168,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- SQL Server -->
|
||||||
|
<template v-if="monitor.type === 'sqlserver'">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="sqlserverConnectionString" class="form-label">SQL Server {{ $t("Connection String") }}</label>
|
||||||
|
<input id="sqlserverConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="sqlserverQuery" class="form-label">SQL Server {{ $t("Query") }}</label>
|
||||||
|
<textarea id="sqlserverQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Interval -->
|
<!-- Interval -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
||||||
@@ -345,18 +368,46 @@
|
|||||||
<textarea id="headers" v-model="monitor.headers" class="form-control" :placeholder="headersPlaceholder"></textarea>
|
<textarea id="headers" v-model="monitor.headers" class="form-control" :placeholder="headersPlaceholder"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- HTTP Basic Auth -->
|
<!-- HTTP Auth -->
|
||||||
<h4 class="mt-5 mb-2">{{ $t("HTTP Basic Auth") }}</h4>
|
<h4 class="mt-5 mb-2">{{ $t("Authentication") }}</h4>
|
||||||
|
|
||||||
|
<!-- Method -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="basicauth" class="form-label">{{ $t("Username") }}</label>
|
<label for="method" class="form-label">{{ $t("Method") }}</label>
|
||||||
<input id="basicauth-user" v-model="monitor.basic_auth_user" type="text" class="form-control" :placeholder="$t('Username')">
|
<select id="method" v-model="monitor.authMethod" class="form-select">
|
||||||
|
<option :value="null">
|
||||||
|
{{ $t("None") }}
|
||||||
|
</option>
|
||||||
|
<option value="basic">
|
||||||
|
{{ $t("HTTP Basic Auth") }}
|
||||||
|
</option>
|
||||||
|
<option value="ntlm">
|
||||||
|
NTLM
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="monitor.authMethod && monitor.authMethod !== null ">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="basicauth" class="form-label">{{ $t("Username") }}</label>
|
||||||
|
<input id="basicauth-user" v-model="monitor.basic_auth_user" type="text" class="form-control" :placeholder="$t('Username')">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="basicauth" class="form-label">{{ $t("Password") }}</label>
|
<label for="basicauth" class="form-label">{{ $t("Password") }}</label>
|
||||||
<input id="basicauth-pass" v-model="monitor.basic_auth_pass" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Password')">
|
<input id="basicauth-pass" v-model="monitor.basic_auth_pass" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Password')">
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="monitor.authMethod === 'ntlm' ">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="basicauth" class="form-label">{{ $t("Domain") }}</label>
|
||||||
|
<input id="basicauth-domain" v-model="monitor.authDomain" type="text" class="form-control" :placeholder="$t('Domain')">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="basicauth" class="form-label">{{ $t("Workstation") }}</label>
|
||||||
|
<input id="basicauth-workstation" v-model="monitor.authWorkstation" type="text" class="form-control" :placeholder="$t('Workstation')">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -522,6 +573,7 @@ export default {
|
|||||||
this.dnsresolvetypeOptions = dnsresolvetypeOptions;
|
this.dnsresolvetypeOptions = dnsresolvetypeOptions;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Initialize the edit monitor form */
|
||||||
init() {
|
init() {
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
|
|
||||||
@@ -532,6 +584,7 @@ export default {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
interval: 60,
|
interval: 60,
|
||||||
retryInterval: this.interval,
|
retryInterval: this.interval,
|
||||||
|
databaseConnectionString: "Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>",
|
||||||
maxretries: 0,
|
maxretries: 0,
|
||||||
notificationIDList: {},
|
notificationIDList: {},
|
||||||
ignoreTls: false,
|
ignoreTls: false,
|
||||||
@@ -546,6 +599,7 @@ export default {
|
|||||||
mqttPassword: "",
|
mqttPassword: "",
|
||||||
mqttTopic: "",
|
mqttTopic: "",
|
||||||
mqttSuccessMessage: "",
|
mqttSuccessMessage: "",
|
||||||
|
authMethod: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||||
@@ -578,6 +632,10 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate form input
|
||||||
|
* @returns {boolean} Is the form input valid?
|
||||||
|
*/
|
||||||
isInputValid() {
|
isInputValid() {
|
||||||
if (this.monitor.body) {
|
if (this.monitor.body) {
|
||||||
try {
|
try {
|
||||||
@@ -598,6 +656,10 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form data for processing
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
async submit() {
|
async submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
@@ -642,14 +704,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Added a Notification Event
|
/**
|
||||||
// Enable it if the notification is added in EditMonitor.vue
|
* Added a Notification Event
|
||||||
|
* Enable it if the notification is added in EditMonitor.vue
|
||||||
|
* @param {number} id ID of notification to add
|
||||||
|
*/
|
||||||
addedNotification(id) {
|
addedNotification(id) {
|
||||||
this.monitor.notificationIDList[id] = true;
|
this.monitor.notificationIDList[id] = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Added a Proxy Event
|
/**
|
||||||
// Enable it if the proxy is added in EditMonitor.vue
|
* Added a Proxy Event
|
||||||
|
* Enable it if the proxy is added in EditMonitor.vue
|
||||||
|
* @param {number} id ID of proxy to add
|
||||||
|
*/
|
||||||
addedProxy(id) {
|
addedProxy(id) {
|
||||||
this.monitor.proxyId = id;
|
this.monitor.proxyId = id;
|
||||||
},
|
},
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<MonitorList />
|
<MonitorList :scrollbar="true" />
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,3 +14,11 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../assets/vars";
|
||||||
|
|
||||||
|
.shadow-box {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
@@ -51,6 +51,11 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Get the correct URL for the icon
|
||||||
|
* @param {string} icon Path for icon
|
||||||
|
* @returns {string} Correctly formatted path including port numbers
|
||||||
|
*/
|
||||||
icon(icon) {
|
icon(icon) {
|
||||||
if (icon === "/icon.svg") {
|
if (icon === "/icon.svg") {
|
||||||
return icon;
|
return icon;
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>{{ $t("Retype the address.") }}</li>
|
<li>{{ $t("Retype the address.") }}</li>
|
||||||
<li><a href="#" class="go-back" @click="goBack()">{{ $t("Go back to the previous page.") }}</a></li>
|
<li><a href="#" class="go-back" @click="goBack()">{{ $t("Go back to the previous page.") }}</a></li>
|
||||||
|
<li><a href="/" class="go-back">Go back to home page.</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,6 +45,7 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** Go back 1 in browser history */
|
||||||
goBack() {
|
goBack() {
|
||||||
history.back();
|
history.back();
|
||||||
}
|
}
|
||||||
|
@@ -118,13 +118,17 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
// For desktop only, mobile do nothing
|
/**
|
||||||
|
* Load the general settings page
|
||||||
|
* For desktop only, on mobile do nothing
|
||||||
|
*/
|
||||||
loadGeneralPage() {
|
loadGeneralPage() {
|
||||||
if (!this.currentPage && !this.$root.isMobile) {
|
if (!this.currentPage && !this.$root.isMobile) {
|
||||||
this.$router.push("/settings/general");
|
this.$router.push("/settings/general");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Load settings from server */
|
||||||
loadSettings() {
|
loadSettings() {
|
||||||
this.$root.getSocket().emit("getSettings", (res) => {
|
this.$root.getSocket().emit("getSettings", (res) => {
|
||||||
this.settings = res.data;
|
this.settings = res.data;
|
||||||
@@ -145,13 +149,24 @@ export default {
|
|||||||
this.settings.keepDataPeriodDays = 180;
|
this.settings.keepDataPeriodDays = 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.settings.tlsExpiryNotifyDays === undefined) {
|
||||||
|
this.settings.tlsExpiryNotifyDays = [ 7, 14, 21 ];
|
||||||
|
}
|
||||||
|
|
||||||
this.settingsLoaded = true;
|
this.settingsLoaded = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for saving settings
|
||||||
|
* @callback saveSettingsCB
|
||||||
|
* @param {Object} res Result of operation
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save Settings
|
* Save Settings
|
||||||
* @param currentPassword (Optional) Only need for disableAuth to true
|
* @param {saveSettingsCB} [callback]
|
||||||
|
* @param {string} [currentPassword] Only need for disableAuth to true
|
||||||
*/
|
*/
|
||||||
saveSettings(callback, currentPassword) {
|
saveSettings(callback, currentPassword) {
|
||||||
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {
|
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {
|
||||||
|
@@ -71,6 +71,10 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Submit form data for processing
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
|
@@ -98,7 +98,7 @@
|
|||||||
<h1 class="mb-4 title-flex">
|
<h1 class="mb-4 title-flex">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<span class="logo-wrapper" @click="showImageCropUploadMethod">
|
<span class="logo-wrapper" @click="showImageCropUploadMethod">
|
||||||
<img :src="logoURL" alt class="logo me-2" :class="logoClass" @load="statusPageLogoLoaded" />
|
<img :src="logoURL" alt class="logo me-2" :class="logoClass" />
|
||||||
<font-awesome-icon v-if="enableEditMode" class="icon-upload" icon="upload" />
|
<font-awesome-icon v-if="enableEditMode" class="icon-upload" icon="upload" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -332,6 +332,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
/** Override for the status page slug */
|
||||||
overrideSlug: {
|
overrideSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -538,7 +539,7 @@ export default {
|
|||||||
this.slug = "default";
|
this.slug = "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get("/api/status-page/" + this.slug).then((res) => {
|
this.getData().then((res) => {
|
||||||
this.config = res.data.config;
|
this.config = res.data.config;
|
||||||
|
|
||||||
if (!this.config.domainNameList) {
|
if (!this.config.domainNameList) {
|
||||||
@@ -551,6 +552,11 @@ export default {
|
|||||||
|
|
||||||
this.incident = res.data.incident;
|
this.incident = res.data.incident;
|
||||||
this.$root.publicGroupList = res.data.publicGroupList;
|
this.$root.publicGroupList = res.data.publicGroupList;
|
||||||
|
}).catch( function (error) {
|
||||||
|
if (error.response.status === 404) {
|
||||||
|
location.href = "/page-not-found";
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5mins a loop
|
// 5mins a loop
|
||||||
@@ -567,10 +573,31 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get status page data
|
||||||
|
* It should be preloaded in window.preloadData
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
getData: function () {
|
||||||
|
if (window.preloadData) {
|
||||||
|
return new Promise(resolve => resolve({
|
||||||
|
data: window.preloadData
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return axios.get("/api/status-page/" + this.slug);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide syntax highlighting for CSS
|
||||||
|
* @param {string} code Text to highlight
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
highlighter(code) {
|
highlighter(code) {
|
||||||
return highlight(code, languages.css);
|
return highlight(code, languages.css);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Update the heartbeat list and update favicon if neccessary */
|
||||||
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) {
|
||||||
@@ -599,14 +626,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Enable editing mode */
|
||||||
edit() {
|
edit() {
|
||||||
if (this.hasToken) {
|
if (this.hasToken) {
|
||||||
this.$root.initSocketIO(true);
|
this.$root.initSocketIO(true);
|
||||||
this.enableEditMode = true;
|
this.enableEditMode = true;
|
||||||
this.clickedEditButton = true;
|
this.clickedEditButton = true;
|
||||||
|
|
||||||
|
// Try to fix #1658
|
||||||
|
this.loadedData = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Save the status page */
|
||||||
save() {
|
save() {
|
||||||
let startTime = new Date();
|
let startTime = new Date();
|
||||||
this.config.slug = this.config.slug.trim().toLowerCase();
|
this.config.slug = this.config.slug.trim().toLowerCase();
|
||||||
@@ -634,10 +666,12 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show dialog confirming deletion */
|
||||||
deleteDialog() {
|
deleteDialog() {
|
||||||
this.$refs.confirmDelete.show();
|
this.$refs.confirmDelete.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Request deletion of this status page */
|
||||||
deleteStatusPage() {
|
deleteStatusPage() {
|
||||||
this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => {
|
this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -649,10 +683,16 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns label for a specifed monitor
|
||||||
|
* @param {Object} monitor Object representing monitor
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
monitorSelectorLabel(monitor) {
|
monitorSelectorLabel(monitor) {
|
||||||
return `${monitor.name}`;
|
return `${monitor.name}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Add a group to the status page */
|
||||||
addGroup() {
|
addGroup() {
|
||||||
let groupName = this.$t("Untitled Group");
|
let groupName = this.$t("Untitled Group");
|
||||||
|
|
||||||
@@ -666,32 +706,32 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Add a domain to the status page */
|
||||||
addDomainField() {
|
addDomainField() {
|
||||||
this.config.domainNameList.push("");
|
this.config.domainNameList.push("");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Discard changes to status page */
|
||||||
discard() {
|
discard() {
|
||||||
location.href = "/status/" + this.slug;
|
location.href = "/status/" + this.slug;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crop Success
|
* Set URL of new image after successful crop operation
|
||||||
|
* @param {string} imgDataUrl URL of image in data:// format
|
||||||
*/
|
*/
|
||||||
cropSuccess(imgDataUrl) {
|
cropSuccess(imgDataUrl) {
|
||||||
this.imgDataUrl = imgDataUrl;
|
this.imgDataUrl = imgDataUrl;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Show image crop dialog if in edit mode */
|
||||||
showImageCropUploadMethod() {
|
showImageCropUploadMethod() {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
this.showImageCropUpload = true;
|
this.showImageCropUpload = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
statusPageLogoLoaded(eventPayload) {
|
/** Create an incident for this status page */
|
||||||
// Remark: may not work in dev, due to CORS
|
|
||||||
favicon.image(eventPayload.target);
|
|
||||||
},
|
|
||||||
|
|
||||||
createIncident() {
|
createIncident() {
|
||||||
this.enableEditIncidentMode = true;
|
this.enableEditIncidentMode = true;
|
||||||
|
|
||||||
@@ -706,6 +746,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Post the incident to the status page */
|
||||||
postIncident() {
|
postIncident() {
|
||||||
if (this.incident.title === "" || this.incident.content === "") {
|
if (this.incident.title === "" || this.incident.content === "") {
|
||||||
toast.error(this.$t("Please input title and content"));
|
toast.error(this.$t("Please input title and content"));
|
||||||
@@ -725,14 +766,13 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/** Click Edit Button */
|
||||||
* Click Edit Button
|
|
||||||
*/
|
|
||||||
editIncident() {
|
editIncident() {
|
||||||
this.enableEditIncidentMode = true;
|
this.enableEditIncidentMode = true;
|
||||||
this.previousIncident = Object.assign({}, this.incident);
|
this.previousIncident = Object.assign({}, this.incident);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Cancel creation or editing of incident */
|
||||||
cancelIncident() {
|
cancelIncident() {
|
||||||
this.enableEditIncidentMode = false;
|
this.enableEditIncidentMode = false;
|
||||||
|
|
||||||
@@ -742,16 +782,25 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Unpin the incident */
|
||||||
unpinIncident() {
|
unpinIncident() {
|
||||||
this.$root.getSocket().emit("unpinIncident", this.slug, () => {
|
this.$root.getSocket().emit("unpinIncident", this.slug, () => {
|
||||||
this.incident = null;
|
this.incident = null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relative time difference of a date from now
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
dateFromNow(date) {
|
dateFromNow(date) {
|
||||||
return dayjs.utc(date).fromNow();
|
return dayjs.utc(date).fromNow();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a domain from the status page
|
||||||
|
* @param {number} index Index of domain to remove
|
||||||
|
*/
|
||||||
removeDomain(index) {
|
removeDomain(index) {
|
||||||
this.config.domainNameList.splice(index, 1);
|
this.config.domainNameList.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
import EmptyLayout from "./layouts/EmptyLayout.vue";
|
import EmptyLayout from "./layouts/EmptyLayout.vue";
|
||||||
import Layout from "./layouts/Layout.vue";
|
import Layout from "./layouts/Layout.vue";
|
||||||
import Dashboard from "./pages/Dashboard.vue";
|
import Dashboard from "./pages/Dashboard.vue";
|
||||||
@@ -8,22 +9,23 @@ import EditMonitor from "./pages/EditMonitor.vue";
|
|||||||
import List from "./pages/List.vue";
|
import List from "./pages/List.vue";
|
||||||
const Settings = () => import("./pages/Settings.vue");
|
const Settings = () => import("./pages/Settings.vue");
|
||||||
import Setup from "./pages/Setup.vue";
|
import Setup from "./pages/Setup.vue";
|
||||||
const StatusPage = () => import("./pages/StatusPage.vue");
|
import StatusPage from "./pages/StatusPage.vue";
|
||||||
import Entry from "./pages/Entry.vue";
|
import Entry from "./pages/Entry.vue";
|
||||||
|
|
||||||
import Appearance from "./components/settings/Appearance.vue";
|
|
||||||
import General from "./components/settings/General.vue";
|
|
||||||
import Notifications from "./components/settings/Notifications.vue";
|
|
||||||
import ReverseProxy from "./components/settings/ReverseProxy.vue";
|
|
||||||
import MonitorHistory from "./components/settings/MonitorHistory.vue";
|
|
||||||
import Security from "./components/settings/Security.vue";
|
|
||||||
import Proxies from "./components/settings/Proxies.vue";
|
|
||||||
import Backup from "./components/settings/Backup.vue";
|
|
||||||
import About from "./components/settings/About.vue";
|
|
||||||
import ManageStatusPage from "./pages/ManageStatusPage.vue";
|
import ManageStatusPage from "./pages/ManageStatusPage.vue";
|
||||||
import AddStatusPage from "./pages/AddStatusPage.vue";
|
import AddStatusPage from "./pages/AddStatusPage.vue";
|
||||||
import NotFound from "./pages/NotFound.vue";
|
import NotFound from "./pages/NotFound.vue";
|
||||||
|
|
||||||
|
// Settings - Sub Pages
|
||||||
|
import Appearance from "./components/settings/Appearance.vue";
|
||||||
|
import General from "./components/settings/General.vue";
|
||||||
|
const Notifications = () => import("./components/settings/Notifications.vue");
|
||||||
|
import ReverseProxy from "./components/settings/ReverseProxy.vue";
|
||||||
|
import MonitorHistory from "./components/settings/MonitorHistory.vue";
|
||||||
|
const Security = () => import("./components/settings/Security.vue");
|
||||||
|
import Proxies from "./components/settings/Proxies.vue";
|
||||||
|
import Backup from "./components/settings/Backup.vue";
|
||||||
|
import About from "./components/settings/About.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
@@ -63,12 +65,12 @@ const routes = [
|
|||||||
path: "/add",
|
path: "/add",
|
||||||
component: EditMonitor,
|
component: EditMonitor,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/list",
|
|
||||||
component: List,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/list",
|
||||||
|
component: List,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
component: Settings,
|
component: Settings,
|
||||||
|
@@ -26,8 +26,8 @@ function getTimezoneOffset(timeZone) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of timezones sorted by their offset from UTC.
|
* Returns a list of timezones sorted by their offset from UTC.
|
||||||
* @param {Array} timezones - An array of timezone objects.
|
* @param {Object[]} timezones An array of timezone objects.
|
||||||
* @returns {Array} A list of the given timezones sorted by their offset from UTC.
|
* @returns {Object[]} A list of the given timezones sorted by their offset from UTC.
|
||||||
*
|
*
|
||||||
* Generated by Trelent
|
* Generated by Trelent
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +63,7 @@ export function timezoneList() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set the locale of the HTML page */
|
||||||
export function setPageLocale() {
|
export function setPageLocale() {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
html.setAttribute("lang", currentLocale() );
|
html.setAttribute("lang", currentLocale() );
|
||||||
@@ -70,7 +71,9 @@ export function setPageLocale() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Get the base URL
|
||||||
* Mainly used for dev, because the backend and the frontend are in different ports.
|
* Mainly used for dev, because the backend and the frontend are in different ports.
|
||||||
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getResBaseURL() {
|
export function getResBaseURL() {
|
||||||
const env = process.env.NODE_ENV;
|
const env = process.env.NODE_ENV;
|
||||||
|
70
src/util.js
70
src/util.js
@@ -18,6 +18,7 @@ exports.PENDING = 2;
|
|||||||
exports.STATUS_PAGE_ALL_DOWN = 0;
|
exports.STATUS_PAGE_ALL_DOWN = 0;
|
||||||
exports.STATUS_PAGE_ALL_UP = 1;
|
exports.STATUS_PAGE_ALL_UP = 1;
|
||||||
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||||
|
/** Flip the status of s */
|
||||||
function flipStatus(s) {
|
function flipStatus(s) {
|
||||||
if (s === exports.UP) {
|
if (s === exports.UP) {
|
||||||
return exports.DOWN;
|
return exports.DOWN;
|
||||||
@@ -28,6 +29,10 @@ function flipStatus(s) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
exports.flipStatus = flipStatus;
|
exports.flipStatus = flipStatus;
|
||||||
|
/**
|
||||||
|
* Delays for specified number of seconds
|
||||||
|
* @param ms Number of milliseconds to sleep for
|
||||||
|
*/
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@@ -83,6 +88,12 @@ class Logger {
|
|||||||
this.debug("server", this.hideLog);
|
this.debug("server", this.hideLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Write a message to the log
|
||||||
|
* @param module The module the log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
|
||||||
|
*/
|
||||||
log(module, msg, level) {
|
log(module, msg, level) {
|
||||||
if (this.hideLog[level] && this.hideLog[level].includes(module)) {
|
if (this.hideLog[level] && this.hideLog[level].includes(module)) {
|
||||||
return;
|
return;
|
||||||
@@ -109,18 +120,44 @@ class Logger {
|
|||||||
console.log(formattedMessage);
|
console.log(formattedMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Log an INFO message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
info(module, msg) {
|
info(module, msg) {
|
||||||
this.log(module, msg, "info");
|
this.log(module, msg, "info");
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Log a WARN message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
warn(module, msg) {
|
warn(module, msg) {
|
||||||
this.log(module, msg, "warn");
|
this.log(module, msg, "warn");
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Log an ERROR message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
error(module, msg) {
|
error(module, msg) {
|
||||||
this.log(module, msg, "error");
|
this.log(module, msg, "error");
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Log a DEBUG message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
debug(module, msg) {
|
debug(module, msg) {
|
||||||
this.log(module, msg, "debug");
|
this.log(module, msg, "debug");
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Log an exeption as an ERROR
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param exception The exeption to include
|
||||||
|
* @param msg The message to write
|
||||||
|
*/
|
||||||
exception(module, exception, msg) {
|
exception(module, exception, msg) {
|
||||||
let finalMessage = exception;
|
let finalMessage = exception;
|
||||||
if (msg) {
|
if (msg) {
|
||||||
@@ -130,13 +167,13 @@ class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.log = new Logger();
|
exports.log = new Logger();
|
||||||
|
/**
|
||||||
|
* String.prototype.replaceAll() polyfill
|
||||||
|
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
||||||
|
* @author Chris Ferdinandi
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
function polyfill() {
|
function polyfill() {
|
||||||
/**
|
|
||||||
* String.prototype.replaceAll() polyfill
|
|
||||||
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
|
||||||
* @author Chris Ferdinandi
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
if (!String.prototype.replaceAll) {
|
if (!String.prototype.replaceAll) {
|
||||||
String.prototype.replaceAll = function (str, newStr) {
|
String.prototype.replaceAll = function (str, newStr) {
|
||||||
// If a regex pattern
|
// If a regex pattern
|
||||||
@@ -153,6 +190,10 @@ class TimeLogger {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.startTime = dayjs().valueOf();
|
this.startTime = dayjs().valueOf();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Output time since start of monitor
|
||||||
|
* @param name Name of monitor
|
||||||
|
*/
|
||||||
print(name) {
|
print(name) {
|
||||||
if (exports.isDev && process.env.TIMELOGGER === "1") {
|
if (exports.isDev && process.env.TIMELOGGER === "1") {
|
||||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
||||||
@@ -201,6 +242,13 @@ let getRandomBytes = ((typeof window !== 'undefined' && window.crypto)
|
|||||||
: function () {
|
: function () {
|
||||||
return require("crypto").randomBytes;
|
return require("crypto").randomBytes;
|
||||||
})();
|
})();
|
||||||
|
/**
|
||||||
|
* Get a random integer suitable for use in cryptography between upper
|
||||||
|
* and lower bounds.
|
||||||
|
* @param min Minimum value of integer
|
||||||
|
* @param max Maximum value of integer
|
||||||
|
* @returns Cryptographically suitable random integer
|
||||||
|
*/
|
||||||
function getCryptoRandomInt(min, max) {
|
function getCryptoRandomInt(min, max) {
|
||||||
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
|
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
@@ -231,6 +279,11 @@ function getCryptoRandomInt(min, max) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.getCryptoRandomInt = getCryptoRandomInt;
|
exports.getCryptoRandomInt = getCryptoRandomInt;
|
||||||
|
/**
|
||||||
|
* Generate a secret
|
||||||
|
* @param length Lenght of secret to generate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function genSecret(length = 64) {
|
function genSecret(length = 64) {
|
||||||
let secret = "";
|
let secret = "";
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
@@ -241,6 +294,11 @@ function genSecret(length = 64) {
|
|||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
exports.genSecret = genSecret;
|
exports.genSecret = genSecret;
|
||||||
|
/**
|
||||||
|
* Get the path of a monitor
|
||||||
|
* @param id ID of monitor
|
||||||
|
* @returns Formatted relative path
|
||||||
|
*/
|
||||||
function getMonitorRelativeURL(id) {
|
function getMonitorRelativeURL(id) {
|
||||||
return "/dashboard/" + id;
|
return "/dashboard/" + id;
|
||||||
}
|
}
|
||||||
|
76
src/util.ts
76
src/util.ts
@@ -19,7 +19,7 @@ export const STATUS_PAGE_ALL_DOWN = 0;
|
|||||||
export const STATUS_PAGE_ALL_UP = 1;
|
export const STATUS_PAGE_ALL_UP = 1;
|
||||||
export const STATUS_PAGE_PARTIAL_DOWN = 2;
|
export const STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||||
|
|
||||||
|
/** Flip the status of s */
|
||||||
export function flipStatus(s: number) {
|
export function flipStatus(s: number) {
|
||||||
if (s === UP) {
|
if (s === UP) {
|
||||||
return DOWN;
|
return DOWN;
|
||||||
@@ -32,6 +32,10 @@ export function flipStatus(s: number) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays for specified number of seconds
|
||||||
|
* @param ms Number of milliseconds to sleep for
|
||||||
|
*/
|
||||||
export function sleep(ms: number) {
|
export function sleep(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@@ -94,6 +98,12 @@ class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a message to the log
|
||||||
|
* @param module The module the log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
|
||||||
|
*/
|
||||||
log(module: string, msg: any, level: string) {
|
log(module: string, msg: any, level: string) {
|
||||||
if (this.hideLog[level] && this.hideLog[level].includes(module)) {
|
if (this.hideLog[level] && this.hideLog[level].includes(module)) {
|
||||||
return;
|
return;
|
||||||
@@ -120,22 +130,48 @@ class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an INFO message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
info(module: string, msg: any) {
|
info(module: string, msg: any) {
|
||||||
this.log(module, msg, "info");
|
this.log(module, msg, "info");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a WARN message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
warn(module: string, msg: any) {
|
warn(module: string, msg: any) {
|
||||||
this.log(module, msg, "warn");
|
this.log(module, msg, "warn");
|
||||||
}
|
}
|
||||||
|
|
||||||
error(module: string, msg: any) {
|
/**
|
||||||
|
* Log an ERROR message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
|
error(module: string, msg: any) {
|
||||||
this.log(module, msg, "error");
|
this.log(module, msg, "error");
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(module: string, msg: any) {
|
/**
|
||||||
|
* Log a DEBUG message
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param msg Message to write
|
||||||
|
*/
|
||||||
|
debug(module: string, msg: any) {
|
||||||
this.log(module, msg, "debug");
|
this.log(module, msg, "debug");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an exeption as an ERROR
|
||||||
|
* @param module Module log comes from
|
||||||
|
* @param exception The exeption to include
|
||||||
|
* @param msg The message to write
|
||||||
|
*/
|
||||||
exception(module: string, exception: any, msg: any) {
|
exception(module: string, exception: any, msg: any) {
|
||||||
let finalMessage = exception
|
let finalMessage = exception
|
||||||
|
|
||||||
@@ -151,13 +187,13 @@ export const log = new Logger();
|
|||||||
|
|
||||||
declare global { interface String { replaceAll(str: string, newStr: string): string; } }
|
declare global { interface String { replaceAll(str: string, newStr: string): string; } }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String.prototype.replaceAll() polyfill
|
||||||
|
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
||||||
|
* @author Chris Ferdinandi
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
export function polyfill() {
|
export function polyfill() {
|
||||||
/**
|
|
||||||
* String.prototype.replaceAll() polyfill
|
|
||||||
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
|
||||||
* @author Chris Ferdinandi
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
if (!String.prototype.replaceAll) {
|
if (!String.prototype.replaceAll) {
|
||||||
String.prototype.replaceAll = function (str: string, newStr: string) {
|
String.prototype.replaceAll = function (str: string, newStr: string) {
|
||||||
// If a regex pattern
|
// If a regex pattern
|
||||||
@@ -177,7 +213,10 @@ export class TimeLogger {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.startTime = dayjs().valueOf();
|
this.startTime = dayjs().valueOf();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Output time since start of monitor
|
||||||
|
* @param name Name of monitor
|
||||||
|
*/
|
||||||
print(name: string) {
|
print(name: string) {
|
||||||
if (isDev && process.env.TIMELOGGER === "1") {
|
if (isDev && process.env.TIMELOGGER === "1") {
|
||||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
|
||||||
@@ -231,6 +270,13 @@ let getRandomBytes = (
|
|||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a random integer suitable for use in cryptography between upper
|
||||||
|
* and lower bounds.
|
||||||
|
* @param min Minimum value of integer
|
||||||
|
* @param max Maximum value of integer
|
||||||
|
* @returns Cryptographically suitable random integer
|
||||||
|
*/
|
||||||
export function getCryptoRandomInt(min: number, max: number):number {
|
export function getCryptoRandomInt(min: number, max: number):number {
|
||||||
|
|
||||||
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
|
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
|
||||||
@@ -267,6 +313,11 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random alphanumeric string of fixed length
|
||||||
|
* @param length Length of string to generate
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
export function genSecret(length = 64) {
|
export function genSecret(length = 64) {
|
||||||
let secret = "";
|
let secret = "";
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
@@ -277,6 +328,11 @@ export function genSecret(length = 64) {
|
|||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path of a monitor
|
||||||
|
* @param id ID of monitor
|
||||||
|
* @returns Formatted relative path
|
||||||
|
*/
|
||||||
export function getMonitorRelativeURL(id: string) {
|
export function getMonitorRelativeURL(id: string) {
|
||||||
return "/dashboard/" + id;
|
return "/dashboard/" + id;
|
||||||
}
|
}
|
||||||
|
@@ -159,7 +159,6 @@ describe("Test genSecret", () => {
|
|||||||
expect(secret).toContain("A");
|
expect(secret).toContain("A");
|
||||||
expect(secret).toContain("9");
|
expect(secret).toContain("9");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test reset-password", () => {
|
describe("Test reset-password", () => {
|
||||||
@@ -169,6 +168,9 @@ describe("Test reset-password", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Test Discord Notification Provider", () => {
|
describe("Test Discord Notification Provider", () => {
|
||||||
|
const hostname = "discord.com";
|
||||||
|
const port = 1337;
|
||||||
|
|
||||||
const sendNotification = async (hostname, port, type) => {
|
const sendNotification = async (hostname, port, type) => {
|
||||||
const discordProvider = new Discord();
|
const discordProvider = new Discord();
|
||||||
|
|
||||||
@@ -191,63 +193,35 @@ describe("Test Discord Notification Provider", () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should send hostname for dns monitors", async () => {
|
|
||||||
const hostname = "discord.com";
|
|
||||||
await sendNotification(hostname, null, "dns");
|
|
||||||
|
|
||||||
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
|
|
||||||
hostname
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should send hostname for ping monitors", async () => {
|
it("should send hostname for ping monitors", async () => {
|
||||||
const hostname = "discord.com";
|
|
||||||
await sendNotification(hostname, null, "ping");
|
await sendNotification(hostname, null, "ping");
|
||||||
|
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(hostname);
|
||||||
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
|
|
||||||
hostname
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send hostname for port monitors", async () => {
|
it.each([ "dns", "port", "steam" ])("should send hostname for %p monitors", async (type) => {
|
||||||
const hostname = "discord.com";
|
await sendNotification(hostname, port, type);
|
||||||
const port = 1337;
|
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(`${hostname}:${port}`);
|
||||||
await sendNotification(hostname, port, "port");
|
|
||||||
|
|
||||||
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
|
|
||||||
`${hostname}:${port}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should send hostname for steam monitors", async () => {
|
|
||||||
const hostname = "discord.com";
|
|
||||||
const port = 1337;
|
|
||||||
await sendNotification(hostname, port, "steam");
|
|
||||||
|
|
||||||
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
|
|
||||||
`${hostname}:${port}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("The function filterAndJoin", () => {
|
describe("The function filterAndJoin", () => {
|
||||||
it("should join and array of strings to one string", () => {
|
it("should join and array of strings to one string", () => {
|
||||||
const result = utilServerRewire.filterAndJoin(["one", "two", "three"]);
|
const result = utilServerRewire.filterAndJoin([ "one", "two", "three" ]);
|
||||||
expect(result).toBe("onetwothree");
|
expect(result).toBe("onetwothree");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should join strings using a given connector", () => {
|
it("should join strings using a given connector", () => {
|
||||||
const result = utilServerRewire.filterAndJoin(["one", "two", "three"], "-");
|
const result = utilServerRewire.filterAndJoin([ "one", "two", "three" ], "-");
|
||||||
expect(result).toBe("one-two-three");
|
expect(result).toBe("one-two-three");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should filter null, undefined and empty strings before joining", () => {
|
it("should filter null, undefined and empty strings before joining", () => {
|
||||||
const result = utilServerRewire.filterAndJoin([undefined, "", "three"], "--");
|
const result = utilServerRewire.filterAndJoin([ undefined, "", "three" ], "--");
|
||||||
expect(result).toBe("three");
|
expect(result).toBe("three");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return an empty string if all parts are filtered out", () => {
|
it("should return an empty string if all parts are filtered out", () => {
|
||||||
const result = utilServerRewire.filterAndJoin([undefined, "", ""], "--");
|
const result = utilServerRewire.filterAndJoin([ undefined, "", "" ], "--");
|
||||||
expect(result).toBe("");
|
expect(result).toBe("");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user