mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 13:36:55 +08:00
Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5d0b6190c3 | ||
|
cb85905c33 | ||
|
981ed5f29f | ||
|
0b45694f2f | ||
|
60531d0b15 | ||
|
a3de63ac3c | ||
|
80eadcb236 | ||
|
7e5a8c896b | ||
|
efe75bde75 | ||
|
af34e861c5 | ||
|
2ae2022e62 | ||
|
37f1d60f82 | ||
|
d39b43dacc | ||
|
7ca80fc086 | ||
|
eb34dc6cc2 | ||
|
ed93aae1c2 | ||
|
6a8ccf627a | ||
|
8f150aaeb9 | ||
|
6ed1d8cb2f | ||
|
71bec74081 | ||
|
2bd735035c | ||
|
48c6d8f19f | ||
|
2d176a38af | ||
|
b14f63491d | ||
|
24b87fcd5a | ||
|
365ea0a189 | ||
|
2461f5084e | ||
|
1d0b332b42 | ||
|
d5149f90b4 | ||
|
e0ae9a9e73 | ||
|
3227a2660b | ||
|
764160f38c | ||
|
70e7945a66 | ||
|
b413427a37 | ||
|
debcac4924 | ||
|
268dd33792 | ||
|
692a11e51e | ||
|
5eb4f55dfd | ||
|
e7cc5340e5 | ||
|
4d4d504d6e | ||
|
2a4695a774 | ||
|
f089bf73c3 | ||
|
f099e4270d | ||
|
81636c7b44 | ||
|
98fa995d3f | ||
|
42d24258cf | ||
|
3f56167198 | ||
|
5163e16482 | ||
|
d93f6e2716 | ||
|
d6fad7f1ef | ||
|
5512b15162 | ||
|
8979311653 | ||
|
4f058c5b47 | ||
|
9ba1743900 | ||
|
1e4f9c7e15 | ||
|
974672f7c1 | ||
|
01ac6d54be | ||
|
113899e278 | ||
|
d1d000bd74 | ||
|
ef4677a640 | ||
|
e39c46ff9b | ||
|
0e46ce42d1 |
@@ -1,11 +0,0 @@
|
||||
spec:
|
||||
name: uptime-kuma
|
||||
services:
|
||||
- name: server
|
||||
git:
|
||||
repo_clone_url: https://github.com/louislam/uptime-kuma
|
||||
branch: master
|
||||
http_port: 3001
|
||||
build_command: npm run setup
|
||||
run_command: npm run start-server
|
||||
|
@@ -36,6 +36,11 @@ module.exports = {
|
||||
"no-multi-spaces": ["error", {
|
||||
ignoreEOLComments: true,
|
||||
}],
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"curly": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"object-curly-newline": "off",
|
||||
|
12
.github/workflows/reviewdog.yml
vendored
12
.github/workflows/reviewdog.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: reviewdog
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
eslint:
|
||||
name: runner / eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: reviewdog/action-eslint@v1
|
||||
with:
|
||||
reporter: github-pr-review
|
||||
eslint_flags: --ext .js,.ts,.vue .
|
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"extends": "stylelint-config-recommended",
|
||||
"extends": "stylelint-config-recommended"
|
||||
}
|
||||
|
63
README.md
63
README.md
@@ -10,16 +10,16 @@ It is a self-hosted monitoring tool like "Uptime Robot".
|
||||
|
||||
<img src="https://louislam.net/uptimekuma/1.jpg" width="512" alt="" />
|
||||
|
||||
## Features
|
||||
## ⭐ Features
|
||||
|
||||
* Monitoring uptime for HTTP(s) / TCP / Ping.
|
||||
* Fancy, Reactive, Fast UI/UX.
|
||||
* Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise.
|
||||
* 20 seconds interval.
|
||||
|
||||
## How to Use
|
||||
## 🔧 How to Install
|
||||
|
||||
### Docker
|
||||
### 🐳 Docker
|
||||
|
||||
```bash
|
||||
# Create a volume
|
||||
@@ -31,18 +31,13 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti
|
||||
|
||||
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:1
|
||||
```
|
||||
If you want to change port and volume, or need to browse via a reserve proxy, please read: https://github.com/louislam/uptime-kuma/wiki/Installation.
|
||||
|
||||
### Without Docker (x86/x64 only)
|
||||
### 💪🏻 Without Docker (Recommanded for x86/x64 only)
|
||||
|
||||
Required Tools: Node.js >= 14, git and pm2.
|
||||
|
||||
(**Not recommanded for ARM CPU users.** Since there is no prebuilt for node-sqlite3, it is hard to get it running)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/louislam/uptime-kuma.git
|
||||
cd uptime-kuma
|
||||
@@ -56,42 +51,15 @@ npm run start-server
|
||||
# Install PM2 if you don't have: npm install pm2 -g
|
||||
pm2 start npm --name uptime-kuma -- run start-server
|
||||
|
||||
# Listen to different port or hostname
|
||||
pm2 start npm --name uptime-kuma -- run start-server -- --port=80 --hostname=0.0.0.0
|
||||
|
||||
```
|
||||
|
||||
More useful commands if you have installed.
|
||||
|
||||
```bash
|
||||
pm2 start uptime-kuma
|
||||
pm2 restart uptime-kuma
|
||||
pm2 stop uptime-kuma
|
||||
```
|
||||
|
||||
Browse to http://localhost:3001 after started.
|
||||
|
||||
### (Optional) One more step for Reverse Proxy
|
||||
If you want to change port and hostname, or need to browse via a reserve proxy, please read: https://github.com/louislam/uptime-kuma/wiki/Installation.
|
||||
|
||||
This is optional for someone who want to do reverse proxy.
|
||||
## 🆙 How to Update
|
||||
|
||||
Unlikely other web apps, Uptime Kuma is based on WebSocket. You need two more headers **"Upgrade"** and **"Connection"** in order to reverse proxy WebSocket.
|
||||
|
||||
Please read wiki for more info:
|
||||
https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy
|
||||
|
||||
### One-click Deploy
|
||||
|
||||
<!---
|
||||
Abort. Heroku instance killed the server.js if idle, stupid.
|
||||
[](https://heroku.com/deploy?template=https://github.com/louislam/uptime-kuma/tree/1.1.0)
|
||||
-->
|
||||
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/louislam/uptime-kuma/tree/master&refcode=e2c7eb658434)
|
||||
|
||||
## How to Update
|
||||
|
||||
### Docker
|
||||
### 🆙🐳 Docker
|
||||
|
||||
Re-pull the latest docker image and create another container with the same volume.
|
||||
|
||||
@@ -106,22 +74,27 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti
|
||||
|
||||
PS: For every new release, it takes some time to build the docker image, please be patient if it is not available yet.
|
||||
|
||||
### Without Docker
|
||||
### 🆙 💪🏻 Without Docker
|
||||
|
||||
```bash
|
||||
cd <uptime-kuma-directory>
|
||||
git fetch --all
|
||||
git checkout 1.1.0 --force
|
||||
git checkout 1.2.0 --force
|
||||
npm install
|
||||
npm run build
|
||||
pm2 restart uptime-kuma
|
||||
```
|
||||
|
||||
## What's Next?
|
||||
## 🆕 What's Next?
|
||||
|
||||
I will mark requests/issues to the next milestone.
|
||||
https://github.com/louislam/uptime-kuma/milestones
|
||||
|
||||
## More Screenshots
|
||||
## 🖼 More Screenshots
|
||||
|
||||
Dark Mode:
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/1336778/128710166-908f8d88-9256-43f3-9c49-bfc2c56011d2.png" width="400" alt="" />
|
||||
|
||||
Settings Page:
|
||||
|
||||
@@ -149,3 +122,5 @@ If you want to report a bug or request a new feature. Free feel to open a new is
|
||||
If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||
|
||||
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.
|
||||
|
||||
🐻
|
||||
|
7
app.json
7
app.json
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "Uptime Kuma",
|
||||
"description": "A fancy self-hosted monitoring tool",
|
||||
"repository": "https://github.com/louislam/uptime-kuma",
|
||||
"logo": "https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png",
|
||||
"keywords": ["node", "express", "socket-io", "uptime-kuma", "uptime"]
|
||||
}
|
1
extra/compile-install-script.ps1
Normal file
1
extra/compile-install-script.ps1
Normal file
@@ -0,0 +1 @@
|
||||
docker run -it --rm -v ${pwd}:/app louislam/batsh /usr/bin/batsh bash --output ./install.sh ./extra/install.batsh
|
11
index.html
11
index.html
@@ -1,13 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg"/>
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="theme-color" content="#5cdd8b"/>
|
||||
<meta name="description" content="Uptime Kuma monitoring tool"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<meta name="theme-color" id="theme-color" content="" />
|
||||
<meta name="description" content="Uptime Kuma monitoring tool" />
|
||||
<title>Uptime Kuma</title>
|
||||
</head>
|
||||
<body>
|
||||
|
290
package-lock.json
generated
290
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.0.10",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -756,9 +756,9 @@
|
||||
}
|
||||
},
|
||||
"@types/bootstrap": {
|
||||
"version": "5.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.0.17.tgz",
|
||||
"integrity": "sha512-uQQQ3p+zw10VjZLvtCuKWI6QgVCYEnK/yHnno3gyEhikfQdiZexS2XPxjWRboGmX135o470GkmCta9eAgQMVLQ==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.1.tgz",
|
||||
"integrity": "sha512-W/fEBlqwaJFh+3sCz/H88LPsLt/zLsEECFlrAOkrRPjWuo/ETl8u0JefIerCdc8+WukowQS1f60eIJOwkCBwhg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@popperjs/core": "^2.9.2",
|
||||
@@ -960,45 +960,45 @@
|
||||
}
|
||||
},
|
||||
"@vitejs/plugin-vue": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.3.0.tgz",
|
||||
"integrity": "sha512-wJvuJdTBjvucUX0vK4fuy60t+A9bJSZxc59vp1Y+8kiOd0NU5kFt4lay72gMWPeR+lSUjrTmGUq8Uzb99Jbw3A==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.4.0.tgz",
|
||||
"integrity": "sha512-RkqfJHz9wdLKBp5Yi+kQL8BAljdrvPoccQm2PTZc/UcL4EjD11xsv2PPCduYx2oV1a/bpSKA3sD5sxOHFhz+LA==",
|
||||
"dev": true
|
||||
},
|
||||
"@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==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.2.tgz",
|
||||
"integrity": "sha512-QhCI0ZU5nAR0LMcLgzW3v75374tIrHGp8XG5CzJS7Nsy+iuignbE4MZ2XJfh5TGIrtpuzfWA4eTIfukZf/cRdg==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.12.0",
|
||||
"@babel/types": "^7.12.0",
|
||||
"@vue/shared": "3.1.5",
|
||||
"@vue/shared": "3.2.2",
|
||||
"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==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.2.tgz",
|
||||
"integrity": "sha512-ggcc+NV/ENIE0Uc3TxVE/sKrhYVpLepMAAmEiQ047332mbKOvUkowz4TTFZ+YkgOIuBOPP0XpCxmCMg7p874mA==",
|
||||
"requires": {
|
||||
"@vue/compiler-core": "3.1.5",
|
||||
"@vue/shared": "3.1.5"
|
||||
"@vue/compiler-core": "3.2.2",
|
||||
"@vue/shared": "3.2.2"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz",
|
||||
"integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.2.tgz",
|
||||
"integrity": "sha512-hrtqpQ5L6IPn5v7yVRo7uvLcQxv0z1+KBjZBWMBOcrXz4t+PKUxU/SWd6Tl9T8FDmYlunzKUh6lcx+2CLo6f5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.13.9",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@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",
|
||||
"@vue/compiler-core": "3.2.2",
|
||||
"@vue/compiler-dom": "3.2.2",
|
||||
"@vue/compiler-ssr": "3.2.2",
|
||||
"@vue/shared": "3.2.2",
|
||||
"consolidate": "^0.16.0",
|
||||
"estree-walker": "^2.0.1",
|
||||
"hash-sum": "^2.0.0",
|
||||
@@ -1009,116 +1009,54 @@
|
||||
"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.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz",
|
||||
"integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.2.tgz",
|
||||
"integrity": "sha512-rVl1agMFhdEN3Go0bCriXo+3cysxKIuRP0yh1Wd8ysRrKfAmokyDhUA8PrGSq2Ymj/LdZTh+4OKfj3p2+C+hlA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@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/compiler-dom": "3.2.2",
|
||||
"@vue/shared": "3.2.2"
|
||||
}
|
||||
},
|
||||
"@vue/devtools-api": {
|
||||
"version": "6.0.0-beta.14",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.14.tgz",
|
||||
"integrity": "sha512-44fPrrN1cqcs6bFkT0C+yxTM6PZXLbR+ESh1U1j8UD22yO04gXvxH62HApMjLbS3WqJO/iCNC+CYT+evPQh2EQ=="
|
||||
"version": "6.0.0-beta.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.15.tgz",
|
||||
"integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA=="
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.2.tgz",
|
||||
"integrity": "sha512-IHjhtmrhK6dzacj/EnLQDWOaA3HuzzVk6w84qgV8EpS4uWGIJXiRalMRg6XvGW2ykJvIl3pLsF0aBFlTMRiLOA==",
|
||||
"requires": {
|
||||
"@vue/shared": "3.1.5"
|
||||
"@vue/shared": "3.2.2"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-core": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.1.5.tgz",
|
||||
"integrity": "sha512-YQbG5cBktN1RowQDKA22itmvQ+b40f0WgQ6CXK4VYoYICAiAfu6Cc14777ve8zp1rJRGtk5oIeS149TOculrTg==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.2.tgz",
|
||||
"integrity": "sha512-/aUk1+GO/VPX0oVxhbzSWE1zrf3/wGCsO1ALNisVokYftKqfqLDjbJHE6mrI2hx3MiuwbHrWjJClkGUVTIOPEQ==",
|
||||
"requires": {
|
||||
"@vue/reactivity": "3.1.5",
|
||||
"@vue/shared": "3.1.5"
|
||||
"@vue/reactivity": "3.2.2",
|
||||
"@vue/shared": "3.2.2"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-dom": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.1.5.tgz",
|
||||
"integrity": "sha512-tNcf3JhVR0RfW0kw1p8xZgv30nvX8Y9rsz7eiQ0dHe273sfoCngAG0y4GvMaY4Xd8FsjUwFedd4suQ8Lu8meXg==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.2.tgz",
|
||||
"integrity": "sha512-1Le/NpCfawCOfePfJezvWUF+oCVLU8N+IHN4oFDOxRe6/PgHNJ+yT+YdxFifBfI+TIAoXI/9PsnqzmJZV+xsmw==",
|
||||
"requires": {
|
||||
"@vue/runtime-core": "3.1.5",
|
||||
"@vue/shared": "3.1.5",
|
||||
"@vue/runtime-core": "3.2.2",
|
||||
"@vue/shared": "3.2.2",
|
||||
"csstype": "^2.6.8"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.2.tgz",
|
||||
"integrity": "sha512-dvYb318tk9uOzHtSaT3WII/HscQSIRzoCZ5GyxEb3JlkEXASpAUAQwKnvSe2CudnF8XHFRTB7VITWSnWNLZUtA=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
@@ -1779,6 +1717,16 @@
|
||||
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
||||
"dev": true
|
||||
},
|
||||
"chart.js": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz",
|
||||
"integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA=="
|
||||
},
|
||||
"chartjs-adapter-dayjs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs/-/chartjs-adapter-dayjs-1.0.0.tgz",
|
||||
"integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg=="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
|
||||
@@ -1945,9 +1893,9 @@
|
||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz",
|
||||
"integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==",
|
||||
"version": "3.16.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.1.tgz",
|
||||
"integrity": "sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw==",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
@@ -2441,9 +2389,9 @@
|
||||
}
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "7.15.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.15.1.tgz",
|
||||
"integrity": "sha512-4/r+n/i+ovyeW2gVRRH92kpy4lkpFbyPR4BMxGBTLtGnwqOKKzjSo6EMSaT0RhWPvEjK9uifcY8e7z5n8BIEgw==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
"integrity": "sha512-0E2dVvVC7I2Xm1HXyx+ZwPj9CNX4NJjs4K4r+GVsHWyt5Pew3JLD4fI7A91b2jeL0TXE7LlszrwLSTJU9eqehw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-utils": "^2.1.0",
|
||||
@@ -4789,9 +4737,9 @@
|
||||
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
|
||||
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz",
|
||||
"integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colorette": "^1.2.2",
|
||||
@@ -4874,9 +4822,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss-modules": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz",
|
||||
"integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.2.2.tgz",
|
||||
"integrity": "sha512-/H08MGEmaalv/OU8j6bUKi/kZr2kqGF6huAW8m9UAgOLWtpFdhA14+gPBoymtqyv+D4MLsmqaF2zvIegdCxJXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"generic-names": "^2.0.1",
|
||||
@@ -5148,9 +5096,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"prom-client": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz",
|
||||
"integrity": "sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng==",
|
||||
"version": "13.2.0",
|
||||
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.2.0.tgz",
|
||||
"integrity": "sha512-wGr5mlNNdRNzEhRYXgboUU2LxHWIojxscJKmtG3R8f4/KiWqyYgXTLHs0+Ted7tG3zFT7pgHJbtomzZ1L0ARaQ==",
|
||||
"requires": {
|
||||
"tdigest": "^0.1.1"
|
||||
}
|
||||
@@ -6805,13 +6753,82 @@
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.1.5.tgz",
|
||||
"integrity": "sha512-Ho7HNb1nfDoO+HVb6qYZgeaobt1XbY6KXFe4HGs1b9X6RhkWG/113n4/SrtM1LUclM6OrP/Se5aPHHvAPG1iVQ==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.2.tgz",
|
||||
"integrity": "sha512-D/LuzAV30CgNJYGyNheE/VUs5N4toL2IgmS6c9qeOxvyh0xyn4exyRqizpXIrsvfx34zG9x5gCI2tdRHCGvF9w==",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.1.5",
|
||||
"@vue/runtime-dom": "3.1.5",
|
||||
"@vue/shared": "3.1.5"
|
||||
"@vue/compiler-dom": "3.2.2",
|
||||
"@vue/runtime-dom": "3.2.2",
|
||||
"@vue/shared": "3.2.2"
|
||||
}
|
||||
},
|
||||
"vue-chart-3": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.7.tgz",
|
||||
"integrity": "sha512-BccfPv2rodY6IOppYHvMluVmIJE1CHfp5uW2DXrHrm1kIzaafLwpQ5SwdrxuCevn/QhKoi7azzcxwRcoWbX9hg==",
|
||||
"requires": {
|
||||
"@vue/runtime-core": "^3.2.1",
|
||||
"@vue/runtime-dom": "^3.2.1",
|
||||
"csstype": "^3.0.8",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "^3.1.23",
|
||||
"vue-demi": "^0.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/reactivity": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz",
|
||||
"integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==",
|
||||
"requires": {
|
||||
"@vue/shared": "3.2.1"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-core": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz",
|
||||
"integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==",
|
||||
"requires": {
|
||||
"@vue/reactivity": "3.2.1",
|
||||
"@vue/shared": "3.2.1"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-dom": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz",
|
||||
"integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==",
|
||||
"requires": {
|
||||
"@vue/runtime-core": "3.2.1",
|
||||
"@vue/shared": "3.2.1",
|
||||
"csstype": "^2.6.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"csstype": {
|
||||
"version": "2.6.17",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz",
|
||||
"integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz",
|
||||
"integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg=="
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
|
||||
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.23",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
|
||||
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-confirm-dialog": {
|
||||
@@ -6819,6 +6836,11 @@
|
||||
"resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz",
|
||||
"integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ=="
|
||||
},
|
||||
"vue-demi": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.10.1.tgz",
|
||||
"integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA=="
|
||||
},
|
||||
"vue-eslint-parser": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz",
|
||||
@@ -6865,9 +6887,9 @@
|
||||
"integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q=="
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz",
|
||||
"integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==",
|
||||
"version": "4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.11.tgz",
|
||||
"integrity": "sha512-sha6I8fx9HWtvTrFZfxZkiQQBpqSeT+UCwauYjkdOQYRvwsGwimlQQE2ayqUwuuXGzquFpCPoXzYKWlzL4OuXg==",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^6.0.0-beta.14"
|
||||
}
|
||||
|
31
package.json
31
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -13,27 +13,32 @@
|
||||
"dev": "vite --host",
|
||||
"start": "npm run start-server",
|
||||
"start-server": "node server/server.js",
|
||||
"start-demo-server": "set NODE_ENV=demo && 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.1.0 --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.2.0 --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.1.0 && npm install && npm run build",
|
||||
"setup": "git checkout 1.2.0 && npm install && npm run build",
|
||||
"update-version": "node extra/update-version.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js"
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
"compile-install-script": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./extra/compile-install-script.ps1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
||||
"@louislam/sqlite3": "^5.0.3",
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"args-parser": "^1.3.0",
|
||||
"axios": "^0.21.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"bootstrap": "^5.1.0",
|
||||
"chart.js": "^3.5.0",
|
||||
"chartjs-adapter-dayjs": "^1.0.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"dayjs": "^1.10.6",
|
||||
"express": "^4.17.1",
|
||||
@@ -43,29 +48,29 @@
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"nodemailer": "^6.6.3",
|
||||
"password-hash": "^1.2.2",
|
||||
"prom-client": "^13.1.0",
|
||||
"prom-client": "^13.2.0",
|
||||
"prometheus-api-metrics": "^3.2.0",
|
||||
"redbean-node": "0.0.21",
|
||||
"socket.io": "^4.1.3",
|
||||
"socket.io-client": "^4.1.3",
|
||||
"@louislam/sqlite3": "^5.0.3",
|
||||
"tcp-ping": "^0.1.1",
|
||||
"v-pagination-3": "^0.1.6",
|
||||
"vue": "^3.1.5",
|
||||
"vue": "^3.2.2",
|
||||
"vue-chart-3": "^0.5.7",
|
||||
"vue-confirm-dialog": "^1.0.2",
|
||||
"vue-multiselect": "^3.0.0-alpha.2",
|
||||
"vue-router": "^4.0.10",
|
||||
"vue-router": "^4.0.11",
|
||||
"vue-toastification": "^2.0.0-rc.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.15.0",
|
||||
"@types/bootstrap": "^5.0.17",
|
||||
"@types/bootstrap": "^5.1.1",
|
||||
"@vitejs/plugin-legacy": "^1.5.1",
|
||||
"@vitejs/plugin-vue": "^1.3.0",
|
||||
"@vue/compiler-sfc": "^3.1.5",
|
||||
"core-js": "^3.16.0",
|
||||
"@vitejs/plugin-vue": "^1.4.0",
|
||||
"@vue/compiler-sfc": "^3.2.2",
|
||||
"core-js": "^3.16.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^7.15.1",
|
||||
"eslint-plugin-vue": "^7.16.0",
|
||||
"sass": "^1.37.5",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-recommended": "^5.0.0",
|
||||
|
@@ -11,6 +11,7 @@ const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-ser
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification")
|
||||
const version = require("../../package.json").version;
|
||||
|
||||
/**
|
||||
* status:
|
||||
@@ -79,6 +80,10 @@ class Monitor extends BeanModel {
|
||||
|
||||
const beat = async () => {
|
||||
|
||||
// Expose here for prometheus update
|
||||
// undefined if not https
|
||||
let tlsInfo = undefined;
|
||||
|
||||
if (! previousBeat) {
|
||||
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
||||
this.id,
|
||||
@@ -110,9 +115,10 @@ class Monitor extends BeanModel {
|
||||
// Use Custom agent to disable session reuse
|
||||
// https://github.com/nodejs/node/issues/3940
|
||||
let res = await axios.get(this.url, {
|
||||
timeout: 15000,
|
||||
timeout: this.interval * 1000 * 0.8,
|
||||
headers: {
|
||||
"User-Agent": "Uptime-Kuma",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
maxCachedSessions: 0,
|
||||
@@ -131,7 +137,7 @@ class Monitor extends BeanModel {
|
||||
let certInfoStartTime = dayjs().valueOf();
|
||||
if (this.getUrl()?.protocol === "https:") {
|
||||
try {
|
||||
await this.updateTlsInfo(checkCertificate(res));
|
||||
tlsInfo = await this.updateTlsInfo(checkCertificate(res));
|
||||
} catch (e) {
|
||||
if (e.message !== "No TLS certificate in response") {
|
||||
console.error(e.message)
|
||||
@@ -253,7 +259,7 @@ class Monitor extends BeanModel {
|
||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||
}
|
||||
|
||||
prometheus.update(bean)
|
||||
prometheus.update(bean, tlsInfo)
|
||||
|
||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||
|
||||
@@ -288,7 +294,7 @@ class Monitor extends BeanModel {
|
||||
/**
|
||||
* Store TLS info to database
|
||||
* @param checkCertificateResult
|
||||
* @returns {Promise<void>}
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async updateTlsInfo(checkCertificateResult) {
|
||||
let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
@@ -300,6 +306,8 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
tls_info_bean.info_json = JSON.stringify(checkCertificateResult);
|
||||
await R.store(tls_info_bean);
|
||||
|
||||
return checkCertificateResult;
|
||||
}
|
||||
|
||||
static async sendStats(io, monitorID, userID) {
|
||||
|
@@ -193,6 +193,34 @@ class Notification {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
} else if (notification.type === "octopush") {
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
'api-key': notification.octopushAPIKey,
|
||||
'api-login': notification.octopushLogin,
|
||||
'cache-control': 'no-cache'
|
||||
}
|
||||
};
|
||||
let data = {
|
||||
"recipients": [
|
||||
{
|
||||
"phone_number": notification.octopushPhoneNumber
|
||||
}
|
||||
],
|
||||
//octopush not supporting non ascii char
|
||||
"text": msg.replace(/[^\x00-\x7F]/g, ""),
|
||||
"type": notification.octopushSMSType,
|
||||
"purpose": "alert",
|
||||
"sender": notification.octopushSenderName
|
||||
};
|
||||
|
||||
await axios.post(`https://api.octopush.com/v1/public/sms-campaign/send`, data, config)
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
} else if (notification.type === "slack") {
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
@@ -326,6 +354,41 @@ class Notification {
|
||||
throwGeneralAxiosError(error)
|
||||
}
|
||||
|
||||
} else if (notification.type === "pushbullet") {
|
||||
try {
|
||||
let pushbulletUrl = `https://api.pushbullet.com/v2/pushes`;
|
||||
let config = {
|
||||
headers: {
|
||||
'Access-Token': notification.pushbulletAccessToken,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
if (heartbeatJSON == null) {
|
||||
let testdata = {
|
||||
"type": "note",
|
||||
"title": "Uptime Kuma Alert",
|
||||
"body": "Testing Successful.",
|
||||
}
|
||||
await axios.post(pushbulletUrl, testdata, config)
|
||||
} else if (heartbeatJSON["status"] == 0) {
|
||||
let downdata = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert:" + monitorJSON["name"],
|
||||
"body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"],
|
||||
}
|
||||
await axios.post(pushbulletUrl, downdata, config)
|
||||
} else if (heartbeatJSON["status"] == 1) {
|
||||
let updata = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert:" + monitorJSON["name"],
|
||||
"body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"],
|
||||
}
|
||||
await axios.post(pushbulletUrl, updata, config)
|
||||
}
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
throwGeneralAxiosError(error)
|
||||
}
|
||||
} else {
|
||||
throw new Error("Notification type is not supported")
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
// https://github.com/ben-bradley/ping-lite/blob/master/ping-lite.js
|
||||
// Fixed on Windows
|
||||
|
||||
let spawn = require("child_process").spawn,
|
||||
const net = require("net");
|
||||
const spawn = require("child_process").spawn,
|
||||
events = require("events"),
|
||||
fs = require("fs"),
|
||||
WIN = /^win/.test(process.platform),
|
||||
LIN = /^linux/.test(process.platform),
|
||||
MAC = /^darwin/.test(process.platform);
|
||||
const { debug } = require("../src/util");
|
||||
|
||||
module.exports = Ping;
|
||||
|
||||
@@ -24,14 +25,30 @@ function Ping(host, options) {
|
||||
this._bin = "c:/windows/system32/ping.exe";
|
||||
this._args = (options.args) ? options.args : [ "-n", "1", "-w", "5000", host ];
|
||||
this._regmatch = /[><=]([0-9.]+?)ms/;
|
||||
|
||||
} else if (LIN) {
|
||||
this._bin = "/bin/ping";
|
||||
this._args = (options.args) ? options.args : [ "-n", "-w", "2", "-c", "1", host ];
|
||||
this._regmatch = /=([0-9.]+?) ms/; // need to verify this
|
||||
|
||||
const defaultArgs = [ "-n", "-w", "2", "-c", "1", host ];
|
||||
|
||||
if (net.isIPv6(host) || options.ipv6) {
|
||||
defaultArgs.unshift("-6");
|
||||
}
|
||||
|
||||
this._args = (options.args) ? options.args : defaultArgs;
|
||||
this._regmatch = /=([0-9.]+?) ms/;
|
||||
|
||||
} else if (MAC) {
|
||||
this._bin = "/sbin/ping";
|
||||
|
||||
if (net.isIPv6(host) || options.ipv6) {
|
||||
this._bin = "/sbin/ping6";
|
||||
} else {
|
||||
this._bin = "/sbin/ping";
|
||||
}
|
||||
|
||||
this._args = (options.args) ? options.args : [ "-n", "-t", "2", "-c", "1", host ];
|
||||
this._regmatch = /=([0-9.]+?) ms/;
|
||||
|
||||
} else {
|
||||
throw new Error("Could not detect your ping binary.");
|
||||
}
|
||||
@@ -49,9 +66,9 @@ Ping.prototype.__proto__ = events.EventEmitter.prototype;
|
||||
|
||||
// SEND A PING
|
||||
// ===========
|
||||
Ping.prototype.send = function(callback) {
|
||||
Ping.prototype.send = function (callback) {
|
||||
let self = this;
|
||||
callback = callback || function(err, ms) {
|
||||
callback = callback || function (err, ms) {
|
||||
if (err) {
|
||||
return self.emit("error", err);
|
||||
}
|
||||
@@ -62,27 +79,27 @@ Ping.prototype.send = function(callback) {
|
||||
|
||||
this._ping = spawn(this._bin, this._args); // spawn the binary
|
||||
|
||||
this._ping.on("error", function(err) { // handle binary errors
|
||||
this._ping.on("error", function (err) { // handle binary errors
|
||||
_errored = true;
|
||||
callback(err);
|
||||
});
|
||||
|
||||
this._ping.stdout.on("data", function(data) { // log stdout
|
||||
this._ping.stdout.on("data", function (data) { // log stdout
|
||||
this._stdout = (this._stdout || "") + data;
|
||||
});
|
||||
|
||||
this._ping.stdout.on("end", function() {
|
||||
this._ping.stdout.on("end", function () {
|
||||
_ended = true;
|
||||
if (_exited && !_errored) {
|
||||
onEnd.call(self._ping);
|
||||
}
|
||||
});
|
||||
|
||||
this._ping.stderr.on("data", function(data) { // log stderr
|
||||
this._ping.stderr.on("data", function (data) { // log stderr
|
||||
this._stderr = (this._stderr || "") + data;
|
||||
});
|
||||
|
||||
this._ping.on("exit", function(code) { // handle complete
|
||||
this._ping.on("exit", function (code) { // handle complete
|
||||
_exited = true;
|
||||
if (_ended && !_errored) {
|
||||
onEnd.call(self._ping);
|
||||
@@ -105,15 +122,15 @@ Ping.prototype.send = function(callback) {
|
||||
ms = stdout.match(self._regmatch); // parse out the ##ms response
|
||||
ms = (ms && ms[1]) ? Number(ms[1]) : ms;
|
||||
|
||||
callback(null, ms);
|
||||
callback(null, ms, stdout);
|
||||
}
|
||||
};
|
||||
|
||||
// CALL Ping#send(callback) ON A TIMER
|
||||
// ===================================
|
||||
Ping.prototype.start = function(callback) {
|
||||
Ping.prototype.start = function (callback) {
|
||||
let self = this;
|
||||
this._i = setInterval(function() {
|
||||
this._i = setInterval(function () {
|
||||
self.send(callback);
|
||||
}, (self._options.interval || 5000));
|
||||
self.send(callback);
|
||||
@@ -121,6 +138,6 @@ Ping.prototype.start = function(callback) {
|
||||
|
||||
// STOP SENDING PINGS
|
||||
// ==================
|
||||
Ping.prototype.stop = function() {
|
||||
Ping.prototype.stop = function () {
|
||||
clearInterval(this._i);
|
||||
};
|
||||
|
@@ -1,22 +1,33 @@
|
||||
const PrometheusClient = require('prom-client');
|
||||
const PrometheusClient = require("prom-client");
|
||||
|
||||
const commonLabels = [
|
||||
'monitor_name',
|
||||
'monitor_type',
|
||||
'monitor_url',
|
||||
'monitor_hostname',
|
||||
'monitor_port',
|
||||
"monitor_name",
|
||||
"monitor_type",
|
||||
"monitor_url",
|
||||
"monitor_hostname",
|
||||
"monitor_port",
|
||||
]
|
||||
|
||||
const monitor_cert_days_remaining = new PrometheusClient.Gauge({
|
||||
name: "monitor_cert_days_remaining",
|
||||
help: "The number of days remaining until the certificate expires",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
|
||||
const monitor_cert_is_valid = new PrometheusClient.Gauge({
|
||||
name: "monitor_cert_is_valid",
|
||||
help: "Is the certificate still valid? (1 = Yes, 0= No)",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
const monitor_response_time = new PrometheusClient.Gauge({
|
||||
name: 'monitor_response_time',
|
||||
help: 'Monitor Response Time (ms)',
|
||||
name: "monitor_response_time",
|
||||
help: "Monitor Response Time (ms)",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
|
||||
const monitor_status = new PrometheusClient.Gauge({
|
||||
name: 'monitor_status',
|
||||
help: 'Monitor Status (1 = UP, 0= DOWN)',
|
||||
name: "monitor_status",
|
||||
help: "Monitor Status (1 = UP, 0= DOWN)",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
|
||||
@@ -33,7 +44,27 @@ class Prometheus {
|
||||
}
|
||||
}
|
||||
|
||||
update(heartbeat) {
|
||||
update(heartbeat, tlsInfo) {
|
||||
if (typeof tlsInfo !== "undefined") {
|
||||
try {
|
||||
let is_valid = 0
|
||||
if (tlsInfo.valid == true) {
|
||||
is_valid = 1
|
||||
} else {
|
||||
is_valid = 0
|
||||
}
|
||||
monitor_cert_is_valid.set(this.monitorLabelValues, is_valid)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
try {
|
||||
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.daysRemaining)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
monitor_status.set(this.monitorLabelValues, heartbeat.status)
|
||||
} catch (e) {
|
||||
@@ -41,7 +72,7 @@ class Prometheus {
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof heartbeat.ping === 'number') {
|
||||
if (typeof heartbeat.ping === "number") {
|
||||
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping)
|
||||
} else {
|
||||
// Is it good?
|
||||
|
@@ -40,7 +40,11 @@ const passwordHash = require("./password-hash");
|
||||
const args = require("args-parser")(process.argv);
|
||||
|
||||
const version = require("../package.json").version;
|
||||
const hostname = process.env.HOST || args.host || "0.0.0.0"
|
||||
|
||||
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
|
||||
// Dual-stack support for (::)
|
||||
const hostname = process.env.HOST || args.host;
|
||||
|
||||
const port = parseInt(process.env.PORT || args.port || 3001);
|
||||
|
||||
console.info("Version: " + version)
|
||||
@@ -566,16 +570,26 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||
|
||||
});
|
||||
|
||||
console.log("Init")
|
||||
console.log("Init the server")
|
||||
|
||||
server.once("error", async (err) => {
|
||||
console.error("Cannot listen: " + err.message);
|
||||
await Database.close();
|
||||
});
|
||||
|
||||
server.listen(port, hostname, () => {
|
||||
console.log(`Listening on ${hostname}:${port}`);
|
||||
if (hostname) {
|
||||
console.log(`Listening on ${hostname}:${port}`);
|
||||
} else {
|
||||
console.log(`Listening on ${port}`);
|
||||
}
|
||||
startMonitors();
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
async function updateMonitorNotification(monitorID, notificationIDList) {
|
||||
R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [
|
||||
await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [
|
||||
monitorID,
|
||||
])
|
||||
|
||||
|
@@ -45,15 +45,30 @@ exports.tcping = function (hostname, port) {
|
||||
});
|
||||
}
|
||||
|
||||
exports.ping = function (hostname) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ping = new Ping(hostname);
|
||||
exports.ping = async (hostname) => {
|
||||
try {
|
||||
return await exports.pingAsync(hostname);
|
||||
} catch (e) {
|
||||
// If the host cannot be resolved, try again with ipv6
|
||||
if (e.message.includes("service not known")) {
|
||||
return await exports.pingAsync(hostname, true);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ping.send(function (err, ms) {
|
||||
exports.pingAsync = function (hostname, ipv6 = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ping = new Ping(hostname, {
|
||||
ipv6
|
||||
});
|
||||
|
||||
ping.send(function (err, ms, stdout) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
} else if (ms === null) {
|
||||
reject(new Error("timeout"))
|
||||
reject(new Error(stdout))
|
||||
} else {
|
||||
resolve(Math.round(ms))
|
||||
}
|
||||
|
@@ -6,8 +6,7 @@
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 31px;
|
||||
height: 38px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -132,7 +131,12 @@ h2 {
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
filter: invert(1);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
|
@@ -22,8 +22,10 @@
|
||||
<option value="slack">Slack</option>
|
||||
<option value="pushover">Pushover</option>
|
||||
<option value="pushy">Pushy</option>
|
||||
<option value="octopush">Octopush</option>
|
||||
<option value="lunasea">LunaSea</option>
|
||||
<option value="apprise">Apprise (Support 50+ Notification services)</option>
|
||||
<option value="pushbullet">Pushbullet</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -252,6 +254,37 @@
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'octopush'">
|
||||
<div class="mb-3">
|
||||
<label for="octopush-key" class="form-label">API KEY</label>
|
||||
<input id="octopush-key" v-model="notification.octopushAPIKey" type="text" class="form-control" required>
|
||||
<label for="octopush-login" class="form-label">API LOGIN</label>
|
||||
<input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-type-sms" class="form-label">SMS Type</label>
|
||||
<select id="octopush-type-sms" v-model="notification.octopushSMSType" class="form-select">
|
||||
<option value="sms_premium">Premium (Fast - recommended for alerting)</option>
|
||||
<option value="sms_low_cost">Low Cost (Slow, sometimes blocked by operator)</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Check octopush prices <a href="https://octopush.com/tarifs-sms-international/" target="_blank">https://octopush.com/tarifs-sms-international/</a>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-phone-number" class="form-label">Phone number (intl format, eg : +33612345678) </label>
|
||||
<input id="octopush-phone-number" v-model="notification.octopushPhoneNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-sender-name" class="form-label">SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)</label>
|
||||
<input id="octopush-sender-name" v-model="notification.octopushSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://octopush.com/api-sms-documentation/envoi-de-sms/" target="_blank">https://octopush.com/api-sms-documentation/envoi-de-sms/</a>
|
||||
</p>
|
||||
</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>
|
||||
@@ -339,6 +372,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'pushbullet'">
|
||||
<div class="mb-3">
|
||||
<label for="pushbullet-access-token" class="form-label">Access Token</label>
|
||||
<input id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
|
||||
|
152
src/components/PingChart.vue
Normal file
152
src/components/PingChart.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<LineChart :chart-data="chartData" :height="100" :options="chartOptions" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BarController, BarElement, Chart, Filler, LinearScale, LineController, LineElement, PointElement, TimeScale, Tooltip } from "chart.js";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import "chartjs-adapter-dayjs";
|
||||
import { LineChart } from "vue-chart-3";
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler);
|
||||
|
||||
export default {
|
||||
components: { LineChart },
|
||||
props: {
|
||||
monitorId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartPeriodHrs: 6,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
chartOptions() {
|
||||
return {
|
||||
responsive: true,
|
||||
layout: {
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 30,
|
||||
top: 30,
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
},
|
||||
bar: {
|
||||
barThickness: "flex",
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "minute",
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
autoSkipPadding: 10,
|
||||
},
|
||||
grid: {
|
||||
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Response Time (ms)",
|
||||
},
|
||||
offset: false,
|
||||
grid: {
|
||||
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
|
||||
},
|
||||
},
|
||||
y1: {
|
||||
display: false,
|
||||
position: "right",
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
min: 0,
|
||||
max: 1,
|
||||
offset: false,
|
||||
},
|
||||
},
|
||||
bounds: "ticks",
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
intersect: false,
|
||||
padding: 10,
|
||||
filter: function (tooltipItem) {
|
||||
return tooltipItem.datasetIndex === 0;
|
||||
},
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
return ` ${new Intl.NumberFormat().format(context.parsed.y)} ms`
|
||||
},
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
chartData() {
|
||||
let ping_data = [];
|
||||
let down_data = [];
|
||||
if (this.monitorId in this.$root.heartbeatList) {
|
||||
ping_data = this.$root.heartbeatList[this.monitorId]
|
||||
.filter(
|
||||
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours")))
|
||||
.map((beat) => {
|
||||
return {
|
||||
x: dayjs.utc(beat.time).tz(this.$root.timezone).format("YYYY-MM-DD HH:mm:ss"),
|
||||
y: beat.ping,
|
||||
};
|
||||
});
|
||||
down_data = this.$root.heartbeatList[this.monitorId]
|
||||
.filter(
|
||||
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours")))
|
||||
.map((beat) => {
|
||||
return {
|
||||
x: dayjs.utc(beat.time).tz(this.$root.timezone).format("YYYY-MM-DD HH:mm:ss"),
|
||||
y: beat.status === 0 ? 1 : 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
data: ping_data,
|
||||
fill: "origin",
|
||||
tension: 0.2,
|
||||
borderColor: "#5CDD8B",
|
||||
backgroundColor: "#5CDD8B38",
|
||||
yAxisID: "y",
|
||||
},
|
||||
{
|
||||
type: "bar",
|
||||
data: down_data,
|
||||
borderColor: "#00000000",
|
||||
backgroundColor: "#DC354568",
|
||||
yAxisID: "y1",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="$root.theme">
|
||||
<div :class="classes">
|
||||
<div v-if="! $root.socket.connected && ! $root.socket.firstConnect" class="lost-connection">
|
||||
<div class="container-fluid">
|
||||
{{ $root.connectionErrorMsg }}
|
||||
@@ -79,29 +79,45 @@
|
||||
import Login from "../components/Login.vue";
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
Login,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
|
||||
computed: {
|
||||
|
||||
// Theme or Mobile
|
||||
classes() {
|
||||
const classes = {};
|
||||
classes[this.$root.theme] = true;
|
||||
classes["mobile"] = this.$root.isMobile;
|
||||
return classes;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route (to, from) {
|
||||
this.init();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
if (this.$route.name === "root") {
|
||||
this.$router.push("/dashboard")
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import EmptyLayout from "./layouts/EmptyLayout.vue";
|
||||
import Layout from "./layouts/Layout.vue";
|
||||
import socket from "./mixins/socket";
|
||||
import theme from "./mixins/theme";
|
||||
import mobile from "./mixins/mobile";
|
||||
import Dashboard from "./pages/Dashboard.vue";
|
||||
import DashboardHome from "./pages/DashboardHome.vue";
|
||||
import Details from "./pages/Details.vue";
|
||||
@@ -78,7 +79,8 @@ const router = createRouter({
|
||||
const app = createApp({
|
||||
mixins: [
|
||||
socket,
|
||||
theme
|
||||
theme,
|
||||
mobile
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
|
25
src/mixins/mobile.js
Normal file
25
src/mixins/mobile.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
windowWidth: window.innerWidth,
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener("resize", this.onResize);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onResize() {
|
||||
this.windowWidth = window.innerWidth;
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.windowWidth <= 767.98;
|
||||
},
|
||||
}
|
||||
|
||||
}
|
@@ -27,7 +27,6 @@ export default {
|
||||
uptimeList: { },
|
||||
certInfoList: {},
|
||||
notificationList: [],
|
||||
windowWidth: window.innerWidth,
|
||||
showListMobile: false,
|
||||
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
|
||||
}
|
||||
@@ -193,10 +192,6 @@ export default {
|
||||
this.$root.showListMobile = false;
|
||||
},
|
||||
|
||||
onResize() {
|
||||
this.windowWidth = window.innerWidth;
|
||||
},
|
||||
|
||||
storage() {
|
||||
return (this.remember) ? localStorage : sessionStorage;
|
||||
},
|
||||
@@ -270,10 +265,6 @@ export default {
|
||||
|
||||
computed: {
|
||||
|
||||
isMobile() {
|
||||
return this.windowWidth <= 767.98;
|
||||
},
|
||||
|
||||
timezone() {
|
||||
|
||||
if (this.userTimezone === "auto") {
|
||||
|
@@ -2,7 +2,7 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
system: (window.matchMedia("(prefers-color-scheme: dark)")) ? "dark" : "light",
|
||||
system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
|
||||
userTheme: localStorage.theme,
|
||||
};
|
||||
},
|
||||
@@ -14,6 +14,7 @@ export default {
|
||||
}
|
||||
|
||||
document.body.classList.add(this.theme);
|
||||
this.updateThemeColorMeta();
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -33,6 +34,17 @@ export default {
|
||||
theme(to, from) {
|
||||
document.body.classList.remove(from);
|
||||
document.body.classList.add(this.theme);
|
||||
this.updateThemeColorMeta();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateThemeColorMeta() {
|
||||
if (this.theme === "dark") {
|
||||
document.querySelector("#theme-color").setAttribute("content", "#161B22");
|
||||
} else {
|
||||
document.querySelector("#theme-color").setAttribute("content", "#5cdd8b");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -42,7 +42,11 @@
|
||||
<div class="col">
|
||||
<h4>{{ pingTitle }}</h4>
|
||||
<p>(Current)</p>
|
||||
<span class="num"><CountUp :value="ping" /></span>
|
||||
<span class="num">
|
||||
<a href="#" @click.prevent="showPingChartBox = !showPingChartBox">
|
||||
<CountUp :value="ping" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4>Avg. {{ pingTitle }}</h4>
|
||||
@@ -70,6 +74,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showPingChartBox" class="shadow-box big-padding text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<PingChart :monitor-id="monitor.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showCertInfoBox" class="shadow-box big-padding text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@@ -155,6 +167,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineAsyncComponent } from "vue";
|
||||
import { useToast } from "vue-toastification"
|
||||
const toast = useToast()
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
@@ -164,6 +177,7 @@ import Datetime from "../components/Datetime.vue";
|
||||
import CountUp from "../components/CountUp.vue";
|
||||
import Uptime from "../components/Uptime.vue";
|
||||
import Pagination from "v-pagination-3";
|
||||
const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -174,6 +188,7 @@ export default {
|
||||
Confirm,
|
||||
Status,
|
||||
Pagination,
|
||||
PingChart,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -181,6 +196,7 @@ export default {
|
||||
perPage: 25,
|
||||
heartBeatList: [],
|
||||
toggleCertInfoBox: false,
|
||||
showPingChartBox: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -306,6 +322,31 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.functions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button, a {
|
||||
margin-left: 10px !important;
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
a.btn {
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.url {
|
||||
color: $primary;
|
||||
margin-bottom: 20px;
|
||||
|
@@ -248,10 +248,22 @@ export default {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.btn-check:active + .btn-outline-primary,
|
||||
.btn-check:checked + .btn-outline-primary,
|
||||
.btn-check:hover + .btn-outline-primary {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.list-group-item {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
|
||||
.btn-check:active + .btn-outline-primary,
|
||||
.btn-check:checked + .btn-outline-primary,
|
||||
.btn-check:hover + .btn-outline-primary {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user