Compare commits

...

123 Commits
1.0.5 ... 1.0.7

Author SHA1 Message Date
LouisLam
4d7c2d329b Update to 1.0.7 2021-07-27 13:47:15 +08:00
Louis Lam
1982e2f8b8 Update README.md 2021-07-27 00:53:26 +08:00
LouisLam
2819094377 improve the page load performance 2021-07-26 23:26:47 +08:00
LouisLam
06c4523ce3 update the latest db version to 3 2021-07-26 23:05:04 +08:00
LouisLam
ac3732f6cc Merge remote-tracking branch 'chakflying/tls-expiry' into tls-expiry
# Conflicts:
#	server/model/monitor.js
#	src/pages/Details.vue
2021-07-26 22:56:17 +08:00
LouisLam
bf3e9dccd2 improve the ui of cert info 2021-07-26 22:53:07 +08:00
LouisLam
5b18a6a518 Merge branch 'master' into tls-expiry
# Conflicts:
#	server/model/monitor.js
2021-07-26 20:35:50 +08:00
LouisLam
caec933186 prevent unexpected error throw from checkCertificate interrupt the beat 2021-07-26 12:25:44 +08:00
Nelson Chan
51ac7a58dc Fix: Fix incorrect error handling 2021-07-26 12:24:13 +08:00
Nelson Chan
db26b7d123 Fix: Fix no certificate caused by session reuse 2021-07-26 12:24:13 +08:00
Nelson Chan
7b8459c73a Fix: use Optional chaining 2021-07-26 12:24:12 +08:00
Nelson Chan
d0c63ebe3e Feat: Add database storage for TLS info 2021-07-26 12:24:12 +08:00
Nelson Chan
803f0d6219 Feat: Add Barebones certificate info display 2021-07-26 12:24:06 +08:00
LouisLam
d556509d07 戈mprove the readibility of important condition 2021-07-24 11:42:14 +08:00
Louis Lam
3cc4955cad Merge pull request #105 from rezzorix/master
Apple touch icon 192px with preserved transparency
2021-07-23 13:00:37 +08:00
LouisLam
48f82b55f8 prevent unexpected error throw from checkCertificate interrupt the beat 2021-07-23 12:58:05 +08:00
rezzorix
280ba84aca Apple touch icon 192px with preserved transparency
Resized icon.png to 192px but preserved transparency
2021-07-23 12:41:02 +08:00
Louis Lam
0dbecca10f Merge pull request #102 from NiNiyas/pushover-enhancements
Pushover enhancements
2021-07-23 12:32:36 +08:00
Louis Lam
8279368b4d Merge pull request #104 from rezzorix/patch-1
Small grammar updates to Settings.vue
2021-07-23 11:57:32 +08:00
rezzorix
2450b3d082 Small grammar updates to Settings.vue
Just small grammar corrections.
2021-07-23 11:48:39 +08:00
Nelson Chan
6b72d5033a Fix: Fix incorrect error handling 2021-07-23 11:23:43 +08:00
Nelson Chan
4d262bbb6a Fix: Fix no certificate caused by session reuse 2021-07-23 11:22:37 +08:00
Louis Lam
d1370a62bd Merge pull request #103 from Spiritreader/master
Fix parenthesis mistake in notification checker (fixes #86)
2021-07-23 09:11:14 +08:00
Sam
063fd6ef43 Merge branch 'master' of https://github.com/Spiritreader/uptime-kuma 2021-07-22 20:25:08 +02:00
Sam
1d4d7fa9c4 fix parenthesis mistake 2021-07-22 20:25:03 +02:00
Louis Lam
248b5292dc Merge pull request #86 from Spiritreader/master
Implement retries (#56)
2021-07-23 00:11:24 +08:00
Niyas
77fbfc23be Pushover enhancements 2021-07-22 19:28:25 +05:30
Niyas
b7a32d4ab6 Pushover enhancements 2021-07-22 19:26:54 +05:30
LouisLam
5a219554b3 grammar 2021-07-22 19:49:46 +08:00
LouisLam
70b1f197c1 rename "Retry Pings" to "Retries" 2021-07-22 19:02:44 +08:00
LouisLam
32a5e838ba add patch3.sql and fix duplicate id in EditMonitor.vue 2021-07-22 17:44:59 +08:00
LouisLam
86e18ac11d Merge branch 'master' into Spiritreader_master
# Conflicts:
#	src/pages/EditMonitor.vue
2021-07-22 17:34:41 +08:00
Nelson Chan
f20ab4b0e3 Fix: use Optional chaining 2021-07-22 16:13:58 +08:00
Nelson Chan
96c60dd94a Feat: Add database storage for TLS info 2021-07-22 16:04:32 +08:00
Nelson Chan
ccda6f05f5 Feat: Add Barebones certificate info display 2021-07-22 14:26:43 +08:00
LouisLam
03b3bb5b30 fix if notification throw exception, the heartbeat is not stored in to the db. 2021-07-22 12:28:47 +08:00
LouisLam
7e4a1ad279 remove used vars 2021-07-22 11:15:53 +08:00
LouisLam
916b9da0dc Merge branch 'master' into something
# Conflicts:
#	server/notification.js
#	src/components/NotificationDialog.vue
2021-07-22 11:12:52 +08:00
LouisLam
a64ce81457 update package-lock.json 2021-07-22 10:55:55 +08:00
LouisLam
c575afc8e0 Merge remote-tracking branch 'origin/master' 2021-07-22 10:47:39 +08:00
Louis Lam
1e42343aee Update patch1.sql
minor
2021-07-22 10:45:22 +08:00
LouisLam
afd4cf2425 Merge branch 'master' into simple_pagination 2021-07-22 10:42:30 +08:00
LouisLam
e02eb72863 add db migration 2021-07-22 02:02:35 +08:00
LouisLam
1c0dc18d72 Merge remote-tracking branch 'origin/master' 2021-07-22 01:30:22 +08:00
Louis Lam
c00612c1a9 Update --please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md 2021-07-21 16:29:15 +08:00
Louis Lam
32345fcbe9 Update issue templates 2021-07-21 16:28:31 +08:00
Louis Lam
fd90458e77 Update issue templates 2021-07-21 16:25:58 +08:00
Louis Lam
d89e6f4649 Merge pull request #89 from Saibamen/more_info_in_server_logs
More info in server logs
2021-07-21 11:34:27 +08:00
Adam Stachowicz
c4ca8e2acb More info in server logs 2021-07-21 00:41:38 +02:00
LouisLam
94b5a557bf Merge remote-tracking branch 'origin/master' 2021-07-20 23:43:59 +08:00
Sam
14e1d1f105 add .vscode directory to dockerignore 2021-07-20 17:39:21 +02:00
Sam
8b905b6b12 Indentation fix in editor
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-07-20 17:38:21 +02:00
Louis Lam
fa57d40c3c Update README.md 2021-07-20 20:27:34 +08:00
Louis Lam
62e231e92b Update README.md 2021-07-20 20:27:15 +08:00
LouisLam
02b4dfc100 prevent the telegram getUpdates URL go out of box 2021-07-20 20:18:56 +08:00
LouisLam
657acf748b update to 1.0.6 2021-07-20 20:04:54 +08:00
Sam
054269ecf0 fix notification when changing from pending -> up 2021-07-20 11:50:33 +02:00
Louis Lam
71dd68bb6d Update README.md 2021-07-20 13:04:21 +08:00
Sam
02230930c5 Merge branch 'master' of https://github.com/Spiritreader/uptime-kuma 2021-07-19 18:26:00 +02:00
Sam
a8b102ad4a add retries for pinging function
backend:
- new field for monitor: maxretries
- new pending status while service is retrying: 2
- pending status event is not marked important
- pending pings however register as downtime in the calculation

frontend:
- added pending status while service is retrying
- added color for new pending status
- added field to configure amount of retries

database:
- IMPORTANT: THIS REQUIRES MIGRATION!!!!
- added field: maxretries with default value 0
2021-07-19 18:23:06 +02:00
Louis Lam
58d029445d Merge pull request #79 from Saibamen/remove_debug_spam
Remove debug spam
2021-07-19 23:12:33 +08:00
LouisLam
77af41bfff env default to production 2021-07-19 23:06:42 +08:00
Louis Lam
058032a26a Merge pull request #81 from Saibamen/use_NODE_ENV
Use `NODE_ENV` from Express and Socket.IO
2021-07-19 22:59:36 +08:00
Louis Lam
cbb9d3f91b set version for docker 2021-07-19 20:18:27 +08:00
Adam Stachowicz
5bd3184ebf Use connect_error event 2021-07-18 20:59:00 +02:00
Adam Stachowicz
59ebe134f1 Fix indentation 2021-07-18 20:46:45 +02:00
Adam Stachowicz
851ceef3d5 Use NODE_ENV from Express and Socket.IO 2021-07-18 20:21:17 +02:00
LouisLam
efd7608ba2 Merge remote-tracking branch 'origin/master' 2021-07-19 00:43:45 +08:00
LouisLam
05fdaf0c96 update package-lock.json 2021-07-19 00:43:25 +08:00
Louis Lam
01b0e82d52 Update README.md 2021-07-19 00:40:10 +08:00
Louis Lam
d2ccfd5366 Update README.md 2021-07-19 00:39:07 +08:00
Adam Stachowicz
7cba9ce231 Remove debug spam 2021-07-18 18:35:40 +02:00
Louis Lam
69e8c56e3e Update README.md 2021-07-19 00:34:48 +08:00
Adam Stachowicz
9928ea8c30 Merge branch 'master' into simple_pagination 2021-07-18 16:48:53 +02:00
LouisLam
25c370c9ff Merge branch 'update_packages'
# Conflicts:
#	package-lock.json
#	package.json
2021-07-18 22:22:19 +08:00
LouisLam
9227ff6ea3 add nightly build for amd64 only 2021-07-18 22:21:34 +08:00
Adam Stachowicz
2d943620c7 Merge branch 'master' into simple_pagination 2021-07-18 15:37:32 +02:00
Louis Lam
f7bd67c413 Merge pull request #77 from Saibamen/fix_docker
Fix Docker build
2021-07-18 20:53:06 +08:00
LouisLam
9ca2444dab improve testing notification response 2021-07-18 20:49:46 +08:00
Adam Stachowicz
1d45a7606d Fix Docker build 2021-07-18 13:51:44 +02:00
Adam Stachowicz
16f363ac38 Merge branch 'master' into simple_pagination 2021-07-18 13:36:57 +02:00
Louis Lam
d6b9403f60 Merge pull request #76 from Saibamen/fix_remember_me
Fix multiple labels for `Remember me`
2021-07-18 19:28:48 +08:00
Louis Lam
92e5ddd97c Merge pull request #69 from Saibamen/dockerignore
Update .dockerignore
2021-07-18 19:27:43 +08:00
Louis Lam
23611e540c Merge pull request #70 from Saibamen/fix_npm_warnings
Fix NPM warnings
2021-07-18 19:27:00 +08:00
Adam Stachowicz
f9274557f3 Fix center 2021-07-18 13:22:39 +02:00
Adam Stachowicz
44b66cbd2e Fix Remember me label 2021-07-18 13:00:59 +02:00
LouisLam
66037e236c add apprise support 2021-07-18 18:51:58 +08:00
Adam Stachowicz
386c002fda Merge branch 'master' into fix_npm_warnings 2021-07-18 11:48:08 +02:00
Adam Stachowicz
7c1aab6a15 Merge branch 'master' into dockerignore 2021-07-18 11:47:53 +02:00
Adam Stachowicz
37884cfd08 Merge branch 'master' into update_packages 2021-07-18 11:47:08 +02:00
Adam Stachowicz
ce6841eae7 Merge branch 'master' into simple_pagination 2021-07-18 11:46:32 +02:00
Adam Stachowicz
7c94c3b502 Update server/notification.js 2021-07-18 09:42:34 +00:00
Adam Stachowicz
268c8e50f5 Merge branch 'master' into something 2021-07-18 09:42:08 +00:00
LouisLam
13c9244e3f fix apprise import issue and loose the healthcheck rule 2021-07-18 17:42:00 +08:00
Louis Lam
13b3a5be9c Merge pull request #68 from Saibamen/use_console_error
Improve printing to console
2021-07-18 14:55:30 +08:00
Louis Lam
2e31e780a1 Merge pull request #67 from Saibamen/lighthouse_improvements
[Lighthouse] Some improvements
2021-07-18 13:48:35 +08:00
Louis Lam
6a2b5f9dd8 Merge pull request #66 from Saibamen/resize_apple_icon
[Lighthouse] Resize apple icon to 192px
2021-07-18 13:47:50 +08:00
Louis Lam
16767dc042 Merge pull request #65 from Saibamen/robots
[Lighthouse] Add robots.txt
2021-07-18 13:47:39 +08:00
Louis Lam
fb3e000dc3 Merge pull request #63 from NiNiyas/docker-healthcheck
Docker Healthcheck
2021-07-18 13:47:25 +08:00
Louis Lam
6f3ea21864 Merge pull request #61 from NiNiyas/slack-enhancements
Slack Enhancements and aligns footer to center
2021-07-18 13:47:04 +08:00
Louis Lam
403137280e Merge pull request #62 from NiNiyas/pushover-support
Pushover support
2021-07-18 12:20:41 +08:00
Adam Stachowicz
d94894b7e0 Fix require-v-for-key, remove unused declarations and double spaces 2021-07-18 03:10:15 +02:00
Adam Stachowicz
a173700cd4 Add pagination 2021-07-18 03:04:40 +02:00
Adam Stachowicz
9c248776e7 Update dependencies 2021-07-18 00:08:35 +02:00
Adam Stachowicz
309caa4279 Fix indentation 2021-07-17 23:57:48 +02:00
Adam Stachowicz
28b14ceb70 Fix NPM warnings 2021-07-17 23:45:36 +02:00
Adam Stachowicz
9f9c42c30b Update .dockerignore 2021-07-17 23:31:44 +02:00
Adam Stachowicz
2bff62cade Improve printing to console 2021-07-17 23:13:54 +02:00
Adam Stachowicz
bfb4a5bcd4 Add alt="Logo" 2021-07-17 22:56:54 +02:00
Adam Stachowicz
e87b78501b rel="noopener" for external link 2021-07-17 22:55:53 +02:00
Adam Stachowicz
637422494f Add description 2021-07-17 22:53:32 +02:00
Adam Stachowicz
db34484ff2 Add theme-color 2021-07-17 22:51:07 +02:00
Adam Stachowicz
790c071f2c Resize apple icon to 192px 2021-07-17 22:45:28 +02:00
Adam Stachowicz
149688e669 Add robots.txt 2021-07-17 22:24:10 +02:00
Niyas
7dae5279fb Docker healthcheck
Copied from https://scoutapm.com/blog/how-to-use-docker-healthcheck
2021-07-17 20:08:01 +05:30
Niyas
c203317b3b Docker healthcheck
Copied from https://scoutapm.com/blog/how-to-use-docker-healthcheck
2021-07-17 20:07:35 +05:30
Niyas
01f2fccb23 Update notification.js 2021-07-17 18:59:26 +05:30
Niyas
7808aef58f Pushover support 2021-07-17 18:42:54 +05:30
Niyas
ce2d78f45a Pushover support 2021-07-17 17:55:02 +05:30
Niyas
20cad50593 Pushover support 2021-07-17 17:49:56 +05:30
Niyas
829a2a191d Footer center align 2021-07-17 13:42:05 +05:30
Niyas
65b320d06b Slack Enhancements 2021-07-17 12:48:42 +05:30
Niyas
1935da5b16 Slack Enhancements 2021-07-17 12:47:52 +05:30
39 changed files with 1204 additions and 303 deletions

View File

@@ -3,3 +3,12 @@
/node_modules
/data/kuma.db
/.do
**/.dockerignore
**/.git
**/.gitignore
**/docker-compose*
**/Dockerfile*
LICENSE
README.md
.editorconfig
.vscode

View File

@@ -0,0 +1,10 @@
---
name: ⚠ Please go to "Discussions" Tab if you want to ask or share something
about: BUG REPORT ONLY HERE
title: ''
labels: ''
assignees: ''
---
BUG REPORT ONLY HERE

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- Uptime Kuma Version:
- Using Docker?: Yes/No
- OS:
- Browser:
**Additional context**
Add any other context about the problem here.

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ dist-ssr
/data
!/data/.gitkeep
.vscode

View File

@@ -15,7 +15,7 @@ It is a self-hosted monitoring tool like "Uptime Robot".
* Monitoring uptime for HTTP(s) / TCP / Ping.
* Fancy, Reactive, Fast UI/UX.
* Notifications via Webhook, Telegram, Discord and email (SMTP).
* Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise.
* 20 seconds interval.
# How to Use
@@ -27,7 +27,7 @@ It is a self-hosted monitoring tool like "Uptime Robot".
docker volume create uptime-kuma
# Start the container
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
```
Browse to http://localhost:3001 after started.
@@ -35,7 +35,7 @@ Browse to http://localhost:3001 after started.
Change Port and Volume
```bash
docker run -d --restart=always -p <YOUR_PORT>:3001 -v <YOUR_DIR OR VOLUME>:/app/data --name uptime-kuma louislam/uptime-kuma
docker run -d --restart=always -p <YOUR_PORT>:3001 -v <YOUR_DIR OR VOLUME>:/app/data --name uptime-kuma louislam/uptime-kuma:1
```
### Without Docker
@@ -80,12 +80,17 @@ PS: For every new release, it takes some time to build the docker image, please
```bash
git fetch --all
git checkout 1.0.5 --force
git checkout 1.0.7 --force
npm install
npm run build
pm2 restart uptime-kuma
```
# What's Next?
I will mark requests/issues to the next milestone.
https://github.com/louislam/uptime-kuma/milestones
# More Screenshots
Settings Page:
@@ -109,3 +114,11 @@ Telegram Notification Sample:
If you love this project, please consider giving me a ⭐.
# Contribute
If you want to report a bug or request a new feature. Free feel to open a new issue.
If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/wiki/%5BDev%5D-Setup-Development-Environment
English proofreading is needed too, because my grammar is not that great sadly. Feel free to correct my grammar in this Readme, source code or wiki.

Binary file not shown.

37
db/patch1.sql Normal file
View File

@@ -0,0 +1,37 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
-- Change Monitor.created_date from "TIMESTAMP" to "DATETIME"
-- SQL Generated by Intellij Idea
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
create table monitor_dg_tmp
(
id INTEGER not null
primary key autoincrement,
name VARCHAR(150),
active BOOLEAN default 1 not null,
user_id INTEGER
references user
on update cascade on delete set null,
interval INTEGER default 20 not null,
url TEXT,
type VARCHAR(20),
weight INTEGER default 2000,
hostname VARCHAR(255),
port INTEGER,
created_date DATETIME,
keyword VARCHAR(255)
);
insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword from monitor;
drop table monitor;
alter table monitor_dg_tmp rename to monitor;
create index user_id on monitor (user_id);
COMMIT;
PRAGMA foreign_keys=on;

9
db/patch2.sql Normal file
View File

@@ -0,0 +1,9 @@
BEGIN TRANSACTION;
CREATE TABLE monitor_tls_info (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
monitor_id INTEGER NOT NULL,
info_json TEXT
);
COMMIT;

37
db/patch3.sql Normal file
View File

@@ -0,0 +1,37 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
-- Add maxretries column to monitor
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
create table monitor_dg_tmp
(
id INTEGER not null
primary key autoincrement,
name VARCHAR(150),
active BOOLEAN default 1 not null,
user_id INTEGER
references user
on update cascade on delete set null,
interval INTEGER default 20 not null,
url TEXT,
type VARCHAR(20),
weight INTEGER default 2000,
hostname VARCHAR(255),
port INTEGER,
created_date DATETIME,
keyword VARCHAR(255),
maxretries INTEGER NOT NULL DEFAULT 0
);
insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword from monitor;
drop table monitor;
alter table monitor_dg_tmp rename to monitor;
create index user_id on monitor (user_id);
COMMIT;
PRAGMA foreign_keys=on;

View File

@@ -17,11 +17,14 @@ RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \
# Compilation Fail 3 => Google Search "alpine opensslv.h" => Add openssl-dev
# Compilation Fail 4 => Google Search "alpine opensslv.h" again => Change to libressl-dev musl-dev
# Compilation Fail 5 => Google Search "ERROR: libressl3.3-libtls-3.3.3-r0: trying to overwrite usr/lib/libtls.so.20 owned by libretls-3.3.3-r0." again => Change back to openssl-dev with musl-dev
# Runtime Error => ModuleNotFoundError: No module named 'six' => pip3 install six
# Runtime Error 2 => ModuleNotFoundError: No module named 'six' => apk add py3-six
ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1
RUN apk add --no-cache python3
RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev cargo py3-pip python3-dev && \
RUN apk add --no-cache python3 py3-pip py3-six cargo
RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev python3-dev && \
pip3 install apprise && \
apk del .build-deps
RUN apprise --version
# New things add here
@@ -31,6 +34,7 @@ RUN npm run build
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js
CMD ["npm", "run", "start-server"]
FROM release AS nightly

19
extra/healthcheck.js Normal file
View File

@@ -0,0 +1,19 @@
var http = require("http");
var options = {
host: "localhost",
port: "3001",
timeout: 2000,
};
var request = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on("error", function (err) {
console.log("ERROR");
process.exit(1);
});
request.end();

View File

@@ -34,6 +34,7 @@ if (newVersion) {
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n")
// Process README.md
fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion))
if (fs.existsSync("README.md")) {
fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion))
}
}

View File

@@ -5,6 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#5cdd8b" />
<meta name="description" content="Uptime Kuma monitoring tool" />
<title>Uptime Kuma</title>
</head>
<body>

226
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "1.0.4",
"version": "1.0.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -40,37 +40,43 @@
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
},
"@types/cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg=="
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"@types/cors": {
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz",
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
},
"@types/estree": {
"version": "0.0.48",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
"integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
"dev": true
},
"@types/node": {
"version": "15.12.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz",
"integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA=="
"version": "16.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.3.tgz",
"integrity": "sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ=="
},
"@vitejs/plugin-legacy": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.3.tgz",
"integrity": "sha512-lxZUJaMWYMQuqvZM1wPzDP6KABQgA/drVL5fnaygEPcz9adc2OHhfFNN/SvvHQ1V0rP8gybIc7uA+iI1gAdkVQ==",
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.4.tgz",
"integrity": "sha512-pVYeQUDPG5InWwrTu7acy187WWjGonJnL/GMqMLmeKCFiwkZ6UcsoUjojiKmCUI0nAJTrrKH5lhjTqkccY9Iow==",
"dev": true,
"requires": {
"@babel/standalone": "^7.14.7",
"core-js": "^3.15.1",
"core-js": "^3.15.2",
"magic-string": "^0.25.7",
"regenerator-runtime": "^0.13.7",
"systemjs": "^6.10.1"
"systemjs": "^6.10.2"
}
},
"@vitejs/plugin-vue": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz",
"integrity": "sha512-LlnLpObkGKZ+b7dcpL4T24l13nPSHLjo+6Oc7MbZiKz5PMAUzADfNJ3EKfYIQ0l0969nxf2jp/9vsfnuJ7h6fw==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.5.tgz",
"integrity": "sha512-GIR31mdXTEfvElmBUaRhDc5v7lfdkEdawWQqJRiaRL/5qKsH+xusukglkvJz5y7+c6dEpxgmvcATv2BbB7+fzQ==",
"dev": true
},
"@vue/compiler-core": {
@@ -95,17 +101,18 @@
}
},
"@vue/compiler-sfc": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.1.tgz",
"integrity": "sha512-lSgMsZaYHF+bAgryq5aUqpvyfhu52GJI2/4LoiJCE5uaxc6FCZfxfgqgw/d9ltiZghv+HiISFtmQVAVvlsk+/w==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz",
"integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==",
"dev": true,
"requires": {
"@babel/parser": "^7.13.9",
"@babel/types": "^7.13.0",
"@vue/compiler-core": "3.1.1",
"@vue/compiler-dom": "3.1.1",
"@vue/compiler-ssr": "3.1.1",
"@vue/shared": "3.1.1",
"@types/estree": "^0.0.48",
"@vue/compiler-core": "3.1.5",
"@vue/compiler-dom": "3.1.5",
"@vue/compiler-ssr": "3.1.5",
"@vue/shared": "3.1.5",
"consolidate": "^0.16.0",
"estree-walker": "^2.0.1",
"hash-sum": "^2.0.0",
@@ -116,16 +123,78 @@
"postcss-modules": "^4.0.0",
"postcss-selector-parser": "^6.0.4",
"source-map": "^0.6.1"
},
"dependencies": {
"@vue/compiler-core": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.5.tgz",
"integrity": "sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==",
"dev": true,
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.1.5",
"estree-walker": "^2.0.1",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz",
"integrity": "sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==",
"dev": true,
"requires": {
"@vue/compiler-core": "3.1.5",
"@vue/shared": "3.1.5"
}
},
"@vue/shared": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true
}
}
},
"@vue/compiler-ssr": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.1.tgz",
"integrity": "sha512-7H6krZtVt3h/YzfNp7eYK41hMDz8ZskiBy+Wby+EDRINX6BD9JQ5C8zyy2xAa7T6Iz2VrQzsaJ/Bb52lTPSS5A==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz",
"integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==",
"dev": true,
"requires": {
"@vue/compiler-dom": "3.1.1",
"@vue/shared": "3.1.1"
"@vue/compiler-dom": "3.1.5",
"@vue/shared": "3.1.5"
},
"dependencies": {
"@vue/compiler-core": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.5.tgz",
"integrity": "sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==",
"dev": true,
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.1.5",
"estree-walker": "^2.0.1",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz",
"integrity": "sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==",
"dev": true,
"requires": {
"@vue/compiler-core": "3.1.5",
"@vue/shared": "3.1.5"
}
},
"@vue/shared": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true
}
}
},
"@vue/devtools-api": {
@@ -323,6 +392,11 @@
"follow-redirects": "^1.10.0"
}
},
"babel-plugin-add-module-exports": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz",
"integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU="
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
@@ -531,9 +605,9 @@
}
},
"bootstrap": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.1.tgz",
"integrity": "sha512-Fl79+wsLOZKoiU345KeEaWD0ik8WKRI5zm0YSPj2oF1Qr+BO7z0fco6GbUtqjoG1h4VI89PeKJnMsMMVQdKKTw=="
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz",
"integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q=="
},
"brace-expansion": {
"version": "1.1.11",
@@ -655,6 +729,11 @@
"delayed-stream": "~1.0.0"
}
},
"command-exists": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
},
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
@@ -753,9 +832,9 @@
}
},
"dayjs": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz",
"integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g=="
"version": "1.10.6",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz",
"integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw=="
},
"debug": {
"version": "4.3.1",
@@ -898,9 +977,9 @@
}
},
"engine.io-client": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.1.tgz",
"integrity": "sha512-jPFpw2HLL0lhZ2KY0BpZhIJdleQcUO9W1xkIpo0h3d6s+5D6+EV/xgQw9qWOmymszv2WXef/6KUUehyxEKomlQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.2.tgz",
"integrity": "sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ==",
"requires": {
"base64-arraybuffer": "0.1.4",
"component-emitter": "~1.3.0",
@@ -922,9 +1001,9 @@
}
},
"esbuild": {
"version": "0.12.9",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz",
"integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw==",
"version": "0.12.15",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz",
"integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==",
"dev": true
},
"escape-html": {
@@ -2019,6 +2098,11 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz",
"integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w=="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -2420,9 +2504,9 @@
}
},
"nodemailer": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.2.tgz",
"integrity": "sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q=="
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz",
"integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew=="
},
"nopt": {
"version": "3.0.6",
@@ -2975,9 +3059,9 @@
}
},
"rollup": {
"version": "2.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz",
"integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==",
"version": "2.53.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz",
"integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -3002,9 +3086,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.35.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz",
"integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==",
"version": "1.35.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz",
"integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0"
@@ -3225,19 +3309,19 @@
}
},
"socket.io": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz",
"integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz",
"integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==",
"requires": {
"@types/cookie": "^0.4.0",
"@types/cors": "^2.8.8",
"@types/cors": "^2.8.10",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.1",
"engine.io": "~5.1.0",
"socket.io-adapter": "~2.3.0",
"socket.io-parser": "~4.0.3"
"engine.io": "~5.1.1",
"socket.io-adapter": "~2.3.1",
"socket.io-parser": "~4.0.4"
}
},
"socket.io-adapter": {
@@ -3246,15 +3330,15 @@
"integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw=="
},
"socket.io-client": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.2.tgz",
"integrity": "sha512-RDpWJP4DQT1XeexmeDyDkm0vrFc0+bUsHDKiVGaNISJvJonhQQOMqV9Vwfg0ZpPJ27LCdan7iqTI92FRSOkFWQ==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.3.tgz",
"integrity": "sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA==",
"requires": {
"@types/component-emitter": "^1.2.10",
"backo2": "~1.0.2",
"component-emitter": "~1.3.0",
"debug": "~4.3.1",
"engine.io-client": "~5.1.1",
"engine.io-client": "~5.1.2",
"parseuri": "0.0.6",
"socket.io-parser": "~4.0.4"
}
@@ -3624,6 +3708,16 @@
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"optional": true
},
"v-pagination-3": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/v-pagination-3/-/v-pagination-3-0.1.6.tgz",
"integrity": "sha512-82J8HnEIYtZijn6F3xhyP/ildI5K7Rv4Yu74VNfQWQsiPWTKntgVvZgBH8UPh/lFEjgWxty/M4N+YHvS+YbGzg==",
"requires": {
"babel-plugin-add-module-exports": "^0.2.1",
"merge": "^2.1.1",
"vue": ">=3.0.0"
}
},
"v8flags": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
@@ -3649,14 +3743,14 @@
}
},
"vite": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.3.8.tgz",
"integrity": "sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.4.2.tgz",
"integrity": "sha512-2MifxD2I9fjyDmmEzbULOo3kOUoqX90A58cT6mECxoVQlMYFuijZsPQBuA14mqSwvV3ydUsqnq+BRWXyO9Qa+w==",
"dev": true,
"requires": {
"esbuild": "^0.12.8",
"fsevents": "~2.3.2",
"postcss": "^8.3.4",
"postcss": "^8.3.5",
"resolve": "^1.20.0",
"rollup": "^2.38.5"
}

View File

@@ -1,15 +1,21 @@
{
"name": "uptime-kuma",
"version": "1.0.5",
"version": "1.0.7",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/louislam/uptime-kuma.git"
},
"scripts": {
"dev": "vite --host",
"start-server": "node server/server.js",
"update": "",
"build": "vite build",
"vite-preview-dist": "vite preview --host",
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.5 --target release . --push",
"build-docker-nightly": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly --target nightly . --push",
"setup": "git checkout 1.0.5 && npm install && npm run build",
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.7 --target release . --push",
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
"setup": "git checkout 1.0.7 && npm install && npm run build",
"version-global-replace": "node extra/version-global-replace.js",
"mark-as-nightly": "node extra/mark-as-nightly.js"
},
@@ -18,30 +24,32 @@
"args-parser": "^1.3.0",
"axios": "^0.21.1",
"bcrypt": "^5.0.1",
"bootstrap": "^5.0.0",
"dayjs": "^1.10.4",
"bootstrap": "^5.0.2",
"command-exists": "^1.2.9",
"dayjs": "^1.10.6",
"express": "^4.17.1",
"form-data": "^4.0.0",
"http-graceful-shutdown": "^3.1.2",
"jsonwebtoken": "^8.5.1",
"nodemailer": "^6.6.2",
"nodemailer": "^6.6.3",
"password-hash": "^1.2.2",
"redbean-node": "0.0.20",
"socket.io": "^4.0.2",
"socket.io-client": "^4.1.2",
"sqlite3": "^5.0.0",
"socket.io": "^4.1.3",
"socket.io-client": "^4.1.3",
"sqlite3": "^5.0.2",
"tcp-ping": "^0.1.1",
"v-pagination-3": "^0.1.6",
"vue": "^3.0.5",
"vue-confirm-dialog": "^1.0.2",
"vue-router": "^4.0.10",
"vue-toastification": "^2.0.0-rc.1"
},
"devDependencies": {
"@vitejs/plugin-legacy": "^1.4.3",
"@vitejs/plugin-vue": "^1.2.3",
"@vue/compiler-sfc": "^3.0.5",
"@vitejs/plugin-legacy": "^1.4.4",
"@vitejs/plugin-vue": "^1.2.5",
"@vue/compiler-sfc": "^3.1.5",
"core-js": "^3.15.2",
"sass": "^1.35.1",
"vite": "^2.3.7"
"sass": "^1.35.2",
"vite": "^2.4.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

119
server/database.js Normal file
View File

@@ -0,0 +1,119 @@
const fs = require("fs");
const {sleep} = require("./util");
const {R} = require("redbean-node");
const {setSetting, setting} = require("./util-server");
class Database {
static templatePath = "./db/kuma.db"
static path = './data/kuma.db';
static latestVersion = 3;
static noReject = true;
static async patch() {
let version = parseInt(await setting("database_version"));
if (! version) {
version = 0;
}
console.info("Your database version: " + version);
console.info("Latest database version: " + this.latestVersion);
if (version === this.latestVersion) {
console.info("Database no need to patch");
} else {
console.info("Database patch is needed")
console.info("Backup the db")
const backupPath = "./data/kuma.db.bak" + version;
fs.copyFileSync(Database.path, backupPath);
// Try catch anything here, if gone wrong, restore the backup
try {
for (let i = version + 1; i <= this.latestVersion; i++) {
const sqlFile = `./db/patch${i}.sql`;
console.info(`Patching ${sqlFile}`);
await Database.importSQLFile(sqlFile);
console.info(`Patched ${sqlFile}`);
await setSetting("database_version", i);
}
console.log("Database Patched Successfully");
} catch (ex) {
await Database.close();
console.error("Patch db failed!!! Restoring the backup")
fs.copyFileSync(backupPath, Database.path);
console.error(ex)
console.error("Start Uptime-Kuma failed due to patch db failed")
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues")
process.exit(1);
}
}
}
/**
* Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
* @param filename
* @returns {Promise<void>}
*/
static async importSQLFile(filename) {
await R.getCell("SELECT 1");
let text = fs.readFileSync(filename).toString();
// Remove all comments (--)
let lines = text.split("\n");
lines = lines.filter((line) => {
return ! line.startsWith("--")
});
// Split statements by semicolon
// Filter out empty line
text = lines.join("\n")
let statements = text.split(";")
.map((statement) => {
return statement.trim();
})
.filter((statement) => {
return statement !== "";
})
for (let statement of statements) {
await R.exec(statement);
}
}
/**
* Special handle, because tarn.js throw a promise reject that cannot be caught
* @returns {Promise<void>}
*/
static async close() {
const listener = (reason, p) => {
Database.noReject = false;
};
process.addListener('unhandledRejection', listener);
console.log("Closing DB")
while (true) {
Database.noReject = true;
await R.close()
await sleep(2000)
if (Database.noReject) {
break;
} else {
console.log("Waiting to close the db")
}
}
console.log("SQLite closed")
process.removeListener('unhandledRejection', listener);
}
}
module.exports = Database;

View File

@@ -3,8 +3,6 @@ const utc = require('dayjs/plugin/utc')
var timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(timezone)
const axios = require("axios");
const {R} = require("redbean-node");
const {BeanModel} = require("redbean-node/dist/bean-model");

View File

@@ -1,22 +1,29 @@
const https = require('https');
const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc')
var timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(timezone)
const axios = require("axios");
const {tcping, ping} = require("../util-server");
const {debug, UP, DOWN, PENDING} = require("../util");
const {tcping, ping, checkCertificate} = require("../util-server");
const {R} = require("redbean-node");
const {BeanModel} = require("redbean-node/dist/bean-model");
const {Notification} = require("../notification")
// Use Custom agent to disable session reuse
// https://github.com/nodejs/node/issues/3940
const customAgent = new https.Agent({
maxCachedSessions: 0
});
/**
* status:
* 0 = DOWN
* 1 = UP
*/
class Monitor extends BeanModel {
async toJSON() {
let notificationIDList = {};
@@ -35,6 +42,7 @@ class Monitor extends BeanModel {
url: this.url,
hostname: this.hostname,
port: this.port,
maxretries: this.maxretries,
weight: this.weight,
active: this.active,
type: this.type,
@@ -46,9 +54,9 @@ class Monitor extends BeanModel {
start(io) {
let previousBeat = null;
let retries = 0;
const beat = async () => {
console.log(`Monitor ${this.id}: Heartbeat`)
if (! previousBeat) {
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
@@ -56,13 +64,15 @@ class Monitor extends BeanModel {
])
}
const isFirstBeat = !previousBeat;
let bean = R.dispense("heartbeat")
bean.monitor_id = this.id;
bean.time = R.isoDateTime(dayjs.utc());
bean.status = 0;
bean.status = DOWN;
// Duration
if (previousBeat) {
if (! isFirstBeat) {
bean.duration = dayjs(bean.time).diff(dayjs(previousBeat.time), 'second');
} else {
bean.duration = 0;
@@ -72,13 +82,27 @@ class Monitor extends BeanModel {
if (this.type === "http" || this.type === "keyword") {
let startTime = dayjs().valueOf();
let res = await axios.get(this.url, {
headers: { 'User-Agent':'Uptime-Kuma' }
})
headers: { "User-Agent": "Uptime-Kuma" },
httpsAgent: customAgent,
});
bean.msg = `${res.status} - ${res.statusText}`
bean.ping = dayjs().valueOf() - startTime;
// Check certificate if https is used
let certInfoStartTime = dayjs().valueOf();
if (this.getUrl()?.protocol === "https:") {
try {
await this.updateTlsInfo(checkCertificate(res));
} catch (e) {
console.error(e.message)
}
}
debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms")
if (this.type === "http") {
bean.status = 1;
bean.status = UP;
} else {
let data = res.data;
@@ -90,7 +114,7 @@ class Monitor extends BeanModel {
if (data.includes(this.keyword)) {
bean.msg += ", keyword is found"
bean.status = 1;
bean.status = UP;
} else {
throw new Error(bean.msg + ", but keyword is not found")
}
@@ -101,32 +125,52 @@ class Monitor extends BeanModel {
} else if (this.type === "port") {
bean.ping = await tcping(this.hostname, this.port);
bean.msg = ""
bean.status = 1;
bean.status = UP;
} else if (this.type === "ping") {
bean.ping = await ping(this.hostname);
bean.msg = ""
bean.status = 1;
bean.status = UP;
}
retries = 0;
} catch (error) {
if ((this.maxretries > 0) && (retries < this.maxretries)) {
retries++;
bean.status = PENDING;
}
bean.msg = error.message;
}
// Mark as important if status changed
if (! previousBeat || previousBeat.status !== bean.status) {
// * ? -> ANY STATUS = important [isFirstBeat]
// UP -> PENDING = not important
// * UP -> DOWN = important
// UP -> UP = not important
// PENDING -> PENDING = not important
// * PENDING -> DOWN = important
// PENDING -> UP = not important
// DOWN -> PENDING = this case not exists
// DOWN -> DOWN = not important
// * DOWN -> UP = important
let isImportant = isFirstBeat ||
(previousBeat.status === UP && bean.status === DOWN) ||
(previousBeat.status === DOWN && bean.status === UP) ||
(previousBeat.status === PENDING && bean.status === DOWN);
// Mark as important if status changed, ignore pending pings,
// Don't notify if disrupted changes to up
if (isImportant) {
bean.important = true;
// Do not send if first beat is UP
if (previousBeat || bean.status !== 1) {
// Send only if the first beat is DOWN
if (!isFirstBeat || bean.status === DOWN) {
let notificationList = await R.getAll(`SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id `, [
this.id
])
let promiseList = [];
let text;
if (bean.status === 1) {
if (bean.status === UP) {
text = "✅ Up"
} else {
text = "🔴 Down"
@@ -135,16 +179,26 @@ class Monitor extends BeanModel {
let msg = `[${this.name}] [${text}] ${bean.msg}`;
for(let notification of notificationList) {
promiseList.push(Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON()));
try {
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON())
} catch (e) {
console.error("Cannot send notification to " + notification.name)
}
}
await Promise.all(promiseList);
}
} else {
bean.important = false;
}
if (bean.status === UP) {
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
} else if (bean.status === PENDING) {
console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Type: ${this.type}`)
} else {
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
}
io.to(this.user_id).emit("heartbeat", bean.toJSON());
await R.store(bean)
@@ -161,10 +215,35 @@ class Monitor extends BeanModel {
clearInterval(this.heartbeatInterval)
}
// Helper Method:
// returns URL object for further usage
// returns null if url is invalid
getUrl() {
try {
return new URL(this.url);
} catch (_) {
return null;
}
}
// Store TLS info to database
async updateTlsInfo(checkCertificateResult) {
let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
this.id
]);
if (tls_info_bean == null) {
tls_info_bean = R.dispense("monitor_tls_info");
tls_info_bean.monitor_id = this.id;
}
tls_info_bean.info_json = JSON.stringify(checkCertificateResult);
await R.store(tls_info_bean);
}
static async sendStats(io, monitorID, userID) {
Monitor.sendAvgPing(24, io, monitorID, userID);
Monitor.sendUptime(24, io, monitorID, userID);
Monitor.sendUptime(24 * 30, io, monitorID, userID);
Monitor.sendCertInfo(io, monitorID, userID);
}
/**
@@ -185,6 +264,15 @@ class Monitor extends BeanModel {
io.to(userID).emit("avgPing", monitorID, avgPing);
}
static async sendCertInfo(io, monitorID, userID) {
let tls_info = await R.findOne("monitor_tls_info", "monitor_id = ?", [
monitorID
]);
if (tls_info != null) {
io.to(userID).emit("certInfo", monitorID, tls_info.info_json);
}
}
/**
* Uptime with calculation
* Calculation based on:
@@ -233,7 +321,7 @@ class Monitor extends BeanModel {
}
total += value;
if (row.status === 0) {
if (row.status === 0 || row.status === 2) {
downtime += value;
}
}

View File

@@ -2,9 +2,22 @@ const axios = require("axios");
const {R} = require("redbean-node");
const FormData = require('form-data');
const nodemailer = require("nodemailer");
const child_process = require("child_process");
class Notification {
/**
*
* @param notification
* @param msg
* @param monitorJSON
* @param heartbeatJSON
* @returns {Promise<string>} Successful msg
* Throw Error with fail msg
*/
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
if (notification.type === "telegram") {
try {
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
@@ -13,15 +26,16 @@ class Notification {
text: msg,
}
})
return true;
return okMsg;
} catch (error) {
console.log(error)
return false;
let msg = (error.response.data.description) ? error.response.data.description : "Error without description"
throw new Error(msg)
}
} else if (notification.type === "gotify") {
try {
if (notification.gotifyserverurl.endsWith("/")) {
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
}
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
@@ -29,15 +43,15 @@ class Notification {
"priority": notification.gotifyPriority || 8,
"title": "Uptime-Kuma"
})
return true;
return okMsg;
} catch (error) {
console.log(error)
return false;
throwGeneralAxiosError(error)
}
} else if (notification.type === "webhook") {
try {
let data = {
heartbeat: heartbeatJSON,
monitor: monitorJSON,
@@ -58,11 +72,11 @@ class Notification {
finalData = data;
}
let res = await axios.post(notification.webhookURL, finalData, config)
return true;
await axios.post(notification.webhookURL, finalData, config)
return okMsg;
} catch (error) {
console.log(error)
return false;
throwGeneralAxiosError(error)
}
} else if (notification.type === "smtp") {
@@ -76,8 +90,8 @@ class Notification {
username: 'Uptime-Kuma',
content: msg
}
let res = await axios.post(notification.discordWebhookUrl, data)
return true;
await axios.post(notification.discordWebhookUrl, data)
return okMsg;
}
// If heartbeatJSON is not null, we go into the normal alerting loop.
if(heartbeatJSON['status'] == 0) {
@@ -102,11 +116,10 @@ class Notification {
]
}]
}
let res = await axios.post(notification.discordWebhookUrl, data)
return true;
await axios.post(notification.discordWebhookUrl, data)
return okMsg;
} catch(error) {
console.log(error)
return false;
throwGeneralAxiosError(error)
}
} else if (notification.type === "signal") {
@@ -118,23 +131,26 @@ class Notification {
};
let config = {};
let res = await axios.post(notification.signalURL, data, config)
return true;
await axios.post(notification.signalURL, data, config)
return okMsg;
} catch (error) {
console.log(error)
return false;
throwGeneralAxiosError(error)
}
} else if (notification.type === "slack") {
try {
if (heartbeatJSON == null) {
let data = {'text': "Uptime Kuma Slack testing successful."}
let res = await axios.post(notification.slackwebhookURL, data)
return true;
let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo}
await axios.post(notification.slackwebhookURL, data)
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel":notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
"blocks": [{
"type": "header",
"text": {
@@ -155,28 +171,59 @@ class Notification {
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Visit Uptime Kuma",
},
"value": "Uptime-Kuma",
"url": notification.slackbutton
}
"value": "Uptime-Kuma",
"url": notification.slackbutton || "https://github.com/louislam/uptime-kuma"
}
]
}
]
}
let res = await axios.post(notification.slackwebhookURL, data)
return true;
await axios.post(notification.slackwebhookURL, data)
return okMsg;
} catch (error) {
console.log(error)
return false;
throwGeneralAxiosError(error)
}
} else if (notification.type === "pushover") {
var pushoverlink = 'https://api.pushover.net/1/messages.json'
try {
if (heartbeatJSON == null) {
let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>",
'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds,
'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1}
await axios.post(pushoverlink, data)
return okMsg;
}
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:"+msg+ '\n<b>Time (UTC)</b>:' +heartbeatJSON["time"],
"user":notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1
}
await axios.post(pushoverlink, data)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "apprise") {
return Notification.apprise(notification, msg)
} else {
throw new Error("Notification type is not supported")
}
@@ -231,27 +278,54 @@ class Notification {
});
// send mail with defined transport object
let info = await transporter.sendMail({
await transporter.sendMail({
from: `"Uptime Kuma" <${notification.smtpFrom}>`,
to: notification.smtpTo,
subject: msg,
text: msg,
});
return true;
return "Sent Successfully.";
}
static async discord(notification, msg) {
const client = new Discord.Client();
await client.login(notification.discordToken)
static async apprise(notification, msg) {
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
const channel = await client.channels.fetch(notification.discordChannelID);
await channel.send(msg);
client.destroy()
let output = (s.stdout) ? s.stdout.toString() : 'ERROR: maybe apprise not found';
return true;
if (output) {
if (! output.includes("ERROR")) {
return "Sent Successfully";
} else {
throw new Error(output)
}
} else {
return ""
}
}
static checkApprise() {
let commandExistsSync = require('command-exists').sync;
let exists = commandExistsSync('apprise');
return exists;
}
}
function throwGeneralAxiosError(error) {
let msg = "Error: " + error + " ";
if (error.response && error.response.data) {
if (typeof error.response.data === "string") {
msg += error.response.data;
} else {
msg += JSON.stringify(error.response.data)
}
}
throw new Error(msg)
}
module.exports = {

View File

@@ -12,6 +12,7 @@ const fs = require("fs");
const {getSettings} = require("./util-server");
const {Notification} = require("./notification")
const gracefulShutdown = require('http-graceful-shutdown');
const Database = require("./database");
const {sleep} = require("./util");
const args = require('args-parser')(process.argv);
@@ -19,7 +20,7 @@ const version = require('../package.json').version;
const hostname = args.host || "0.0.0.0"
const port = args.port || 3001
console.log("Version: " + version)
console.info("Version: " + version)
console.log("Creating express and socket.io instance")
const app = express();
@@ -27,27 +28,48 @@ const server = http.createServer(app);
const io = new Server(server);
app.use(express.json())
/**
* Total WebSocket client connected to server currently, no actual use
* @type {number}
*/
let totalClient = 0;
/**
* Use for decode the auth object
* @type {null}
*/
let jwtSecret = null;
/**
* Main monitor list
* @type {{}}
*/
let monitorList = {};
/**
* Show Setup Page
* @type {boolean}
*/
let needSetup = false;
(async () => {
await initDatabase();
console.log("Adding route")
app.use('/', express.static("dist"));
app.get('*', function(request, response, next) {
response.sendFile(process.cwd() + '/dist/index.html');
});
console.log("Adding socket handler")
io.on('connection', async (socket) => {
socket.emit("info", {
version,
})
console.log('a user connected');
totalClient++;
if (needSetup) {
@@ -56,7 +78,6 @@ let needSetup = false;
}
socket.on('disconnect', () => {
console.log('user disconnected');
totalClient--;
});
@@ -162,10 +183,6 @@ let needSetup = false;
msg: e.message
});
}
});
// Auth Only API
@@ -216,6 +233,7 @@ let needSetup = false;
bean.url = monitor.url
bean.interval = monitor.interval
bean.hostname = monitor.hostname;
bean.maxretries = monitor.maxretries;
bean.port = monitor.port;
bean.keyword = monitor.keyword;
@@ -236,7 +254,7 @@ let needSetup = false;
});
} catch (e) {
console.log(e)
console.error(e)
callback({
ok: false,
msg: e.message
@@ -437,25 +455,36 @@ let needSetup = false;
try {
checkLogin(socket)
await Notification.send(notification, notification.name + " Testing")
let msg = await Notification.send(notification, notification.name + " Testing")
callback({
ok: true,
msg: "Sent Successfully"
msg
});
} catch (e) {
console.error(e)
callback({
ok: false,
msg: e.message
});
}
});
socket.on("checkApprise", async (callback) => {
try {
checkLogin(socket)
callback(Notification.checkApprise());
} catch (e) {
callback(false);
}
});
});
console.log("Init")
server.listen(port, hostname, () => {
console.log(`Listening on ${hostname}:${port}`);
startMonitors();
});
@@ -514,12 +543,12 @@ async function afterLogin(socket, user) {
let monitorList = await sendMonitorList(socket)
for (let monitorID in monitorList) {
await sendHeartbeatList(socket, monitorID);
await sendImportantHeartbeatList(socket, monitorID);
await Monitor.sendStats(io, monitorID, user.id)
sendHeartbeatList(socket, monitorID);
sendImportantHeartbeatList(socket, monitorID);
Monitor.sendStats(io, monitorID, user.id)
}
await sendNotificationList(socket)
sendNotificationList(socket)
}
async function getMonitorJSONList(userID) {
@@ -543,18 +572,21 @@ function checkLogin(socket) {
}
async function initDatabase() {
const path = './data/kuma.db';
if (! fs.existsSync(path)) {
if (! fs.existsSync(Database.path)) {
console.log("Copying Database")
fs.copyFileSync("./db/kuma.db", path);
fs.copyFileSync(Database.templatePath, Database.path);
}
console.log("Connecting to Database")
R.setup('sqlite', {
filename: path
filename: Database.path
});
console.log("Connected")
// Patch the database
await Database.patch()
// Auto map the model to a bean object
R.freeze(true)
await R.autoloadModels("./server/model");
@@ -569,10 +601,12 @@ async function initDatabase() {
jwtSecretBean.value = passwordHash.generate(dayjs() + "")
await R.store(jwtSecretBean)
console.log("Stored JWT secret into database")
} else {
console.log("Load JWT secret from database.")
}
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
if ((await R.count("user")) === 0) {
console.log("No user, need setup")
needSetup = true;
@@ -691,11 +725,6 @@ const startGracefulShutdown = async () => {
}
let noReject = true;
process.on('unhandledRejection', (reason, p) => {
noReject = false;
});
async function shutdownFunction(signal) {
console.log('Called signal: ' + signal);
@@ -704,24 +733,8 @@ async function shutdownFunction(signal) {
let monitor = monitorList[id]
monitor.stop()
}
await sleep(2000)
console.log("Closing DB")
// Special handle, because tarn.js throw a promise reject that cannot be caught
while (true) {
noReject = true;
await R.close()
await sleep(2000)
if (noReject) {
break;
} else {
console.log("Waiting...")
}
}
console.log("OK")
await sleep(2000);
await Database.close();
}
function finalFunction() {

View File

@@ -45,6 +45,18 @@ exports.setting = async function (key) {
])
}
exports.setSetting = async function (key, value) {
let bean = await R.findOne("setting", " `key` = ? ", [
key
])
if (! bean) {
bean = R.dispense("setting")
bean.key = key;
}
bean.value = value;
await R.store(bean)
}
exports.getSettings = async function (type) {
let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [
type
@@ -58,3 +70,52 @@ exports.getSettings = async function (type) {
return result;
}
// ssl-checker by @dyaa
// param: res - response object from axios
// return an object containing the certificate information
const getDaysBetween = (validFrom, validTo) =>
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
const getDaysRemaining = (validFrom, validTo) => {
const daysRemaining = getDaysBetween(validFrom, validTo);
if (new Date(validTo).getTime() < new Date().getTime()) {
return -daysRemaining;
}
return daysRemaining;
};
exports.checkCertificate = function (res) {
const {
valid_from,
valid_to,
subjectaltname,
issuer,
fingerprint,
} = res.request.res.socket.getPeerCertificate(false);
if (!valid_from || !valid_to || !subjectaltname) {
throw { message: 'No TLS certificate in response' };
}
const valid = res.request.res.socket.authorized || false;
const validTo = new Date(valid_to);
const validFor = subjectaltname
.replace(/DNS:|IP Address:/g, "")
.split(", ");
const daysRemaining = getDaysRemaining(new Date(), validTo);
return {
valid,
validFor,
validTo,
daysRemaining,
issuer,
fingerprint,
};
}

View File

@@ -1,6 +1,10 @@
// Common JS cannot be used in frontend sadly
// sleep, ucfirst is duplicated in ../src/util-frontend.js
exports.DOWN = 0;
exports.UP = 1;
exports.PENDING = 2;
exports.sleep = function (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
@@ -14,3 +18,9 @@ exports.ucfirst = function (str) {
return firstLetter.toUpperCase() + str.substr(1);
}
exports.debug = (msg) => {
if (process.env.NODE_ENV === "development") {
console.log(msg)
}
}

View File

@@ -1,7 +1,8 @@
$primary: #5CDD8B;
$danger: #DC3545;
$warning: #f8a306;
$link-color: #111;
$border-radius: 50rem;
$highlight: #7ce8a4;
$highlight-white: #e7faec;
$highlight-white: #e7faec;

View File

@@ -4,9 +4,9 @@
<script>
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone' // dependent on utc plugin
import relativeTime from "dayjs/plugin/relativeTime"
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone' // dependent on utc plugin
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(relativeTime)
@@ -14,12 +14,23 @@ dayjs.extend(relativeTime)
export default {
props: {
value: String,
dateOnly: {
type: Boolean,
default: false,
},
},
computed: {
displayText() {
let format = "YYYY-MM-DD HH:mm:ss";
return dayjs.utc(this.value).tz(this.$root.timezone).format(format)
if (this.value !== undefined && this.value !== "") {
let format = "YYYY-MM-DD HH:mm:ss";
if (this.dateOnly) {
format = "YYYY-MM-DD";
}
return dayjs.utc(this.value).tz(this.$root.timezone).format(format);
} else {
return "";
}
},
}
}

View File

@@ -3,7 +3,7 @@
<div class="hp-bar-big" :style="barStyle">
<div
class="beat"
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0) }"
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }"
:style="beatStyle"
v-for="(beat, index) in shortBeatList"
:key="index"
@@ -166,6 +166,10 @@ export default {
background-color: $danger;
}
&.pending {
background-color: $warning;
}
&:not(.empty):hover {
transition: all ease-in-out 0.15s;
opacity: 0.8;

View File

@@ -15,14 +15,14 @@
<label for="floatingPassword">Password</label>
</div>
<div class="form-check mb-3 mt-3" >
<label>
<div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4">
<div class="form-check">
<input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember">
<label class="form-check-label" for="remember">
Remember me
</label>
</label>
</div>
</div>
<button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button>

View File

@@ -10,70 +10,72 @@
</div>
<div class="modal-body">
<div class="mb-3">
<label for="type" class="form-label">Notification Type</label>
<select class="form-select" id="type" v-model="notification.type">
<option value="telegram">Telegram</option>
<option value="webhook">Webhook</option>
<option value="smtp">Email (SMTP)</option>
<option value="discord">Discord</option>
<option value="signal">Signal</option>
<option value="gotify">Gotify</option>
<option value="slack">Slack</option>
<option value="pushover">Pushover</option>
<option value="apprise">Apprise (Support 50+ Notification services)</option>
</select>
</div>
<div class="mb-3">
<label for="name" class="form-label">Friendly Name</label>
<input type="text" class="form-control" id="name" required v-model="notification.name">
</div>
<template v-if="notification.type === 'telegram'">
<div class="mb-3">
<label for="type" class="form-label">Notification Type</label>
<select class="form-select" id="type" v-model="notification.type">
<option value="telegram">Telegram</option>
<option value="webhook">Webhook</option>
<option value="smtp">Email (SMTP)</option>
<option value="discord">Discord</option>
<option value="signal">Signal</option>
<option value="gotify">Gotify</option>
<option value="slack">Slack</option>
</select>
<label for="telegram-bot-token" class="form-label">Bot Token</label>
<input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken">
<div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div>
</div>
<div class="mb-3">
<label for="name" class="form-label">Friendly Name</label>
<input type="text" class="form-control" id="name" required v-model="notification.name">
<label for="telegram-chat-id" class="form-label">Chat ID</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID">
<button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button>
</div>
<div class="form-text">
Support Direct Chat / Group / Channel's Chat ID
<p style="margin-top: 8px;">
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
</p>
<p style="margin-top: 8px;">
<template v-if="notification.telegramBotToken">
<a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a>
</template>
<template v-else>
{{ telegramGetUpdatesURL }}
</template>
</p>
</div>
</div>
<template v-if="notification.type === 'telegram'">
<div class="mb-3">
<label for="telegram-bot-token" class="form-label">Bot Token</label>
<input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken">
<div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div>
</div>
<div class="mb-3">
<label for="telegram-chat-id" class="form-label">Chat ID</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID">
<button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button>
</div>
<div class="form-text">
Support Direct Chat / Group / Channel's Chat ID
<p style="margin-top: 8px;">
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
</p>
<p style="margin-top: 8px;">
<template v-if="notification.telegramBotToken">
<a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a>
</template>
<template v-else>
{{ telegramGetUpdatesURL }}
</template>
</p>
</div>
</div>
</template>
</template>
<template v-if="notification.type === 'webhook'">
<div class="mb-3">
<label for="webhook-url" class="form-label">Post URL</label>
<input type="url" pattern="https?://.+" class="form-control" id="webhook-url" required v-model="notification.webhookURL">
<input type="url" pattern="https?://.+" class="form-control" id="webhook-url" required v-model="notification.webhookURL">
</div>
<div class="mb-3">
<label for="webhook-content-type" class="form-label">Content Type</label>
<select class="form-select" id="webhook-content-type" v-model="notification.webhookContentType" required>
<select class="form-select" id="webhook-content-type" v-model="notification.webhookContentType" required>
<option value="json">application/json</option>
<option value="form-data">multipart/form-data</option>
</select>
@@ -98,7 +100,7 @@
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="secure" v-model="notification.smtpSecure">
<input class="form-check-input" type="checkbox" value="" id="secure" v-model="notification.smtpSecure">
<label class="form-check-label" for="secure">
Secure
</label>
@@ -139,7 +141,7 @@
<template v-if="notification.type === 'signal'">
<div class="mb-3">
<label for="signal-url" class="form-label">Post URL</label>
<input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL">
<input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL">
</div>
@@ -191,14 +193,108 @@
<template v-if="notification.type === 'slack'">
<div class="mb-3">
<label for="slack-webhook-url" class="form-label">Slack Webhook URL</label>
<input type="text" class="form-control" id="slack-webhook-url" required v-model="notification.slackwebhookURL" autocomplete="false">
<label for="gotify-server-url" class="form-label">Uptime Kuma URL</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="slack-button" required v-model="notification.slackbutton" autocomplete="false">
</div>
<label for="slack-webhook-url" class="form-label">Webhook URL<span style="color:red;"><sup>*</sup></span></label>
<input type="text" class="form-control" id="slack-webhook-url" required v-model="notification.slackwebhookURL">
<label for="slack-username" class="form-label">Username</label>
<input type="text" class="form-control" id="slack-username" v-model="notification.slackusername">
<label for="slack-iconemo" class="form-label">Icon Emoji</label>
<input type="text" class="form-control" id="slack-iconemo" v-model="notification.slackiconemo">
<label for="slack-channel" class="form-label">Channel Name</label>
<input type="text" class="form-control" id="slack-channel-name" v-model="notification.slackchannel">
<label for="slack-button-url" class="form-label">Uptime Kuma URL</label>
<input type="text" class="form-control" id="slack-button" v-model="notification.slackbutton">
<div class="form-text">
<span style="color:red;"><sup>*</sup></span>Required
<p style="margin-top: 8px;">
More info about webhooks on: <a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a>
</p>
<p style="margin-top: 8px;">
Enter the channel name on Slack Channel Name field if you want to bypass the webhook channel. Ex: #other-channel
</p>
<p style="margin-top: 8px;">
If you leave the Uptime Kuma URL field blank, it will default to the Project Github page.
</p>
<p style="margin-top: 8px;">
Emoji cheat sheet: <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a>
</p>
</div>
</div>
</template>
<template v-if="notification.type === 'pushover'">
<div class="mb-3">
<label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label>
<input type="text" class="form-control" id="pushover-user" required v-model="notification.pushoveruserkey">
<label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label>
<input type="text" class="form-control" id="pushover-app-token" required v-model="notification.pushoverapptoken">
<label for="pushover-device" class="form-label">Device</label>
<input type="text" class="form-control" id="pushover-device" v-model="notification.pushoverdevice">
<label for="pushover-device" class="form-label">Message Title</label>
<input type="text" class="form-control" id="pushover-title" v-model="notification.pushovertitle">
<label for="pushover-priority" class="form-label">Priority</label>
<select class="form-select" id="pushover-priority" v-model="notification.pushoverpriority">
<option>-2</option>
<option>-1</option>
<option>0</option>
<option>1</option>
<option>2</option>
</select>
<label for="pushover-sound" class="form-label">Notification Sound</label>
<select class="form-select" id="pushover-sound" v-model="notification.pushoversounds">
<option>pushover</option>
<option>bike</option>
<option>bugle</option>
<option>cashregister</option>
<option>classical</option>
<option>cosmic</option>
<option>falling</option>
<option>gamelan</option>
<option>incoming</option>
<option>intermission</option>
<option>mechanical</option>
<option>pianobar</option>
<option>siren</option>
<option>spacealarm</option>
<option>tugboat</option>
<option>alien</option>
<option>climb</option>
<option>persistent</option>
<option>echo</option>
<option>updown</option>
<option>vibrate</option>
<option>none</option>
</select>
<div class="form-text">
<span style="color:red;"><sup>*</sup></span>Required
<p style="margin-top: 8px;">
More info on: <a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a>
More info on: <a href="https://pushover.net/api" target="_blank">https://pushover.net/api</a>
</p>
<p style="margin-top: 8px;">
Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.
</p>
<p style="margin-top: 8px;">
If you want to send notifications to different devices, fill out Device field.
</p>
</div>
</div>
</template>
<template v-if="notification.type === 'apprise'">
<div class="mb-3">
<label for="apprise-url" class="form-label">Apprise URL</label>
<input type="text" class="form-control" id="apprise-url" required v-model="notification.appriseURL">
<div class="form-text">
<p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p>
<p>
Read more: <a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a>
</p>
</div>
</div>
<div class="mb-3">
<p>
Status:
<span class="text-primary" v-if="appriseInstalled">Apprise is installed</span>
<span class="text-danger" v-else>Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span>
</p>
</div>
</template>
@@ -241,17 +337,15 @@ export default {
type: null,
gotifyPriority: 8
},
appriseInstalled: false,
}
},
mounted() {
this.modal = new Modal(this.$refs.modal)
// TODO: for edit
this.$root.getSocket().emit("getSettings", "notification", (data) => {
// this.notification = data
this.$root.getSocket().emit("checkApprise", (installed) => {
this.appriseInstalled = installed;
})
},
methods: {

View File

@@ -14,6 +14,8 @@ export default {
return "danger"
} else if (this.status === 1) {
return "primary"
} else if (this.status === 2) {
return "warning"
} else {
return "secondary"
}
@@ -24,6 +26,8 @@ export default {
return "Down"
} else if (this.status === 1) {
return "Up"
} else if (this.status === 2) {
return "Pending"
} else {
return "Unknown"
}
@@ -34,6 +38,6 @@ export default {
<style scoped>
span {
width: 45px;
width: 54px;
}
</style>

View File

@@ -30,6 +30,8 @@ export default {
return "danger"
} else if (this.lastHeartBeat.status === 1) {
return "primary"
} else if (this.lastHeartBeat.status === 2) {
return "warning"
} else {
return "secondary"
}

View File

@@ -9,7 +9,7 @@
<!-- Desktop header -->
<header class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom" v-if="! $root.isMobile">
<router-link to="/dashboard" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg"></object>
<object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" alt="Logo"></object>
<span class="fs-4 title">Uptime Kuma</span>
</router-link>
@@ -37,7 +37,7 @@
<div class="container-fluid">
Uptime Kuma -
Version: {{ $root.info.version }} -
<a href="https://github.com/louislam/uptime-kuma/releases" target="_blank">Check Update On GitHub</a>
<a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">Check Update On GitHub</a>
</div>
</footer>
@@ -146,6 +146,7 @@ footer {
font-size: 13px;
margin-bottom: 30px;
margin-left: 10px;
text-align: center;
}
</style>

View File

@@ -25,6 +25,7 @@ export default {
importantHeartbeatList: { },
avgPingList: { },
uptimeList: { },
certInfoList: {},
notificationList: [],
windowWidth: window.innerWidth,
showListMobile: false,
@@ -35,7 +36,8 @@ export default {
window.addEventListener('resize', this.onResize);
let wsHost;
if (localStorage.dev === "dev") {
const env = process.env.NODE_ENV || "production";
if (env === "development" || localStorage.dev === "dev") {
wsHost = ":3001"
} else {
wsHost = ""
@@ -45,6 +47,10 @@ export default {
transports: ['websocket']
});
socket.on("connect_error", (err) => {
console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
});
socket.on('info', (info) => {
this.info = info;
});
@@ -53,7 +59,17 @@ export default {
this.$router.push("/setup")
});
socket.on('monitorList', (data) => {
socket.on("monitorList", (data) => {
// Add Helper function
Object.entries(data).forEach(([monitorID, monitor]) => {
monitor.getUrl = () => {
try {
return new URL(monitor.url);
} catch (_) {
return null;
}
};
});
this.monitorList = data;
});
@@ -109,6 +125,10 @@ export default {
this.uptimeList[`${monitorID}_${type}`] = data
});
socket.on('certInfo', (monitorID, data) => {
this.certInfoList[monitorID] = JSON.parse(data)
});
socket.on('importantHeartbeatList', (monitorID, data) => {
if (! (monitorID in this.importantHeartbeatList)) {
this.importantHeartbeatList[monitorID] = data;
@@ -158,7 +178,7 @@ export default {
},
getSocket() {
return socket;
return socket;
},
toastRes(res) {
@@ -274,6 +294,11 @@ export default {
text: "Down",
color: "danger"
};
} else if (lastHeartBeat.status === 2) {
result[monitorID] = {
text: "Pending",
color: "warning"
};
} else {
result[monitorID] = unknown;
}

View File

@@ -13,7 +13,7 @@
No Monitors, please <router-link to="/add">add one</router-link>.
</div>
<router-link :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" v-for="item in sortedMonitorList" @click="$root.cancelActiveList">
<router-link :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" v-for="(item, index) in sortedMonitorList" @click="$root.cancelActiveList" :key="index">
<div class="row">
<div class="col-6 col-md-8 small-padding">

View File

@@ -47,7 +47,7 @@
</tr>
</thead>
<tbody>
<tr v-for="beat in importantHeartBeatList">
<tr v-for="(beat, index) in displayedRecords" :key="index">
<td>{{ beat.name }}</td>
<td><Status :status="beat.status" /></td>
<td><Datetime :value="beat.time" /></td>
@@ -59,6 +59,13 @@
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center kuma_pagination">
<pagination
v-model="page"
:records=importantHeartBeatList.length
:per-page="perPage" />
</div>
</div>
</div>
@@ -68,8 +75,21 @@
<script>
import Status from "../components/Status.vue";
import Datetime from "../components/Datetime.vue";
import Pagination from "v-pagination-3";
export default {
components: {Datetime, Status},
components: {
Datetime,
Status,
Pagination,
},
data() {
return {
page: 1,
perPage: 25,
heartBeatList: [],
}
},
computed: {
stats() {
let result = {
@@ -90,6 +110,8 @@ export default {
result.up++;
} else if (beat.status === 0) {
result.down++;
} else if (beat.status === 2) {
result.up++;
} else {
result.unknown++;
}
@@ -127,8 +149,16 @@ export default {
}
});
this.heartBeatList = result;
return result;
}
},
displayedRecords() {
const startIndex = this.perPage * (this.page - 1);
const endIndex = startIndex + this.perPage;
return this.heartBeatList.slice(startIndex, endIndex);
},
}
}
</script>

View File

@@ -12,7 +12,7 @@
<div class="functions">
<button class="btn btn-light" @click="pauseDialog" v-if="monitor.active">Pause</button>
<button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active">Resume</button>
<button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active">Resume</button>
<router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary">Edit</router-link>
<button class="btn btn-danger" @click="deleteDialog">Delete</button>
</div>
@@ -51,6 +51,46 @@
<p>(30-day)</p>
<span class="num"><Uptime :monitor="monitor" type="720" /></span>
</div>
<div class="col" v-if="certInfo">
<h4>CertExp.</h4>
<p>(<Datetime :value="certInfo.validTo" date-only />)</p>
<span class="num" >
<a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{certInfo.daysRemaining}} days</a>
</span>
</div>
</div>
</div>
<div class="shadow-box big-padding text-center" v-if="showCertInfoBox">
<div class="row">
<div class="col">
<h4>Certificate Info</h4>
<table class="text-start">
<tbody>
<tr class="my-3">
<td class="px-3">Valid: </td>
<td>{{ certInfo.valid }}</td>
</tr>
<tr class="my-3">
<td class="px-3">Valid To: </td>
<td><Datetime :value="certInfo.validTo" /></td>
</tr>
<tr class="my-3">
<td class="px-3">Days Remaining: </td>
<td>{{ certInfo.daysRemaining }}</td>
</tr>
<tr class="my-3">
<td class="px-3">Issuer: </td>
<td>{{ certInfo.issuer }}</td>
</tr>
<tr class="my-3">
<td class="px-3">Fingerprint: </td>
<td>{{ certInfo.fingerprint }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
@@ -64,7 +104,7 @@
</tr>
</thead>
<tbody>
<tr v-for="beat in importantHeartBeatList">
<tr v-for="(beat, index) in displayedRecords" :key="index">
<td><Status :status="beat.status" /></td>
<td><Datetime :value="beat.time" /></td>
<td>{{ beat.msg }}</td>
@@ -75,6 +115,13 @@
</tr>
</tbody>
</table>
<div class="d-flex justify-content-center kuma_pagination">
<pagination
v-model="page"
:records=importantHeartBeatList.length
:per-page="perPage" />
</div>
</div>
<Confirm ref="confirmPause" @yes="pauseMonitor">
@@ -95,6 +142,7 @@ import Status from "../components/Status.vue";
import Datetime from "../components/Datetime.vue";
import CountUp from "../components/CountUp.vue";
import Uptime from "../components/Uptime.vue";
import Pagination from "v-pagination-3";
export default {
components: {
@@ -104,13 +152,17 @@ export default {
HeartbeatBar,
Confirm,
Status,
Pagination,
},
mounted() {
},
data() {
return {
page: 1,
perPage: 25,
heartBeatList: [],
toggleCertInfoBox: false,
}
},
computed: {
@@ -154,6 +206,7 @@ export default {
importantHeartBeatList() {
if (this.$root.importantHeartbeatList[this.monitor.id]) {
this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id];
return this.$root.importantHeartbeatList[this.monitor.id]
} else {
return [];
@@ -166,8 +219,25 @@ export default {
} else {
return { }
}
}
},
certInfo() {
if (this.$root.certInfoList[this.monitor.id]) {
return this.$root.certInfoList[this.monitor.id]
} else {
return null
}
},
showCertInfoBox() {
return this.certInfo != null && this.toggleCertInfoBox;
},
displayedRecords() {
const startIndex = this.perPage * (this.page - 1);
const endIndex = startIndex + this.perPage;
return this.heartBeatList.slice(startIndex, endIndex);
},
},
methods: {
testNotification() {
@@ -251,4 +321,12 @@ table {
font-size: 13px;
color: #AAA;
}
.stats {
padding: 10px;
.col {
margin: 20px 0;
}
}
</style>

View File

@@ -48,6 +48,12 @@
<input type="number" class="form-control" id="interval" v-model="monitor.interval" required min="20" step="1">
</div>
<div class="mb-3">
<label for="maxRetries" class="form-label">Retries</label>
<input type="number" class="form-control" id="maxRetries" v-model="monitor.maxretries" required min="0" step="1">
<div class="form-text">Maximum retries before the service is marked as down and a notification is sent</div>
</div>
<div>
<button class="btn btn-primary" type="submit" :disabled="processing">Save</button>
</div>
@@ -61,7 +67,7 @@
<h2>Notifications</h2>
<p v-if="$root.notificationList.length === 0">Not available, please setup.</p>
<div class="form-check form-switch mb-3" v-for="notification in $root.notificationList">
<div class="form-check form-switch mb-3" :key="notification.id" v-for="notification in $root.notificationList">
<input class="form-check-input" type="checkbox" :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]">
<label class="form-check-label" :for=" 'notification' + notification.id">
@@ -119,6 +125,7 @@ export default {
name: "",
url: "https://",
interval: 60,
maxretries: 0,
notificationIDList: {},
}
} else if (this.isEdit) {

View File

@@ -11,7 +11,7 @@
<label for="timezone" class="form-label">Timezone</label>
<select class="form-select" id="timezone" v-model="$root.userTimezone">
<option value="auto">Auto: {{ guessTimezone }}</option>
<option v-for="timezone in timezoneList" :value="timezone.value">{{ timezone.name }}</option>
<option v-for="(timezone, index) in timezoneList" :value="timezone.value" :key="index">{{ timezone.name }}</option>
</select>
</div>
@@ -36,7 +36,7 @@
<label for="repeat-new-password" class="form-label">Repeat New Password</label>
<input type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" id="repeat-new-password" required v-model="password.repeatNewPassword">
<div class="invalid-feedback">
The repeat password is not match.
The repeat password does not match.
</div>
</div>
@@ -56,10 +56,10 @@
<h2>Notifications</h2>
<p v-if="$root.notificationList.length === 0">Not available, please setup.</p>
<p v-else>Please assign the notification to monitor(s) to get it works.</p>
<p v-else>Please assign a notification to monitor(s) to get it to work.</p>
<ul class="list-group mb-3" style="border-radius: 1rem;">
<li class="list-group-item" v-for="notification in $root.notificationList">
<li class="list-group-item" v-for="(notification, index) in $root.notificationList" :key="index">
{{ notification.name }}<br />
<a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a>
</li>
@@ -77,8 +77,8 @@
<script>
import dayjs from "dayjs";
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import NotificationDialog from "../components/NotificationDialog.vue";
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -1,6 +1,6 @@
import dayjs from "dayjs";
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
dayjs.extend(utc)
dayjs.extend(timezone)
@@ -29,6 +29,7 @@ function getTimezoneOffset(timeZone) {
}
// From: https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript
// TODO: Move to separate file
const aryIannaTimeZones = [
'Europe/Andorra',
'Asia/Dubai',
@@ -394,7 +395,7 @@ export function timezoneList() {
time: getTimezoneOffset(timezone),
})
} catch (e) {
console.log(e.message);
console.error(e.message);
console.log("Skip this timezone")
}
@@ -412,4 +413,3 @@ export function timezoneList() {
return result;
};