mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 16:54:04 +00:00
Merge branch 'master' into something
# Conflicts: # server/notification.js # src/components/NotificationDialog.vue
This commit is contained in:
commit
916b9da0dc
19 changed files with 703 additions and 232 deletions
|
@ -3,3 +3,11 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/data/kuma.db
|
/data/kuma.db
|
||||||
/.do
|
/.do
|
||||||
|
**/.dockerignore
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
.editorconfig
|
||||||
|
|
10
.github/ISSUE_TEMPLATE/--please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/--please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md
vendored
Normal 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
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
19
README.md
19
README.md
|
@ -27,7 +27,7 @@ It is a self-hosted monitoring tool like "Uptime Robot".
|
||||||
docker volume create uptime-kuma
|
docker volume create uptime-kuma
|
||||||
|
|
||||||
# Start the container
|
# 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.
|
Browse to http://localhost:3001 after started.
|
||||||
|
@ -35,7 +35,7 @@ Browse to http://localhost:3001 after started.
|
||||||
Change Port and Volume
|
Change Port and Volume
|
||||||
|
|
||||||
```bash
|
```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
|
### Without Docker
|
||||||
|
@ -80,12 +80,17 @@ PS: For every new release, it takes some time to build the docker image, please
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git fetch --all
|
git fetch --all
|
||||||
git checkout 1.0.5 --force
|
git checkout 1.0.6 --force
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
pm2 restart uptime-kuma
|
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
|
# More Screenshots
|
||||||
|
|
||||||
Settings Page:
|
Settings Page:
|
||||||
|
@ -109,3 +114,11 @@ Telegram Notification Sample:
|
||||||
|
|
||||||
If you love this project, please consider giving me a ⭐.
|
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.
|
||||||
|
|
37
db/patch1.sql
Normal file
37
db/patch1.sql
Normal 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;
|
|
@ -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 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 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
|
# 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
|
ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1
|
||||||
RUN apk add --no-cache python3
|
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 cargo py3-pip python3-dev && \
|
RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev python3-dev && \
|
||||||
pip3 install apprise && \
|
pip3 install apprise && \
|
||||||
apk del .build-deps
|
apk del .build-deps
|
||||||
|
RUN apprise --version
|
||||||
|
|
||||||
# New things add here
|
# New things add here
|
||||||
|
|
||||||
|
@ -31,7 +34,7 @@ RUN npm run build
|
||||||
|
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
VOLUME ["/app/data"]
|
VOLUME ["/app/data"]
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --start-period=30s CMD node extra/healthcheck.js
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js
|
||||||
CMD ["npm", "run", "start-server"]
|
CMD ["npm", "run", "start-server"]
|
||||||
|
|
||||||
FROM release AS nightly
|
FROM release AS nightly
|
||||||
|
|
|
@ -34,6 +34,7 @@ if (newVersion) {
|
||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n")
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n")
|
||||||
|
|
||||||
// Process README.md
|
// 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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
226
package-lock.json
generated
226
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.0.4",
|
"version": "1.0.6",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -40,37 +40,43 @@
|
||||||
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
||||||
},
|
},
|
||||||
"@types/cookie": {
|
"@types/cookie": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||||
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg=="
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||||
},
|
},
|
||||||
"@types/cors": {
|
"@types/cors": {
|
||||||
"version": "2.8.10",
|
"version": "2.8.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||||
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
|
"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": {
|
"@types/node": {
|
||||||
"version": "15.12.4",
|
"version": "16.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.3.tgz",
|
||||||
"integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA=="
|
"integrity": "sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ=="
|
||||||
},
|
},
|
||||||
"@vitejs/plugin-legacy": {
|
"@vitejs/plugin-legacy": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.4.tgz",
|
||||||
"integrity": "sha512-lxZUJaMWYMQuqvZM1wPzDP6KABQgA/drVL5fnaygEPcz9adc2OHhfFNN/SvvHQ1V0rP8gybIc7uA+iI1gAdkVQ==",
|
"integrity": "sha512-pVYeQUDPG5InWwrTu7acy187WWjGonJnL/GMqMLmeKCFiwkZ6UcsoUjojiKmCUI0nAJTrrKH5lhjTqkccY9Iow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/standalone": "^7.14.7",
|
"@babel/standalone": "^7.14.7",
|
||||||
"core-js": "^3.15.1",
|
"core-js": "^3.15.2",
|
||||||
"magic-string": "^0.25.7",
|
"magic-string": "^0.25.7",
|
||||||
"regenerator-runtime": "^0.13.7",
|
"regenerator-runtime": "^0.13.7",
|
||||||
"systemjs": "^6.10.1"
|
"systemjs": "^6.10.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vitejs/plugin-vue": {
|
"@vitejs/plugin-vue": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.5.tgz",
|
||||||
"integrity": "sha512-LlnLpObkGKZ+b7dcpL4T24l13nPSHLjo+6Oc7MbZiKz5PMAUzADfNJ3EKfYIQ0l0969nxf2jp/9vsfnuJ7h6fw==",
|
"integrity": "sha512-GIR31mdXTEfvElmBUaRhDc5v7lfdkEdawWQqJRiaRL/5qKsH+xusukglkvJz5y7+c6dEpxgmvcATv2BbB7+fzQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@vue/compiler-core": {
|
"@vue/compiler-core": {
|
||||||
|
@ -95,17 +101,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-sfc": {
|
"@vue/compiler-sfc": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz",
|
||||||
"integrity": "sha512-lSgMsZaYHF+bAgryq5aUqpvyfhu52GJI2/4LoiJCE5uaxc6FCZfxfgqgw/d9ltiZghv+HiISFtmQVAVvlsk+/w==",
|
"integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.13.9",
|
"@babel/parser": "^7.13.9",
|
||||||
"@babel/types": "^7.13.0",
|
"@babel/types": "^7.13.0",
|
||||||
"@vue/compiler-core": "3.1.1",
|
"@types/estree": "^0.0.48",
|
||||||
"@vue/compiler-dom": "3.1.1",
|
"@vue/compiler-core": "3.1.5",
|
||||||
"@vue/compiler-ssr": "3.1.1",
|
"@vue/compiler-dom": "3.1.5",
|
||||||
"@vue/shared": "3.1.1",
|
"@vue/compiler-ssr": "3.1.5",
|
||||||
|
"@vue/shared": "3.1.5",
|
||||||
"consolidate": "^0.16.0",
|
"consolidate": "^0.16.0",
|
||||||
"estree-walker": "^2.0.1",
|
"estree-walker": "^2.0.1",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
|
@ -116,16 +123,78 @@
|
||||||
"postcss-modules": "^4.0.0",
|
"postcss-modules": "^4.0.0",
|
||||||
"postcss-selector-parser": "^6.0.4",
|
"postcss-selector-parser": "^6.0.4",
|
||||||
"source-map": "^0.6.1"
|
"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": {
|
"@vue/compiler-ssr": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz",
|
||||||
"integrity": "sha512-7H6krZtVt3h/YzfNp7eYK41hMDz8ZskiBy+Wby+EDRINX6BD9JQ5C8zyy2xAa7T6Iz2VrQzsaJ/Bb52lTPSS5A==",
|
"integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-dom": "3.1.1",
|
"@vue/compiler-dom": "3.1.5",
|
||||||
"@vue/shared": "3.1.1"
|
"@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": {
|
"@vue/devtools-api": {
|
||||||
|
@ -323,6 +392,11 @@
|
||||||
"follow-redirects": "^1.10.0"
|
"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": {
|
"backo2": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||||
|
@ -531,9 +605,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz",
|
||||||
"integrity": "sha512-Fl79+wsLOZKoiU345KeEaWD0ik8WKRI5zm0YSPj2oF1Qr+BO7z0fco6GbUtqjoG1h4VI89PeKJnMsMMVQdKKTw=="
|
"integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q=="
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
|
@ -655,6 +729,11 @@
|
||||||
"delayed-stream": "~1.0.0"
|
"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": {
|
"commander": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||||
|
@ -753,9 +832,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dayjs": {
|
"dayjs": {
|
||||||
"version": "1.10.5",
|
"version": "1.10.6",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz",
|
||||||
"integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g=="
|
"integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw=="
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
|
@ -898,9 +977,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"engine.io-client": {
|
"engine.io-client": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.2.tgz",
|
||||||
"integrity": "sha512-jPFpw2HLL0lhZ2KY0BpZhIJdleQcUO9W1xkIpo0h3d6s+5D6+EV/xgQw9qWOmymszv2WXef/6KUUehyxEKomlQ==",
|
"integrity": "sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"base64-arraybuffer": "0.1.4",
|
"base64-arraybuffer": "0.1.4",
|
||||||
"component-emitter": "~1.3.0",
|
"component-emitter": "~1.3.0",
|
||||||
|
@ -922,9 +1001,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"esbuild": {
|
"esbuild": {
|
||||||
"version": "0.12.9",
|
"version": "0.12.15",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz",
|
||||||
"integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw==",
|
"integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"escape-html": {
|
"escape-html": {
|
||||||
|
@ -2019,6 +2098,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
"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": {
|
"merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
@ -2420,9 +2504,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nodemailer": {
|
"nodemailer": {
|
||||||
"version": "6.6.2",
|
"version": "6.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz",
|
||||||
"integrity": "sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q=="
|
"integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew=="
|
||||||
},
|
},
|
||||||
"nopt": {
|
"nopt": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
|
@ -2975,9 +3059,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"version": "2.52.2",
|
"version": "2.53.2",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz",
|
||||||
"integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==",
|
"integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
|
@ -3002,9 +3086,9 @@
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"sass": {
|
"sass": {
|
||||||
"version": "1.35.1",
|
"version": "1.35.2",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz",
|
||||||
"integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==",
|
"integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0"
|
"chokidar": ">=3.0.0 <4.0.0"
|
||||||
|
@ -3225,19 +3309,19 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"socket.io": {
|
"socket.io": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz",
|
||||||
"integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==",
|
"integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/cookie": "^0.4.0",
|
"@types/cookie": "^0.4.0",
|
||||||
"@types/cors": "^2.8.8",
|
"@types/cors": "^2.8.10",
|
||||||
"@types/node": ">=10.0.0",
|
"@types/node": ">=10.0.0",
|
||||||
"accepts": "~1.3.4",
|
"accepts": "~1.3.4",
|
||||||
"base64id": "~2.0.0",
|
"base64id": "~2.0.0",
|
||||||
"debug": "~4.3.1",
|
"debug": "~4.3.1",
|
||||||
"engine.io": "~5.1.0",
|
"engine.io": "~5.1.1",
|
||||||
"socket.io-adapter": "~2.3.0",
|
"socket.io-adapter": "~2.3.1",
|
||||||
"socket.io-parser": "~4.0.3"
|
"socket.io-parser": "~4.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"socket.io-adapter": {
|
"socket.io-adapter": {
|
||||||
|
@ -3246,15 +3330,15 @@
|
||||||
"integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw=="
|
"integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw=="
|
||||||
},
|
},
|
||||||
"socket.io-client": {
|
"socket.io-client": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.3.tgz",
|
||||||
"integrity": "sha512-RDpWJP4DQT1XeexmeDyDkm0vrFc0+bUsHDKiVGaNISJvJonhQQOMqV9Vwfg0ZpPJ27LCdan7iqTI92FRSOkFWQ==",
|
"integrity": "sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/component-emitter": "^1.2.10",
|
"@types/component-emitter": "^1.2.10",
|
||||||
"backo2": "~1.0.2",
|
"backo2": "~1.0.2",
|
||||||
"component-emitter": "~1.3.0",
|
"component-emitter": "~1.3.0",
|
||||||
"debug": "~4.3.1",
|
"debug": "~4.3.1",
|
||||||
"engine.io-client": "~5.1.1",
|
"engine.io-client": "~5.1.2",
|
||||||
"parseuri": "0.0.6",
|
"parseuri": "0.0.6",
|
||||||
"socket.io-parser": "~4.0.4"
|
"socket.io-parser": "~4.0.4"
|
||||||
}
|
}
|
||||||
|
@ -3624,6 +3708,16 @@
|
||||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||||
"optional": true
|
"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": {
|
"v8flags": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
|
||||||
|
@ -3649,14 +3743,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vite": {
|
"vite": {
|
||||||
"version": "2.3.8",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-2.4.2.tgz",
|
||||||
"integrity": "sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ==",
|
"integrity": "sha512-2MifxD2I9fjyDmmEzbULOo3kOUoqX90A58cT6mECxoVQlMYFuijZsPQBuA14mqSwvV3ydUsqnq+BRWXyO9Qa+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"esbuild": "^0.12.8",
|
"esbuild": "^0.12.8",
|
||||||
"fsevents": "~2.3.2",
|
"fsevents": "~2.3.2",
|
||||||
"postcss": "^8.3.4",
|
"postcss": "^8.3.5",
|
||||||
"resolve": "^1.20.0",
|
"resolve": "^1.20.0",
|
||||||
"rollup": "^2.38.5"
|
"rollup": "^2.38.5"
|
||||||
}
|
}
|
||||||
|
|
38
package.json
38
package.json
|
@ -1,15 +1,21 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/louislam/uptime-kuma.git"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"start-server": "node server/server.js",
|
"start-server": "node server/server.js",
|
||||||
"update": "",
|
"update": "",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"vite-preview-dist": "vite preview --host",
|
"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": "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.6 --target release . --push",
|
||||||
"build-docker-nightly": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||||
"setup": "git checkout 1.0.5 && npm install && npm run build",
|
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
|
||||||
|
"setup": "git checkout 1.0.6 && npm install && npm run build",
|
||||||
"version-global-replace": "node extra/version-global-replace.js",
|
"version-global-replace": "node extra/version-global-replace.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
||||||
},
|
},
|
||||||
|
@ -18,30 +24,32 @@
|
||||||
"args-parser": "^1.3.0",
|
"args-parser": "^1.3.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"bootstrap": "^5.0.0",
|
"bootstrap": "^5.0.2",
|
||||||
"dayjs": "^1.10.4",
|
"command-exists": "^1.2.9",
|
||||||
|
"dayjs": "^1.10.6",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"http-graceful-shutdown": "^3.1.2",
|
"http-graceful-shutdown": "^3.1.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"nodemailer": "^6.6.2",
|
"nodemailer": "^6.6.3",
|
||||||
"password-hash": "^1.2.2",
|
"password-hash": "^1.2.2",
|
||||||
"redbean-node": "0.0.20",
|
"redbean-node": "0.0.20",
|
||||||
"socket.io": "^4.0.2",
|
"socket.io": "^4.1.3",
|
||||||
"socket.io-client": "^4.1.2",
|
"socket.io-client": "^4.1.3",
|
||||||
"sqlite3": "^5.0.0",
|
"sqlite3": "^5.0.2",
|
||||||
"tcp-ping": "^0.1.1",
|
"tcp-ping": "^0.1.1",
|
||||||
|
"v-pagination-3": "^0.1.6",
|
||||||
"vue": "^3.0.5",
|
"vue": "^3.0.5",
|
||||||
"vue-confirm-dialog": "^1.0.2",
|
"vue-confirm-dialog": "^1.0.2",
|
||||||
"vue-router": "^4.0.10",
|
"vue-router": "^4.0.10",
|
||||||
"vue-toastification": "^2.0.0-rc.1"
|
"vue-toastification": "^2.0.0-rc.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-legacy": "^1.4.3",
|
"@vitejs/plugin-legacy": "^1.4.4",
|
||||||
"@vitejs/plugin-vue": "^1.2.3",
|
"@vitejs/plugin-vue": "^1.2.5",
|
||||||
"@vue/compiler-sfc": "^3.0.5",
|
"@vue/compiler-sfc": "^3.1.5",
|
||||||
"core-js": "^3.15.2",
|
"core-js": "^3.15.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.2",
|
||||||
"vite": "^2.3.7"
|
"vite": "^2.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
119
server/database.js
Normal file
119
server/database.js
Normal 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 = 1;
|
||||||
|
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;
|
|
@ -48,8 +48,6 @@ class Monitor extends BeanModel {
|
||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
console.log(`Monitor ${this.id}: Heartbeat`)
|
|
||||||
|
|
||||||
if (! previousBeat) {
|
if (! previousBeat) {
|
||||||
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
||||||
this.id
|
this.id
|
||||||
|
@ -145,6 +143,12 @@ class Monitor extends BeanModel {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bean.status === 1) {
|
||||||
|
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | 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());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
|
|
||||||
await R.store(bean)
|
await R.store(bean)
|
||||||
|
|
|
@ -2,9 +2,22 @@ const axios = require("axios");
|
||||||
const {R} = require("redbean-node");
|
const {R} = require("redbean-node");
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const nodemailer = require("nodemailer");
|
const nodemailer = require("nodemailer");
|
||||||
|
const child_process = require("child_process");
|
||||||
|
|
||||||
class Notification {
|
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) {
|
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
let okMsg = "Sent Successfully. ";
|
||||||
|
|
||||||
if (notification.type === "telegram") {
|
if (notification.type === "telegram") {
|
||||||
try {
|
try {
|
||||||
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
|
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
|
||||||
|
@ -13,15 +26,16 @@ class Notification {
|
||||||
text: msg,
|
text: msg,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return true;
|
return okMsg;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
let msg = (error.response.data.description) ? error.response.data.description : "Error without description"
|
||||||
return false;
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.type === "gotify") {
|
} else if (notification.type === "gotify") {
|
||||||
try {
|
try {
|
||||||
if (notification.gotifyserverurl.endsWith("/")) {
|
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
|
||||||
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
|
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
|
||||||
}
|
}
|
||||||
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
|
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
|
||||||
|
@ -29,15 +43,15 @@ class Notification {
|
||||||
"priority": notification.gotifyPriority || 8,
|
"priority": notification.gotifyPriority || 8,
|
||||||
"title": "Uptime-Kuma"
|
"title": "Uptime-Kuma"
|
||||||
})
|
})
|
||||||
return true;
|
|
||||||
|
return okMsg;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
throwGeneralAxiosError(error)
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.type === "webhook") {
|
} else if (notification.type === "webhook") {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
heartbeat: heartbeatJSON,
|
heartbeat: heartbeatJSON,
|
||||||
monitor: monitorJSON,
|
monitor: monitorJSON,
|
||||||
|
@ -58,11 +72,11 @@ class Notification {
|
||||||
finalData = data;
|
finalData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.post(notification.webhookURL, finalData, config)
|
let res = await axios.post(notification.webhookURL, finalData, config)
|
||||||
return true;
|
return okMsg;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
throwGeneralAxiosError(error)
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.type === "smtp") {
|
} else if (notification.type === "smtp") {
|
||||||
|
@ -77,7 +91,7 @@ class Notification {
|
||||||
content: msg
|
content: msg
|
||||||
}
|
}
|
||||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||||
return true;
|
return okMsg;
|
||||||
}
|
}
|
||||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||||
if(heartbeatJSON['status'] == 0) {
|
if(heartbeatJSON['status'] == 0) {
|
||||||
|
@ -102,12 +116,10 @@ class Notification {
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||||
await axios.post(notification.discordWebhookUrl, data)
|
return okMsg;
|
||||||
return true;
|
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.error(error)
|
throwGeneralAxiosError(error)
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.type === "signal") {
|
} else if (notification.type === "signal") {
|
||||||
|
@ -119,19 +131,18 @@ class Notification {
|
||||||
};
|
};
|
||||||
let config = {};
|
let config = {};
|
||||||
|
|
||||||
await axios.post(notification.signalURL, data, config)
|
let res = await axios.post(notification.signalURL, data, config)
|
||||||
return true;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
throwGeneralAxiosError(error)
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.type === "slack") {
|
} else if (notification.type === "slack") {
|
||||||
try {
|
try {
|
||||||
if (heartbeatJSON == null) {
|
if (heartbeatJSON == null) {
|
||||||
let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo}
|
let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo}
|
||||||
await axios.post(notification.slackwebhookURL, data)
|
let res = await axios.post(notification.slackwebhookURL, data)
|
||||||
return true;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = heartbeatJSON["time"];
|
const time = heartbeatJSON["time"];
|
||||||
|
@ -175,11 +186,10 @@ class Notification {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
await axios.post(notification.slackwebhookURL, data)
|
let res = await axios.post(notification.slackwebhookURL, data)
|
||||||
return true;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
throwGeneralAxiosError(error)
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.type === "pushover") {
|
} else if (notification.type === "pushover") {
|
||||||
|
@ -190,7 +200,7 @@ class Notification {
|
||||||
'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds,
|
'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds,
|
||||||
'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1}
|
'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1}
|
||||||
let res = await axios.post(pushoverlink, data)
|
let res = await axios.post(pushoverlink, data)
|
||||||
return true;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
|
@ -205,12 +215,15 @@ class Notification {
|
||||||
"html": 1
|
"html": 1
|
||||||
}
|
}
|
||||||
let res = await axios.post(pushoverlink, data)
|
let res = await axios.post(pushoverlink, data)
|
||||||
return true;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
throwGeneralAxiosError(error)
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (notification.type === "apprise") {
|
||||||
|
|
||||||
|
return Notification.apprise(notification, msg)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Notification type is not supported")
|
throw new Error("Notification type is not supported")
|
||||||
}
|
}
|
||||||
|
@ -272,20 +285,47 @@ class Notification {
|
||||||
text: msg,
|
text: msg,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return "Sent Successfully.";
|
||||||
}
|
}
|
||||||
|
|
||||||
static async discord(notification, msg) {
|
static async apprise(notification, msg) {
|
||||||
const client = new Discord.Client();
|
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
|
||||||
await client.login(notification.discordToken)
|
|
||||||
|
|
||||||
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 = {
|
module.exports = {
|
||||||
|
|
|
@ -12,6 +12,7 @@ const fs = require("fs");
|
||||||
const {getSettings} = require("./util-server");
|
const {getSettings} = require("./util-server");
|
||||||
const {Notification} = require("./notification")
|
const {Notification} = require("./notification")
|
||||||
const gracefulShutdown = require('http-graceful-shutdown');
|
const gracefulShutdown = require('http-graceful-shutdown');
|
||||||
|
const Database = require("./database");
|
||||||
const {sleep} = require("./util");
|
const {sleep} = require("./util");
|
||||||
const args = require('args-parser')(process.argv);
|
const args = require('args-parser')(process.argv);
|
||||||
|
|
||||||
|
@ -27,27 +28,48 @@ const server = http.createServer(app);
|
||||||
const io = new Server(server);
|
const io = new Server(server);
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total WebSocket client connected to server currently, no actual use
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
let totalClient = 0;
|
let totalClient = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use for decode the auth object
|
||||||
|
* @type {null}
|
||||||
|
*/
|
||||||
let jwtSecret = null;
|
let jwtSecret = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main monitor list
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
let monitorList = {};
|
let monitorList = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show Setup Page
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
let needSetup = false;
|
let needSetup = false;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
|
|
||||||
|
console.log("Adding route")
|
||||||
app.use('/', express.static("dist"));
|
app.use('/', express.static("dist"));
|
||||||
|
|
||||||
app.get('*', function(request, response, next) {
|
app.get('*', function(request, response, next) {
|
||||||
response.sendFile(process.cwd() + '/dist/index.html');
|
response.sendFile(process.cwd() + '/dist/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
console.log("Adding socket handler")
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
|
|
||||||
socket.emit("info", {
|
socket.emit("info", {
|
||||||
version,
|
version,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('a user connected');
|
|
||||||
totalClient++;
|
totalClient++;
|
||||||
|
|
||||||
if (needSetup) {
|
if (needSetup) {
|
||||||
|
@ -56,7 +78,6 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log('user disconnected');
|
|
||||||
totalClient--;
|
totalClient--;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -433,25 +454,36 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket)
|
checkLogin(socket)
|
||||||
|
|
||||||
await Notification.send(notification, notification.name + " Testing")
|
let msg = await Notification.send(notification, notification.name + " Testing")
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Sent Successfully"
|
msg
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message
|
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, () => {
|
server.listen(port, hostname, () => {
|
||||||
console.log(`Listening on ${hostname}:${port}`);
|
console.log(`Listening on ${hostname}:${port}`);
|
||||||
|
|
||||||
startMonitors();
|
startMonitors();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -539,18 +571,21 @@ function checkLogin(socket) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initDatabase() {
|
async function initDatabase() {
|
||||||
const path = './data/kuma.db';
|
if (! fs.existsSync(Database.path)) {
|
||||||
|
|
||||||
if (! fs.existsSync(path)) {
|
|
||||||
console.log("Copying Database")
|
console.log("Copying Database")
|
||||||
fs.copyFileSync("./db/kuma.db", path);
|
fs.copyFileSync(Database.templatePath, Database.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Connecting to Database")
|
console.log("Connecting to Database")
|
||||||
|
|
||||||
R.setup('sqlite', {
|
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)
|
R.freeze(true)
|
||||||
await R.autoloadModels("./server/model");
|
await R.autoloadModels("./server/model");
|
||||||
|
|
||||||
|
@ -565,10 +600,12 @@ async function initDatabase() {
|
||||||
|
|
||||||
jwtSecretBean.value = passwordHash.generate(dayjs() + "")
|
jwtSecretBean.value = passwordHash.generate(dayjs() + "")
|
||||||
await R.store(jwtSecretBean)
|
await R.store(jwtSecretBean)
|
||||||
|
console.log("Stored JWT secret into database")
|
||||||
} else {
|
} else {
|
||||||
console.log("Load JWT secret from database.")
|
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) {
|
if ((await R.count("user")) === 0) {
|
||||||
console.log("No user, need setup")
|
console.log("No user, need setup")
|
||||||
needSetup = true;
|
needSetup = true;
|
||||||
|
@ -687,11 +724,6 @@ const startGracefulShutdown = async () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let noReject = true;
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
|
||||||
noReject = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function shutdownFunction(signal) {
|
async function shutdownFunction(signal) {
|
||||||
console.log('Called signal: ' + signal);
|
console.log('Called signal: ' + signal);
|
||||||
|
|
||||||
|
@ -700,24 +732,8 @@ async function shutdownFunction(signal) {
|
||||||
let monitor = monitorList[id]
|
let monitor = monitorList[id]
|
||||||
monitor.stop()
|
monitor.stop()
|
||||||
}
|
}
|
||||||
await sleep(2000)
|
await sleep(2000);
|
||||||
|
await Database.close();
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalFunction() {
|
function finalFunction() {
|
||||||
|
|
|
@ -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) {
|
exports.getSettings = async function (type) {
|
||||||
let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [
|
let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [
|
||||||
type
|
type
|
||||||
|
|
|
@ -15,14 +15,14 @@
|
||||||
<label for="floatingPassword">Password</label>
|
<label for="floatingPassword">Password</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3 mt-3" >
|
<div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4">
|
||||||
<label>
|
<div class="form-check">
|
||||||
<input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember">
|
<input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember">
|
||||||
|
|
||||||
<label class="form-check-label" for="remember">
|
<label class="form-check-label" for="remember">
|
||||||
Remember me
|
Remember me
|
||||||
</label>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button>
|
<button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button>
|
||||||
|
|
||||||
|
|
|
@ -10,60 +10,61 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<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">
|
<div class="mb-3">
|
||||||
<label for="type" class="form-label">Notification Type</label>
|
<label for="telegram-bot-token" class="form-label">Bot Token</label>
|
||||||
<select class="form-select" id="type" v-model="notification.type">
|
<input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken">
|
||||||
<option value="telegram">Telegram</option>
|
<div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div>
|
||||||
<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>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="name" class="form-label">Friendly Name</label>
|
<label for="telegram-chat-id" class="form-label">Chat ID</label>
|
||||||
<input type="text" class="form-control" id="name" required v-model="notification.name">
|
|
||||||
|
<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>
|
</div>
|
||||||
|
</template>
|
||||||
<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 v-if="notification.type === 'webhook'">
|
<template v-if="notification.type === 'webhook'">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -269,6 +270,29 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="notification.type === 'apprise'">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="gotify-application-token" class="form-label">Apprise URL</label>
|
||||||
|
<input type="text" class="form-control" id="gotify-application-token" 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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button>
|
<button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button>
|
||||||
|
@ -307,17 +331,15 @@ export default {
|
||||||
type: null,
|
type: null,
|
||||||
gotifyPriority: 8
|
gotifyPriority: 8
|
||||||
},
|
},
|
||||||
|
appriseInstalled: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.modal = new Modal(this.$refs.modal)
|
this.modal = new Modal(this.$refs.modal)
|
||||||
|
|
||||||
// TODO: for edit
|
this.$root.getSocket().emit("checkApprise", (installed) => {
|
||||||
this.$root.getSocket().emit("getSettings", "notification", (data) => {
|
this.appriseInstalled = installed;
|
||||||
// this.notification = data
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ export default {
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
|
|
||||||
let wsHost;
|
let wsHost;
|
||||||
if (localStorage.dev === "dev") {
|
const env = process.env.NODE_ENV || "production";
|
||||||
|
if (env === "development" || localStorage.dev === "dev") {
|
||||||
wsHost = ":3001"
|
wsHost = ":3001"
|
||||||
} else {
|
} else {
|
||||||
wsHost = ""
|
wsHost = ""
|
||||||
|
@ -45,6 +46,10 @@ export default {
|
||||||
transports: ['websocket']
|
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) => {
|
socket.on('info', (info) => {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
});
|
});
|
||||||
|
@ -158,7 +163,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
getSocket() {
|
getSocket() {
|
||||||
return socket;
|
return socket;
|
||||||
},
|
},
|
||||||
|
|
||||||
toastRes(res) {
|
toastRes(res) {
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="beat in importantHeartBeatList">
|
<tr v-for="(beat, index) in displayedRecords" :key="index">
|
||||||
<td>{{ beat.name }}</td>
|
<td>{{ beat.name }}</td>
|
||||||
<td><Status :status="beat.status" /></td>
|
<td><Status :status="beat.status" /></td>
|
||||||
<td><Datetime :value="beat.time" /></td>
|
<td><Datetime :value="beat.time" /></td>
|
||||||
|
@ -59,6 +59,13 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center kuma_pagination">
|
||||||
|
<pagination
|
||||||
|
v-model="page"
|
||||||
|
:records=importantHeartBeatList.length
|
||||||
|
:per-page="perPage" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -68,8 +75,21 @@
|
||||||
<script>
|
<script>
|
||||||
import Status from "../components/Status.vue";
|
import Status from "../components/Status.vue";
|
||||||
import Datetime from "../components/Datetime.vue";
|
import Datetime from "../components/Datetime.vue";
|
||||||
|
import Pagination from "v-pagination-3";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Datetime, Status},
|
components: {
|
||||||
|
Datetime,
|
||||||
|
Status,
|
||||||
|
Pagination,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
perPage: 25,
|
||||||
|
heartBeatList: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
stats() {
|
stats() {
|
||||||
let result = {
|
let result = {
|
||||||
|
@ -127,8 +147,16 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.heartBeatList = result;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
displayedRecords() {
|
||||||
|
const startIndex = this.perPage * (this.page - 1);
|
||||||
|
const endIndex = startIndex + this.perPage;
|
||||||
|
return this.heartBeatList.slice(startIndex, endIndex);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="beat in importantHeartBeatList">
|
<tr v-for="(beat, index) in displayedRecords" :key="index">
|
||||||
<td><Status :status="beat.status" /></td>
|
<td><Status :status="beat.status" /></td>
|
||||||
<td><Datetime :value="beat.time" /></td>
|
<td><Datetime :value="beat.time" /></td>
|
||||||
<td>{{ beat.msg }}</td>
|
<td>{{ beat.msg }}</td>
|
||||||
|
@ -75,6 +75,13 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center kuma_pagination">
|
||||||
|
<pagination
|
||||||
|
v-model="page"
|
||||||
|
:records=importantHeartBeatList.length
|
||||||
|
:per-page="perPage" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Confirm ref="confirmPause" @yes="pauseMonitor">
|
<Confirm ref="confirmPause" @yes="pauseMonitor">
|
||||||
|
@ -95,6 +102,7 @@ import Status from "../components/Status.vue";
|
||||||
import Datetime from "../components/Datetime.vue";
|
import Datetime from "../components/Datetime.vue";
|
||||||
import CountUp from "../components/CountUp.vue";
|
import CountUp from "../components/CountUp.vue";
|
||||||
import Uptime from "../components/Uptime.vue";
|
import Uptime from "../components/Uptime.vue";
|
||||||
|
import Pagination from "v-pagination-3";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -104,13 +112,16 @@ export default {
|
||||||
HeartbeatBar,
|
HeartbeatBar,
|
||||||
Confirm,
|
Confirm,
|
||||||
Status,
|
Status,
|
||||||
|
Pagination,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
page: 1,
|
||||||
|
perPage: 25,
|
||||||
|
heartBeatList: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -154,6 +165,7 @@ export default {
|
||||||
|
|
||||||
importantHeartBeatList() {
|
importantHeartBeatList() {
|
||||||
if (this.$root.importantHeartbeatList[this.monitor.id]) {
|
if (this.$root.importantHeartbeatList[this.monitor.id]) {
|
||||||
|
this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id];
|
||||||
return this.$root.importantHeartbeatList[this.monitor.id]
|
return this.$root.importantHeartbeatList[this.monitor.id]
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@ -166,8 +178,13 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
displayedRecords() {
|
||||||
|
const startIndex = this.perPage * (this.page - 1);
|
||||||
|
const endIndex = startIndex + this.perPage;
|
||||||
|
return this.heartBeatList.slice(startIndex, endIndex);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
testNotification() {
|
testNotification() {
|
||||||
|
|
Loading…
Reference in a new issue