Merge branch 'louislam:master' into master

pull/217/head
Ponkhy 3 years ago committed by GitHub
commit 91d4c15b4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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="" /> <img src="https://louislam.net/uptimekuma/1.jpg" width="512" alt="" />
## Features ## Features
* Monitoring uptime for HTTP(s) / TCP / Ping. * Monitoring uptime for HTTP(s) / TCP / Ping.
* Fancy, Reactive, Fast UI/UX. * Fancy, Reactive, Fast UI/UX.
* Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise. * Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise.
* 20 seconds interval. * 20 seconds interval.
## How to Use ## 🔧 How to Install
### Docker ### 🐳 Docker
```bash ```bash
# Create a volume # 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. Browse to http://localhost:3001 after started.
Change Port and Volume
```bash 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.
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 (x86/x64 only) ### 💪🏻 Without Docker (Recommanded for x86/x64 only)
Required Tools: Node.js >= 14, git and pm2. 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 ```bash
git clone https://github.com/louislam/uptime-kuma.git git clone https://github.com/louislam/uptime-kuma.git
cd uptime-kuma cd uptime-kuma
@ -56,33 +51,15 @@ npm run start-server
# Install PM2 if you don't have: npm install pm2 -g # Install PM2 if you don't have: npm install pm2 -g
pm2 start npm --name uptime-kuma -- run start-server 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. 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.
Unlikely other web apps, Uptime Kuma is based on WebSocket. You need two more headers **"Upgrade"** and **"Connection"** in order to reverse proxy WebSocket. ## 🆙 How to Update
Please read wiki for more info: ### 🆙🐳 Docker
https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy
## How to Update
### Docker
Re-pull the latest docker image and create another container with the same volume. Re-pull the latest docker image and create another container with the same volume.
@ -97,9 +74,10 @@ 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. 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 ```bash
cd <uptime-kuma-directory>
git fetch --all git fetch --all
git checkout 1.1.0 --force git checkout 1.1.0 --force
npm install npm install
@ -107,12 +85,12 @@ npm run build
pm2 restart uptime-kuma pm2 restart uptime-kuma
``` ```
## What's Next? ## 🆕 What's Next?
I will mark requests/issues to the next milestone. I will mark requests/issues to the next milestone.
https://github.com/louislam/uptime-kuma/milestones https://github.com/louislam/uptime-kuma/milestones
## More Screenshots ## 🖼 More Screenshots
Dark Mode: Dark Mode:
@ -144,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 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. 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.
🐻

@ -5,8 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/svg+xml" href="/icon.svg" /> <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" id="theme-color" content="" />
<meta name="theme-color" content="#5cdd8b"/>
<meta name="description" content="Uptime Kuma monitoring tool" /> <meta name="description" content="Uptime Kuma monitoring tool" />
<title>Uptime Kuma</title> <title>Uptime Kuma</title>
</head> </head>

284
package-lock.json generated

@ -756,9 +756,9 @@
} }
}, },
"@types/bootstrap": { "@types/bootstrap": {
"version": "5.0.17", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.0.17.tgz", "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.1.tgz",
"integrity": "sha512-uQQQ3p+zw10VjZLvtCuKWI6QgVCYEnK/yHnno3gyEhikfQdiZexS2XPxjWRboGmX135o470GkmCta9eAgQMVLQ==", "integrity": "sha512-W/fEBlqwaJFh+3sCz/H88LPsLt/zLsEECFlrAOkrRPjWuo/ETl8u0JefIerCdc8+WukowQS1f60eIJOwkCBwhg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@popperjs/core": "^2.9.2", "@popperjs/core": "^2.9.2",
@ -960,45 +960,45 @@
} }
}, },
"@vitejs/plugin-vue": { "@vitejs/plugin-vue": {
"version": "1.3.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.4.0.tgz",
"integrity": "sha512-wJvuJdTBjvucUX0vK4fuy60t+A9bJSZxc59vp1Y+8kiOd0NU5kFt4lay72gMWPeR+lSUjrTmGUq8Uzb99Jbw3A==", "integrity": "sha512-RkqfJHz9wdLKBp5Yi+kQL8BAljdrvPoccQm2PTZc/UcL4EjD11xsv2PPCduYx2oV1a/bpSKA3sD5sxOHFhz+LA==",
"dev": true "dev": true
}, },
"@vue/compiler-core": { "@vue/compiler-core": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.2.tgz",
"integrity": "sha512-UEJf2ZGww5wGVdrWIXIZo04KdJFGPmI2bHRUsBZ3AdyCAqJ5ykRXKOBn1OR1hvA2YzimudOEyHM+DpbBv91Kww==", "integrity": "sha512-QhCI0ZU5nAR0LMcLgzW3v75374tIrHGp8XG5CzJS7Nsy+iuignbE4MZ2XJfh5TGIrtpuzfWA4eTIfukZf/cRdg==",
"requires": { "requires": {
"@babel/parser": "^7.12.0", "@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0", "@babel/types": "^7.12.0",
"@vue/shared": "3.2.1", "@vue/shared": "3.2.2",
"estree-walker": "^2.0.1", "estree-walker": "^2.0.1",
"source-map": "^0.6.1" "source-map": "^0.6.1"
} }
}, },
"@vue/compiler-dom": { "@vue/compiler-dom": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.2.tgz",
"integrity": "sha512-tXg8tkPb3j54zNfWqoao9T1JI41yWPz8TROzmif/QNNA46eq8/SRuRsBd36i47GWaz7mh+yg3vOJ87/YBjcMyQ==", "integrity": "sha512-ggcc+NV/ENIE0Uc3TxVE/sKrhYVpLepMAAmEiQ047332mbKOvUkowz4TTFZ+YkgOIuBOPP0XpCxmCMg7p874mA==",
"requires": { "requires": {
"@vue/compiler-core": "3.2.1", "@vue/compiler-core": "3.2.2",
"@vue/shared": "3.2.1" "@vue/shared": "3.2.2"
} }
}, },
"@vue/compiler-sfc": { "@vue/compiler-sfc": {
"version": "3.1.5", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.2.tgz",
"integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==", "integrity": "sha512-hrtqpQ5L6IPn5v7yVRo7uvLcQxv0z1+KBjZBWMBOcrXz4t+PKUxU/SWd6Tl9T8FDmYlunzKUh6lcx+2CLo6f5A==",
"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",
"@types/estree": "^0.0.48", "@types/estree": "^0.0.48",
"@vue/compiler-core": "3.1.5", "@vue/compiler-core": "3.2.2",
"@vue/compiler-dom": "3.1.5", "@vue/compiler-dom": "3.2.2",
"@vue/compiler-ssr": "3.1.5", "@vue/compiler-ssr": "3.2.2",
"@vue/shared": "3.1.5", "@vue/shared": "3.2.2",
"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",
@ -1009,116 +1009,54 @@
"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.5", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.2.tgz",
"integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==", "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, "dev": true,
"requires": { "requires": {
"@vue/compiler-core": "3.1.5", "@vue/compiler-dom": "3.2.2",
"@vue/shared": "3.1.5" "@vue/shared": "3.2.2"
}
},
"@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": {
"version": "6.0.0-beta.14", "version": "6.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.14.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.15.tgz",
"integrity": "sha512-44fPrrN1cqcs6bFkT0C+yxTM6PZXLbR+ESh1U1j8UD22yO04gXvxH62HApMjLbS3WqJO/iCNC+CYT+evPQh2EQ==" "integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA=="
}, },
"@vue/reactivity": { "@vue/reactivity": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.2.tgz",
"integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==", "integrity": "sha512-IHjhtmrhK6dzacj/EnLQDWOaA3HuzzVk6w84qgV8EpS4uWGIJXiRalMRg6XvGW2ykJvIl3pLsF0aBFlTMRiLOA==",
"requires": { "requires": {
"@vue/shared": "3.2.1" "@vue/shared": "3.2.2"
} }
}, },
"@vue/runtime-core": { "@vue/runtime-core": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.2.tgz",
"integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==", "integrity": "sha512-/aUk1+GO/VPX0oVxhbzSWE1zrf3/wGCsO1ALNisVokYftKqfqLDjbJHE6mrI2hx3MiuwbHrWjJClkGUVTIOPEQ==",
"requires": { "requires": {
"@vue/reactivity": "3.2.1", "@vue/reactivity": "3.2.2",
"@vue/shared": "3.2.1" "@vue/shared": "3.2.2"
} }
}, },
"@vue/runtime-dom": { "@vue/runtime-dom": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.2.tgz",
"integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==", "integrity": "sha512-1Le/NpCfawCOfePfJezvWUF+oCVLU8N+IHN4oFDOxRe6/PgHNJ+yT+YdxFifBfI+TIAoXI/9PsnqzmJZV+xsmw==",
"requires": { "requires": {
"@vue/runtime-core": "3.2.1", "@vue/runtime-core": "3.2.2",
"@vue/shared": "3.2.1", "@vue/shared": "3.2.2",
"csstype": "^2.6.8" "csstype": "^2.6.8"
} }
}, },
"@vue/shared": { "@vue/shared": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.2.tgz",
"integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg==" "integrity": "sha512-dvYb318tk9uOzHtSaT3WII/HscQSIRzoCZ5GyxEb3JlkEXASpAUAQwKnvSe2CudnF8XHFRTB7VITWSnWNLZUtA=="
}, },
"abbrev": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
@ -1779,6 +1717,16 @@
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"dev": true "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": { "chokidar": {
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
@ -1945,9 +1893,9 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
}, },
"core-js": { "core-js": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.1.tgz",
"integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==", "integrity": "sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw==",
"dev": true "dev": true
}, },
"core-util-is": { "core-util-is": {
@ -2441,9 +2389,9 @@
} }
}, },
"eslint-plugin-vue": { "eslint-plugin-vue": {
"version": "7.15.1", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.15.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
"integrity": "sha512-4/r+n/i+ovyeW2gVRRH92kpy4lkpFbyPR4BMxGBTLtGnwqOKKzjSo6EMSaT0RhWPvEjK9uifcY8e7z5n8BIEgw==", "integrity": "sha512-0E2dVvVC7I2Xm1HXyx+ZwPj9CNX4NJjs4K4r+GVsHWyt5Pew3JLD4fI7A91b2jeL0TXE7LlszrwLSTJU9eqehw==",
"dev": true, "dev": true,
"requires": { "requires": {
"eslint-utils": "^2.1.0", "eslint-utils": "^2.1.0",
@ -4789,9 +4737,9 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
}, },
"postcss": { "postcss": {
"version": "8.3.5", "version": "8.3.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz",
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==", "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==",
"dev": true, "dev": true,
"requires": { "requires": {
"colorette": "^1.2.2", "colorette": "^1.2.2",
@ -4874,9 +4822,9 @@
"dev": true "dev": true
}, },
"postcss-modules": { "postcss-modules": {
"version": "4.1.3", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz", "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.2.2.tgz",
"integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==", "integrity": "sha512-/H08MGEmaalv/OU8j6bUKi/kZr2kqGF6huAW8m9UAgOLWtpFdhA14+gPBoymtqyv+D4MLsmqaF2zvIegdCxJXg==",
"dev": true, "dev": true,
"requires": { "requires": {
"generic-names": "^2.0.1", "generic-names": "^2.0.1",
@ -5148,9 +5096,9 @@
"dev": true "dev": true
}, },
"prom-client": { "prom-client": {
"version": "13.1.0", "version": "13.2.0",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz", "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.2.0.tgz",
"integrity": "sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng==", "integrity": "sha512-wGr5mlNNdRNzEhRYXgboUU2LxHWIojxscJKmtG3R8f4/KiWqyYgXTLHs0+Ted7tG3zFT7pgHJbtomzZ1L0ARaQ==",
"requires": { "requires": {
"tdigest": "^0.1.1" "tdigest": "^0.1.1"
} }
@ -6805,20 +6753,94 @@
} }
}, },
"vue": { "vue": {
"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.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", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz",
"integrity": "sha512-0jhXluF5mzTAK5bXw/8yq4McvsI8HwEWI4cnQwJeN8NYGRbwh9wwuE4FNv1Kej9pxBB5ajTNsWr0M6DPs5EJZg==", "integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==",
"requires": { "requires": {
"@vue/compiler-dom": "3.2.1", "@vue/reactivity": "3.2.1",
"@vue/runtime-dom": "3.2.1",
"@vue/shared": "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": { "vue-confirm-dialog": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz",
"integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==" "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": { "vue-eslint-parser": {
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz", "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==" "integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q=="
}, },
"vue-router": { "vue-router": {
"version": "4.0.10", "version": "4.0.11",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.11.tgz",
"integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==", "integrity": "sha512-sha6I8fx9HWtvTrFZfxZkiQQBpqSeT+UCwauYjkdOQYRvwsGwimlQQE2ayqUwuuXGzquFpCPoXzYKWlzL4OuXg==",
"requires": { "requires": {
"@vue/devtools-api": "^6.0.0-beta.14" "@vue/devtools-api": "^6.0.0-beta.14"
} }

@ -31,11 +31,14 @@
"@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-4", "@fortawesome/vue-fontawesome": "^3.0.0-4",
"@louislam/sqlite3": "^5.0.3",
"@popperjs/core": "^2.9.3", "@popperjs/core": "^2.9.3",
"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.1.0", "bootstrap": "^5.1.0",
"chart.js": "^3.5.0",
"chartjs-adapter-dayjs": "^1.0.0",
"command-exists": "^1.2.9", "command-exists": "^1.2.9",
"dayjs": "^1.10.6", "dayjs": "^1.10.6",
"express": "^4.17.1", "express": "^4.17.1",
@ -45,29 +48,29 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"nodemailer": "^6.6.3", "nodemailer": "^6.6.3",
"password-hash": "^1.2.2", "password-hash": "^1.2.2",
"prom-client": "^13.1.0", "prom-client": "^13.2.0",
"prometheus-api-metrics": "^3.2.0", "prometheus-api-metrics": "^3.2.0",
"redbean-node": "0.0.21", "redbean-node": "0.0.21",
"socket.io": "^4.1.3", "socket.io": "^4.1.3",
"socket.io-client": "^4.1.3", "socket.io-client": "^4.1.3",
"@louislam/sqlite3": "^5.0.3",
"tcp-ping": "^0.1.1", "tcp-ping": "^0.1.1",
"v-pagination-3": "^0.1.6", "v-pagination-3": "^0.1.6",
"vue": "^3.2.1", "vue": "^3.2.2",
"vue-chart-3": "^0.5.7",
"vue-confirm-dialog": "^1.0.2", "vue-confirm-dialog": "^1.0.2",
"vue-multiselect": "^3.0.0-alpha.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" "vue-toastification": "^2.0.0-rc.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.15.0", "@babel/eslint-parser": "^7.15.0",
"@types/bootstrap": "^5.0.17", "@types/bootstrap": "^5.1.1",
"@vitejs/plugin-legacy": "^1.5.1", "@vitejs/plugin-legacy": "^1.5.1",
"@vitejs/plugin-vue": "^1.3.0", "@vitejs/plugin-vue": "^1.4.0",
"@vue/compiler-sfc": "^3.1.5", "@vue/compiler-sfc": "^3.2.2",
"core-js": "^3.16.0", "core-js": "^3.16.1",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^7.15.1", "eslint-plugin-vue": "^7.16.0",
"sass": "^1.37.5", "sass": "^1.37.5",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-recommended": "^5.0.0", "stylelint-config-recommended": "^5.0.0",

@ -11,7 +11,7 @@ const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-ser
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification") const { Notification } = require("../notification")
const version = require("../package.json").version; const version = require("../../package.json").version;
/** /**
* status: * status:
@ -80,6 +80,10 @@ class Monitor extends BeanModel {
const beat = async () => { const beat = async () => {
// Expose here for prometheus update
// undefined if not https
let tlsInfo = undefined;
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,
@ -133,7 +137,7 @@ class Monitor extends BeanModel {
let certInfoStartTime = dayjs().valueOf(); let certInfoStartTime = dayjs().valueOf();
if (this.getUrl()?.protocol === "https:") { if (this.getUrl()?.protocol === "https:") {
try { try {
await this.updateTlsInfo(checkCertificate(res)); tlsInfo = await this.updateTlsInfo(checkCertificate(res));
} catch (e) { } catch (e) {
if (e.message !== "No TLS certificate in response") { if (e.message !== "No TLS certificate in response") {
console.error(e.message) console.error(e.message)
@ -255,7 +259,7 @@ class Monitor extends BeanModel {
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`) 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()); io.to(this.user_id).emit("heartbeat", bean.toJSON());
@ -290,7 +294,7 @@ class Monitor extends BeanModel {
/** /**
* Store TLS info to database * Store TLS info to database
* @param checkCertificateResult * @param checkCertificateResult
* @returns {Promise<void>} * @returns {Promise<object>}
*/ */
async updateTlsInfo(checkCertificateResult) { async updateTlsInfo(checkCertificateResult) {
let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
@ -302,6 +306,8 @@ class Monitor extends BeanModel {
} }
tls_info_bean.info_json = JSON.stringify(checkCertificateResult); tls_info_bean.info_json = JSON.stringify(checkCertificateResult);
await R.store(tls_info_bean); await R.store(tls_info_bean);
return checkCertificateResult;
} }
static async sendStats(io, monitorID, userID) { static async sendStats(io, monitorID, userID) {

@ -193,6 +193,34 @@ class Notification {
console.log(error) console.log(error)
return false; 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") { } else if (notification.type === "slack") {
try { try {
if (heartbeatJSON == null) { if (heartbeatJSON == null) {
@ -326,6 +354,41 @@ class Notification {
throwGeneralAxiosError(error) 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 { } else {
throw new Error("Notification type is not supported") throw new Error("Notification type is not supported")
} }

@ -1,22 +1,33 @@
const PrometheusClient = require('prom-client'); const PrometheusClient = require("prom-client");
const commonLabels = [ const commonLabels = [
'monitor_name', "monitor_name",
'monitor_type', "monitor_type",
'monitor_url', "monitor_url",
'monitor_hostname', "monitor_hostname",
'monitor_port', "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({ const monitor_response_time = new PrometheusClient.Gauge({
name: 'monitor_response_time', name: "monitor_response_time",
help: 'Monitor Response Time (ms)', help: "Monitor Response Time (ms)",
labelNames: commonLabels labelNames: commonLabels
}); });
const monitor_status = new PrometheusClient.Gauge({ const monitor_status = new PrometheusClient.Gauge({
name: 'monitor_status', name: "monitor_status",
help: 'Monitor Status (1 = UP, 0= DOWN)', help: "Monitor Status (1 = UP, 0= DOWN)",
labelNames: commonLabels 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 { try {
monitor_status.set(this.monitorLabelValues, heartbeat.status) monitor_status.set(this.monitorLabelValues, heartbeat.status)
} catch (e) { } catch (e) {
@ -41,7 +72,7 @@ class Prometheus {
} }
try { try {
if (typeof heartbeat.ping === 'number') { if (typeof heartbeat.ping === "number") {
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping) monitor_response_time.set(this.monitorLabelValues, heartbeat.ping)
} else { } else {
// Is it good? // Is it good?

@ -22,8 +22,10 @@
<option value="slack">Slack</option> <option value="slack">Slack</option>
<option value="pushover">Pushover</option> <option value="pushover">Pushover</option>
<option value="pushy">Pushy</option> <option value="pushy">Pushy</option>
<option value="octopush">Octopush</option>
<option value="lunasea">LunaSea</option> <option value="lunasea">LunaSea</option>
<option value="apprise">Apprise (Support 50+ Notification services)</option> <option value="apprise">Apprise (Support 50+ Notification services)</option>
<option value="pushbullet">Pushbullet</option>
</select> </select>
</div> </div>
@ -252,6 +254,37 @@
</p> </p>
</template> </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'"> <template v-if="notification.type === 'pushover'">
<div class="mb-3"> <div class="mb-3">
<label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label> <label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label>
@ -339,6 +372,17 @@
</div> </div>
</div> </div>
</template> </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>
<div class="modal-footer"> <div class="modal-footer">
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">

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

@ -2,7 +2,7 @@ export default {
data() { data() {
return { return {
system: (window.matchMedia("(prefers-color-scheme: dark)")) ? "dark" : "light", system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
userTheme: localStorage.theme, userTheme: localStorage.theme,
}; };
}, },
@ -14,6 +14,7 @@ export default {
} }
document.body.classList.add(this.theme); document.body.classList.add(this.theme);
this.updateThemeColorMeta();
}, },
computed: { computed: {
@ -33,6 +34,17 @@ export default {
theme(to, from) { theme(to, from) {
document.body.classList.remove(from); document.body.classList.remove(from);
document.body.classList.add(this.theme); 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"> <div class="col">
<h4>{{ pingTitle }}</h4> <h4>{{ pingTitle }}</h4>
<p>(Current)</p> <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>
<div class="col"> <div class="col">
<h4>Avg. {{ pingTitle }}</h4> <h4>Avg. {{ pingTitle }}</h4>
@ -70,6 +74,14 @@
</div> </div>
</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 v-if="showCertInfoBox" class="shadow-box big-padding text-center">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -155,6 +167,7 @@
</template> </template>
<script> <script>
import { defineAsyncComponent } from "vue";
import { useToast } from "vue-toastification" import { useToast } from "vue-toastification"
const toast = useToast() const toast = useToast()
import Confirm from "../components/Confirm.vue"; import Confirm from "../components/Confirm.vue";
@ -164,6 +177,7 @@ 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"; import Pagination from "v-pagination-3";
const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
export default { export default {
components: { components: {
@ -174,6 +188,7 @@ export default {
Confirm, Confirm,
Status, Status,
Pagination, Pagination,
PingChart,
}, },
data() { data() {
return { return {
@ -181,6 +196,7 @@ export default {
perPage: 25, perPage: 25,
heartBeatList: [], heartBeatList: [],
toggleCertInfoBox: false, toggleCertInfoBox: false,
showPingChartBox: true,
} }
}, },
computed: { computed: {

Loading…
Cancel
Save