mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-13 23:17:00 +08:00
Compare commits
91 Commits
1.15.1
...
1.16.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
398219f847 | ||
|
7a50f0e3f3 | ||
|
4178b003a2 | ||
|
8ede6d888f | ||
|
cec0521834 | ||
|
73b603dd10 | ||
|
92a43e1f30 | ||
|
0cf395dfc3 | ||
|
749ca6f4a8 | ||
|
66971deaf4 | ||
|
8077744c60 | ||
|
c5faf709b8 | ||
|
7da9f139c1 | ||
|
7acbfd2474 | ||
|
9f493bbec7 | ||
|
5bf58cc6c4 | ||
|
d344914ca0 | ||
|
b680371746 | ||
|
e488e2dc0a | ||
|
4e3258579d | ||
|
aa8ea6d398 | ||
|
8b0813ceff | ||
|
91178ce6a5 | ||
|
429ad384d0 | ||
|
4b9dc2890d | ||
|
f9004bcbed | ||
|
bc174c3325 | ||
|
4c2753af46 | ||
|
c6ba5b621c | ||
|
96536ae391 | ||
|
ba46544772 | ||
|
5c852db1cf | ||
|
069d3765f0 | ||
|
15820c6937 | ||
|
000cbeb0ce | ||
|
e118d59ac8 | ||
|
39aa0a7f07 | ||
|
a12dffd1bc | ||
|
410805052e | ||
|
02a8147f22 | ||
|
d962ab7a1c | ||
|
63c8d24d6f | ||
|
254a6bfd36 | ||
|
29f3cbe8c6 | ||
|
53b98ad3e4 | ||
|
dbd7c087e0 | ||
|
272956025c | ||
|
db50ba91cc | ||
|
42ea3fb412 | ||
|
9f8b3151d8 | ||
|
73e38a13d2 | ||
|
369477b4b9 | ||
|
2347a01f7c | ||
|
c114c053d6 | ||
|
ae2c49a729 | ||
|
b9e72b9645 | ||
|
5a069b278d | ||
|
65ea2e6aeb | ||
|
e82fc1df61 | ||
|
7dd5f5ea0d | ||
|
45da7c5431 | ||
|
64a33d7455 | ||
|
e103ac8335 | ||
|
288ed1e3ca | ||
|
0765f05090 | ||
|
2638d68c97 | ||
|
1b1e0f6dd9 | ||
|
0961c6d9b3 | ||
|
ce7d8c38c5 | ||
|
454c1687cf | ||
|
28be32fc68 | ||
|
dd3992063e | ||
|
0313acd4c5 | ||
|
cd19b9fc49 | ||
|
c57b2c4d28 | ||
|
3dda5938f2 | ||
|
f00ec4dfef | ||
|
43f8fc701c | ||
|
499042504f | ||
|
faf6719e7c | ||
|
a9d264ccfc | ||
|
df8f93f0c2 | ||
|
28c0e16a0c | ||
|
6acc9546a0 | ||
|
f455e3a454 | ||
|
7abbf421d0 | ||
|
3625915a85 | ||
|
d74404e106 | ||
|
1c5bce8afa | ||
|
8b5997691e | ||
|
35360e2069 |
12
.github/workflows/auto-test.yml
vendored
12
.github/workflows/auto-test.yml
vendored
@@ -11,12 +11,14 @@ on:
|
||||
|
||||
jobs:
|
||||
auto-test:
|
||||
needs: [ check-linters ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
node: [14, 16, 17]
|
||||
node: [ 14, 16, 17, 18 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@@ -28,7 +30,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
- run: npm run install-legacy
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
env:
|
||||
@@ -41,10 +43,10 @@ jobs:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js LTS
|
||||
- name: Use Node.js 14
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 14
|
||||
cache: 'npm'
|
||||
- run: npm run install-legacy
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
|
@@ -8,7 +8,7 @@ services:
|
||||
image: louislam/uptime-kuma:1
|
||||
container_name: uptime-kuma
|
||||
volumes:
|
||||
- ./uptime-kuma:/app/data
|
||||
- ./uptime-kuma-data:/app/data
|
||||
ports:
|
||||
- 3001:3001
|
||||
- 3001:3001 # <Host Port>:<Container Port>
|
||||
restart: always
|
||||
|
200
package-lock.json
generated
200
package-lock.json
generated
@@ -1,22 +1,23 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
||||
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
||||
"@louislam/sqlite3": "~15.0.3",
|
||||
"@louislam/sqlite3": "~15.0.6",
|
||||
"@popperjs/core": "~2.10.2",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.26.1",
|
||||
"badge-maker": "^3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"bootstrap": "5.1.3",
|
||||
"bree": "~7.1.5",
|
||||
@@ -24,6 +25,7 @@
|
||||
"chart.js": "~3.6.2",
|
||||
"chartjs-adapter-dayjs": "~1.0.0",
|
||||
"check-password-strength": "^2.0.5",
|
||||
"chroma-js": "^2.1.2",
|
||||
"command-exists": "~1.2.9",
|
||||
"compare-versions": "~3.6.0",
|
||||
"dayjs": "~1.10.8",
|
||||
@@ -2697,9 +2699,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@louislam/sqlite3": {
|
||||
"version": "15.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@louislam/sqlite3/-/sqlite3-15.0.3.tgz",
|
||||
"integrity": "sha512-rCH6PIaa+TgBzpTRnqBKUa4H/5G2hIk5ukYK5rXxK+8hVGykRin3UMGzGejrPzIKzDnZGByIF0XD4ndi6lprRQ==",
|
||||
"version": "15.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@louislam/sqlite3/-/sqlite3-15.0.6.tgz",
|
||||
"integrity": "sha512-+HF/4OEy+yakYzJlSPJbLDtf499t0s0eaglXC9y3Oa9OBZ+dKAaTW5+Ft1RCvfUJLFw/oyYjHtMsg9V+7NT05g==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||
@@ -3939,6 +3941,14 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/anafanafo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anafanafo/-/anafanafo-2.0.0.tgz",
|
||||
"integrity": "sha512-Nlfq7NC4AOkTJerWRIZcOAiMNtIDVIGWGvQ98O7Jl6Kr2Dk0dX5u4MqN778kSRTy5KRqchpLdF2RtLFEz9FVkQ==",
|
||||
"dependencies": {
|
||||
"char-width-table-consumer": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-align": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||
@@ -4366,6 +4376,22 @@
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
},
|
||||
"node_modules/badge-maker": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/badge-maker/-/badge-maker-3.3.1.tgz",
|
||||
"integrity": "sha512-OO/PS7Zg2E6qaUWzHEHt21Q5VjcFBAJVA8ztgT/fIdSZFBUwoyeo0ZhA6V5tUM8Vcjq8DJl6jfGhpjESssyqMQ==",
|
||||
"dependencies": {
|
||||
"anafanafo": "2.0.0",
|
||||
"css-color-converter": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10",
|
||||
"npm": ">= 5"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -4443,6 +4469,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-search": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
|
||||
"integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="
|
||||
},
|
||||
"node_modules/bintrees": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz",
|
||||
@@ -4945,6 +4976,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/char-width-table-consumer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/char-width-table-consumer/-/char-width-table-consumer-1.0.0.tgz",
|
||||
"integrity": "sha512-Fz4UD0LBpxPgL9i29CJ5O4KANwaMnX/OhhbxzvNa332h+9+nRKyeuLw4wA51lt/ex67+/AdsoBQJF3kgX2feYQ==",
|
||||
"dependencies": {
|
||||
"binary-search": "^1.3.5"
|
||||
}
|
||||
},
|
||||
"node_modules/chardet": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-1.4.0.tgz",
|
||||
@@ -5004,6 +5043,29 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/chroma-js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.1.2.tgz",
|
||||
"integrity": "sha512-ri/ouYDWuxfus3UcaMxC1Tfp3IE9K5iQzxc2hSxbBRVNQFut1UuGAsZmiAf2mOUubzGJwgMSv9lHg+XqLaz1QQ==",
|
||||
"dependencies": {
|
||||
"cross-env": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/chroma-js/node_modules/cross-env": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz",
|
||||
"integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz",
|
||||
@@ -5555,7 +5617,6 @@
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -5574,6 +5635,26 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-converter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-converter/-/css-color-converter-2.0.0.tgz",
|
||||
"integrity": "sha512-oLIG2soZz3wcC3aAl/7Us5RS8Hvvc6I8G8LniF/qfMmrm7fIKQ8RIDDRZeKyGL2SrWfNqYspuLShbnjBMVWm8g==",
|
||||
"dependencies": {
|
||||
"color-convert": "^0.5.2",
|
||||
"color-name": "^1.1.4",
|
||||
"css-unit-converter": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-converter/node_modules/color-convert": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
|
||||
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
|
||||
},
|
||||
"node_modules/css-color-converter/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/css-functions-list": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.0.1.tgz",
|
||||
@@ -5583,6 +5664,11 @@
|
||||
"node": ">=12.22"
|
||||
}
|
||||
},
|
||||
"node_modules/css-unit-converter": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
|
||||
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -8694,8 +8780,7 @@
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"devOptional": true
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"node_modules/isobject": {
|
||||
"version": "3.0.1",
|
||||
@@ -13143,7 +13228,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -14689,7 +14773,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
@@ -14701,7 +14784,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -16532,7 +16614,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
@@ -18665,9 +18746,9 @@
|
||||
}
|
||||
},
|
||||
"@louislam/sqlite3": {
|
||||
"version": "15.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@louislam/sqlite3/-/sqlite3-15.0.3.tgz",
|
||||
"integrity": "sha512-rCH6PIaa+TgBzpTRnqBKUa4H/5G2hIk5ukYK5rXxK+8hVGykRin3UMGzGejrPzIKzDnZGByIF0XD4ndi6lprRQ==",
|
||||
"version": "15.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@louislam/sqlite3/-/sqlite3-15.0.6.tgz",
|
||||
"integrity": "sha512-+HF/4OEy+yakYzJlSPJbLDtf499t0s0eaglXC9y3Oa9OBZ+dKAaTW5+Ft1RCvfUJLFw/oyYjHtMsg9V+7NT05g==",
|
||||
"requires": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||
"node-addon-api": "^4.2.0",
|
||||
@@ -19745,6 +19826,14 @@
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"anafanafo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anafanafo/-/anafanafo-2.0.0.tgz",
|
||||
"integrity": "sha512-Nlfq7NC4AOkTJerWRIZcOAiMNtIDVIGWGvQ98O7Jl6Kr2Dk0dX5u4MqN778kSRTy5KRqchpLdF2RtLFEz9FVkQ==",
|
||||
"requires": {
|
||||
"char-width-table-consumer": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ansi-align": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||
@@ -20087,6 +20176,15 @@
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
},
|
||||
"badge-maker": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/badge-maker/-/badge-maker-3.3.1.tgz",
|
||||
"integrity": "sha512-OO/PS7Zg2E6qaUWzHEHt21Q5VjcFBAJVA8ztgT/fIdSZFBUwoyeo0ZhA6V5tUM8Vcjq8DJl6jfGhpjESssyqMQ==",
|
||||
"requires": {
|
||||
"anafanafo": "2.0.0",
|
||||
"css-color-converter": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -20143,6 +20241,11 @@
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"binary-search": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
|
||||
"integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="
|
||||
},
|
||||
"bintrees": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz",
|
||||
@@ -20510,6 +20613,14 @@
|
||||
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
|
||||
"dev": true
|
||||
},
|
||||
"char-width-table-consumer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/char-width-table-consumer/-/char-width-table-consumer-1.0.0.tgz",
|
||||
"integrity": "sha512-Fz4UD0LBpxPgL9i29CJ5O4KANwaMnX/OhhbxzvNa332h+9+nRKyeuLw4wA51lt/ex67+/AdsoBQJF3kgX2feYQ==",
|
||||
"requires": {
|
||||
"binary-search": "^1.3.5"
|
||||
}
|
||||
},
|
||||
"chardet": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-1.4.0.tgz",
|
||||
@@ -20551,6 +20662,24 @@
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
|
||||
},
|
||||
"chroma-js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.1.2.tgz",
|
||||
"integrity": "sha512-ri/ouYDWuxfus3UcaMxC1Tfp3IE9K5iQzxc2hSxbBRVNQFut1UuGAsZmiAf2mOUubzGJwgMSv9lHg+XqLaz1QQ==",
|
||||
"requires": {
|
||||
"cross-env": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz",
|
||||
"integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
|
||||
"requires": {
|
||||
"cross-spawn": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz",
|
||||
@@ -20993,7 +21122,6 @@
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -21006,12 +21134,39 @@
|
||||
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
|
||||
"dev": true
|
||||
},
|
||||
"css-color-converter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-converter/-/css-color-converter-2.0.0.tgz",
|
||||
"integrity": "sha512-oLIG2soZz3wcC3aAl/7Us5RS8Hvvc6I8G8LniF/qfMmrm7fIKQ8RIDDRZeKyGL2SrWfNqYspuLShbnjBMVWm8g==",
|
||||
"requires": {
|
||||
"color-convert": "^0.5.2",
|
||||
"color-name": "^1.1.4",
|
||||
"css-unit-converter": "^1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"color-convert": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
|
||||
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"css-functions-list": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.0.1.tgz",
|
||||
"integrity": "sha512-PriDuifDt4u4rkDgnqRCLnjfMatufLmWNfQnGCq34xZwpY3oabwhB9SqRBmuvWUgndbemCFlKqg+nO7C2q0SBw==",
|
||||
"dev": true
|
||||
},
|
||||
"css-unit-converter": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
|
||||
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
|
||||
},
|
||||
"cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -23331,8 +23486,7 @@
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"devOptional": true
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
@@ -26732,8 +26886,7 @@
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
@@ -27893,7 +28046,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
@@ -27901,8 +28053,7 @@
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.7",
|
||||
@@ -29304,7 +29455,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
|
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.15.1",
|
||||
"version": "1.16.0-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -10,8 +10,8 @@
|
||||
"node": "14.* || >=16.*"
|
||||
},
|
||||
"scripts": {
|
||||
"install-legacy": "npm install --legacy-peer-deps",
|
||||
"update-legacy": "npm update --legacy-peer-deps",
|
||||
"install-legacy": "npm install",
|
||||
"update-legacy": "npm update",
|
||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
||||
"lint-fix:js": "eslint --ext \".js,.vue\" --fix --ignore-path .gitignore .",
|
||||
"lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore",
|
||||
@@ -64,10 +64,11 @@
|
||||
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
||||
"@louislam/sqlite3": "~15.0.3",
|
||||
"@louislam/sqlite3": "~15.0.6",
|
||||
"@popperjs/core": "~2.10.2",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.26.1",
|
||||
"badge-maker": "^3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"bootstrap": "5.1.3",
|
||||
"bree": "~7.1.5",
|
||||
@@ -75,6 +76,7 @@
|
||||
"chart.js": "~3.6.2",
|
||||
"chartjs-adapter-dayjs": "~1.0.0",
|
||||
"check-password-strength": "^2.0.5",
|
||||
"chroma-js": "^2.1.2",
|
||||
"command-exists": "~1.2.9",
|
||||
"compare-versions": "~3.6.0",
|
||||
"dayjs": "~1.10.8",
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
@@ -1,7 +1,20 @@
|
||||
const args = require("args-parser")(process.argv);
|
||||
const demoMode = args["demo"] || false;
|
||||
|
||||
const badgeConstants = {
|
||||
naColor: "#999",
|
||||
defaultUpColor: "#66c20a",
|
||||
defaultDownColor: "#c2290a",
|
||||
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
||||
defaultStyle: "flat",
|
||||
defaultPingValueSuffix: "ms",
|
||||
defaultPingLabelSuffix: "h",
|
||||
defaultUptimeValueSuffix: "%",
|
||||
defaultUptimeLabelSuffix: "h",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
args,
|
||||
demoMode
|
||||
demoMode,
|
||||
badgeConstants,
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ dayjs.extend(timezone);
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog, mqttAsync } = require("../util-server");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mqttAsync } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification");
|
||||
@@ -15,6 +15,7 @@ const { Proxy } = require("../proxy");
|
||||
const { demoMode } = require("../config");
|
||||
const version = require("../../package.json").version;
|
||||
const apicache = require("../modules/apicache");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
/**
|
||||
* status:
|
||||
@@ -181,7 +182,7 @@ class Monitor extends BeanModel {
|
||||
// undefined if not https
|
||||
let tlsInfo = undefined;
|
||||
|
||||
if (!previousBeat) {
|
||||
if (!previousBeat || this.type === "push") {
|
||||
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
||||
this.id,
|
||||
]);
|
||||
@@ -376,9 +377,6 @@ class Monitor extends BeanModel {
|
||||
log.debug("monitor", "heartbeatCount" + heartbeatCount + " " + time);
|
||||
|
||||
if (heartbeatCount <= 0) {
|
||||
// Fix #922, since previous heartbeat could be inserted by api, it should get from database
|
||||
previousBeat = await Monitor.getPreviousHeartbeat(this.id);
|
||||
|
||||
throw new Error("No heartbeat in the time window");
|
||||
} else {
|
||||
// No need to insert successful heartbeat for push type, so end here
|
||||
@@ -521,7 +519,7 @@ class Monitor extends BeanModel {
|
||||
await beat();
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
errorLog(e, false);
|
||||
UptimeKumaServer.errorLog(e, false);
|
||||
log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues");
|
||||
|
||||
if (! this.isStop) {
|
||||
|
@@ -93,8 +93,23 @@ class AliyunSMS extends NotificationProvider {
|
||||
param2[key] = param[key];
|
||||
}
|
||||
|
||||
// Escape more characters than encodeURIComponent does.
|
||||
// For generating Aliyun signature, all characters except A-Za-z0-9~-._ are encoded.
|
||||
// See https://help.aliyun.com/document_detail/315526.html
|
||||
// This encoding methods as known as RFC 3986 (https://tools.ietf.org/html/rfc3986)
|
||||
let moreEscapesTable = function (m) {
|
||||
return {
|
||||
"!": "%21",
|
||||
"*": "%2A",
|
||||
"'": "%27",
|
||||
"(": "%28",
|
||||
")": "%29"
|
||||
}[m];
|
||||
};
|
||||
|
||||
for (let key in param2) {
|
||||
data.push(`${encodeURIComponent(key)}=${encodeURIComponent(param2[key])}`);
|
||||
let value = encodeURIComponent(param2[key]).replace(/[!*'()]/g, moreEscapesTable);
|
||||
data.push(`${encodeURIComponent(key)}=${value}`);
|
||||
}
|
||||
|
||||
let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`;
|
||||
|
@@ -6,9 +6,14 @@ class Apprise extends NotificationProvider {
|
||||
name = "apprise";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let s = childProcess.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL ]);
|
||||
const args = [ "-vv", "-b", msg, notification.appriseURL ];
|
||||
if (notification.title) {
|
||||
args.push("-t");
|
||||
args.push(notification.title);
|
||||
}
|
||||
const s = childProcess.spawnSync("apprise", args);
|
||||
|
||||
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
|
||||
const output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
|
||||
|
||||
if (output) {
|
||||
|
||||
|
@@ -22,16 +22,23 @@ class Discord extends NotificationProvider {
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let url;
|
||||
let address;
|
||||
|
||||
if (monitorJSON["type"] === "port") {
|
||||
url = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
url += ":" + monitorJSON["port"];
|
||||
}
|
||||
|
||||
} else {
|
||||
url = monitorJSON["url"];
|
||||
switch (monitorJSON["type"]) {
|
||||
case "ping":
|
||||
address = monitorJSON["hostname"];
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
address += ":" + monitorJSON["port"];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
address = monitorJSON["url"];
|
||||
break;
|
||||
}
|
||||
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
@@ -48,8 +55,8 @@ class Discord extends NotificationProvider {
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
name: "Service URL",
|
||||
value: url,
|
||||
name: "Service URL / Address",
|
||||
value: address,
|
||||
},
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
@@ -84,7 +91,7 @@ class Discord extends NotificationProvider {
|
||||
},
|
||||
{
|
||||
name: "Service URL",
|
||||
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url,
|
||||
value: address.startsWith("http") ? "[Visit Service](" + address + ")" : address,
|
||||
},
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
let express = require("express");
|
||||
const { allowDevAllOrigin } = require("../util-server");
|
||||
const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const apicache = require("../modules/apicache");
|
||||
const Monitor = require("../model/monitor");
|
||||
@@ -7,6 +7,9 @@ const dayjs = require("dayjs");
|
||||
const { UP, DOWN, flipStatus, log } = require("../../src/util");
|
||||
const StatusPage = require("../model/status_page");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { makeBadge } = require("badge-maker");
|
||||
const { badgeConstants } = require("../config");
|
||||
|
||||
let router = express.Router();
|
||||
|
||||
let cache = apicache.middleware;
|
||||
@@ -197,6 +200,182 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response) => {
|
||||
allowAllOrigin(response);
|
||||
|
||||
const {
|
||||
label,
|
||||
upLabel = "Up",
|
||||
downLabel = "Down",
|
||||
upColor = badgeConstants.defaultUpColor,
|
||||
downColor = badgeConstants.defaultDownColor,
|
||||
style = badgeConstants.defaultStyle,
|
||||
value, // for demo purpose only
|
||||
} = request.query;
|
||||
|
||||
try {
|
||||
const requestedMonitorId = parseInt(request.params.id, 10);
|
||||
const overrideValue = value !== undefined ? parseInt(value) : undefined;
|
||||
|
||||
let publicMonitor = await R.getRow(`
|
||||
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
|
||||
WHERE monitor_group.group_id = \`group\`.id
|
||||
AND monitor_group.monitor_id = ?
|
||||
AND public = 1
|
||||
`,
|
||||
[ requestedMonitorId ]
|
||||
);
|
||||
|
||||
const badgeValues = { style };
|
||||
|
||||
if (!publicMonitor) {
|
||||
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non exsitant
|
||||
|
||||
badgeValues.message = "N/A";
|
||||
badgeValues.color = badgeConstants.naColor;
|
||||
} else {
|
||||
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
||||
const state = overrideValue !== undefined ? overrideValue : heartbeat.status === 1;
|
||||
|
||||
badgeValues.color = state ? upColor : downColor;
|
||||
badgeValues.message = label ?? state ? upLabel : downLabel;
|
||||
}
|
||||
|
||||
// build the svg based on given values
|
||||
const svg = makeBadge(badgeValues);
|
||||
|
||||
response.type("image/svg+xml");
|
||||
response.send(svg);
|
||||
} catch (error) {
|
||||
send403(response, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (request, response) => {
|
||||
allowAllOrigin(response);
|
||||
|
||||
const {
|
||||
label,
|
||||
labelPrefix,
|
||||
labelSuffix = badgeConstants.defaultUptimeLabelSuffix,
|
||||
prefix,
|
||||
suffix = badgeConstants.defaultUptimeValueSuffix,
|
||||
color,
|
||||
labelColor,
|
||||
style = badgeConstants.defaultStyle,
|
||||
value, // for demo purpose only
|
||||
} = request.query;
|
||||
|
||||
try {
|
||||
const requestedMonitorId = parseInt(request.params.id, 10);
|
||||
// if no duration is given, set value to 24 (h)
|
||||
const requestedDuration = request.params.duration !== undefined ? parseInt(request.params.duration, 10) : 24;
|
||||
const overrideValue = value && parseFloat(value);
|
||||
|
||||
let publicMonitor = await R.getRow(`
|
||||
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
|
||||
WHERE monitor_group.group_id = \`group\`.id
|
||||
AND monitor_group.monitor_id = ?
|
||||
AND public = 1
|
||||
`,
|
||||
[ requestedMonitorId ]
|
||||
);
|
||||
|
||||
const badgeValues = { style };
|
||||
|
||||
if (!publicMonitor) {
|
||||
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non exsitant
|
||||
badgeValues.message = "N/A";
|
||||
badgeValues.color = badgeConstants.naColor;
|
||||
} else {
|
||||
const uptime = overrideValue ?? await Monitor.calcUptime(
|
||||
requestedDuration,
|
||||
requestedMonitorId
|
||||
);
|
||||
|
||||
// limit the displayed uptime percentage to four (two, when displayed as percent) decimal digits
|
||||
const cleanUptime = parseFloat(uptime.toPrecision(4));
|
||||
|
||||
// use a given, custom color or calculate one based on the uptime value
|
||||
badgeValues.color = color ?? percentageToColor(uptime);
|
||||
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||
badgeValues.labelColor = labelColor ?? "";
|
||||
// build a lable string. If a custom label is given, override the default one (requestedDuration)
|
||||
badgeValues.label = filterAndJoin([ labelPrefix, label ?? requestedDuration, labelSuffix ]);
|
||||
badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
|
||||
}
|
||||
|
||||
// build the SVG based on given values
|
||||
const svg = makeBadge(badgeValues);
|
||||
|
||||
response.type("image/svg+xml");
|
||||
response.send(svg);
|
||||
} catch (error) {
|
||||
send403(response, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request, response) => {
|
||||
allowAllOrigin(response);
|
||||
|
||||
const {
|
||||
label,
|
||||
labelPrefix,
|
||||
labelSuffix = badgeConstants.defaultPingLabelSuffix,
|
||||
prefix,
|
||||
suffix = badgeConstants.defaultPingValueSuffix,
|
||||
color = badgeConstants.defaultPingColor,
|
||||
labelColor,
|
||||
style = badgeConstants.defaultStyle,
|
||||
value, // for demo purpose only
|
||||
} = request.query;
|
||||
|
||||
try {
|
||||
const requestedMonitorId = parseInt(request.params.id, 10);
|
||||
|
||||
// Default duration is 24 (h) if not defined in queryParam, limited to 720h (30d)
|
||||
const requestedDuration = Math.min(request.params.duration ? parseInt(request.params.duration, 10) : 24, 720);
|
||||
const overrideValue = value && parseFloat(value);
|
||||
|
||||
const publicAvgPing = parseInt(await R.getCell(`
|
||||
SELECT AVG(ping) FROM monitor_group, \`group\`, heartbeat
|
||||
WHERE monitor_group.group_id = \`group\`.id
|
||||
AND heartbeat.time > DATETIME('now', ? || ' hours')
|
||||
AND heartbeat.ping IS NOT NULL
|
||||
AND public = 1
|
||||
AND heartbeat.monitor_id = ?
|
||||
`,
|
||||
[ -requestedDuration, requestedMonitorId ]
|
||||
));
|
||||
|
||||
const badgeValues = { style };
|
||||
|
||||
if (!publicAvgPing) {
|
||||
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non exsitant
|
||||
|
||||
badgeValues.message = "N/A";
|
||||
badgeValues.color = badgeConstants.naColor;
|
||||
} else {
|
||||
const avgPing = parseInt(overrideValue ?? publicAvgPing);
|
||||
|
||||
badgeValues.color = color;
|
||||
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||
badgeValues.labelColor = labelColor ?? "";
|
||||
// build a lable string. If a custom label is given, override the default one (requestedDuration)
|
||||
badgeValues.label = filterAndJoin([ labelPrefix, label ?? requestedDuration, labelSuffix ]);
|
||||
badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]);
|
||||
}
|
||||
|
||||
// build the SVG based on given values
|
||||
const svg = makeBadge(badgeValues);
|
||||
|
||||
response.type("image/svg+xml");
|
||||
response.send(svg);
|
||||
} catch (error) {
|
||||
send403(response, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Send a 403 response
|
||||
* @param {Object} res Express response object
|
||||
|
@@ -60,7 +60,7 @@ log.info("server", "Importing this project modules");
|
||||
log.debug("server", "Importing Monitor");
|
||||
const Monitor = require("./model/monitor");
|
||||
log.debug("server", "Importing Settings");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword } = require("./util-server");
|
||||
|
||||
log.debug("server", "Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
@@ -1694,6 +1694,6 @@ gracefulShutdown(server.httpServer, {
|
||||
// Catch unexpected errors here
|
||||
process.addListener("unhandledRejection", (error, promise) => {
|
||||
console.trace(error);
|
||||
errorLog(error, false);
|
||||
UptimeKumaServer.errorLog(error, false);
|
||||
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
|
||||
});
|
||||
|
@@ -5,13 +5,14 @@ const http = require("http");
|
||||
const { Server } = require("socket.io");
|
||||
const { R } = require("redbean-node");
|
||||
const { log } = require("../src/util");
|
||||
const Database = require("./database");
|
||||
const util = require("util");
|
||||
|
||||
/**
|
||||
* `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
|
||||
* @type {UptimeKumaServer}
|
||||
*/
|
||||
class UptimeKumaServer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {UptimeKumaServer}
|
||||
@@ -83,6 +84,32 @@ class UptimeKumaServer {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write error to log file
|
||||
* @param {any} error The error to write
|
||||
* @param {boolean} outputToConsole Should the error also be output to console?
|
||||
*/
|
||||
static errorLog(error, outputToConsole = true) {
|
||||
const errorLogStream = fs.createWriteStream(Database.dataDir + "/error.log", {
|
||||
flags: "a"
|
||||
});
|
||||
|
||||
errorLogStream.on("error", () => {
|
||||
log.info("", "Cannot write to error.log");
|
||||
});
|
||||
|
||||
if (errorLogStream) {
|
||||
const dateTime = R.isoDateTime();
|
||||
errorLogStream.write(`[${dateTime}] ` + util.format(error) + "\n");
|
||||
|
||||
if (outputToConsole) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
errorLogStream.end();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@@ -7,9 +7,9 @@ const { Resolver } = require("dns");
|
||||
const childProcess = require("child_process");
|
||||
const iconv = require("iconv-lite");
|
||||
const chardet = require("chardet");
|
||||
const fs = require("fs");
|
||||
const nodeJsUtil = require("util");
|
||||
const mqtt = require("mqtt");
|
||||
const chroma = require("chroma-js");
|
||||
const { badgeConstants } = require("./config");
|
||||
|
||||
// From ping-lite
|
||||
exports.WIN = /^win/.test(process.platform);
|
||||
@@ -206,7 +206,7 @@ exports.dnsResolve = function (hostname, resolverServer, rrtype) {
|
||||
/**
|
||||
* Retrieve value of setting based on key
|
||||
* @param {string} key Key of setting to retrieve
|
||||
* @returns {Promise<Object>} Object representation of setting
|
||||
* @returns {Promise<any>} Value
|
||||
*/
|
||||
exports.setting = async function (key) {
|
||||
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
|
||||
@@ -525,28 +525,32 @@ exports.convertToUTF8 = (body) => {
|
||||
return str.toString();
|
||||
};
|
||||
|
||||
let logFile;
|
||||
|
||||
try {
|
||||
logFile = fs.createWriteStream("./data/error.log", {
|
||||
flags: "a"
|
||||
});
|
||||
} catch (_) { }
|
||||
/**
|
||||
* Returns a color code in hex format based on a given percentage:
|
||||
* 0% => hue = 10 => red
|
||||
* 100% => hue = 90 => green
|
||||
*
|
||||
* @param {number} percentage float, 0 to 1
|
||||
* @param {number} maxHue
|
||||
* @param {number} minHue, int
|
||||
* @returns {string}, hex value
|
||||
*/
|
||||
exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
|
||||
const hue = percentage * (maxHue - minHue) + minHue;
|
||||
try {
|
||||
return chroma(`hsl(${hue}, 90%, 40%)`).hex();
|
||||
} catch (err) {
|
||||
return badgeConstants.naColor;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write error to log file
|
||||
* @param {any} error The error to write
|
||||
* @param {boolean} outputToConsole Should the error also be output to console?
|
||||
* Joins and array of string to one string after filtering out empty values
|
||||
*
|
||||
* @param {string[]} parts
|
||||
* @param {string} connector
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.errorLog = (error, outputToConsole = true) => {
|
||||
try {
|
||||
if (logFile) {
|
||||
const dateTime = R.isoDateTime();
|
||||
logFile.write(`[${dateTime}] ` + nodeJsUtil.format(error) + "\n");
|
||||
|
||||
if (outputToConsole) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
} catch (_) { }
|
||||
exports.filterAndJoin = (parts, connector = "") => {
|
||||
return parts.filter((part) => !!part && part !== "").join(connector);
|
||||
};
|
||||
|
@@ -367,7 +367,7 @@ textarea.form-control {
|
||||
.item {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 13px 15px 10px 15px;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
transition: all ease-in-out 0.15s;
|
||||
|
||||
|
@@ -10,7 +10,10 @@ import { sleep } from "../util.ts";
|
||||
export default {
|
||||
|
||||
props: {
|
||||
value: [ String, Number ],
|
||||
value: {
|
||||
type: [ String, Number ],
|
||||
default: 0,
|
||||
},
|
||||
time: {
|
||||
type: Number,
|
||||
default: 0.3,
|
||||
|
@@ -13,7 +13,10 @@ dayjs.extend(relativeTime);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: String,
|
||||
value: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
dateOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="wrap" class="wrap" :style="wrapStyle">
|
||||
<div class="hp-bar-big" :style="barStyle">
|
||||
<div class="hp-bar-big d-flex" :style="barStyle">
|
||||
<div
|
||||
v-for="(beat, index) in shortBeatList"
|
||||
:key="index"
|
||||
@@ -8,7 +8,11 @@
|
||||
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }"
|
||||
:style="beatStyle"
|
||||
:title="getBeatTitle(beat)"
|
||||
/>
|
||||
@mouseenter="toggleActivateSibling"
|
||||
@mouseleave="toggleActivateSibling"
|
||||
>
|
||||
<div class="beat-inner" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -168,6 +172,29 @@ export default {
|
||||
|
||||
getBeatTitle(beat) {
|
||||
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
|
||||
},
|
||||
|
||||
// Toggling the activeSibling class on hover over the current hover item
|
||||
toggleActivateSibling(e) {
|
||||
// Variable definition
|
||||
const element = e.target;
|
||||
const previous = element.previousSibling;
|
||||
const next = element.nextSibling;
|
||||
|
||||
// Return if the hovered element has empty class
|
||||
if (element.classList.contains("empty")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Previous Sibling is heartbar element and doesn't have the empty class
|
||||
if (previous.children && !previous.classList.contains("empty")) {
|
||||
previous.classList.toggle("active-sibling");
|
||||
}
|
||||
|
||||
// Check if Next Sibling is heartbar element and doesn't have the empty class
|
||||
if (next.children && !next.classList.contains("empty")) {
|
||||
next.classList.toggle("active-sibling");
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -184,9 +211,10 @@ export default {
|
||||
|
||||
.hp-bar-big {
|
||||
.beat {
|
||||
display: inline-block;
|
||||
background-color: $primary;
|
||||
border-radius: $border-radius;
|
||||
display: inline-block;
|
||||
transition: all ease 0.6s;
|
||||
|
||||
&.empty {
|
||||
background-color: aliceblue;
|
||||
@@ -200,11 +228,23 @@ export default {
|
||||
background-color: $warning;
|
||||
}
|
||||
|
||||
.beat-inner {
|
||||
border-radius: $border-radius;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
&:not(.empty):hover {
|
||||
transition: all ease-in-out 0.15s;
|
||||
transition: all ease 0.15s;
|
||||
opacity: 0.8;
|
||||
transform: scale(var(--hover-scale));
|
||||
}
|
||||
|
||||
&.active-sibling {
|
||||
transform: scale(1.15);
|
||||
transition: all ease 0.15s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,7 @@ import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { LineChart } from "vue-chart-3";
|
||||
import { useToast } from "vue-toastification";
|
||||
import { DOWN } from "../util.ts";
|
||||
import { DOWN, log } from "../util.ts";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
@@ -217,8 +217,9 @@ export default {
|
||||
watch: {
|
||||
// Update chart data when the selected chart period changes
|
||||
chartPeriodHrs: function (newPeriod) {
|
||||
if (newPeriod === "0") {
|
||||
newPeriod = null;
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (newPeriod == "0") {
|
||||
this.heartbeatList = null;
|
||||
this.$root.storage().removeItem(`chart-period-${this.monitorId}`);
|
||||
} else {
|
||||
@@ -241,7 +242,11 @@ export default {
|
||||
// And mirror latest change to this.heartbeatList
|
||||
this.$watch(() => this.$root.heartbeatList[this.monitorId],
|
||||
(heartbeatList) => {
|
||||
if (this.chartPeriodHrs !== 0) {
|
||||
|
||||
log.debug("ping_chart", `this.chartPeriodHrs type ${typeof this.chartPeriodHrs}, value: ${this.chartPeriodHrs}`);
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (this.chartPeriodHrs != "0") {
|
||||
const newBeat = heartbeatList.at(-1);
|
||||
if (newBeat && dayjs.utc(newBeat.time) > dayjs.utc(this.heartbeatList.at(-1)?.time)) {
|
||||
this.heartbeatList.push(heartbeatList.at(-1));
|
||||
|
@@ -33,19 +33,19 @@
|
||||
<template #item="monitor">
|
||||
<div class="item">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding">
|
||||
<div class="info">
|
||||
<div class="col-9 col-md-8 small-padding d-flex align-items-center flex-wrap">
|
||||
<div class="info d-flex align-items-center gap-3 w-100">
|
||||
<font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" />
|
||||
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" />
|
||||
|
||||
<Uptime :monitor="monitor.element" type="24" :pill="true" />
|
||||
{{ monitor.element.name }}
|
||||
</div>
|
||||
<div v-if="showTags" class="tags">
|
||||
<div v-if="showTags && monitor.element.tags.length > 0" class="tags">
|
||||
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4 d-flex align-items-center">
|
||||
<HeartbeatBar size="small" :monitor-id="monitor.element.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -5,7 +5,10 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
status: Number,
|
||||
status: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@@ -5,8 +5,14 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
monitor: Object,
|
||||
type: String,
|
||||
monitor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
pill: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@@ -8,6 +8,9 @@
|
||||
<a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<label for="title" class="form-label">{{ $t("Title") }}</label>
|
||||
<input id="title" v-model="$parent.notification.title" type="text" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<i18n-t tag="p" keypath="Status:">
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="clicksendsms-login" class="form-label">API Username</label>
|
||||
<div class="form-text">
|
||||
{{ $t("apiCredentials") }}
|
||||
<label for="clicksendsms-login" class="form-label">{{ $t("API Username") }}</label>
|
||||
<i18n-t tag="div" class="form-text" keypath="wayToGetClickSendSMSToken">
|
||||
<a href="http://dashboard.clicksend.com/account/subaccounts" target="_blank">{{ $t("here") }}</a>
|
||||
</div>
|
||||
</i18n-t>
|
||||
<input id="clicksendsms-login" v-model="$parent.notification.clicksendsmsLogin" type="text" class="form-control" required>
|
||||
<label for="clicksendsms-key" class="form-label">API Key</label>
|
||||
<label for="clicksendsms-key" class="form-label">{{ $t("API Key") }}</label>
|
||||
<HiddenInput id="clicksendsms-key" v-model="$parent.notification.clicksendsmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@@ -16,15 +15,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="clicksendsms-to-number" class="form-label">Recipient Number</label>
|
||||
<label for="clicksendsms-to-number" class="form-label">{{ $t("Recipient Number") }}</label>
|
||||
<input id="clicksendsms-to-number" v-model="$parent.notification.clicksendsmsToNumber" type="text" minlength="8" maxlength="14" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="clicksendsms-sender-name" class="form-label">From Name/Number -
|
||||
<a href="https://help.clicksend.com/article/4kgj7krx00-what-is-a-sender-id-or-sender-number" target="_blank">More Info</a>
|
||||
<label for="clicksendsms-sender-name" class="form-label">{{ $t("From Name/Number") }} -
|
||||
<a href="https://help.clicksend.com/article/4kgj7krx00-what-is-a-sender-id-or-sender-number" target="_blank">{{ $t("Read more") }}</a>
|
||||
</label>
|
||||
<input id="clicksendsms-sender-name" v-model="$parent.notification.clicksendsmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
<div class="form-text">Leave blank to use a shared sender number.</div>
|
||||
<div class="form-text">{{ $t("Leave blank to use a shared sender number.") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<b>{{ $t("Basic Settings") }}</b>
|
||||
</i18n-t>
|
||||
<div class="mb-3" style="margin-top: 12px;">
|
||||
<label for="line-user-id" class="form-label">User ID</label>
|
||||
<label for="line-user-id" class="form-label">{{ $t("User ID") }}</label>
|
||||
<input id="line-user-id" v-model="$parent.notification.lineUserID" type="text" class="form-control" required>
|
||||
</div>
|
||||
<i18n-t tag="div" keypath="lineDevConsoleTo" class="form-text">
|
||||
|
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-version" class="form-label">Octopush API Version</label>
|
||||
<label for="octopush-version" class="form-label">{{ $t("Octopush API Version") }}</label>
|
||||
<select id="octopush-version" v-model="$parent.notification.octopushVersion" class="form-select">
|
||||
<option value="2">Octopush (endpoint: api.octopush.com)</option>
|
||||
<option value="1">Legacy Octopush-DM (endpoint: www.octopush-dm.com)</option>
|
||||
<option value="2">{{ $t("octopush") }} ({{ $t("endpoint") }}: api.octopush.com)</option>
|
||||
<option value="1">{{ $t("Legacy Octopush-DM") }} ({{ $t("endpoint") }}: www.octopush-dm.com)</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
{{ $t("octopushLegacyHint") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-key" class="form-label">API KEY</label>
|
||||
<label for="octopush-key" class="form-label">{{ $t("octopushAPIKey") }}</label>
|
||||
<HiddenInput id="octopush-key" v-model="$parent.notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="octopush-login" class="form-label">API LOGIN</label>
|
||||
<label for="octopush-login" class="form-label">{{ $t("octopushLogin") }}</label>
|
||||
<input id="octopush-login" v-model="$parent.notification.octopushLogin" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="promosms-login" class="form-label">API LOGIN</label>
|
||||
<label for="promosms-login" class="form-label">{{ $("promosmsLogin") }}</label>
|
||||
<input id="promosms-login" v-model="$parent.notification.promosmsLogin" type="text" class="form-control" required>
|
||||
<label for="promosms-key" class="form-label">API PASSWORD</label>
|
||||
<label for="promosms-key" class="form-label">{{ $("promosmsPassword") }}</label>
|
||||
<HiddenInput id="promosms-key" v-model="$parent.notification.promosmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
@@ -18,28 +18,29 @@
|
||||
</select>
|
||||
<label for="pushover-sound" class="form-label">{{ $t("Notification Sound") }}</label>
|
||||
<select id="pushover-sound" v-model="$parent.notification.pushoversounds" class="form-select">
|
||||
<option>pushover</option>
|
||||
<option>bike</option>
|
||||
<option>bugle</option>
|
||||
<option>cashregister</option>
|
||||
<option>classical</option>
|
||||
<option>cosmic</option>
|
||||
<option>falling</option>
|
||||
<option>gamelan</option>
|
||||
<option>incoming</option>
|
||||
<option>intermission</option>
|
||||
<option>mechanical</option>
|
||||
<option>pianobar</option>
|
||||
<option>siren</option>
|
||||
<option>spacealarm</option>
|
||||
<option>tugboat</option>
|
||||
<option>alien</option>
|
||||
<option>climb</option>
|
||||
<option>persistent</option>
|
||||
<option>echo</option>
|
||||
<option>updown</option>
|
||||
<option>vibrate</option>
|
||||
<option>none</option>
|
||||
<option value="pushover">{{ $t("pushoversounds pushover") }}</option>
|
||||
<option value="bike">{{ $t("pushoversounds bike") }}</option>
|
||||
<option value="bugle">{{ $t("pushoversounds bugle") }}</option>
|
||||
<option value="cashregister">{{ $t("pushoversounds cashregister") }}</option>
|
||||
<option value="classical">{{ $t("pushoversounds classical") }}</option>
|
||||
<option value="cosmic">{{ $t("pushoversounds cosmic") }}</option>
|
||||
<option value="falling">{{ $t("pushoversounds falling") }}</option>
|
||||
<option value="gamelan">{{ $t("pushoversounds gamelan") }}</option>
|
||||
<option value="incoming">{{ $t("pushoversounds incoming") }}</option>
|
||||
<option value="intermission">{{ $t("pushoversounds intermission") }}</option>
|
||||
<option value="magic">{{ $t("pushoversounds magic") }}</option>
|
||||
<option value="mechanical">{{ $t("pushoversounds mechanical") }}</option>
|
||||
<option value="pianobar">{{ $t("pushoversounds pianobar") }}</option>
|
||||
<option value="siren">{{ $t("pushoversounds siren") }}</option>
|
||||
<option value="spacealarm">{{ $t("pushoversounds spacealarm") }}</option>
|
||||
<option value="tugboat">{{ $t("pushoversounds tugboat") }}</option>
|
||||
<option value="alien">{{ $t("pushoversounds alien") }}</option>
|
||||
<option value="climb">{{ $t("pushoversounds climb") }}</option>
|
||||
<option value="persistent">{{ $t("pushoversounds persistent") }}</option>
|
||||
<option value="echo">{{ $t("pushoversounds echo") }}</option>
|
||||
<option value="updown">{{ $t("pushoversounds updown") }}</option>
|
||||
<option value="vibrate">{{ $t("pushoversounds vibrate") }}</option>
|
||||
<option value="none">{{ $t("pushoversounds none") }}</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pushy-app-token" class="form-label">API_KEY</label>
|
||||
<label for="pushy-app-token" class="form-label">{{ $t("pushyAPIKey") }}</label>
|
||||
<HiddenInput id="pushy-app-token" v-model="$parent.notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pushy-user-key" class="form-label">USER_TOKEN</label>
|
||||
<label for="pushy-user-key" class="form-label">{{ $t("pushyToken") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="push-api-key" class="form-label">API_KEY</label>
|
||||
<label for="push-api-key" class="form-label">{{ $t("API Key") }}</label>
|
||||
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
|
@@ -9,11 +9,11 @@
|
||||
|
||||
<div class="mt-1">
|
||||
<div class="form-check">
|
||||
<label><input v-model="settings.checkUpdate" type="checkbox" @change="saveSettings()" /> Show update if available</label>
|
||||
<label><input v-model="settings.checkUpdate" type="checkbox" @change="saveSettings()" /> {{ $t("Show update if available") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label><input v-model="settings.checkBeta" type="checkbox" :disabled="!settings.checkUpdate" @change="saveSettings()" /> Also check beta release</label>
|
||||
<label><input v-model="settings.checkBeta" type="checkbox" :disabled="!settings.checkUpdate" @change="saveSettings()" /> {{ $t("Also check beta release") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -12,15 +12,15 @@ export default {
|
||||
keywordDescription: "Търси ключова дума в чист html или JSON отговор - чувствителна е към регистъра",
|
||||
pauseDashboardHome: "Пауза",
|
||||
deleteMonitorMsg: "Наистина ли желаете да изтриете този монитор?",
|
||||
deleteNotificationMsg: "Наистина ли желаете да изтриете това известяване за всички монитори?",
|
||||
deleteNotificationMsg: "Наистина ли желаете да изтриете това известие за всички монитори?",
|
||||
resolverserverDescription: "Cloudflare е сървърът по подразбиране, но можете да го промените по всяко време.",
|
||||
rrtypeDescription: "Изберете ресурсния запис, който желаете да наблюдавате",
|
||||
pauseMonitorMsg: "Наистина ли желаете да поставите в режим пауза?",
|
||||
enableDefaultNotificationDescription: "За всеки нов монитор това известяване ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
|
||||
enableDefaultNotificationDescription: "За всеки нов монитор това известие ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
|
||||
clearEventsMsg: "Наистина ли желаете да изтриете всички събития за този монитор?",
|
||||
clearHeartbeatsMsg: "Наистина ли желаете да изтриете всички записи за честотни проверки на този монитор?",
|
||||
confirmClearStatisticsMsg: "Наистина ли желаете да изтриете всички статистически данни?",
|
||||
importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известяване със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известяване.",
|
||||
importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известие със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известие.",
|
||||
confirmImportMsg: "Сигурни ли сте, че желаете импортирането на архива? Моля, уверете се, че сте избрали правилната опция за импортиране.",
|
||||
twoFAVerifyLabel: "Моля, въведете вашия токен код, за да проверите дали 2FA работи",
|
||||
tokenValidSettingsMsg: "Токен кодът е валиден! Вече можете да запазите настройките за 2FA.",
|
||||
@@ -76,9 +76,9 @@ export default {
|
||||
"Max. Redirects": "Макс. брой пренасочвания",
|
||||
"Accepted Status Codes": "Допустими статус кодове",
|
||||
Save: "Запази",
|
||||
Notifications: "Известявания",
|
||||
Notifications: "Известия",
|
||||
"Not available, please setup.": "Не са налични. Моля, настройте.",
|
||||
"Setup Notification": "Настрой известяване",
|
||||
"Setup Notification": "Настрой известие",
|
||||
Light: "Светла",
|
||||
Dark: "Тъмна",
|
||||
Auto: "Автоматично",
|
||||
@@ -109,7 +109,7 @@ export default {
|
||||
Login: "Вход",
|
||||
"No Monitors, please": "Все още няма монитори. Моля, добавете поне ",
|
||||
"add one": "един.",
|
||||
"Notification Type": "Тип известяване",
|
||||
"Notification Type": "Тип известие",
|
||||
Email: "Имейл",
|
||||
Test: "Тест",
|
||||
"Certificate Info": "Информация за сертификат",
|
||||
@@ -131,9 +131,9 @@ export default {
|
||||
Events: "Събития",
|
||||
Heartbeats: "Проверки",
|
||||
"Auto Get": "Авт. попълване",
|
||||
backupDescription: "Можете да архивирате всички монитори и всички известявания в JSON файл.",
|
||||
backupDescription: "Можете да архивирате всички монитори и всички известия в JSON файл.",
|
||||
backupDescription2: "PS: Имайте предвид, че данните за история и събития няма да бъдат включени.",
|
||||
backupDescription3: "Чувствителни данни, като токен кодове за известяване, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.",
|
||||
backupDescription3: "Чувствителни данни, като токен кодове за известия, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.",
|
||||
alertNoFile: "Моля, изберете файл за импортиране.",
|
||||
alertWrongFileType: "Моля, изберете JSON файл.",
|
||||
"Clear all statistics": "Изтрий цялата статистика",
|
||||
@@ -202,7 +202,7 @@ export default {
|
||||
"Push URL": "Генериран Push URL адрес",
|
||||
needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди",
|
||||
pushOptionalParams: "Допълнителни, но не задължителни параметри: {0}",
|
||||
defaultNotificationName: "Моето {notification} известяване ({number})",
|
||||
defaultNotificationName: "Моето {notification} известие ({number})",
|
||||
here: "тук",
|
||||
Required: "Задължително поле",
|
||||
"Bot Token": "Бот токен",
|
||||
@@ -252,7 +252,7 @@ export default {
|
||||
"Notification Sound": "Звуков сигнал",
|
||||
"More info on:": "Повече информация на: {0}",
|
||||
pushoverDesc1: "Приоритет Спешно (2) по подразбиране изчаква 30 секунди между повторните опити и изтича след 1 час.",
|
||||
pushoverDesc2: "Ако желаете да изпратите известявания до различни устройства, попълнете полето Устройство.",
|
||||
pushoverDesc2: "Ако желаете да изпратите известия до различни устройства, попълнете полето Устройство.",
|
||||
"SMS Type": "SMS тип",
|
||||
octopushTypePremium: "Премиум (Бърз - препоръчителен в случай на тревога)",
|
||||
octopushTypeLowCost: "Евтин (Бавен - понякога бива блокиран от оператора)",
|
||||
@@ -275,7 +275,7 @@ export default {
|
||||
lineDevConsoleTo: "Line - Конзола за разработчици - {0}",
|
||||
"Basic Settings": "Основни настройки",
|
||||
"User ID": "Потребител ID",
|
||||
"Messaging API": "API за известяване",
|
||||
"Messaging API": "API за съобщаване",
|
||||
wayToGetLineChannelToken: "Необходимо е първо да посетите {0}, за да създадете (Messaging API) за доставчик и канал, след което може да вземете токен кода за канал и потребителско ID от споменатите по-горе елементи на менюто.",
|
||||
"Icon URL": "URL адрес за иконка",
|
||||
aboutIconURL: "Може да предоставите линк към картинка в поле \"URL Адрес за иконка\" за да отмените картинката на профила по подразбиране. Няма да се използва, ако вече сте настроили емотикон.",
|
||||
@@ -291,7 +291,7 @@ export default {
|
||||
matrixHomeserverURL: "Сървър URL адрес (започва с http(s):// и порт по желание)",
|
||||
"Internal Room Id": "ID на вътрешна стая",
|
||||
matrixDesc1: "Може да намерите \"ID на вътрешна стая\" в разширените настройки на стаята във вашия Matrix клиент. Примерен изглед: !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известяванията. Токен код за достъп ще получите изпълнявайки {0}",
|
||||
matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известията. Токен код за достъп ще получите изпълнявайки {0}",
|
||||
Method: "Метод",
|
||||
Body: "Съобщение",
|
||||
Headers: "Хедъри",
|
||||
@@ -449,7 +449,7 @@ export default {
|
||||
Customize: "Персонализирай",
|
||||
"Custom Footer": "Персонализиран долен колонтитул",
|
||||
"Custom CSS": "Потребителски CSS",
|
||||
"Domain Name Expiry Notification": "Известяване при изтичащ домейн",
|
||||
"Domain Name Expiry Notification": "Известие при изтичащ домейн",
|
||||
Proxy: "Прокси",
|
||||
"Date Created": "Дата на създаване",
|
||||
onebotHttpAddress: "OneBot HTTP адрес",
|
||||
|
@@ -464,4 +464,55 @@ export default {
|
||||
"Domain Names": "Domain Names",
|
||||
signedInDisp: "Signed in as {0}",
|
||||
signedInDispDisabled: "Auth Disabled.",
|
||||
"Certificate Expiry Notification": "Certificate Expiry Notification",
|
||||
"API Username": "API Username",
|
||||
"API Key": "API Key",
|
||||
"Recipient Number": "Recipient Number",
|
||||
"From Name/Number": "From Name/Number",
|
||||
"Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
|
||||
"Octopush API Version": "Octopush API Version",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
"endpoint": "endpoint",
|
||||
octopushAPIKey: "\"API key\" from HTTP API credentials in control panel",
|
||||
octopushLogin: "\"Login\" from HTTP API credentials in control panel",
|
||||
promosmsLogin: "API Login Name",
|
||||
promosmsPassword: "API Password",
|
||||
"pushoversounds pushover": "Pushover (default)",
|
||||
"pushoversounds bike": "Bike",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds cashregister": "Cash Register",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds magic": "Magic",
|
||||
"pushoversounds mechanical": "Mechanical",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Siren",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm (long)",
|
||||
"pushoversounds climb": "Climb (long)",
|
||||
"pushoversounds persistent": "Persistent (long)",
|
||||
"pushoversounds echo": "Pushover Echo (long)",
|
||||
"pushoversounds updown": "Up Down (long)",
|
||||
"pushoversounds vibrate": "Vibrate Only",
|
||||
"pushoversounds none": "None (silent)",
|
||||
pushyAPIKey: "Secret API Key",
|
||||
pushyToken: "Device token",
|
||||
"Show update if available": "Show update if available",
|
||||
"Also check beta release": "Also check beta release",
|
||||
"Using a Reverse Proxy?": "Using a Reverse Proxy?",
|
||||
"Check how to config it for WebSocket": "Check how to config it for WebSocket",
|
||||
"Steam Game Server": "Steam Game Server",
|
||||
"Most likely causes:": "Most likely causes:",
|
||||
"The resource is no longer available.": "The resource is no longer available.",
|
||||
"There might be a typing error in the address.": "There might be a typing error in the address.",
|
||||
"What you can try:": "What you can try:",
|
||||
"Retype the address.": "Retype the address.",
|
||||
"Go back to the previous page.": "Go back to the previous page.",
|
||||
"Coming Soon": "Coming Soon",
|
||||
wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .",
|
||||
};
|
||||
|
@@ -88,7 +88,7 @@ export default {
|
||||
Dark: "黑暗",
|
||||
Auto: "自动",
|
||||
"Theme - Heartbeat Bar": "主题 - 心跳栏",
|
||||
Normal: "正常", // 此处还供 Gorush 的通知优先级功能使用,不应翻译为“正常显示”
|
||||
Normal: "正常",
|
||||
Bottom: "靠下",
|
||||
None: "不显示",
|
||||
Timezone: "时区",
|
||||
@@ -398,11 +398,9 @@ export default {
|
||||
Invalid: "无效",
|
||||
AccessKeyId: "AccessKey ID",
|
||||
SecretAccessKey: "AccessKey Secret",
|
||||
/* 以下为阿里云短信服务 API Dysms#SendSms 的参数 */
|
||||
PhoneNumbers: "PhoneNumbers",
|
||||
TemplateCode: "TemplateCode",
|
||||
SignName: "SignName",
|
||||
/* 以上为阿里云短信服务 API Dysms#SendSms 的参数 */
|
||||
"Bark Endpoint": "Bark 接入点",
|
||||
"Device Token": "Apple Device Token",
|
||||
Platform: "平台",
|
||||
@@ -441,7 +439,7 @@ export default {
|
||||
"No Proxy": "无代理",
|
||||
"HTTP Basic Auth": "HTTP 基础身份验证",
|
||||
"New Status Page": "新的状态页",
|
||||
"Page Not Found": "状态页未找到",
|
||||
"Page Not Found": "未找到该页面",
|
||||
"Reverse Proxy": "反向代理",
|
||||
"Subject:": "颁发给:",
|
||||
"Valid To:": "有效期至:",
|
||||
@@ -469,4 +467,57 @@ export default {
|
||||
"Footer Text": "底部自定义文本",
|
||||
"Show Powered By": "显示 Powered By",
|
||||
"Domain Names": "域名",
|
||||
"Certificate Expiry Notification": "证书到期时通知",
|
||||
"API Username": "API 凭证 Username",
|
||||
"API Key": "API 凭证 Key",
|
||||
"Recipient Number": "收件人手机号码",
|
||||
"From Name/Number": "发件人名称/手机号码",
|
||||
"Leave blank to use a shared sender number.": "留空以使用平台共享的发件人手机号码",
|
||||
"Octopush API Version": "Octopush API 版本",
|
||||
"Legacy Octopush-DM": "旧版本 Octopush-DM",
|
||||
endpoint: "接入点",
|
||||
octopushAPIKey: "控制台 HTTP API credentials 里的 \"API key\"",
|
||||
octopushLogin: "控制台 HTTP API credentials 里的 \"Login\"",
|
||||
promosmsLogin: "API 登录名",
|
||||
promosmsPassword: "API 密码",
|
||||
"pushoversounds pushover": "Pushover(默认)",
|
||||
"pushoversounds bike": "Bike",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds cashregister": "Cash Register",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds magic": "Magic",
|
||||
"pushoversounds mechanical": "Mechanical",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Siren",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm(长铃声)",
|
||||
"pushoversounds climb": "Climb(长铃声)",
|
||||
"pushoversounds persistent": "Persistent(长铃声)",
|
||||
"pushoversounds echo": "Pushover Echo(长铃声)",
|
||||
"pushoversounds updown": "Up Down(长铃声)",
|
||||
"pushoversounds vibrate": "仅震动",
|
||||
"pushoversounds none": "无(禁音)",
|
||||
pushyAPIKey: "API 密钥",
|
||||
pushyToken: "设备 Token",
|
||||
"Show update if available": "有更新时通知",
|
||||
"Also check beta release": "一并检查 Beta 版更新",
|
||||
"Using a Reverse Proxy?": "正在使用反向代理?",
|
||||
"Check how to config it for WebSocket": "查看如何将反向代理与 WebSocket 一起使用",
|
||||
"Steam Game Server": "Steam 游戏服务器",
|
||||
"Most likely causes:": "最可能的原因:",
|
||||
"The resource is no longer available.": "您所请求的资源已不再可用;",
|
||||
"There might be a typing error in the address.": "您输入的地址可能有误。",
|
||||
"What you can try:": "您可以尝试以下操作:",
|
||||
"Retype the address.": "重新输入地址;",
|
||||
"Go back to the previous page.": "返回到上一页面。",
|
||||
"Coming Soon": "即将推出",
|
||||
wayToGetClickSendSMSToken: "您可以从 {0} 获取 API 凭证 Username 和 凭证 Key。",
|
||||
signedInDisp: "当前用户: {0}",
|
||||
signedInDispDisabled: "已禁用身份验证",
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="container-fluid">
|
||||
{{ $root.connectionErrorMsg }}
|
||||
<div v-if="$root.showReverseProxyGuide">
|
||||
Using a Reverse Proxy? <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">Check how to config it for WebSocket</a>
|
||||
{{ $t("Using a Reverse Proxy?") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">{{ $t("Check how to config it for WebSocket") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +33,7 @@
|
||||
</li>
|
||||
<li v-if="$root.loggedIn" class="nav-item">
|
||||
<div class="dropdown dropdown-profile-pic">
|
||||
<div type="button" class="nav-link" data-bs-toggle="dropdown">
|
||||
<div class="nav-link" data-bs-toggle="dropdown">
|
||||
<div class="profile-pic">{{ $root.usernameFirstChar }}</div>
|
||||
<font-awesome-icon icon="angle-down" />
|
||||
</div>
|
||||
@@ -71,7 +71,7 @@
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<router-view v-if="$root.loggedIn || forceShowContent" />
|
||||
<router-view v-if="$root.loggedIn" />
|
||||
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
||||
</main>
|
||||
|
||||
|
@@ -30,7 +30,7 @@
|
||||
Push
|
||||
</option>
|
||||
<option value="steam">
|
||||
Steam Game Server
|
||||
{{ $t("Steam Game Server") }}
|
||||
</option>
|
||||
<option value="mqtt">
|
||||
MQTT
|
||||
@@ -421,7 +421,7 @@ export default {
|
||||
},
|
||||
|
||||
pushURL() {
|
||||
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?status=true&msg=OK&ping=";
|
||||
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?status=up&msg=OK&ping=";
|
||||
},
|
||||
|
||||
bodyPlaceholder() {
|
||||
|
@@ -22,16 +22,16 @@
|
||||
</div>
|
||||
|
||||
<div class="guide">
|
||||
Most likely causes:
|
||||
{{ $t("Most likely causes:") }}
|
||||
<ul>
|
||||
<li>The resource is no longer available.</li>
|
||||
<li>There might be a typing error in the address.</li>
|
||||
<li>{{ $t("The resource is no longer available.") }}</li>
|
||||
<li>{{ $t("There might be a typing error in the address.") }}</li>
|
||||
</ul>
|
||||
|
||||
What you can try:<br />
|
||||
{{ $t("What you can try:") }}<br />
|
||||
<ul>
|
||||
<li>Retype the address.</li>
|
||||
<li><a href="#" class="go-back" @click="goBack()">Go back to the previous page.</a></li>
|
||||
<li>{{ $t("Retype the address.") }}</li>
|
||||
<li><a href="#" class="go-back" @click="goBack()">{{ $t("Go back to the previous page.") }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="my-3">
|
||||
<label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label>
|
||||
<label for="password" class="form-label">{{ $t("Password") }} <sup>{{ $t("Coming Soon") }}</sup></label>
|
||||
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
|
||||
</div>
|
||||
|
||||
@@ -104,15 +104,16 @@
|
||||
|
||||
<!-- Uploader -->
|
||||
<!-- url="/api/status-page/upload-logo" -->
|
||||
<ImageCropUpload v-model="showImageCropUpload"
|
||||
field="img"
|
||||
:width="128"
|
||||
:height="128"
|
||||
:langType="$i18n.locale"
|
||||
img-format="png"
|
||||
:noCircle="true"
|
||||
:noSquare="false"
|
||||
@crop-success="cropSuccess"
|
||||
<ImageCropUpload
|
||||
v-model="showImageCropUpload"
|
||||
field="img"
|
||||
:width="128"
|
||||
:height="128"
|
||||
:langType="$i18n.locale"
|
||||
img-format="png"
|
||||
:noCircle="true"
|
||||
:noSquare="false"
|
||||
@crop-success="cropSuccess"
|
||||
/>
|
||||
|
||||
<!-- Title -->
|
||||
@@ -281,22 +282,21 @@
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||
import ImageCropUpload from "vue-image-crop-upload";
|
||||
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_PARTIAL_DOWN, UP } from "../util.ts";
|
||||
import { useToast } from "vue-toastification";
|
||||
import dayjs from "dayjs";
|
||||
import Favico from "favico.js";
|
||||
import { getResBaseURL } from "../util-frontend";
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
// import Prism Editor
|
||||
import { PrismEditor } from "vue-prism-editor";
|
||||
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
|
||||
|
||||
// import highlighting library (you can use any library you want just return html string)
|
||||
import { highlight, languages } from "prismjs/components/prism-core";
|
||||
import "prismjs/components/prism-css";
|
||||
import "prismjs/themes/prism-tomorrow.css"; // import syntax highlighting styles
|
||||
import ImageCropUpload from "vue-image-crop-upload";
|
||||
// import Prism Editor
|
||||
import { PrismEditor } from "vue-prism-editor";
|
||||
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
|
||||
import { useToast } from "vue-toastification";
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||
import { getResBaseURL } from "../util-frontend";
|
||||
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_PARTIAL_DOWN, UP } from "../util.ts";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
|
@@ -102,7 +102,7 @@ class Logger {
|
||||
}
|
||||
else if (level === "DEBUG") {
|
||||
if (exports.isDev) {
|
||||
console.debug(formattedMessage);
|
||||
console.log(formattedMessage);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@@ -113,7 +113,7 @@ class Logger {
|
||||
console.error(formattedMessage);
|
||||
} else if (level === "DEBUG") {
|
||||
if (isDev) {
|
||||
console.debug(formattedMessage);
|
||||
console.log(formattedMessage);
|
||||
}
|
||||
} else {
|
||||
console.log(formattedMessage);
|
||||
|
@@ -1,5 +1,9 @@
|
||||
const { genSecret } = require("../src/util");
|
||||
const { genSecret, DOWN } = require("../src/util");
|
||||
const utilServerRewire = require("../server/util-server");
|
||||
const Discord = require("../server/notification-providers/discord");
|
||||
const axios = require("axios");
|
||||
|
||||
jest.mock("axios");
|
||||
|
||||
describe("Test parseCertificateInfo", () => {
|
||||
it("should handle undefined", async () => {
|
||||
@@ -164,3 +168,86 @@ describe("Test reset-password", () => {
|
||||
}, 120000);
|
||||
});
|
||||
|
||||
describe("Test Discord Notification Provider", () => {
|
||||
const sendNotification = async (hostname, port, type) => {
|
||||
const discordProvider = new Discord();
|
||||
|
||||
axios.post.mockResolvedValue({});
|
||||
|
||||
await discordProvider.send(
|
||||
{
|
||||
discordUsername: "Uptime Kuma",
|
||||
discordWebhookUrl: "https://discord.com",
|
||||
},
|
||||
"test message",
|
||||
{
|
||||
type,
|
||||
hostname,
|
||||
port,
|
||||
},
|
||||
{
|
||||
status: DOWN,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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 () => {
|
||||
const hostname = "discord.com";
|
||||
await sendNotification(hostname, null, "ping");
|
||||
|
||||
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
|
||||
hostname
|
||||
);
|
||||
});
|
||||
|
||||
it("should send hostname for port monitors", async () => {
|
||||
const hostname = "discord.com";
|
||||
const port = 1337;
|
||||
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", () => {
|
||||
it("should join and array of strings to one string", () => {
|
||||
const result = utilServerRewire.filterAndJoin(["one", "two", "three"]);
|
||||
expect(result).toBe("onetwothree");
|
||||
});
|
||||
|
||||
it("should join strings using a given connector", () => {
|
||||
const result = utilServerRewire.filterAndJoin(["one", "two", "three"], "-");
|
||||
expect(result).toBe("one-two-three");
|
||||
});
|
||||
|
||||
it("should filter null, undefined and empty strings before joining", () => {
|
||||
const result = utilServerRewire.filterAndJoin([undefined, "", "three"], "--");
|
||||
expect(result).toBe("three");
|
||||
});
|
||||
|
||||
it("should return an empty string if all parts are filtered out", () => {
|
||||
const result = utilServerRewire.filterAndJoin([undefined, "", ""], "--");
|
||||
expect(result).toBe("");
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user