Compare commits

...

133 Commits

Author SHA1 Message Date
Louis Lam
d5da5af174 Update to 1.17.0 2022-06-22 20:13:39 +08:00
Louis Lam
c36d9a4b8b Fix port data type #1802 2022-06-21 22:33:09 +08:00
Louis Lam
a7063b8aca Fix logo height in the status page list 2022-06-21 15:46:22 +08:00
Louis Lam
0a8046c98e Merge remote-tracking branch 'origin/master' 2022-06-21 15:12:37 +08:00
Louis Lam
7ba717ee55 Fix Lunasea do not handle general message correctly #1790 2022-06-21 15:12:24 +08:00
Louis Lam
ea400ac35f Merge pull request #1795 from MrEddX/bulgarian
Update bg-BG.js
2022-06-20 21:50:00 +08:00
Louis Lam
15db2c060d Merge pull request #1791 from Saibamen/patch-1
Update README for i18n
2022-06-20 21:49:42 +08:00
MrEddX
89717495dc Update bg-BG.js
Added and translated new fields
2022-06-20 11:52:12 +03:00
Adam Stachowicz
7b710af12c Update README for i18n
Related: https://github.com/louislam/uptime-kuma/pull/1777
2022-06-19 21:17:03 +02:00
Louis Lam
5b278ca500 Add a description that certification expiry have to be assigned to a monitor 2022-06-19 20:57:52 +08:00
Louis Lam
b97019eea8 Fix cloudflared cannot be stopped in No Auth mode due to password checking 2022-06-18 19:06:03 +08:00
Louis Lam
d65abe5b8c Merge pull request #1777 from MarcHagen/change/move_translations_to_i18n
[change] Move i18n from Security to locale files
2022-06-18 17:17:06 +08:00
Louis Lam
bcd616a4d0 Fix names in CONTRIBUTING.md 2022-06-17 15:07:55 +08:00
Louis Lam
7533041696 Merge pull request #1784 from AnnAngela/master
Update i18n for en & zh-CN
2022-06-17 13:30:47 +08:00
Louis Lam
7b0deb5e20 Merge pull request #1785 from MrEddX/bulgarian
Update bg-BG.js
2022-06-17 13:30:18 +08:00
MrEddX
b4a4171178 Update bg-BG.js
Translation fixes.
2022-06-16 21:08:17 +03:00
AnnAngela-work
012be23509 Update i18n 2022-06-16 21:20:12 +08:00
Louis Lam
dd09351c8e Update to 1.17.0-beta.1 2022-06-16 19:34:47 +08:00
Louis Lam
ffad990ca4 Merge pull request #1773 from christopherpickering/patch-3
[beta] updated translation for "basic" auth
2022-06-16 14:37:05 +08:00
Marc Hagen
f7afe121e3 [change] Move i18n to locale files 2022-06-15 19:41:21 +02:00
Christopher Pickering
42848bcd2e updated translations 2022-06-15 09:00:47 -05:00
Louis Lam
a3b94aa532 Merge pull request #1550 from Computroniks/jsdoc-for-src
JSDoc for src/*/*
2022-06-15 19:29:51 +08:00
Louis Lam
fdbdf83a0d Fix data type of notification.isDefault and notification.active (#1765) 2022-06-15 19:11:52 +08:00
Louis Lam
81d5360520 Merge remote-tracking branch 'origin/master' 2022-06-15 19:03:22 +08:00
Louis Lam
8f1e193de3 [vite] Change legacy browse target to since 2015 2022-06-15 19:02:55 +08:00
Louis Lam
da91317760 Merge pull request #1772 from chakflying/fix/mobile-monitor-list
Fix: Fix monitor list layout on mobile
2022-06-15 15:50:04 +08:00
Louis Lam
bef0febede Merge pull request #1768 from christopherpickering/patch-1
[beta] prevent null workstation #'s from passing..
2022-06-15 15:33:03 +08:00
Louis Lam
7d63b700e1 Merge pull request #1767 from christopherpickering/patch-2
[beta] workstation field should be text, not password
2022-06-15 15:31:24 +08:00
Louis Lam
0223f86a2a Merge remote-tracking branch 'origin/master' 2022-06-15 15:18:29 +08:00
Louis Lam
c170b1edd0 Better optgroup text color 2022-06-15 15:18:14 +08:00
Louis Lam
5943514a92 Merge pull request #1771 from christopherpickering/master
[beta] added default value for sql server connection string
2022-06-15 14:08:48 +08:00
Nelson Chan
62acd2edb1 Fix: misc. layout fix on mobile 2022-06-14 22:43:44 +08:00
Christopher Pickering
483cbfb636 added default value for sql server connection string 2022-06-14 09:00:23 -05:00
Christopher Pickering
660005b143 cleaned up code 2022-06-14 08:49:36 -05:00
Christopher Pickering
98f3c126e5 passed lint 2022-06-14 07:58:35 -05:00
sur.la.route
6995a29980 workstation field should be text, not password 2022-06-14 07:45:04 -05:00
sur.la.route
cf2ca71dee prevent null workstation #'s from passing..
to axios-ntlm
2022-06-14 07:42:53 -05:00
Louis Lam
0bd1c42080 Merge pull request #1763 from chakflying/fix/cert-exp-setting-default
Fix: Fix missing certificate exp. notif. default
2022-06-14 17:27:45 +08:00
Louis Lam
9b21b86e70 Merge pull request #1762 from kaysond/master
Fix upside down push monitors
2022-06-14 17:25:32 +08:00
Nelson Chan
f723930d11 Fix: Unify design with Security page 2022-06-14 15:04:46 +08:00
Nelson Chan
e425e408a2 Fix: Fix missing default settings 2022-06-14 15:04:14 +08:00
Aram Akhavan
c690d1c3a1 fix timeout bypass for upside down push monitor 2022-06-13 22:05:58 -07:00
Louis Lam
8abbc9fd15 Update to 1.17.0-beta.0 2022-06-14 11:00:55 +08:00
Louis Lam
af7c905b44 Merge pull request #1759 from MrEddX/bulgarian
Bulgarian
2022-06-14 10:57:14 +08:00
Louis Lam
0e8f6d2f85 Merge pull request #1639 from christopherpickering/ntml-auth
Add NTML Auth Option for HTTP
2022-06-14 10:56:55 +08:00
Louis Lam
d16be6fb7d Fix and use null as authMethod None instead of empty string 2022-06-14 10:49:30 +08:00
Louis Lam
f25ca96308 Update package-lock.json 2022-06-14 10:37:15 +08:00
Louis Lam
6682839ec8 Merge remote-tracking branch 'origin/master' into ntml-auth
# Conflicts:
#	package-lock.json
#	package.json
#	server/database.js
#	server/model/monitor.js
#	server/server.js
#	server/util-server.js
2022-06-14 10:36:29 +08:00
Louis Lam
817f6db4fd Remove SQL Server code (which in another pr already) 2022-06-14 10:32:38 +08:00
Louis Lam
dc2302244f Merge pull request #1754 from SIMULATAN/patch-1
Remove template comment in SECURITY.md
2022-06-14 10:22:08 +08:00
MrEddX
7a27d3752a Merge branch 'louislam:master' into bulgarian 2022-06-13 22:36:00 +03:00
MrEddX
f0e8f34aeb Update bg-BG.js
Added new fields
2022-06-13 22:32:55 +03:00
Louis Lam
69273a6c41 Merge remote-tracking branch 'origin/master' 2022-06-13 21:16:03 +08:00
Louis Lam
6424fe77ab Change successful log from info to debug in order to avoid large log and less disk usage 2022-06-13 21:15:47 +08:00
Louis Lam
fa60672cce Merge pull request #1728 from tamasmagyar/test/simplify-discord-unit-tests
test: simplified discord unit tests
2022-06-13 21:02:12 +08:00
Louis Lam
6e43ef1dd3 Merge remote-tracking branch 'origin/master' into feat/cert-exp-settings
# Conflicts:
#	server/model/monitor.js
#	src/languages/en.js
2022-06-13 20:56:14 +08:00
Louis Lam
f4f2b8ddb8 Try to fix #1658 2022-06-13 19:24:05 +08:00
Louis Lam
436bc13aeb Merge pull request #1598 from gregdev/feature/axios-cached-dns-resolve
add axios cached dns resolve to monitor
2022-06-13 18:58:16 +08:00
Louis Lam
b72a279361 Update package-lock.json 2022-06-13 18:54:02 +08:00
Louis Lam
a28ef56553 Merge remote-tracking branch 'gregdev/feature/axios-cached-dns-resolve' into feature/axios-cached-dns-resolve
# Conflicts:
#	package-lock.json
#	package.json
2022-06-13 18:53:19 +08:00
Louis Lam
7f432bd916 Remove axios-cached-dns-resolve-test 2022-06-13 18:52:08 +08:00
Louis Lam
f570d41142 Merge remote-tracking branch 'origin/master' into feature/axios-cached-dns-resolve
# Conflicts:
#	package-lock.json
#	package.json
2022-06-13 18:50:43 +08:00
Louis Lam
d4485fe62f Make the monitor type list a bit clear 2022-06-13 18:14:47 +08:00
Louis Lam
e1681ce370 Merge pull request #1636 from christopherpickering/master
Add SQLServer Monitor
2022-06-13 18:03:56 +08:00
Jakob
69d6633e6d Remove template comment 2022-06-13 11:34:36 +02:00
Louis Lam
04e22f17a9 Merge remote-tracking branch 'origin/master' into christopherpickering_master
# Conflicts:
#	package-lock.json
#	src/languages/en.js
2022-06-11 20:59:58 +08:00
Louis Lam
11243a6ca1 Merge pull request #1222 from NETivism/issue-1201
Show some pure text body in notification when keyword not found
2022-06-09 19:33:10 +08:00
Louis Lam
87428231ad Merge pull request #1727 from chakflying/patch-2
Fix: Fix error when status page desc. is null
2022-06-07 16:45:28 +08:00
tamasmagyar
a68d945cdc simplified backend unit tests 2022-06-07 09:10:50 +02:00
Nelson Chan
2c0180f323 Fix: Fix error when status page desc. is null 2022-06-07 14:57:23 +08:00
Louis Lam
4fdaa1abb6 [Push API] Response 404 if error, fix #1721 2022-06-06 22:40:26 +08:00
Louis Lam
6ee7b3696a Merge pull request #1633 from domingospanta/bugfix/1451_blank_page_on_unkown_resource
Bugfix/1451 blank page on unkown resource
2022-06-06 21:54:49 +08:00
Louis Lam
cc258dce14 Merge pull request #1674 from philippdormann/feature/ntfy-support
feat: ntfy push support
2022-06-06 21:52:41 +08:00
Louis Lam
fb420fa1b1 Compress SVG when building dist 2022-06-05 23:49:48 +08:00
Louis Lam
a707b51053 Page Loading Speed Optimization (#1711)
* Update Vite.js to 2.9.9 and add Rollup Plugin Visualizer
* Prebuild gzip and brotli for assets

Original: ~1.2MB
Optimized: ~370KB
2022-06-05 23:43:25 +08:00
Matthew Nickson
a927f5cd15 Fixed typos + improved clarity and detail of some JSDoc
Apply suggestions from code review

Co-authored-by: Nelson Chan <chakflying@hotmail.com>
2022-06-02 16:40:56 +01:00
Matthew Nickson
0e28707307 Minor formatting for JSDoc comments
Added a number of minor formatting changes to JSDoc comments in /src
2022-06-02 15:15:21 +01:00
Matthew Nickson
c94dcf1533 Added JSDoc for src/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 14:32:38 +01:00
Matthew Nickson
b0476cfb5b Added JSDoc for src/pages/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 13:46:44 +01:00
Matthew Nickson
2170229031 Improve JSDoc for some components
Apply suggestions from code review

Co-authored-by: Nelson Chan <chakflying@hotmail.com>
2022-06-02 10:42:37 +01:00
Matthew Nickson
213aca4fc3 Added JSDoc for src/mixins/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 10:38:17 +01:00
Matthew Nickson
2b42c3c828 Added JSDoc for src/components/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 00:32:05 +01:00
Matthew Nickson
d939d03690 Added JSDoc for src/components/settings/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-01 23:44:10 +01:00
Matthew Nickson
07888e43f1 [empty commit] pull request for JSDoc src/* 2022-06-01 22:51:26 +01:00
Louis Lam
c6c1bb5b5c Merge pull request #1710 from AnnAngela/master
Update zh-CN translation
2022-06-01 16:05:23 +08:00
Louis Lam
3210264e28 Update PULL_REQUEST_TEMPLATE.md 2022-06-01 14:20:49 +08:00
Louis Lam
54e948c2ca Update CONTRIBUTING.md 2022-06-01 14:08:10 +08:00
Louis Lam
80094ec4e1 Merge pull request #1513 from louislam/status-page-inject-html
[Status Page] Render title, meta tag or favicon etc. in server side
2022-06-01 13:25:41 +08:00
Louis Lam
091158cfe7 [Status Page] Preload data 2022-06-01 13:05:12 +08:00
AnnAngela-work
abb6ce2366 Update zhCN translation 2022-06-01 11:28:10 +08:00
Louis Lam
e4ad8cbfc8 Remove unused variables 2022-05-31 23:06:43 +08:00
Louis Lam
a674caa520 [Status Page] Add og meta tags 2022-05-31 22:53:48 +08:00
Nelson Chan
179e3569b5 Chore: Add code comments 2022-05-31 16:24:39 +08:00
Nelson Chan
43527f2f40 Chore: Update remaining languages 2022-05-30 18:05:28 +08:00
Nelson Chan
26ff6f45a0 Feat: Use i18n pluralization 2022-05-30 17:53:32 +08:00
Louis Lam
c095767f4a [Status Page] SSR 2022-05-30 15:45:44 +08:00
Louis Lam
ffb7ba176c Merge remote-tracking branch 'origin/master' into status-page-inject-html 2022-05-30 14:00:39 +08:00
Nelson Chan
cfa5b551a5 Feat: Make the expiry days sorted 2022-05-26 12:17:24 +08:00
Nelson Chan
46ee149b70 Chore: Slightly improve design 2022-05-26 12:12:29 +08:00
Philipp Dormann
54184350a4 fix: missing semicolons 2022-05-23 21:13:57 +02:00
Philipp Dormann
14dbe7c334 clean up + default ntfs.sh server url 2022-05-23 21:11:01 +02:00
Philipp Dormann
122e6a842b clean up ntfy topic input 2022-05-23 21:08:56 +02:00
Philipp Dormann
77ef22bdb4 set proper ntfy priorities 2022-05-23 21:08:11 +02:00
Philipp Dormann
59f983d506 fix: unused import "HiddenInput" 2022-05-23 20:57:10 +02:00
Philipp Dormann
71f031c14e add ntfy support
ref https://github.com/louislam/uptime-kuma/issues/1622
2022-05-23 10:55:03 +02:00
Domingos F. Panta Jr
73b965c867 Merge branch 'master' into bugfix/1451_blank_page_on_unkown_resource 2022-05-19 19:35:58 +01:00
Nelson Chan
b7ba6330db Feat: Add cert exp. settings 2022-05-19 16:49:34 +08:00
Christopher Pickering
ef73af391f added option for ntlm authorization 2022-05-13 12:58:23 -05:00
Christopher Pickering
44f6fca945 added finally to close connection pool 2022-05-13 09:34:31 -05:00
Christopher Pickering
23ce7c6623 started db update script 2022-05-13 09:06:41 -05:00
Christopher Pickering
c346ea7864 updated name on export 2022-05-13 08:57:06 -05:00
Christopher Pickering
f0ad32a252 merged 2022-05-13 08:41:31 -05:00
Christopher Pickering
5720017fb4 updated name on import 2022-05-13 08:40:46 -05:00
Christopher Pickering
b7dc8e3ef8 started ui update 2022-05-13 07:22:52 -05:00
sur.la.route
5bba19f866 updated format
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2022-05-12 19:54:12 -05:00
sur.la.route
e198f2f1ab updated format
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2022-05-12 19:54:02 -05:00
Christopher Pickering
f91e5b98f9 [empty commit] pull request for Add NTML Auth Option for HTTP 2022-05-12 13:23:41 -05:00
Christopher Pickering
87f933df4f added sqlserver monitor 2022-05-12 12:48:03 -05:00
Christopher Pickering
332b9ab248 [empty commit] pull request for Add SQLServer Monitor 2022-05-12 10:39:12 -05:00
Domingos Panta
7cc89979f0 Moving change from axios interceptor to specific request. 2022-05-12 15:52:21 +01:00
Domingos Panta
668e97c5a9 fix: lint errors. 2022-05-12 13:39:10 +01:00
Domingos Panta
fdd781b081 Added a link to the home page on the lists of actions.
s new link was necessary to prevent a loop when the user tries to access a unknown resource, like status/123, and was redirected to /page-not-found. In this case going back to the last page would be /status/123 which does not exists.
2022-05-11 17:58:52 +01:00
Domingos Panta
373bd9b962 Added response interceptor to axios response.
This interceptor checks for response code 404 and redicts the user if that is the case.
2022-05-11 17:56:31 +01:00
Louis Lam
59be9bb971 working 2022-05-11 00:51:11 +08:00
Louis Lam
201a25c659 Draft 2022-05-09 00:26:49 +08:00
Greg Smith
cbfecab850 switch to the more-up-to-date esm-wallaby
https://github.com/wallabyjs/esm
2022-05-04 15:45:18 +09:30
Louis Lam
25cc54bf72 Try to give more time for axios-cached-dns-resolve test 2022-05-04 13:24:18 +08:00
Louis Lam
3700b16c5b Copy and add axios-cached-dns-resolve test 2022-05-04 13:16:22 +08:00
Greg Smith
d0546afe71 fix esm require: no ugly warnings 2022-05-01 10:22:16 +09:30
Greg Smith
f4515ad8c5 add axios cached dns resolve to monitor 2022-04-30 21:40:47 +09:30
Jimmy Huang
a4be651118 Update server/model/monitor.js
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2022-04-01 15:26:50 +08:00
Jimmy Huang
244a7b3671 Update server/model/monitor.js
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2022-02-07 18:46:16 +08:00
Jimmy Huang
ee90d2713f refs issue-1201 in upstream.
Add 100 characters from response body to bean.msg after keyword not match.
2022-01-25 17:39:19 +08:00
104 changed files with 5441 additions and 1006 deletions

View File

@@ -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
Fixes #(issue)

View File

@@ -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?
(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:
- Bug/Security fix
- Translations
- Adding notification providers
⚠️ Discuss First
⚠️ Discussion First
- Large pull requests
- 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
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. 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
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
- 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
- The web UI styling should be consistent and nice.
## Coding Styles
@@ -80,8 +86,8 @@ I personally do not like something need to learn so much and need to config so m
## Name convention
- Javascript/Typescript: camelCaseType
- SQLite: underscore_type
- CSS/SCSS: dash-type
- SQLite: snake_case (Underscore)
- CSS/SCSS: kebab-case (Dash)
## Tools

View File

@@ -8,9 +8,6 @@ Do not use the issue tracker or discuss it in the public as it will cause more d
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
### 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.

View File

@@ -1,18 +1,32 @@
import legacy from "@vitejs/plugin-legacy";
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
import visualizer from "rollup-plugin-visualizer";
import viteCompression from "vite-plugin-compression";
const postCssScss = require("postcss-scss");
const postcssRTLCSS = require("postcss-rtlcss");
const viteCompressionFilter = /\.(js|mjs|json|css|html|svg)$/i;
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
legacy({
targets: [ "ie > 11" ],
additionalLegacyPolyfills: [ "regenerator-runtime/runtime" ]
})
targets: [ "since 2015" ],
}),
visualizer({
filename: "tmp/dist-stats.html"
}),
viteCompression({
algorithm: "gzip",
filter: viteCompressionFilter,
}),
viteCompression({
algorithm: "brotliCompress",
filter: viteCompressionFilter,
}),
],
css: {
postcss: {
@@ -21,4 +35,13 @@ export default defineConfig({
"plugins": [ postcssRTLCSS ]
}
},
build: {
rollupOptions: {
output: {
manualChunks(id, { getModuleInfo, getModuleIds }) {
}
}
},
}
});

View 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;

View File

@@ -0,0 +1,10 @@
BEGIN TRANSACTION;
ALTER TABLE monitor
ADD database_connection_string VARCHAR(2000);
ALTER TABLE monitor
ADD database_query TEXT;
COMMIT

View File

@@ -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 \
sqlite3 iputils-ping util-linux dumb-init && \
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
# 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 --yes --no-install-recommends install ./cloudflared.deb && \
rm -rf /var/lib/apt/lists/* && \
rm -f cloudflared.deb
rm -f cloudflared.deb && \
apt --yes autoremove

4256
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "1.16.1",
"version": "1.17.0",
"license": "MIT",
"repository": {
"type": "git",
@@ -39,7 +39,7 @@
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.16.1 && npm ci --production && npm run download-dist",
"setup": "git checkout 1.17.0 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js",
@@ -57,7 +57,8 @@
"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-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": {
"@fortawesome/fontawesome-svg-core": "~1.2.36",
@@ -68,6 +69,8 @@
"@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0",
"axios": "~0.26.1",
"axios-cached-dns-resolve": "^3.0.6",
"axios-ntlm": "^1.3.0",
"badge-maker": "^3.3.1",
"bcryptjs": "~2.4.3",
"bootstrap": "5.1.3",
@@ -76,12 +79,16 @@
"chart.js": "~3.6.2",
"chartjs-adapter-dayjs": "~1.0.0",
"check-password-strength": "^2.0.5",
"cheerio": "^1.0.0-rc.10",
"chroma-js": "^2.1.2",
"command-exists": "~1.2.9",
"compare-versions": "~3.6.0",
"compression": "^1.7.4",
"dayjs": "^1.11.0",
"esm-wallaby": "^3.2.26",
"express": "~4.17.3",
"express-basic-auth": "~1.2.1",
"express-static-gzip": "^2.1.7",
"favico.js": "^0.3.10",
"form-data": "~4.0.0",
"http-graceful-shutdown": "~3.1.7",
@@ -92,6 +99,7 @@
"jwt-decode": "^3.1.2",
"limiter": "^2.1.0",
"mqtt": "^4.2.8",
"mssql": "^8.1.0",
"node-cloudflared-tunnel": "~1.0.9",
"nodemailer": "~6.6.5",
"notp": "~2.0.3",
@@ -129,27 +137,31 @@
"@babel/eslint-parser": "~7.17.0",
"@babel/preset-env": "^7.15.8",
"@types/bootstrap": "~5.1.9",
"@vitejs/plugin-legacy": "~1.6.4",
"@vitejs/plugin-vue": "~1.9.4",
"@vue/compiler-sfc": "~3.2.31",
"@vitejs/plugin-legacy": "~1.8.2",
"@vitejs/plugin-vue": "~2.3.3",
"@vue/compiler-sfc": "~3.2.36",
"aedes": "^0.46.3",
"babel-plugin-rewire": "~1.2.0",
"concurrently": "^7.1.0",
"core-js": "~3.18.3",
"cross-env": "~7.0.3",
"delay": "^5.0.0",
"dns2": "~2.0.1",
"eslint": "~8.14.0",
"eslint-plugin-vue": "~8.7.1",
"jest": "~27.2.5",
"jest-puppeteer": "~6.0.3",
"lru-cache": "^7.7.1",
"npm-check-updates": "^12.5.9",
"postcss-html": "^1.3.1",
"puppeteer": "~13.1.3",
"rollup-plugin-visualizer": "^5.6.0",
"sass": "~1.42.1",
"stylelint": "~14.7.1",
"stylelint-config-standard": "~25.0.0",
"typescript": "~4.4.4",
"vite": "~2.6.14",
"vite": "~2.9.9",
"vite-plugin-compression": "^0.5.1",
"wait-on": "^6.0.1"
}
}

View File

@@ -22,7 +22,10 @@ async function sendNotificationList(socket) {
]);
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);

View File

@@ -58,6 +58,8 @@ class Database {
"patch-monitor-expiry-notification.sql": true,
"patch-status-page-footer-css.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" ] },
};
/**

View File

@@ -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, 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 { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
@@ -17,6 +17,12 @@ const version = require("../../package.json").version;
const apicache = require("../modules/apicache");
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:
* 0 = DOWN
@@ -87,7 +93,12 @@ class Monitor extends BeanModel {
mqttUsername: this.mqttUsername,
mqttPassword: this.mqttPassword,
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) {
@@ -213,7 +224,7 @@ class Monitor extends BeanModel {
// HTTP basic auth
let basicAuthHeader = {};
if (this.basic_auth_user) {
if (this.auth_method === "basic") {
basicAuthHeader = {
"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 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.ping = dayjs().valueOf() - startTime;
@@ -312,7 +337,11 @@ class Monitor extends BeanModel {
bean.msg += ", keyword is found";
bean.status = UP;
} 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
// 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");
} else {
let timeout = beatInterval * 1000 - msSinceLastBeat;
@@ -405,7 +434,7 @@ class Monitor extends BeanModel {
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,
headers: {
"Accept": "*/*",
@@ -443,6 +472,14 @@ class Monitor extends BeanModel {
interval: this.interval,
});
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 {
bean.msg = "Unknown Monitor Type";
bean.status = PENDING;
@@ -493,7 +530,7 @@ class Monitor extends BeanModel {
}
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) {
if (this.retryInterval > 0) {
beatInterval = this.retryInterval;
@@ -837,10 +874,19 @@ class Monitor extends BeanModel {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this);
log.debug("monitor", "call sendCertNotificationByTargetDays");
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList);
let notifyDays = await setting("tlsExpiryNotifyDays");
if (notifyDays == null || !Array.isArray(notifyDays)) {
// Reset Default
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);
}
}
}
}

View File

@@ -1,10 +1,104 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
const { R } = require("redbean-node");
const cheerio = require("cheerio");
const { UptimeKumaServer } = require("../uptime-kuma-server");
class StatusPage extends BeanModel {
/**
* Like this: { "test-uptime.kuma.pet": "default" }
* @type {{}}
*/
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
* Return object like this: { "test-uptime.kuma.pet": "default" }

View File

@@ -14,7 +14,7 @@ class LunaSea extends NotificationProvider {
if (heartbeatJSON == null) {
let testdata = {
"title": "Uptime Kuma Alert",
"body": "Testing Successful.",
"body": msg,
};
await axios.post(lunaseadevice, testdata);
return okMsg;

View 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;

View File

@@ -2,6 +2,7 @@ const { R } = require("redbean-node");
const Apprise = require("./notification-providers/apprise");
const Discord = require("./notification-providers/discord");
const Gotify = require("./notification-providers/gotify");
const Ntfy = require("./notification-providers/ntfy");
const Line = require("./notification-providers/line");
const LunaSea = require("./notification-providers/lunasea");
const Mattermost = require("./notification-providers/mattermost");
@@ -52,6 +53,7 @@ class Notification {
new Discord(),
new Teams(),
new Gotify(),
new Ntfy(),
new Line(),
new LunaSea(),
new Feishu(),

View File

@@ -1,5 +1,5 @@
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 apicache = require("../modules/apicache");
const Monitor = require("../model/monitor");
@@ -92,115 +92,13 @@ router.get("/api/push/:pushToken", async (request, response) => {
}
} catch (e) {
response.json({
response.status(404).json({
ok: false,
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) => {
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;

View 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;

View File

@@ -16,7 +16,7 @@ if (nodeVersion < requiredVersion) {
}
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");
log.info("server", "Welcome to Uptime Kuma");
@@ -35,6 +35,7 @@ const fs = require("fs");
log.info("server", "Importing 3rd-party libraries");
log.debug("server", "Importing express");
const express = require("express");
const expressStaticGzip = require("express-static-gzip");
log.debug("server", "Importing redbean-node");
const { R } = require("redbean-node");
log.debug("server", "Importing jsonwebtoken");
@@ -148,22 +149,6 @@ let jwtSecret = null;
*/
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 () => {
Database.init(args);
await initDatabase(testMode);
@@ -179,13 +164,17 @@ try {
// Entry Page
app.get("/", async (request, response) => {
debug(`Request Domain: ${request.hostname}`);
log.debug("entry", `Request Domain: ${request.hostname}`);
if (request.hostname in StatusPage.domainMappingList) {
debug("This is a status page domain");
response.send(indexHTML);
log.debug("entry", "This is a status page domain");
let slug = StatusPage.domainMappingList[request.hostname];
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
} else if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
response.redirect("/status/" + exports.entryPage.replace("statusPage-", ""));
} else {
response.redirect("/dashboard");
}
@@ -214,7 +203,9 @@ try {
// With Basic Auth using the first user's username/password
app.get("/metrics", basicAuth, prometheusAPIMetrics());
app.use("/", express.static("dist"));
app.use("/", expressStaticGzip("dist", {
enableBrotli: true,
}));
// ./data/upload
app.use("/upload", express.static(Database.uploadDir));
@@ -227,12 +218,16 @@ try {
const apiRouter = require("./routers/api-router");
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.
app.get("*", async (_request, response) => {
if (_request.originalUrl.startsWith("/upload/")) {
response.status(404).send("File not found.");
} else {
response.send(indexHTML);
response.send(server.indexHTML);
}
});
@@ -659,7 +654,7 @@ try {
bean.retryInterval = monitor.retryInterval;
bean.hostname = monitor.hostname;
bean.maxretries = monitor.maxretries;
bean.port = monitor.port;
bean.port = parseInt(monitor.port);
bean.keyword = monitor.keyword;
bean.ignoreTls = monitor.ignoreTls;
bean.expiryNotification = monitor.expiryNotification;
@@ -674,6 +669,11 @@ try {
bean.mqttPassword = monitor.mqttPassword;
bean.mqttTopic = monitor.mqttTopic;
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);
@@ -1247,8 +1247,11 @@ try {
method: monitorListData[i].method || "GET",
body: monitorListData[i].body,
headers: monitorListData[i].headers,
authMethod: monitorListData[i].authMethod,
basic_auth_user: monitorListData[i].basic_auth_user,
basic_auth_pass: monitorListData[i].basic_auth_pass,
authWorkstation: monitorListData[i].authWorkstation,
authDomain: monitorListData[i].authDomain,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
hostname: monitorListData[i].hostname,

View File

@@ -63,7 +63,10 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "stop", async (currentPassword, callback) => {
try {
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
const disabledAuth = await setting("disableAuth");
if (!disabledAuth) {
await doubleCheckPassword(socket, currentPassword);
}
cloudflared.stop();
} catch (error) {
callback({

View File

@@ -29,6 +29,12 @@ class UptimeKumaServer {
httpServer = undefined;
io = undefined;
/**
* Cache Index HTML
* @type {string}
*/
indexHTML = "";
static getInstance(args) {
if (UptimeKumaServer.instance == null) {
UptimeKumaServer.instance = new UptimeKumaServer(args);
@@ -55,6 +61,16 @@ class UptimeKumaServer {
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);
}

View File

@@ -10,6 +10,8 @@ const chardet = require("chardet");
const mqtt = require("mqtt");
const chroma = require("chroma-js");
const { badgeConstants } = require("./config");
const mssql = require("mssql");
const { NtlmClient } = require("axios-ntlm");
// From ping-lite
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
* @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
// prevent issues with ::1:5300 (::1 port 5300)
resolverServer = resolverServer.replace("[", "").replace("]", "");
resolver.setServers([`[${resolverServer}]:${resolverPort}`]);
resolver.setServers([ `[${resolverServer}]:${resolverPort}` ]);
return new Promise((resolve, reject) => {
if (rrtype === "PTR") {
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
* @param {string} key Key of setting to retrieve
@@ -558,3 +605,15 @@ exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
exports.filterAndJoin = (parts, 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,
});
};

View File

@@ -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 {
background: #ccc;
border-radius: 20px;
@@ -363,6 +382,12 @@ textarea.form-control {
overflow-y: auto;
height: calc(100% - 65px);
}
@media (max-width: 770px) {
&.scrollbar {
height: calc(100% - 40px);
}
}
.item {
display: block;
@@ -473,6 +498,14 @@ textarea.form-control {
outline: none !important;
}
h5.settings-subheading::after {
content: "";
display: block;
width: 50%;
padding-top: 8px;
border-bottom: 1px solid $dark-border-color;
}
// Localization
@import "localization.scss";

View 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>

View File

@@ -25,10 +25,12 @@ export default {
CertificateInfoRow,
},
props: {
/** Object representing certificate */
certInfo: {
type: Object,
required: true,
},
/** Is the TLS certificate valid? */
valid: {
type: Boolean,
required: true,

View File

@@ -56,12 +56,19 @@ export default {
Datetime,
},
props: {
/** Object representing certificate */
cert: {
type: Object,
required: true,
},
},
methods: {
/**
* Format the subject of the certificate
* @param {Object} subject Object representing the certificates
* subject
* @returns {string}
*/
formatSubject(subject) {
if (subject.O && subject.CN && subject.C) {
return `${subject.CN} - ${subject.O} (${subject.C})`;

View File

@@ -29,14 +29,17 @@ import { Modal } from "bootstrap";
export default {
props: {
/** Style of button */
btnStyle: {
type: String,
default: "btn-primary",
},
/** Text to use as yes */
yesText: {
type: String,
default: "Yes", // TODO: No idea what to translate this
},
/** Text to use as no */
noText: {
type: String,
default: "No",
@@ -50,9 +53,13 @@ export default {
this.modal = new Modal(this.$refs.modal);
},
methods: {
/** Show the confirm dialog */
show() {
this.modal.show();
},
/**
* @emits string "yes" Notify the parent when Yes is pressed
*/
yes() {
this.$emit("yes");
},

View File

@@ -25,33 +25,41 @@ let timeout;
export default {
props: {
/** ID of this input */
id: {
type: String,
default: ""
},
/** Type of input */
type: {
type: String,
default: "text"
},
/** The value of the input */
modelValue: {
type: String,
default: ""
},
/** A placeholder to use */
placeholder: {
type: String,
default: ""
},
/** Should the field auto complete */
autocomplete: {
type: String,
default: undefined,
},
/** Is the input required? */
required: {
type: Boolean
},
/** Should the input be read only? */
readonly: {
type: String,
default: undefined,
},
/** Is the input disabled? */
disabled: {
type: String,
default: undefined,
@@ -79,14 +87,21 @@ export default {
},
methods: {
/** Show the input */
showInput() {
this.visibility = "text";
},
/** Hide the input */
hideInput() {
this.visibility = "password";
},
/**
* Copy the provided text to the users clipboard
* @param {string} textToCopy
* @returns {Promise<void>}
*/
copyToClipboard(textToCopy) {
this.icon = "check";

View File

@@ -10,6 +10,7 @@ import { sleep } from "../util.ts";
export default {
props: {
/** Value to count */
value: {
type: [ String, Number ],
default: 0,
@@ -18,6 +19,7 @@ export default {
type: Number,
default: 0.3,
},
/** Unit of the value */
unit: {
type: String,
default: "ms",
@@ -43,9 +45,7 @@ export default {
let frames = 12;
let step = Math.floor(diff / frames);
if (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0) {
// Lazy to NOT this condition, hahaha.
} else {
if (! (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0)) {
for (let i = 1; i < frames; i++) {
this.output += step;
await sleep(15);

View File

@@ -13,10 +13,12 @@ dayjs.extend(relativeTime);
export default {
props: {
/** Value of date time */
value: {
type: String,
default: null,
},
/** Should only the date be displayed? */
dateOnly: {
type: Boolean,
default: false,

View File

@@ -17,14 +17,17 @@
export default {
props: {
/** Size of the heartbeat bar */
size: {
type: String,
default: "big",
},
/** ID of the monitor */
monitorId: {
type: Number,
required: true,
},
/** Array of the monitors heartbeats */
heartbeatList: {
type: Array,
default: null,
@@ -160,12 +163,19 @@ export default {
this.resize();
},
methods: {
/** Resize the heartbeat bar */
resize() {
if (this.$refs.wrap) {
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) {
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
},

View File

@@ -24,25 +24,31 @@
<script>
export default {
props: {
/** The value of the input */
modelValue: {
type: String,
default: ""
},
/** A placeholder to use */
placeholder: {
type: String,
default: ""
},
/** Maximum length of the input */
maxlength: {
type: Number,
default: 255
},
/** Should the field auto complete */
autocomplete: {
type: String,
default: undefined,
},
/** Is the input required? */
required: {
type: Boolean
},
/** Should the input be read only? */
readonly: {
type: String,
default: undefined,
@@ -68,9 +74,11 @@ export default {
},
methods: {
/** Show users input in plain text */
showInput() {
this.visibility = "text";
},
/** Censor users input */
hideInput() {
this.visibility = "password";
},

View File

@@ -55,6 +55,7 @@ export default {
};
},
methods: {
/** Submit the user details and attempt to log in */
submit() {
this.processing = true;

View File

@@ -58,6 +58,7 @@ export default {
Tag,
},
props: {
/** Should the scrollbar be shown */
scrollbar: {
type: Boolean,
},
@@ -69,10 +70,22 @@ export default {
};
},
computed: {
/**
* Improve the sticky appearance of the list by increasing its
* height as user scrolls down.
* Not used on mobile.
*/
boxStyle() {
return {
height: `calc(100vh - 160px + ${this.windowTop}px)`,
};
if (window.innerWidth > 550) {
return {
height: `calc(100vh - 160px + ${this.windowTop}px)`,
};
} else {
return {
height: "calc(100vh - 160px)",
};
}
},
sortedMonitorList() {
@@ -124,6 +137,7 @@ export default {
window.removeEventListener("scroll", this.onScroll);
},
methods: {
/** Handle user scroll */
onScroll() {
if (window.top.scrollY <= 133) {
this.windowTop = window.top.scrollY;
@@ -131,9 +145,15 @@ export default {
this.windowTop = 133;
}
},
/**
* Get URL of monitor
* @param {number} id ID of monitor
* @returns {string} Relative URL of monitor
*/
monitorURL(id) {
return getMonitorRelativeURL(id);
},
/** Clear the search bar */
clearSearchText() {
this.searchText = "";
}

View File

@@ -125,11 +125,16 @@ export default {
},
methods: {
/** Show dialog to confirm deletion */
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
},
/**
* Show settings for specified notification
* @param {number} notificationID ID of notification to show
*/
show(notificationID) {
if (notificationID) {
this.id = notificationID;
@@ -152,6 +157,7 @@ export default {
this.modal.show();
},
/** Submit the form to the server */
submit() {
this.processing = true;
this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => {
@@ -170,6 +176,7 @@ export default {
});
},
/** Test the notification endpoint */
test() {
this.processing = true;
this.$root.getSocket().emit("testNotification", this.notification, (res) => {
@@ -178,6 +185,7 @@ export default {
});
},
/** Delete the notification endpoint */
deleteNotification() {
this.processing = true;
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
* @return {string}
*/

View File

@@ -35,6 +35,7 @@ Chart.register(LineController, BarController, LineElement, PointElement, TimeSca
export default {
components: { LineChart },
props: {
/** ID of monitor */
monitorId: {
type: Number,
required: true,

View File

@@ -130,11 +130,16 @@ export default {
},
methods: {
/** Show dialog to confirm deletion */
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
},
/**
* Show settings for specified proxy
* @param {number} proxyID ID of proxy to show
*/
show(proxyID) {
if (proxyID) {
this.id = proxyID;
@@ -163,6 +168,7 @@ export default {
this.modal.show();
},
/** Submit form data for saving */
submit() {
this.processing = true;
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
@@ -180,6 +186,7 @@ export default {
});
},
/** Delete this proxy */
deleteProxy() {
this.processing = true;
this.$root.getSocket().emit("deleteProxy", this.id, (res) => {

View File

@@ -72,10 +72,12 @@ export default {
Tag,
},
props: {
/** Are we in edit mode? */
editMode: {
type: Boolean,
required: true,
},
/** Should tags be shown? */
showTags: {
type: Boolean,
}
@@ -94,10 +96,20 @@ export default {
},
methods: {
/**
* Remove the specified group
* @param {number} index Index of group to remove
*/
removeGroup(index) {
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) {
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
},

View File

@@ -5,6 +5,7 @@
<script>
export default {
props: {
/** Current status of monitor */
status: {
type: Number,
default: 0,

View File

@@ -20,14 +20,20 @@
<script>
export default {
props: {
/** Object representing tag */
item: {
type: Object,
required: true,
},
/** Function to remove tag */
remove: {
type: Function,
default: null,
},
/**
* Size of tag
* @values normal, small
*/
size: {
type: String,
default: "normal",

View File

@@ -139,6 +139,7 @@ export default {
VueMultiselect,
},
props: {
/** Array of tags to be pre-selected */
preSelectedTags: {
type: Array,
default: () => [],
@@ -241,9 +242,11 @@ export default {
this.getExistingTags();
},
methods: {
/** Show the add tag dialog */
showAddDialog() {
this.modal.show();
},
/** Get all existing tags */
getExistingTags() {
this.$root.getSocket().emit("getTags", (res) => {
if (res.ok) {
@@ -253,6 +256,10 @@ export default {
}
});
},
/**
* Delete the specified tag
* @param {Object} tag Object representing tag to delete
*/
deleteTag(item) {
if (item.new) {
// Undo Adding a new Tag
@@ -262,6 +269,13 @@ export default {
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) {
if (option.color) {
return "white";
@@ -269,6 +283,7 @@ export default {
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
}
},
/** Add a draft tag */
addDraftTag() {
console.log("Adding Draft Tag: ", this.newDraftTag);
if (this.newDraftTag.select != null) {
@@ -296,6 +311,7 @@ export default {
}
this.clearDraftTag();
},
/** Remove a draft tag */
clearDraftTag() {
this.newDraftTag = {
name: null,
@@ -307,26 +323,51 @@ export default {
};
this.modal.hide();
},
/**
* Add a tag asynchronously
* @param {Object} newTag Object representing new tag to add
* @returns {Promise<void>}
*/
addTagAsync(newTag) {
return new Promise((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) {
return new Promise((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) {
return new Promise((resolve) => {
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
});
},
/** Handle pressing Enter key when inside the modal */
onEnter() {
if (!this.validateDraftTag.invalid) {
this.addDraftTag();
}
},
/**
* Submit the form data
* @param {number} monitorId ID of monitor this change affects
* @returns {void}
*/
async submit(monitorId) {
console.log(`Submitting tag changes for monitor ${monitorId}...`);
this.processing = true;

View File

@@ -29,10 +29,12 @@
<script>
export default {
props: {
/** Heading of the section */
heading: {
type: String,
default: "",
},
/** Should the section be open by default? */
defaultOpen: {
type: Boolean,
default: false,

View File

@@ -100,18 +100,22 @@ export default {
this.getStatus();
},
methods: {
/** Show the dialog */
show() {
this.modal.show();
},
/** Show dialog to confirm enabling 2FA */
confirmEnableTwoFA() {
this.$refs.confirmEnableTwoFA.show();
},
/** Show dialog to confirm disabling 2FA */
confirmDisableTwoFA() {
this.$refs.confirmDisableTwoFA.show();
},
/** Prepare 2FA configuration */
prepare2FA() {
this.processing = true;
@@ -126,6 +130,7 @@ export default {
});
},
/** Save the current 2FA configuration */
save2FA() {
this.processing = true;
@@ -143,6 +148,7 @@ export default {
});
},
/** Disable 2FA for this user */
disable2FA() {
this.processing = true;
@@ -160,6 +166,7 @@ export default {
});
},
/** Verify the token generated by the user */
verifyToken() {
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
if (res.ok) {
@@ -170,6 +177,7 @@ export default {
});
},
/** Get current status of 2FA */
getStatus() {
this.$root.getSocket().emit("twoFAStatus", (res) => {
if (res.ok) {

View File

@@ -5,14 +5,17 @@
<script>
export default {
props: {
/** Monitor this represents */
monitor: {
type: Object,
default: null,
},
/** Type of monitor */
type: {
type: String,
default: null,
},
/** Is this a pill? */
pill: {
type: Boolean,
default: false,

View 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>

View File

@@ -1,8 +1,8 @@
<template>
<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>
<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>
</div>
<div class="mb-3">

View File

@@ -4,6 +4,7 @@ import Discord from "./Discord.vue";
import Webhook from "./Webhook.vue";
import Signal from "./Signal.vue";
import Gotify from "./Gotify.vue";
import Ntfy from "./Ntfy.vue";
import Slack from "./Slack.vue";
import RocketChat from "./RocketChat.vue";
import Teams from "./Teams.vue";
@@ -46,6 +47,7 @@ const NotificationFormList = {
"teams": Teams,
"signal": Signal,
"gotify": Gotify,
"ntfy": Ntfy,
"slack": Slack,
"rocket.chat": RocketChat,
"pushover": Pushover,

View File

@@ -133,10 +133,15 @@ export default {
},
methods: {
/**
* Show the confimation dialog confirming the configuration
* be imported
*/
confirmImport() {
this.$refs.confirmImport.show();
},
/** Download a backup of the configuration */
downloadBackup() {
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
let fileName = `Uptime_Kuma_Backup_${time}.json`;
@@ -157,6 +162,10 @@ export default {
downloadItem.click();
},
/**
* Import the specified backup file
* @returns {?string}
*/
importBackup() {
this.processing = true;
let uploadItem = document.getElementById("import-backend").files;

View File

@@ -178,10 +178,12 @@ export default {
},
methods: {
/** Save the settings */
saveGeneral() {
localStorage.timezone = this.$root.userTimezone;
this.saveSettings();
},
/** Get the base URL of the application */
autoGetPrimaryBaseURL() {
this.settings.primaryBaseURL = location.protocol + "//" + location.host;
},

View File

@@ -90,6 +90,7 @@ export default {
},
methods: {
/** Get the current size of the database */
loadDatabaseSize() {
log.debug("monitorhistory", "load database size");
this.$root.getSocket().emit("getDatabaseSize", (res) => {
@@ -102,6 +103,7 @@ export default {
});
},
/** Request that the database is shrunk */
shrinkDatabase() {
this.$root.getSocket().emit("shrinkDatabase", (res) => {
if (res.ok) {
@@ -113,10 +115,12 @@ export default {
});
},
/** Show the dialog to confirm clearing stats */
confirmClearStatistics() {
this.$refs.confirmClearStatistics.show();
},
/** Send the request to clear stats */
clearStatistics() {
this.$root.clearStatistics((res) => {
if (res.ok) {

View File

@@ -20,16 +20,92 @@
</button>
</div>
<div class="my-4 pt-4">
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
<p>{{ $t("certificationExpiryDescription") }}</p>
<p>{{ $t("notificationDescription") }}</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" />
</div>
</template>
<script>
import NotificationDialog from "../../components/NotificationDialog.vue";
import ActionInput from "../ActionInput.vue";
export default {
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>
@@ -37,10 +113,27 @@ export default {
<style lang="scss" scoped>
@import "../../assets/vars.scss";
.btn-rm-expiry {
padding-left: 11px;
padding-right: 11px;
}
.dark {
.list-group-item {
background-color: $dark-bg2;
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>

View File

@@ -68,7 +68,9 @@
<Confirm ref="confirmStop" btn-style="btn-danger" :yes-text="$t('Stop') + ' cloudflared'" :no-text="$t('Cancel')" @yes="stop">
{{ $t("The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.") }}
<div class="mt-3">
<p class="mt-2">{{ $t("disableCloudflaredNoAuthMsg") }}</p>
<div v-if="!settings.disableAuth" class="mt-3">
<label for="current-password2" class="form-label">
{{ $t("Current Password") }}
</label>
@@ -108,7 +110,9 @@ export default {
return this.$root.cloudflared;
},
computed: {
settings() {
return this.$parent.$parent.$parent.settings;
},
},
watch: {
@@ -120,14 +124,17 @@ export default {
this.$root.getSocket().emit(prefix + "leave");
},
methods: {
/** Start the Cloudflare tunnel */
start() {
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
},
/** Stop the Cloudflare tunnel */
stop() {
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
this.$root.toastRes(res);
});
},
/** Remove the token for the Cloudflare tunnel */
removeToken() {
this.$root.getSocket().emit(prefix + "removeToken");
this.cloudflareTunnelToken = "";

View File

@@ -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>
</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">
<div class="mb-3">
<label for="current-password" class="form-label">
@@ -62,7 +62,7 @@
</template>
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
<h5 class="my-4">
<h5 class="my-4 settings-subheading">
{{ $t("Two Factor Authentication") }}
</h5>
<div class="mb-4">
@@ -78,7 +78,7 @@
<div class="my-4">
<!-- Advanced -->
<h5 class="my-4">{{ $t("Advanced") }}</h5>
<h5 class="my-4 settings-subheading">{{ $t("Advanced") }}</h5>
<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>
@@ -90,162 +90,11 @@
<TwoFADialog ref="TwoFADialog" />
<Confirm ref="confirmDisableAuth" btn-style="btn-danger" :yes-text="$t('I understand, please disable')" :no-text="$t('Leave')" @yes="disableAuth">
<template v-if="$i18n.locale === 'es-ES' ">
<p>Seguro que deseas <strong>deshabilitar la autenticación</strong>?</p>
<p>Es para <strong>quien implementa autenticación de terceros</strong> ante Uptime Kuma como por ejemplo Cloudflare Access.</p>
<p>Por favor usar con cuidado.</p>
</template>
<template v-else-if="$i18n.locale === 'pt-BR' ">
<p>Você tem certeza que deseja <strong>desativar a autenticação</strong>?</p>
<p>Isso é para <strong>alguém que tem autenticação de terceiros</strong> na frente do 'UpTime Kuma' como o Cloudflare Access.</p>
<p>Por favor, utilize isso com cautela.</p>
</template>
<template v-else-if="$i18n.locale === 'zh-HK' ">
<p>你是否確認<strong>取消登入認証</strong></p>
<p>這個功能是設計給已有<strong>第三方認証</strong>的用家例如 Cloudflare Access</p>
<p>請小心使用</p>
</template>
<template v-else-if="$i18n.locale === 'zh-CN' ">
<p>是否确定 <strong>取消登录验证</strong></p>
<p>这是为 <strong>有第三方认证</strong> 的用户提供的功能 Cloudflare Access</p>
<p>请谨慎使用</p>
</template>
<template v-else-if="$i18n.locale === 'zh-TW' ">
<p>你是否要<strong>取消登入驗證</strong></p>
<p>此功能是設計給已有<strong>第三方認證</strong>的使用者例如 Cloudflare Access</p>
<p>請謹慎使用</p>
</template>
<template v-else-if="$i18n.locale === 'de-DE' ">
<p>Bist du sicher das du die <strong>Authentifizierung deaktivieren</strong> möchtest?</p>
<p>Es ist für <strong>jemanden der eine externe Authentifizierung</strong> vor Uptime Kuma geschaltet hat, wie z.B. Cloudflare Access.</p>
<p>Bitte mit Vorsicht nutzen.</p>
</template>
<template v-else-if="$i18n.locale === 'sl-SI' ">
<p>Ali ste prepričani, da želite onemogočiti <strong>avtentikacijo</strong>?</p>
<p>Namenjen je <strong>nekomu, ki ima pred programom Uptime Kuma vklopljeno zunanje preverjanje pristnosti</strong>, na primer Cloudflare Access.</p>
<p>Uporabljajte previdno.</p>
</template>
<template v-else-if="$i18n.locale === 'sr' ">
<p>Да ли сте сигурни да желите да <strong>искључите аутентификацију</strong>?</p>
<p>То је за <strong>оне који имају додату аутентификацију</strong> испред Uptime Kuma као на пример Cloudflare Access.</p>
<p>Молим Вас користите ово са пажњом.</p>
</template>
<template v-else-if="$i18n.locale === 'sr-latn' ">
<p>Da li ste sigurni da želite da <strong>isključite autentifikaciju</strong>?</p>
<p>To je za <strong>one koji imaju dodatu autentifikaciju</strong> ispred Uptime Kuma kao na primer Cloudflare Access.</p>
<p>Molim Vas koristite ovo sa pažnjom.</p>
</template>
<template v-if="$i18n.locale === 'hr-HR' ">
<p>Jeste li sigurni da želite <strong>isključiti autentikaciju</strong>?</p>
<p>To je za <strong>korisnike koji imaju vanjsku autentikaciju stranice</strong> ispred Uptime Kume, poput usluge Cloudflare Access.</p>
<p>Pažljivo koristite ovu opciju.</p>
</template>
<template v-else-if="$i18n.locale === 'tr-TR' ">
<p><strong>Şifreli girişi devre dışı bırakmak istediğinizden</strong>emin misiniz?</p>
<p>Bu, Uptime Kuma'nın önünde Cloudflare Access gibi <strong>üçüncü taraf yetkilendirmesi olan</strong> kişiler içindir.</p>
<p>Lütfen dikkatli kullanın.</p>
</template>
<template v-else-if="$i18n.locale === 'ko-KR' ">
<p>정말로 <strong>인증 기능을 끌까요</strong>?</p>
<p>이 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong>을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.</p>
<p>신중하게 사용하세요.</p>
</template>
<template v-else-if="$i18n.locale === 'pl' ">
<p>Czy na pewno chcesz <strong>wyłączyć autoryzację</strong>?</p>
<p>Jest przeznaczony dla <strong>kogoś, kto ma autoryzację zewnętrzną</strong> przed Uptime Kuma, taką jak Cloudflare Access.</p>
<p>Proszę używać ostrożnie.</p>
</template>
<template v-else-if="$i18n.locale === 'et-EE' ">
<p>Kas soovid <strong>lülitada autentimise välja</strong>?</p>
<p>Kastuamiseks <strong>välise autentimispakkujaga</strong>, näiteks Cloudflare Access.</p>
<p>Palun kasuta vastutustundlikult.</p>
</template>
<template v-else-if="$i18n.locale === 'it-IT' ">
<p><strong>Disabilitare l'autenticazione?</strong></p>
<p><strong>Questa opzione è per chi un sistema di autenticazione gestito da terze parti</strong> messo davanti ad Uptime Kuma, ad esempio Cloudflare Access.</p>
<p>Utilizzare con attenzione!</p>
</template>
<template v-else-if="$i18n.locale === 'id-ID' ">
<p>Apakah Anda yakin ingin <strong>menonaktifkan autentikasi</strong>?</p>
<p>Ini untuk <strong>mereka yang memiliki autentikasi pihak ketiga</strong> diletakkan di depan Uptime Kuma, misalnya akses Cloudflare.</p>
<p>Gunakan dengan hati-hati.</p>
</template>
<template v-else-if="$i18n.locale === 'ru-RU' ">
<p>Вы уверены, что хотите <strong>отключить авторизацию</strong>?</p>
<p>Это подходит для <strong>тех, у кого стоит другая авторизация</strong> перед открытием Uptime Kuma, например Cloudflare Access.</p>
<p>Пожалуйста, используйте с осторожностью.</p>
</template>
<template v-else-if="$i18n.locale === 'uk-UA' ">
<p>Ви впевнені, що бажаєте <strong>вимкнути авторизацію</strong>?</p>
<p>Це підходить для <strong>тих, у кого встановлена інша авторизація</strong> пееред відкриттям Uptime Kuma, наприклад Cloudflare Access.</p>
<p>Будь ласка, використовуйте з обережністю.</p>
</template>
<template v-else-if="$i18n.locale === 'fa' ">
<p>آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?</p>
<p>این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کردهاند</strong>، مانند Cloudflare Access.</p>
<p>لطفا از این امکان با دقت استفاده کنید.</p>
</template>
<template v-else-if="$i18n.locale === 'bg-BG' ">
<p>Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?</p>
<p>Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access, Authelia или друг механизъм за удостоверяване.</p>
<p>Моля, използвайте с повишено внимание.</p>
</template>
<template v-else-if="$i18n.locale === 'hu' ">
<p>Biztos benne, hogy <strong>kikapcsolja a hitelesítést</strong>?</p>
<p>Akkor érdemes, ha <strong>van 3rd-party hitelesítés</strong> az Uptime Kuma-t megelőzően mint a Cloudflare Access.</p>
<p>Használja megfontoltan!</p>
</template>
<template v-else-if="$i18n.locale === 'nb-NO' ">
<p>Er du sikker at du vil <strong>deaktiver autentisering</strong>?</p>
<p>Dette er for <strong>de som har tredjepartsautorisering</strong> foran Uptime Kuma, for eksempel Cloudflare Access.</p>
<p>Vennligst vær forsiktig.</p>
</template>
<template v-else-if="$i18n.locale === 'cs-CZ' ">
<p>Opravdu chcete <strong>deaktivovat autentifikaci</strong>?</p>
<p>Tato možnost je určena pro případy, kdy <strong>máte autentifikaci zajištěnou třetí stranou</strong> ještě před přístupem do Uptime Kuma, například prostřednictvím Cloudflare Access.</p>
<p>Používejte ji prosím s rozmyslem.</p>
</template>
<template v-else-if="$i18n.locale === 'vi-VN' ">
<p>Bạn muốn <strong>TẮT XÁC THỰC</strong> không?</p>
<p>Điều này rất nguy hiểm<strong>BẤT KỲ AI</strong> cũng thể truy cập cướp quyền điều khiển.</p>
<p>Vui lòng <strong>cẩn thận</strong>.</p>
</template>
<template v-else-if="$i18n.locale === 'th-TH' ">
<p>ณตองการทจะ <strong>ดใชงานระบบรบรองความถกตองใชหรอไม</strong>?</p>
<p>ระบบนกออกแบบมาเพอการใชงานกบระบบรบรองความถกตองของบคคลทสามเช Cloudflare Access, Authelia หรอวการอ </p>
<p>โปรดใชความระมดระวงในการเลอกใชงานระบบน !</p>
</template>
<!-- English (en) -->
<template v-else>
<p>Are you sure want to <strong>disable authentication</strong>?</p>
<p>It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.</p>
<p>Please use this option carefully!</p>
</template>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="$t('disableauth.message1')"></p>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="$t('disableauth.message2')"></p>
<p>{{ $t("Please use this option carefully!") }}</p>
<div class="mb-3">
<label for="current-password2" class="form-label">
@@ -303,6 +152,7 @@ export default {
},
methods: {
/** Check new passwords match before saving them */
savePassword() {
if (this.password.newPassword !== this.password.repeatNewPassword) {
this.invalidPassword = true;
@@ -320,6 +170,7 @@ export default {
}
},
/** Disable authentication for web app access */
disableAuth() {
this.settings.disableAuth = true;
@@ -332,6 +183,7 @@ export default {
}, this.password.currentPassword);
},
/** Enable authentication for web app access */
enableAuth() {
this.settings.disableAuth = false;
this.saveSettings();
@@ -346,15 +198,3 @@ export default {
},
};
</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>

View File

@@ -4,8 +4,7 @@
2. Create a language file (e.g. `zh-TW.js`). The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm
3. Run `npm run update-language-files`. You can also use this command to check if there are new strings to translate for your language.
4. Your language file should be filled in. You can translate now.
5. Translate `src/components/settings/Security.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
6. Add it into `languageList` constant.
7. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
5. Add it into `languageList` constant.
6. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
If you do not have programming skills, let me know in [the issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏

View File

@@ -55,8 +55,7 @@ export default {
Current: "Текущ",
Uptime: "Достъпност",
"Cert Exp.": "Вал. сертификат",
days: "дни",
day: "ден",
day: "ден | дни",
"-day": "-дни",
hour: "час",
"-hour": "-часa",
@@ -90,13 +89,16 @@ export default {
"Search Engine Visibility": "Видимост за търсачки",
"Allow indexing": "Разреши индексиране",
"Discourage search engines from indexing site": "Не позволявай на търсачките да индексират този сайт",
"Change Password": "Промени парола",
"Change Password": "Промяна на парола",
"Current Password": "Текуща парола",
"New Password": "Нова парола",
"Repeat New Password": "Повторете новата парола",
"Update Password": "Актуализирай парола",
"Update Password": "Актуализирай паролата",
"Disable Auth": "Изключи удостоверяване",
"Enable Auth": "Включи удостоверяване",
"disableauth.message1": "Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?",
"disableauth.message2": "Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access, Authelia или друг механизъм за удостоверяване.",
"Please use this option carefully!": "Моля, използвайте с повишено внимание.",
Logout: "Изход от профила",
Leave: "Отказ",
"I understand, please disable": "Разбирам. Моля, изключи",
@@ -145,7 +147,7 @@ export default {
"Setup 2FA": "Настройка 2FA",
"Enable 2FA": "Включи 2FA",
"Disable 2FA": "Изключи 2FA",
"2FA Settings": "Настройки 2FA",
"2FA Settings": "Настройка за 2FA",
"Two Factor Authentication": "Двуфакторно удостоверяване",
Active: "Активно",
Inactive: "Неактивно",
@@ -299,7 +301,7 @@ export default {
HeadersInvalidFormat: "Заявените хедъри не са валидни JSON: ",
BodyInvalidFormat: "Заявеното съобщение не е валиден JSON: ",
"Monitor History": "История на мониторите",
clearDataOlderThan: "Ще се съхранява {0} дни.",
clearDataOlderThan: "Ще се съхранява за {0} дни.",
records: "записа",
"One record": "Един запис",
steamApiKeyDescription: "За да мониторирате Steam Gameserver се нуждаете от Steam Web-API ключ. Може да регистрирате Вашия API ключ тук: ",
@@ -308,12 +310,12 @@ export default {
PasswordsDoNotMatch: "Паролите не съвпадат.",
"Current User": "Текущ потребител",
recent: "Скорошни",
shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не нужно.",
shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не е нужно.",
Done: "Готово",
Info: "Информация",
Security: "Сигурност",
"Steam API Key": "Steam API ключ",
"Shrink Database": "Редуциране база данни",
"Shrink Database": "Редуцирай базата данни",
"Pick a RR-Type...": "Изберете вида на ресурсния запис за мониторитане...",
"Pick Accepted Status Codes...": "Изберете статус кодове, които да се считат за успешен отговор...",
Default: "По подразбиране",
@@ -422,6 +424,7 @@ export default {
Next: "Следващ",
"The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
"No Proxy": "Без прокси",
Authentication: "Удостоверяване",
"HTTP Basic Auth": "HTTP основно удостоверяване",
"New Status Page": "Нова статус страница",
"Page Not Found": "Страницата не е открита",
@@ -515,4 +518,22 @@ export default {
"Go back to the previous page.": "Да се върнете към предишната страница.",
"Coming Soon": "Очаквайте скоро",
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 сертификат, през следващите:",
"ntfy Topic": "ntfy Тема",
Domain: "Домейн",
Workstation: "Работна станция",
disableCloudflaredNoAuthMsg: "Тъй като сте в режим \"No Auth mode\", парола не се изисква.",
};

View File

@@ -56,8 +56,7 @@ export default {
Current: "Aktuální",
Uptime: "Doba provozu",
"Cert Exp.": "Platnost certifikátu",
days: "dny/í",
day: "den",
day: "den | dny/í",
"-day": "-dní",
hour: "hodina",
"-hour": "-hodin",
@@ -101,6 +100,9 @@ export default {
"Update Password": "Aktualizovat heslo",
"Disable Auth": "Deaktivovat ověřování",
"Enable Auth": "Povolit ověřování",
"disableauth.message1": "Opravdu chcete <strong>deaktivovat autentifikaci</strong>?",
"disableauth.message2": "Tato možnost je určena pro případy, kdy <strong>máte autentifikaci zajištěnou třetí stranou</strong> ještě před přístupem do Uptime Kuma, například prostřednictvím Cloudflare Access.",
"Please use this option carefully!": "Používejte ji prosím s rozmyslem.",
Logout: "Odhlášení",
Leave: "Odejít",
"I understand, please disable": "Rozumím, chci ji deaktivovat",

View File

@@ -30,8 +30,7 @@ export default {
Current: "Aktuelt",
Uptime: "Oppetid",
"Cert Exp.": "Certifikatets udløb",
days: "Dage",
day: "Dag",
day: "Dag | Dage",
"-day": "-Dage",
hour: "Timer",
"-hour": "-Timer",

View File

@@ -30,8 +30,7 @@ export default {
Current: "Aktuell",
Uptime: "Verfügbarkeit",
"Cert Exp.": "Zertifikatsablauf",
days: "Tage",
day: "Tag",
day: "Tag | Tage",
"-day": "-Tage",
hour: "Stunde",
"-hour": "-Stunden",
@@ -78,6 +77,9 @@ export default {
"Update Password": "Passwort aktualisieren",
"Disable Auth": "Authentifizierung deaktivieren",
"Enable Auth": "Authentifizierung aktivieren",
"disableauth.message1": "Bist du sicher das du die <strong>Authentifizierung deaktivieren</strong> möchtest?",
"disableauth.message2": "Es ist für <strong>jemanden der eine externe Authentifizierung</strong> vor Uptime Kuma geschaltet hat, wie z.B. Cloudflare Access.",
"Please use this option carefully!": "Bitte mit Vorsicht nutzen.",
Logout: "Ausloggen",
notificationDescription: "Benachrichtigungen müssen einem Monitor zugewiesen werden, damit diese funktionieren.",
Leave: "Verlassen",
@@ -422,6 +424,7 @@ export default {
Next: "Weiter",
"The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.",
"No Proxy": "Kein Proxy",
Authentication: "Authentifizierung",
"HTTP Basic Auth": "HTTP Basisauthentifizierung",
"New Status Page": "Neue Status-Seite",
"Page Not Found": "Seite nicht gefunden",

View File

@@ -57,8 +57,7 @@ export default {
Current: "Current",
Uptime: "Uptime",
"Cert Exp.": "Cert Exp.",
days: "days",
day: "day",
day: "day | days",
"-day": "-day",
hour: "hour",
"-hour": "-hour",
@@ -102,6 +101,9 @@ export default {
"Update Password": "Update Password",
"Disable Auth": "Disable Auth",
"Enable Auth": "Enable Auth",
"disableauth.message1": "Are you sure want to <strong>disable authentication</strong>?",
"disableauth.message2": "It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.",
"Please use this option carefully!": "Please use this option carefully!",
Logout: "Logout",
Leave: "Leave",
"I understand, please disable": "I understand, please disable",
@@ -439,6 +441,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: "Authentication",
"HTTP Basic Auth": "HTTP Basic Auth",
"New Status Page": "New Status Page",
"Page Not Found": "Page Not Found",
@@ -525,4 +528,12 @@ export default {
"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} .",
"Connection String": "Connection String",
"Query": "Query",
settingsCertificateExpiry: "TLS Certificate Expiry",
certificationExpiryDescription: "HTTPS Monitors trigger notification when TLS certificate expires in:",
"ntfy Topic": "ntfy Topic",
"Domain": "Domain",
"Workstation": "Workstation",
disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.",
};

View File

@@ -44,8 +44,7 @@ export default {
Current: "Actual",
Uptime: "Tiempo activo",
"Cert Exp.": "Caducidad cert.",
days: "días",
day: "día",
day: "día | días",
"-day": "-día",
hour: "hora",
"-hour": "-hora",
@@ -85,6 +84,9 @@ export default {
"Update Password": "Actualizar contraseña",
"Disable Auth": "Deshabilitar Autenticación",
"Enable Auth": "Habilitar Autenticación",
"disableauth.message1": "Seguro que deseas <strong>deshabilitar la autenticación</strong>?",
"disableauth.message2": "Es para <strong>quien implementa autenticación de terceros</strong> ante Uptime Kuma como por ejemplo Cloudflare Access.",
"Please use this option carefully!": "Por favor usar con cuidado.",
Logout: "Cerrar sesión",
Leave: "Salir",
"I understand, please disable": "Entiendo, por favor deshabilitar",

View File

@@ -47,8 +47,7 @@ export default {
Current: "Hetkeseisund",
Uptime: "Eluiga",
"Cert Exp.": "Sert. aegumine",
days: "päeva",
day: "päev",
day: "päev | päeva",
"-day": "-päev",
hour: "tund",
"-hour": "-tund",
@@ -88,6 +87,9 @@ export default {
"Update Password": "Uuenda salasõna",
"Disable Auth": "Lülita autentimine välja",
"Enable Auth": "Lülita autentimine sisse",
"disableauth.message1": "Kas soovid <strong>lülitada autentimise välja</strong>?",
"disableauth.message2": "Kastuamiseks <strong>välise autentimispakkujaga</strong>, näiteks Cloudflare Access.",
"Please use this option carefully!": "Palun kasuta vastutustundlikult.",
Logout: "Logi välja",
Leave: "Lahku",
"I understand, please disable": "Olen tutvunud riskidega, lülita välja",

View File

@@ -55,7 +55,6 @@ export default {
Current: "فعلی",
Uptime: "آپتایم",
"Cert Exp.": "تاریخ انقضای SSL",
days: "روز",
day: "روز",
"-day": "-روز",
hour: "ساعت",
@@ -97,6 +96,9 @@ export default {
"Update Password": "بروز رسانی رمز عبور",
"Disable Auth": "غیر فعال سازی تایید هویت",
"Enable Auth": "فعال سازی تایید هویت",
"disableauth.message1": "آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?",
"disableauth.message2": "این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کرده‌اند</strong>، مانند Cloudflare Access.",
"Please use this option carefully!": "لطفا از این امکان با دقت استفاده کنید.",
Logout: "خروج",
Leave: "منصرف شدم",
"I understand, please disable": "متوجه هستم، لطفا غیرفعال کنید!",

View File

@@ -55,8 +55,7 @@ export default {
Current: "Actuellement",
Uptime: "Uptime",
"Cert Exp.": "Expiration SSL",
days: "jours",
day: "jour",
day: "jour | jours",
"-day": "-jours",
hour: "-heure",
"-hour": "-heures",

View File

@@ -56,8 +56,7 @@ export default {
Current: "Trenutno",
Uptime: "Dostupnost",
"Cert Exp.": "Istek cert.",
days: "dana",
day: "dan",
day: "dan | dana",
"-day": "-dnevno",
hour: "sat",
"-hour": "-satno",
@@ -101,6 +100,9 @@ export default {
"Update Password": "Spremi novu lozinku",
"Disable Auth": "Onemogući autentikaciju",
"Enable Auth": "Omogući autentikaciju",
"disableauth.message1": "Jeste li sigurni da želite <strong>isključiti autentikaciju</strong>?",
"disableauth.message2": "To je za <strong>korisnike koji imaju vanjsku autentikaciju stranice</strong> ispred Uptime Kume, poput usluge Cloudflare Access.",
"Please use this option carefully!": "Pažljivo koristite ovu opciju.",
Logout: "Odjava",
Leave: "Poništi",
"I understand, please disable": "Razumijem, svejedno onemogući",

View File

@@ -55,7 +55,6 @@ export default {
Current: "Aktuális",
Uptime: "Uptime",
"Cert Exp.": "SSL lejárat",
days: "nap",
day: "nap",
"-day": " nap",
hour: "óra",
@@ -97,6 +96,9 @@ export default {
"Update Password": "Jelszó módosítása",
"Disable Auth": "Hitelesítés tiltása",
"Enable Auth": "Hitelesítés engedélyezése",
"disableauth.message1": "Biztos benne, hogy <strong>kikapcsolja a hitelesítést</strong>?",
"disableauth.message2": "Akkor érdemes, ha <strong>van 3rd-party hitelesítés</strong> az Uptime Kuma-t megelőzően mint a Cloudflare Access.",
"Please use this option carefully!": "Használja megfontoltan!",
Logout: "Kijelentkezés",
Leave: "Elhagy",
"I understand, please disable": "Megértettem, kérem tiltsa le",

View File

@@ -55,8 +55,7 @@ export default {
Current: "Saat ini",
Uptime: "Waktu aktif",
"Cert Exp.": "Cert Exp.",
days: "hari-hari",
day: "hari",
day: "hari | hari-hari",
"-day": "-hari",
hour: "Jam",
"-hour": "-Jam",
@@ -97,6 +96,9 @@ export default {
"Update Password": "Perbarui Kata Sandi",
"Disable Auth": "Nonaktifkan Autentikasi",
"Enable Auth": "Aktifkan Autentikasi",
"disableauth.message1": "Apakah Anda yakin ingin <strong>menonaktifkan autentikasi</strong>?",
"disableauth.message2": "Ini untuk <strong>mereka yang memiliki autentikasi pihak ketiga</strong> diletakkan di depan Uptime Kuma, misalnya akses Cloudflare.",
"Please use this option carefully!": "Gunakan dengan hati-hati.",
Logout: "Keluar",
Leave: "Pergi",
"I understand, please disable": "Saya mengerti, silakan dinonaktifkan",

View File

@@ -56,8 +56,7 @@ export default {
Current: "Corrente",
Uptime: "Tempo di attività",
"Cert Exp.": "Scadenza certificato",
days: "giorni",
day: "giorno",
day: "giorno | giorni",
"-day": "-giorni",
hour: "ora",
"-hour": "-ore",
@@ -101,6 +100,9 @@ export default {
"Update Password": "Modifica password",
"Disable Auth": "Disabilita autenticazione",
"Enable Auth": "Abilita autenticazione",
"disableauth.message1": "<strong>Disabilitare l'autenticazione?</strong>",
"disableauth.message2": "<strong>Questa opzione è per chi un sistema di autenticazione gestito da terze parti</strong> messo davanti ad Uptime Kuma, ad esempio Cloudflare Access.",
"Please use this option carefully!": "Utilizzare con attenzione!",
Logout: "Esci",
Leave: "Annulla",
"I understand, please disable": "Lo capisco, disabilitare l'autenticazione.",

View File

@@ -44,8 +44,7 @@ export default {
Current: "現在",
Uptime: "起動時間",
"Cert Exp.": "証明書有効期限",
days: "日間",
day: "日",
day: "日 | 日間",
"-day": "-日",
hour: "時間",
"-hour": "-時間",

View File

@@ -55,7 +55,6 @@ export default {
Current: "현재",
Uptime: "업타임",
"Cert Exp.": "인증서 만료",
days: "일",
day: "일",
"-day": "-일",
hour: "시간",
@@ -97,6 +96,9 @@ export default {
"Update Password": "비밀번호 변경",
"Disable Auth": "인증 비활성화",
"Enable Auth": "인증 활성화",
"disableauth.message1": "정말로 <strong>인증 기능을 끌까요</strong>?",
"disableauth.message2": "이 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong>을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.",
"Please use this option carefully!": "신중하게 사용하세요.",
Logout: "로그아웃",
Leave: "나가기",
"I understand, please disable": "기능에 대해 이해했으니 꺼주세요.",
@@ -439,6 +441,7 @@ export default {
Next: "다음",
"The slug is already taken. Please choose another slug.": "이미 존재하는 주소에요. 다른 주소를 사용해 주세요.",
"No Proxy": "프록시 없음",
Authentication: "인증",
"HTTP Basic Auth": "HTTP 인증",
"New Status Page": "새로운 상태 페이지",
"Page Not Found": "페이지를 찾을 수 없어요",

View File

@@ -55,8 +55,7 @@ export default {
Current: "Nåværende",
Uptime: "Oppetid",
"Cert Exp.": "Sertifikat utløper",
days: "dager",
day: "dag",
day: "dag | dager",
"-day": "-dag",
hour: "time",
"-hour": "-time",
@@ -97,6 +96,9 @@ export default {
"Update Password": "Oppdater passord",
"Disable Auth": "Deaktiver autentisering",
"Enable Auth": "Aktiver autentisering",
"disableauth.message1": "Er du sikker på at du vil <strong>deaktiver autentisering</strong>?",
"disableauth.message2": "Dette er for <strong>de som har tredjepartsautorisering</strong> foran Uptime Kuma, for eksempel Cloudflare Access.",
"Please use this option carefully!": "Vennligst vær forsiktig.",
Logout: "Logg ut",
Leave: "Forlat",
"I understand, please disable": "Jeg forstår, vennligst deaktiver",

View File

@@ -52,8 +52,7 @@ export default {
Current: "Huidig",
Uptime: "Uptime",
"Cert Exp.": "Cert. verl.",
days: "dagen",
day: "dag",
day: "dag | dagen",
"-day": "-dag",
hour: "uur",
"-hour": "-uur",

View File

@@ -55,8 +55,7 @@ export default {
Current: "Aktualny",
Uptime: "Czas pracy",
"Cert Exp.": "Certyfikat wygasa",
days: "dni",
day: "dzień",
day: "dzień | dni",
"-day": " dni",
hour: "godzina",
"-hour": " godzin",
@@ -97,6 +96,9 @@ export default {
"Update Password": "Zaktualizuj hasło",
"Disable Auth": "Wyłącz autoryzację",
"Enable Auth": "Włącz autoryzację",
"disableauth.message1": "Czy na pewno chcesz <strong>wyłączyć autoryzację</strong>?",
"disableauth.message2": "Jest przeznaczony dla <strong>kogoś, kto ma autoryzację zewnętrzną</strong> przed Uptime Kuma, taką jak Cloudflare Access.",
"Please use this option carefully!": "Proszę używać ostrożnie.",
Logout: "Wyloguj",
Leave: "Zostaw",
"I understand, please disable": "Rozumiem, proszę wyłączyć",
@@ -429,6 +431,7 @@ export default {
Next: "Dalej",
"The slug is already taken. Please choose another slug.": "Ten symbol jest już zajęty. Proszę, wybierz inny.",
"No Proxy": "Bez proxy",
Authentication: "Uwierzytelnianie",
"HTTP Basic Auth": "Podstawowa autoryzacja HTTP",
"New Status Page": "Nowa strona statusu",
"Page Not Found": "Strona nie została znaleziona",

View File

@@ -55,8 +55,7 @@ export default {
Current: "Atual",
Uptime: "Tempo de atividade",
"Cert Exp.": "Cert Exp.",
days: "dias",
day: "dia",
day: "dia | dias",
"-day": "-dia",
hour: "hora",
"-hour": "-hora",
@@ -97,6 +96,9 @@ export default {
"Update Password": "Atualizar Senha",
"Disable Auth": "Desativar Autenticação",
"Enable Auth": "Ativar Autenticação",
"disableauth.message1": "Você tem certeza que deseja <strong>desativar a autenticação</strong>?",
"disableauth.message2": "Isso é para <strong>alguém que tem autenticação de terceiros</strong> na frente do 'UpTime Kuma' como o Cloudflare Access.",
"Please use this option carefully!": "Por favor, utilize isso com cautela.",
Logout: "Deslogar",
Leave: "Sair",
"I understand, please disable": "Eu entendo, por favor desative.",

View File

@@ -44,8 +44,7 @@ export default {
Current: "Текущий",
Uptime: "Аптайм",
"Cert Exp.": "Сертификат истекает",
days: "дней",
day: "день",
day: "день | дней",
"-day": " дней",
hour: "час",
"-hour": " часа",
@@ -85,6 +84,9 @@ export default {
"Update Password": "Обновить пароль",
"Disable Auth": "Отключить авторизацию",
"Enable Auth": "Включить авторизацию",
"disableauth.message1": "Вы уверены, что хотите <strong>отключить авторизацию</strong>?",
"disableauth.message2": "Это подходит для <strong>тех, у кого стоит другая авторизация</strong> перед открытием Uptime Kuma, например Cloudflare Access.",
"Please use this option carefully!": "Пожалуйста, используйте с осторожностью.",
Logout: "Выйти",
Leave: "Отмена",
"I understand, please disable": "Я понимаю, всё равно отключить",
@@ -352,7 +354,8 @@ export default {
"Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9",
"No consecutive dashes --": "Запрещено использовать тире --",
"HTTP Options": "HTTP Опции",
"Basic Auth": "HTTP Авторизация",
Authentication: "Аутентификация",
"HTTP Basic Auth": "HTTP Авторизация",
PushByTechulus: "Push by Techulus",
clicksendsms: "ClickSend SMS",
GoogleChat: "Google Chat (только Google Workspace)",

View File

@@ -56,8 +56,7 @@ export default {
Current: "Trenutno",
Uptime: "Uptime",
"Cert Exp.": "Potek certifikata",
days: "dni",
day: "dan",
day: "dan | dni",
"-day": "-dni",
hour: "ura",
"-hour": "-ur",
@@ -101,6 +100,9 @@ export default {
"Update Password": "Posodobi geslo",
"Disable Auth": "Onemogoči auth",
"Enable Auth": "Omogoči auth",
"disableauth.message1": "Ali ste prepričani, da želite onemogočiti <strong>avtentikacijo</strong>?",
"disableauth.message2": "Namenjen je <strong>nekomu, ki ima pred programom Uptime Kuma vklopljeno zunanje preverjanje pristnosti</strong>, na primer Cloudflare Access.",
"Please use this option carefully!": "Uporabljajte previdno.",
Logout: "Odjava",
Leave: "Zapusti",
"I understand, please disable": "Razumem, prosim onemogočite",

View File

@@ -44,8 +44,7 @@ export default {
Current: "Trenutno",
Uptime: "Vreme rada",
"Cert Exp.": "Istek sert.",
days: "dana",
day: "dan",
day: "dan | dana",
"-day": "-dana",
hour: "sat",
"-hour": "-sata",
@@ -85,6 +84,9 @@ export default {
"Update Password": "Izmeni lozinku",
"Disable Auth": "Isključi autentifikaciju",
"Enable Auth": "Uključi autentifikaciju",
"disableauth.message1": "Da li ste sigurni da želite da <strong>isključite autentifikaciju</strong>?",
"disableauth.message2": "To je za <strong>one koji imaju dodatu autentifikaciju</strong> ispred Uptime Kuma kao na primer Cloudflare Access.",
"Please use this option carefully!": "Molim Vas koristite ovo sa pažnjom.",
Logout: "Odloguj se",
Leave: "Izađi",
"I understand, please disable": "Razumem, molim te isključi",

View File

@@ -44,8 +44,7 @@ export default {
Current: "Тренутно",
Uptime: "Време рада",
"Cert Exp.": "Истек серт.",
days: "дана",
day: "дан",
day: "дан | дана",
"-day": "-дана",
hour: "сат",
"-hour": "-сата",
@@ -85,6 +84,9 @@ export default {
"Update Password": "Измени лозинку",
"Disable Auth": "Искључи аутентификацију",
"Enable Auth": "Укључи аутентификацију",
"disableauth.message1": "Да ли сте сигурни да желите да <strong>искључите аутентификацију</strong>?",
"disableauth.message2": "То је за <strong>оне који имају додату аутентификацију</strong> испред Uptime Kuma као на пример Cloudflare Access.",
"Please use this option carefully!": "Молим Вас користите ово са пажњом.",
Logout: "Одлогуј се",
Leave: "Изађи",
"I understand, please disable": "Разумем, молим те искључи",

View File

@@ -44,8 +44,7 @@ export default {
Current: "Nuvarande",
Uptime: "Drifttid",
"Cert Exp.": "Certifikat utgår",
days: "dagar",
day: "dag",
day: "dag | dagar",
"-day": " dagar",
hour: "timme",
"-hour": " timmar",

View File

@@ -101,6 +101,9 @@ export default {
"Update Password": "อัพเดทรหัสผ่าน",
"Disable Auth": "ปิดใช้งานการตรวจสอบสิทธิ์",
"Enable Auth": "เปิดใช้งานการตรวจสอบสิทธิ์",
"disableauth.message1": "คุณต้องการที่จะ <strong>ปิดใช้งานระบบรับรองความถูกต้องใช่หรือไม่</strong>?",
"disableauth.message2": "ระบบนี้ถูกออกแบบมาเพื่อการใช้งานกับระบบรับรองความถูกต้องของบุคคลที่สามเช่น Cloudflare Access, Authelia หรือวิธีการอื่น ๆ",
"Please use this option carefully!": "โปรดใช้ความระมัดระวังในการเลือกใช้งานระบบนี้ !",
Logout: "ออกจากระบบ",
Leave: "ออก",
"I understand, please disable": "ฉันเข้าใจแล้ว, กรุณาปิดการใช้งาน",

View File

@@ -57,8 +57,7 @@ export default {
Current: "Şu anda",
Uptime: "Çalışma zamanı",
"Cert Exp.": "Sertifika Süresi",
days: "günler",
day: "gün",
day: "gün | günler",
"-day": "-gün",
hour: "saat",
"-hour": "-saat",
@@ -102,6 +101,9 @@ export default {
"Update Password": "Şifreyi Değiştir",
"Disable Auth": "Şifreli girişi iptal et.",
"Enable Auth": "Şifreli girişi aktif et.",
"disableauth.message1": "<strong>Şifreli girişi devre dışı bırakmak istediğinizden</strong>emin misiniz?",
"disableauth.message2": "Bu, Uptime Kuma'nın önünde Cloudflare Access gibi <strong>üçüncü taraf yetkilendirmesi olan</strong> kişiler içindir.",
"Please use this option carefully!": "Lütfen dikkatli kullanın.",
Logout: ıkış yap",
Leave: "Ayrıl",
"I understand, please disable": "Evet farkındayım, iptal et",

View File

@@ -44,8 +44,7 @@ export default {
Current: "Поточний",
Uptime: "Аптайм",
"Cert Exp.": "Сертифікат спливає",
days: "днів",
day: "день",
day: "день | днів",
"-day": " днів",
hour: "година",
"-hour": " години",
@@ -85,6 +84,9 @@ export default {
"Update Password": "Оновити пароль",
"Disable Auth": "Вимкнути авторизацію",
"Enable Auth": "Увімкнути авторизацію",
"disableauth.message1": "Ви впевнені, що бажаєте <strong>вимкнути авторизацію</strong>?",
"disableauth.message2": "Це підходить для <strong>тих, у кого встановлена інша авторизація</strong> пееред відкриттям Uptime Kuma, наприклад Cloudflare Access.",
"Please use this option carefully!": "Будь ласка, використовуйте з обережністю.",
Logout: "Вийти",
Leave: "Відміна",
"I understand, please disable": "Я розумію, все одно відключити",
@@ -351,7 +353,8 @@ export default {
"Start or end with a-z 0-9 only": "Початок та закінчення імені лише на символи: a-z 0-9",
"No consecutive dashes --": "Заборонено використовувати тире --",
"HTTP Options": "HTTP Опції",
"Basic Auth": "HTTP Авторизація",
Authentication: "Аутентифікація",
"HTTP Basic Auth": "HTTP Авторизація",
PushByTechulus: "Push by Techulus",
clicksendsms: "ClickSend SMS",
GoogleChat: "Google Chat (тільки Google Workspace)",

View File

@@ -56,7 +56,6 @@ export default {
Current: "Hiện tại",
Uptime: "Uptime",
"Cert Exp.": "Cert hết hạn",
days: "ngày",
day: "ngày",
"-day": "-ngày",
hour: "giờ",
@@ -101,6 +100,9 @@ export default {
"Update Password": "Cập nhật mật khẩu",
"Disable Auth": "Tắt xác minh",
"Enable Auth": "Bật xác minh",
"disableauth.message1": "Bạn có muốn <strong>TẮT XÁC THỰC</strong> không?",
"disableauth.message2": "Điều này rất nguy hiểm<strong>BẤT KỲ AI</strong> cũng có thể truy cập và cướp quyền điều khiển.",
"Please use this option carefully!": "Vui lòng <strong>cẩn thận</strong>.",
Logout: "Đăng xuất",
Leave: "Rời",
"I understand, please disable": "Tôi hiểu, làm ơn hãy tắt!",

View File

@@ -57,7 +57,6 @@ export default {
Current: "当前",
Uptime: "在线时间",
"Cert Exp.": "证书有效期",
days: "天",
day: "天",
"-day": " 天",
hour: "小时",
@@ -102,6 +101,9 @@ export default {
"Update Password": "更新密码",
"Disable Auth": "禁用身份验证",
"Enable Auth": "启用身份验证",
"disableauth.message1": "是否确定 <strong>取消登录验证</strong>",
"disableauth.message2": "这是为 <strong>有第三方认证</strong> 的用户提供的功能,如 Cloudflare Access",
"Please use this option carefully!": "请谨慎使用!",
Logout: "退出",
Leave: "离开",
"I understand, please disable": "我已了解,继续禁用",
@@ -437,6 +439,7 @@ export default {
Next: "下一步",
"The slug is already taken. Please choose another slug.": "该路径已被使用。请选择其他路径。",
"No Proxy": "无代理",
Authentication: "验证",
"HTTP Basic Auth": "HTTP 基础身份验证",
"New Status Page": "新的状态页",
"Page Not Found": "未找到该页面",
@@ -520,4 +523,21 @@ export default {
wayToGetClickSendSMSToken: "您可以从 {0} 获取 API 凭证 Username 和 凭证 Key。",
signedInDisp: "当前用户: {0}",
signedInDispDisabled: "已禁用身份验证",
dnsPortDescription: "DNS 服务器端口,默认为 53你可以在任何时候更改此端口.",
error: "错误",
critical: "关键",
wayToGetPagerDutyKey: "你可以在 Service -> Service Directory -> (选择一个 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": "自动标记为已解决",
"Connection String": "连接字符串",
Query: "查询语句",
settingsCertificateExpiry: "TLS 证书过期通知",
certificationExpiryDescription: "HTTPS 监控项发现被监控目标的 TLS 证书剩余有效期少于以下天数时将发出通知:",
"ntfy Topic": "ntfy 主题",
"Domain": "域名",
"Workstation": "工作站",
};

View File

@@ -30,7 +30,6 @@ export default {
Current: "目前",
Uptime: "上線率",
"Cert Exp.": "証書期限",
days: "日",
day: "日",
"-day": "日",
hour: "小時",
@@ -78,6 +77,9 @@ export default {
"Update Password": "更新密碼",
"Disable Auth": "取消登入認証",
"Enable Auth": "開啟登入認証",
"disableauth.message1": "你是否確認<strong>取消登入認証</strong>",
"disableauth.message2": "這個功能是設計給已有<strong>第三方認証</strong>的用家,例如 Cloudflare Access。",
"Please use this option carefully!": "請小心使用。",
Logout: "登出",
notificationDescription: "新增後,你需要在監測器裡啟用。",
Leave: "離開",

View File

@@ -56,7 +56,6 @@ export default {
Current: "目前",
Uptime: "運作率",
"Cert Exp.": "憑證期限",
days: "天",
day: "天",
"-day": "天",
hour: "小時",
@@ -101,6 +100,9 @@ export default {
"Update Password": "更新密碼",
"Disable Auth": "停用驗證",
"Enable Auth": "啟用驗證",
"disableauth.message1": ">你是否要<strong>取消登入驗證</strong>",
"disableauth.message2": "此功能是設計給已有<strong>第三方認證</strong>的使用者,例如 Cloudflare Access。",
"Please use this option carefully!": "請謹慎使用。",
Logout: "登出",
Leave: "離開",
"I understand, please disable": "我了解了,請停用",
@@ -429,6 +431,7 @@ export default {
Next: "下一步",
"The slug is already taken. Please choose another slug.": "此 slug 已被使用。請選擇其他 slug。",
"No Proxy": "無 Proxy",
Authentication: "驗證",
"HTTP Basic Auth": "HTTP 基本驗證",
"New Status Page": "新狀態頁",
"Page Not Found": "找不到頁面",

View File

@@ -18,14 +18,31 @@ export default {
},
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) {
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) {
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) {
let secondString;
if (second) {
@@ -36,6 +53,12 @@ export default {
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) {
if (value !== undefined && value !== "") {
return dayjs.utc(value).tz(this.timezone).format(format);

View File

@@ -22,6 +22,7 @@ export default {
},
methods: {
/** Change the application language */
async changeLang(lang) {
let message = (await langModules["../languages/" + lang + ".js"]()).default;
this.$i18n.setLocaleMessage(lang, message);

View File

@@ -12,11 +12,13 @@ export default {
},
methods: {
/** Handle screen resize */
onResize() {
this.windowWidth = window.innerWidth;
this.updateBody();
},
/** Add css-class "mobile" to body if needed */
updateBody() {
if (this.isMobile) {
document.body.classList.add("mobile");

View File

@@ -62,6 +62,12 @@ export default {
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) {
// No need to re-init
if (this.socket.initedSocketIO) {
@@ -258,10 +264,18 @@ export default {
socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res);
},
/**
* The storage currently in use
* @returns {Storage}
*/
storage() {
return (this.remember) ? localStorage : sessionStorage;
},
/**
* Get payload of JWT cookie
* @returns {(Object|undefined)}
*/
getJWTPayload() {
const jwtToken = this.$root.storage().token;
@@ -271,10 +285,18 @@ export default {
return undefined;
},
/**
* Get current socket
* @returns {Socket}
*/
getSocket() {
return socket;
},
/**
* Show success or error toast dependant on response status code
* @param {Object} res Response object
*/
toastRes(res) {
if (res.ok) {
toast.success(res.msg);
@@ -283,14 +305,35 @@ export default {
}
},
/**
* Show a success toast
* @param {string} msg Message to show
*/
toastSuccess(msg) {
toast.success(msg);
},
/**
* Show an error toast
* @param {string} msg Message to show
*/
toastError(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) {
socket.emit("login", {
username,
@@ -315,6 +358,10 @@ export default {
});
},
/**
* Log in using a token
* @param {string} token Token to log in with
*/
loginByToken(token) {
socket.emit("loginByToken", token, (res) => {
this.allowLoginDialog = true;
@@ -328,6 +375,7 @@ export default {
});
},
/** Log out of the web application */
logout() {
socket.emit("logout", () => { });
this.storage().removeItem("token");
@@ -337,26 +385,54 @@ export default {
this.clearData();
},
/**
* Callback for general socket requests
* @callback socketCB
* @param {Object} res Result of operation
*/
/** Prepare 2FA configuration */
prepare2FA(callback) {
socket.emit("prepare2FA", callback);
},
/**
* Save the current 2FA configuration
* @param {any} secret Unused
* @param {socketCB} callback
*/
save2FA(secret, callback) {
socket.emit("save2FA", callback);
},
/**
* Disable 2FA for this user
* @param {socketCB} callback
*/
disable2FA(callback) {
socket.emit("disable2FA", callback);
},
/**
* Verify the provided 2FA token
* @param {string} token Token to verify
* @param {socketCB} callback
*/
verifyToken(token, callback) {
socket.emit("verifyToken", token, callback);
},
/**
* Get current 2FA status
* @param {socketCB} callback
*/
twoFAStatus(callback) {
socket.emit("twoFAStatus", callback);
},
/**
* Get list of monitors
* @param {socketCB} callback
*/
getMonitorList(callback) {
if (! callback) {
callback = () => { };
@@ -364,36 +440,74 @@ export default {
socket.emit("getMonitorList", callback);
},
/**
* Add a monitor
* @param {Object} monitor Object representing monitor to add
* @param {socketCB} callback
*/
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) {
socket.emit("deleteMonitor", monitorID, callback);
},
/** Clear the hearbeat list */
clearData() {
console.log("reset heartbeat list");
this.heartbeatList = {};
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) {
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) {
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) {
socket.emit("clearHeartbeats", monitorID, callback);
},
/**
* Clear all statistics
* @param {socketCB} callback
*/
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) {
socket.emit("getMonitorBeats", monitorID, period, callback);
}

View File

@@ -75,6 +75,7 @@ export default {
},
methods: {
/** Update the theme color meta tag */
updateThemeColorMeta() {
if (this.theme === "dark") {
document.querySelector("#theme-color").setAttribute("content", "#161B22");

View File

@@ -51,6 +51,7 @@ export default {
};
},
methods: {
/** Submit form data to add new status page */
async submit() {
this.processing = true;

View File

@@ -77,7 +77,7 @@
<h4>{{ $t("Cert Exp.") }}</h4>
<p>(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p>
<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>
</div>
</div>
@@ -289,39 +289,47 @@ export default {
},
methods: {
/** Request a test notification be sent for this monitor */
testNotification() {
this.$root.getSocket().emit("testNotification", this.monitor.id);
toast.success("Test notification is requested.");
},
/** Show dialog to confirm pause */
pauseDialog() {
this.$refs.confirmPause.show();
},
/** Resume this monitor */
resumeMonitor() {
this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => {
this.$root.toastRes(res);
});
},
/** Request that this monitor is paused */
pauseMonitor() {
this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => {
this.$root.toastRes(res);
});
},
/** Show dialog to confirm deletion */
deleteDialog() {
this.$refs.confirmDelete.show();
},
/** Show dialog to confirm clearing events */
clearEventsDialog() {
this.$refs.confirmClearEvents.show();
},
/** Show dialog to confirm clearing heartbeats */
clearHeartbeatsDialog() {
this.$refs.confirmClearHeartbeats.show();
},
/** Request that this monitor is deleted */
deleteMonitor() {
this.$root.deleteMonitor(this.monitor.id, (res) => {
if (res.ok) {
@@ -333,6 +341,7 @@ export default {
});
},
/** Request that this monitors events are cleared */
clearEvents() {
this.$root.clearEvents(this.monitor.id, (res) => {
if (! res.ok) {
@@ -341,6 +350,7 @@ export default {
});
},
/** Request that this monitors heartbeats are cleared */
clearHeartbeats() {
this.$root.clearHeartbeats(this.monitor.id, (res) => {
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) {
let translationPrefix = "";
if (average) {

View File

@@ -11,30 +11,41 @@
<div class="my-3">
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
<select id="type" v-model="monitor.type" class="form-select">
<option value="http">
HTTP(s)
</option>
<option value="port">
TCP Port
</option>
<option value="ping">
Ping
</option>
<option value="keyword">
HTTP(s) - {{ $t("Keyword") }}
</option>
<option value="dns">
DNS
</option>
<option value="push">
Push
</option>
<option value="steam">
{{ $t("Steam Game Server") }}
</option>
<option value="mqtt">
MQTT
</option>
<optgroup label="General Monitor Type">
<option value="http">
HTTP(s)
</option>
<option value="port">
TCP Port
</option>
<option value="ping">
Ping
</option>
<option value="keyword">
HTTP(s) - {{ $t("Keyword") }}
</option>
<option value="dns">
DNS
</option>
</optgroup>
<optgroup label="Passive Monitor Type">
<option value="push">
Push
</option>
</optgroup>
<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>
</div>
@@ -157,6 +168,18 @@
</div>
</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 -->
<div class="my-3">
<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>
</div>
<!-- HTTP Basic Auth -->
<h4 class="mt-5 mb-2">{{ $t("HTTP Basic Auth") }}</h4>
<!-- HTTP Auth -->
<h4 class="mt-5 mb-2">{{ $t("Authentication") }}</h4>
<!-- Method -->
<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')">
<label for="method" class="form-label">{{ $t("Method") }}</label>
<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>
<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">
<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')">
</div>
<div class="my-3">
<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')">
</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>
</div>
</div>
@@ -484,7 +535,7 @@ export default {
if (this.monitor.type === "dns") {
this.monitor.port = "53";
} else {
this.monitor.port = "";
this.monitor.port = undefined;
}
}
}
@@ -522,6 +573,7 @@ export default {
this.dnsresolvetypeOptions = dnsresolvetypeOptions;
},
methods: {
/** Initialize the edit monitor form */
init() {
if (this.isAdd) {
@@ -532,6 +584,7 @@ export default {
method: "GET",
interval: 60,
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,
notificationIDList: {},
ignoreTls: false,
@@ -546,6 +599,7 @@ export default {
mqttPassword: "",
mqttTopic: "",
mqttSuccessMessage: "",
authMethod: null,
};
if (this.$root.proxyList && !this.monitor.proxyId) {
@@ -578,6 +632,10 @@ export default {
},
/**
* Validate form input
* @returns {boolean} Is the form input valid?
*/
isInputValid() {
if (this.monitor.body) {
try {
@@ -598,6 +656,10 @@ export default {
return true;
},
/**
* Submit the form data for processing
* @returns {void}
*/
async submit() {
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) {
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) {
this.monitor.proxyId = id;
},

View File

@@ -1,6 +1,6 @@
<template>
<transition name="slide-fade" appear>
<MonitorList />
<MonitorList :scrollbar="true" />
</transition>
</template>
@@ -14,3 +14,11 @@ export default {
};
</script>
<style lang="scss" scoped>
@import "../assets/vars";
.shadow-box {
padding: 20px;
}
</style>

View File

@@ -51,6 +51,11 @@ export default {
},
methods: {
/**
* Get the correct URL for the icon
* @param {string} icon Path for icon
* @returns {string} Correctly formatted path including port numbers
*/
icon(icon) {
if (icon === "/icon.svg") {
return icon;
@@ -86,6 +91,7 @@ export default {
.logo {
width: $logo-width;
height: $logo-width;
// Better when the image is loading
min-height: 1px;

View File

@@ -32,6 +32,7 @@
<ul>
<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">Go back to home page.</a></li>
</ul>
</div>
</div>
@@ -44,6 +45,7 @@ export default {
},
methods: {
/** Go back 1 in browser history */
goBack() {
history.back();
}

View File

@@ -118,13 +118,17 @@ export default {
methods: {
// For desktop only, mobile do nothing
/**
* Load the general settings page
* For desktop only, on mobile do nothing
*/
loadGeneralPage() {
if (!this.currentPage && !this.$root.isMobile) {
this.$router.push("/settings/general");
}
},
/** Load settings from server */
loadSettings() {
this.$root.getSocket().emit("getSettings", (res) => {
this.settings = res.data;
@@ -145,13 +149,24 @@ export default {
this.settings.keepDataPeriodDays = 180;
}
if (this.settings.tlsExpiryNotifyDays === undefined) {
this.settings.tlsExpiryNotifyDays = [ 7, 14, 21 ];
}
this.settingsLoaded = true;
});
},
/**
* Callback for saving settings
* @callback saveSettingsCB
* @param {Object} res Result of operation
*/
/**
* 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) {
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {

View File

@@ -71,6 +71,10 @@ export default {
});
},
methods: {
/**
* Submit form data for processing
* @returns {void}
*/
submit() {
this.processing = true;

View File

@@ -98,7 +98,7 @@
<h1 class="mb-4 title-flex">
<!-- Logo -->
<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" />
</span>
@@ -332,6 +332,7 @@ export default {
},
props: {
/** Override for the status page slug */
overrideSlug: {
type: String,
required: false,
@@ -538,7 +539,7 @@ export default {
this.slug = "default";
}
axios.get("/api/status-page/" + this.slug).then((res) => {
this.getData().then((res) => {
this.config = res.data.config;
if (!this.config.domainNameList) {
@@ -551,6 +552,11 @@ export default {
this.incident = res.data.incident;
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
@@ -567,10 +573,31 @@ export default {
},
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) {
return highlight(code, languages.css);
},
/** Update the heartbeat list and update favicon if neccessary */
updateHeartbeatList() {
// If editMode, it will use the data from websocket.
if (! this.editMode) {
@@ -599,14 +626,19 @@ export default {
}
},
/** Enable editing mode */
edit() {
if (this.hasToken) {
this.$root.initSocketIO(true);
this.enableEditMode = true;
this.clickedEditButton = true;
// Try to fix #1658
this.loadedData = true;
}
},
/** Save the status page */
save() {
let startTime = new Date();
this.config.slug = this.config.slug.trim().toLowerCase();
@@ -634,10 +666,12 @@ export default {
});
},
/** Show dialog confirming deletion */
deleteDialog() {
this.$refs.confirmDelete.show();
},
/** Request deletion of this status page */
deleteStatusPage() {
this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => {
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) {
return `${monitor.name}`;
},
/** Add a group to the status page */
addGroup() {
let groupName = this.$t("Untitled Group");
@@ -666,32 +706,32 @@ export default {
});
},
/** Add a domain to the status page */
addDomainField() {
this.config.domainNameList.push("");
},
/** Discard changes to status page */
discard() {
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) {
this.imgDataUrl = imgDataUrl;
},
/** Show image crop dialog if in edit mode */
showImageCropUploadMethod() {
if (this.editMode) {
this.showImageCropUpload = true;
}
},
statusPageLogoLoaded(eventPayload) {
// Remark: may not work in dev, due to CORS
favicon.image(eventPayload.target);
},
/** Create an incident for this status page */
createIncident() {
this.enableEditIncidentMode = true;
@@ -706,6 +746,7 @@ export default {
};
},
/** Post the incident to the status page */
postIncident() {
if (this.incident.title === "" || this.incident.content === "") {
toast.error(this.$t("Please input title and content"));
@@ -725,14 +766,13 @@ export default {
},
/**
* Click Edit Button
*/
/** Click Edit Button */
editIncident() {
this.enableEditIncidentMode = true;
this.previousIncident = Object.assign({}, this.incident);
},
/** Cancel creation or editing of incident */
cancelIncident() {
this.enableEditIncidentMode = false;
@@ -742,16 +782,25 @@ export default {
}
},
/** Unpin the incident */
unpinIncident() {
this.$root.getSocket().emit("unpinIncident", this.slug, () => {
this.incident = null;
});
},
/**
* Get the relative time difference of a date from now
* @returns {string}
*/
dateFromNow(date) {
return dayjs.utc(date).fromNow();
},
/**
* Remove a domain from the status page
* @param {number} index Index of domain to remove
*/
removeDomain(index) {
this.config.domainNameList.splice(index, 1);
},

View File

@@ -1,4 +1,5 @@
import { createRouter, createWebHistory } from "vue-router";
import EmptyLayout from "./layouts/EmptyLayout.vue";
import Layout from "./layouts/Layout.vue";
import Dashboard from "./pages/Dashboard.vue";
@@ -8,22 +9,23 @@ import EditMonitor from "./pages/EditMonitor.vue";
import List from "./pages/List.vue";
const Settings = () => import("./pages/Settings.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 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 AddStatusPage from "./pages/AddStatusPage.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 = [
{
path: "/",
@@ -63,12 +65,12 @@ const routes = [
path: "/add",
component: EditMonitor,
},
{
path: "/list",
component: List,
},
],
},
{
path: "/list",
component: List,
},
{
path: "/settings",
component: Settings,

Some files were not shown because too many files have changed in this diff Show More