mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 13:36:55 +08:00
Compare commits
82 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
78f5d2cd8b | ||
|
f62b70c9a9 | ||
|
dfa9b3a0ca | ||
|
b3bff8d735 | ||
|
f2af5bc064 | ||
|
91b736f391 | ||
|
275f77d4bb | ||
|
b00524067a | ||
|
25a93b05dc | ||
|
53e203d2f9 | ||
|
f48f957ba9 | ||
|
bfb117cb76 | ||
|
2b8e33caed | ||
|
60493f0f86 | ||
|
63c6e29e62 | ||
|
5f6d5588a6 | ||
|
18744d834f | ||
|
8dd5b97b79 | ||
|
386c8bfdf1 | ||
|
126f00e739 | ||
|
80466ac957 | ||
|
3b52433202 | ||
|
137f5da3da | ||
|
338d002d42 | ||
|
77ab9fbc57 | ||
|
b6b7835d7e | ||
|
d4fe5908f5 | ||
|
af838d62e8 | ||
|
5a6e83b777 | ||
|
c81930cacc | ||
|
6d4694da43 | ||
|
9c23cd09ce | ||
|
a60bf1528a | ||
|
1f3b337806 | ||
|
010ebea210 | ||
|
b3a5d868a7 | ||
|
312dec7393 | ||
|
be1ef24cce | ||
|
c5de82b220 | ||
|
fef41b44a8 | ||
|
6af65b688d | ||
|
3e4a98b6bc | ||
|
866bf56319 | ||
|
99afdabcac | ||
|
0f1a95fde9 | ||
|
edbab8163e | ||
|
551d00fc24 | ||
|
3e4cdbecf2 | ||
|
622681470d | ||
|
d9e2c230bf | ||
|
010302395f | ||
|
e053ee6573 | ||
|
c4bc95927f | ||
|
3e305b79b2 | ||
|
c6237277c0 | ||
|
900219deb1 | ||
|
7ebeee3455 | ||
|
0abd3b2d16 | ||
|
f452bf6b13 | ||
|
8cd90d1e96 | ||
|
789094a2ee | ||
|
5515437eab | ||
|
7acb347012 | ||
|
9d57e93367 | ||
|
dae92d0ae4 | ||
|
1259ff5368 | ||
|
8debce82b1 | ||
|
11a2adcb7c | ||
|
ad615d1a90 | ||
|
613c42b6d8 | ||
|
a6e16116f2 | ||
|
c7dfb36349 | ||
|
459dde2761 | ||
|
cb94ab3bb5 | ||
|
763d7f2683 | ||
|
e4f38d833d | ||
|
8b83266b00 | ||
|
6fb1b344f6 | ||
|
b15b44e290 | ||
|
66d991bd05 | ||
|
673d3c124c | ||
|
e568cad22c |
@@ -2,3 +2,4 @@
|
||||
/dist
|
||||
/node_modules
|
||||
/data/kuma.db
|
||||
/.do
|
||||
|
@@ -13,3 +13,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
34
README.md
34
README.md
@@ -1,5 +1,8 @@
|
||||
# Uptime Kuma
|
||||
|
||||
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a>
|
||||
|
||||
|
||||
<div align="center" width="100%">
|
||||
<img src="./public/icon.svg" width="128" alt="" />
|
||||
</div>
|
||||
@@ -18,8 +21,13 @@ It is a self-hosted monitoring tool like "Uptime Robot".
|
||||
# How to Use
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker run -d --restart=always -p 3001:3001 louislam/uptime-kuma
|
||||
# Create a volume
|
||||
docker volume create uptime-kuma
|
||||
|
||||
# Start the container
|
||||
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma
|
||||
```
|
||||
|
||||
Browse to http://localhost:3001 after started.
|
||||
@@ -27,7 +35,7 @@ Browse to http://localhost:3001 after started.
|
||||
Change Port and Volume
|
||||
|
||||
```bash
|
||||
docker run -d --restart=always -p <YOUR_PORT>:3001 -v <YOUR_DIR OR VOLUME>:/app/data louislam/uptime-kuma
|
||||
docker run -d --restart=always -p <YOUR_PORT>:3001 -v <YOUR_DIR OR VOLUME>:/app/data --name uptime-kuma louislam/uptime-kuma
|
||||
```
|
||||
|
||||
### Without Docker
|
||||
@@ -47,6 +55,9 @@ 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
|
||||
|
||||
```
|
||||
|
||||
Browse to http://localhost:3001 after started.
|
||||
@@ -55,6 +66,25 @@ Browse to http://localhost:3001 after started.
|
||||
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/louislam/uptime-kuma/tree/master&refcode=e2c7eb658434)
|
||||
|
||||
Choose Cheapest Plan is enough. (US$ 5)
|
||||
|
||||
# How to Update
|
||||
|
||||
### Docker
|
||||
|
||||
Re-pull the latest docker image and create another container with the same volume.
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
git fetch --all
|
||||
git checkout 1.0.5 --force
|
||||
npm install
|
||||
npm run build
|
||||
pm2 restart uptime-kuma
|
||||
```
|
||||
|
||||
# More Screenshots
|
||||
|
||||
|
BIN
db/kuma.db
BIN
db/kuma.db
Binary file not shown.
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Simple docker-composer.yml
|
||||
# You can change your port or volume location
|
||||
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma
|
||||
container_name: uptime-kuma
|
||||
volumes:
|
||||
- ./uptime-kuma:/app/data
|
||||
ports:
|
||||
- 3001:3001
|
31
dockerfile
31
dockerfile
@@ -1,6 +1,30 @@
|
||||
FROM node:14
|
||||
|
||||
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
|
||||
FROM node:14-alpine3.12 AS release
|
||||
WORKDIR /app
|
||||
|
||||
# split the sqlite install here, so that it can caches the arm prebuilt
|
||||
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \
|
||||
ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
npm install sqlite3@5.0.2 bcrypt@5.0.1 && \
|
||||
apk del .build-deps
|
||||
|
||||
# Touching above code may causes sqlite3 re-compile again, painful slow.
|
||||
|
||||
# Install apprise
|
||||
# Hate pip!!! I never run pip install successfully in first run for anything in my life without Google :/
|
||||
# Compilation Fail 1 => Google Search "alpine ffi.h" => Add libffi-dev
|
||||
# Compilation Fail 2 => Google Search "alpine cargo" => Add cargo
|
||||
# Compilation Fail 3 => Google Search "alpine opensslv.h" => Add openssl-dev
|
||||
# Compilation Fail 4 => Google Search "alpine opensslv.h" again => Change to libressl-dev musl-dev
|
||||
# Compilation Fail 5 => Google Search "ERROR: libressl3.3-libtls-3.3.3-r0: trying to overwrite usr/lib/libtls.so.20 owned by libretls-3.3.3-r0." again => Change back to openssl-dev with musl-dev
|
||||
ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1
|
||||
RUN apk add --no-cache python3
|
||||
RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev cargo py3-pip python3-dev && \
|
||||
pip3 install apprise && \
|
||||
apk del .build-deps
|
||||
|
||||
# New things add here
|
||||
|
||||
COPY . .
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
@@ -8,3 +32,6 @@ RUN npm run build
|
||||
EXPOSE 3001
|
||||
VOLUME ["/app/data"]
|
||||
CMD ["npm", "run", "start-server"]
|
||||
|
||||
FROM release AS nightly
|
||||
RUN npm run mark-as-nightly
|
||||
|
39
extra/mark-as-nightly.js
Normal file
39
extra/mark-as-nightly.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* String.prototype.replaceAll() polyfill
|
||||
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
||||
* @author Chris Ferdinandi
|
||||
* @license MIT
|
||||
*/
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function(str, newStr){
|
||||
|
||||
// If a regex pattern
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
|
||||
return this.replace(str, newStr);
|
||||
}
|
||||
|
||||
// If a string
|
||||
return this.replace(new RegExp(str, 'g'), newStr);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const fs = require("fs");
|
||||
const oldVersion = pkg.version
|
||||
const newVersion = oldVersion + "-nightly"
|
||||
|
||||
console.log("Old Version: " + oldVersion)
|
||||
console.log("New Version: " + newVersion)
|
||||
|
||||
if (newVersion) {
|
||||
// Process package.json
|
||||
pkg.version = newVersion
|
||||
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion)
|
||||
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion)
|
||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n")
|
||||
|
||||
// Process README.md
|
||||
fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion))
|
||||
}
|
||||
|
39
extra/version-global-replace.js
Normal file
39
extra/version-global-replace.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* String.prototype.replaceAll() polyfill
|
||||
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
||||
* @author Chris Ferdinandi
|
||||
* @license MIT
|
||||
*/
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function(str, newStr){
|
||||
|
||||
// If a regex pattern
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
|
||||
return this.replace(str, newStr);
|
||||
}
|
||||
|
||||
// If a string
|
||||
return this.replace(new RegExp(str, 'g'), newStr);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const fs = require("fs");
|
||||
const oldVersion = pkg.version
|
||||
const newVersion = process.argv[2]
|
||||
|
||||
console.log("Old Version: " + oldVersion)
|
||||
console.log("New Version: " + newVersion)
|
||||
|
||||
if (newVersion) {
|
||||
// Process package.json
|
||||
pkg.version = newVersion
|
||||
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion)
|
||||
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion)
|
||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n")
|
||||
|
||||
// Process README.md
|
||||
fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion))
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Uptime Kuma</title>
|
||||
</head>
|
||||
|
236
package-lock.json
generated
236
package-lock.json
generated
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"requires": true,
|
||||
"version": "1.0.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.5",
|
||||
@@ -28,21 +29,6 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@discordjs/collection": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
|
||||
"integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ=="
|
||||
},
|
||||
"@discordjs/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
|
||||
@@ -184,14 +170,6 @@
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"requires": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
@@ -201,6 +179,14 @@
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"requires": {
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -407,6 +393,70 @@
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
||||
"integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==",
|
||||
"requires": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||
"node-addon-api": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz",
|
||||
"integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^4.1.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.4",
|
||||
"tar": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
@@ -792,28 +842,6 @@
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||
},
|
||||
"discord.js": {
|
||||
"version": "12.5.3",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz",
|
||||
"integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==",
|
||||
"requires": {
|
||||
"@discordjs/collection": "^0.1.6",
|
||||
"@discordjs/form-data": "^3.0.1",
|
||||
"abort-controller": "^3.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"prism-media": "^1.2.9",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"ws": "^7.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
@@ -919,11 +947,6 @@
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
|
||||
},
|
||||
"expand-brackets": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||
@@ -1248,6 +1271,14 @@
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"requires": {
|
||||
"minipass": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -1488,6 +1519,14 @@
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"http-graceful-shutdown": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/http-graceful-shutdown/-/http-graceful-shutdown-3.1.2.tgz",
|
||||
"integrity": "sha512-2vmU3kWOsZqZy4Kn4EZp00CF+6glpNNN/NAYJPkO9bnMX/D8sRl29TsxIu9Vgyo8ygtCWazWJp720zHfqhSdXg==",
|
||||
"requires": {
|
||||
"debug": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
@@ -1499,6 +1538,15 @@
|
||||
"sshpk": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"requires": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -1930,6 +1978,21 @@
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"requires": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"make-iterator": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
|
||||
@@ -2103,6 +2166,37 @@
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
|
||||
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"requires": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"mixin-deep": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||
@@ -2122,6 +2216,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -2657,11 +2756,6 @@
|
||||
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"prism-media": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz",
|
||||
"integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw=="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
@@ -3005,11 +3099,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
@@ -3321,6 +3410,31 @@
|
||||
"integrity": "sha512-PwaC0Z6Y1E6gFekY2u38EC5+5w2M65jYVrD1aAcOptpHVhCwPIwPFJvYJyryQKUyeuQ5bKKI3PBHWNjdE9aizg==",
|
||||
"dev": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
|
||||
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
|
||||
"requires": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^3.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"tarn": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.1.tgz",
|
||||
|
12
package.json
12
package.json
@@ -1,29 +1,35 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.0.5",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"start-server": "node server/server.js",
|
||||
"update": "",
|
||||
"build": "vite build",
|
||||
"vite-preview-dist": "vite preview --host",
|
||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 . --push",
|
||||
"setup": "git checkout 1.0.0 && npm install && npm run build"
|
||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.5 --target release . --push",
|
||||
"build-docker-nightly": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||
"setup": "git checkout 1.0.5 && npm install && npm run build",
|
||||
"version-global-replace": "node extra/version-global-replace.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"args-parser": "^1.3.0",
|
||||
"axios": "^0.21.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"bootstrap": "^5.0.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"discord.js": "^12.5.3",
|
||||
"express": "^4.17.1",
|
||||
"form-data": "^4.0.0",
|
||||
"http-graceful-shutdown": "^3.1.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"nodemailer": "^6.6.2",
|
||||
"password-hash": "^1.2.2",
|
||||
"redbean-node": "0.0.20",
|
||||
"socket.io": "^4.0.2",
|
||||
"socket.io-client": "^4.1.2",
|
||||
"sqlite3": "^5.0.0",
|
||||
"tcp-ping": "^0.1.1",
|
||||
"vue": "^3.0.5",
|
||||
"vue-confirm-dialog": "^1.0.2",
|
||||
|
BIN
public/apple-touch-icon.png
Normal file
BIN
public/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@@ -2,7 +2,6 @@ const axios = require("axios");
|
||||
const {R} = require("redbean-node");
|
||||
const FormData = require('form-data');
|
||||
const nodemailer = require("nodemailer");
|
||||
const Discord = require('discord.js');
|
||||
|
||||
class Notification {
|
||||
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
@@ -20,6 +19,22 @@ class Notification {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (notification.type === "gotify") {
|
||||
try {
|
||||
if (notification.gotifyserverurl.endsWith("/")) {
|
||||
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
|
||||
}
|
||||
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
|
||||
"message": msg,
|
||||
"priority": notification.gotifyPriority || 8,
|
||||
"title": "Uptime-Kuma"
|
||||
})
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (notification.type === "webhook") {
|
||||
try {
|
||||
|
||||
@@ -54,7 +69,113 @@ class Notification {
|
||||
return await Notification.smtp(notification, msg)
|
||||
|
||||
} else if (notification.type === "discord") {
|
||||
return await Notification.discord(notification, msg)
|
||||
try {
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if(heartbeatJSON == null) {
|
||||
let data = {
|
||||
username: 'Uptime-Kuma',
|
||||
content: msg
|
||||
}
|
||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||
return true;
|
||||
}
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
if(heartbeatJSON['status'] == 0) {
|
||||
var alertColor = "16711680";
|
||||
} else if(heartbeatJSON['status'] == 1) {
|
||||
var alertColor = "65280";
|
||||
}
|
||||
let data = {
|
||||
username: 'Uptime-Kuma',
|
||||
embeds: [{
|
||||
title: "Uptime-Kuma Alert",
|
||||
color: alertColor,
|
||||
fields: [
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
value: heartbeatJSON["time"]
|
||||
},
|
||||
{
|
||||
name: "Message",
|
||||
value: msg
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||
return true;
|
||||
} catch(error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (notification.type === "signal") {
|
||||
try {
|
||||
let data = {
|
||||
"message": msg,
|
||||
"number": notification.signalNumber,
|
||||
"recipients": notification.signalRecipients.replace(/\s/g, '').split(",")
|
||||
};
|
||||
let config = {};
|
||||
|
||||
let res = await axios.post(notification.signalURL, data, config)
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (notification.type === "slack") {
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
let data = {'text': "Uptime Kuma Slack testing successful."}
|
||||
let res = await axios.post(notification.slackwebhookURL, data)
|
||||
return true;
|
||||
}
|
||||
|
||||
const time = heartbeatJSON["time"];
|
||||
let data = {
|
||||
"blocks": [{
|
||||
"type": "header",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "Uptime Kuma Alert"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"fields": [{
|
||||
"type": "mrkdwn",
|
||||
"text": '*Message*\n'+msg
|
||||
},
|
||||
{
|
||||
"type": "mrkdwn",
|
||||
"text": "*Time (UTC)*\n"+time
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "actions",
|
||||
"elements": [
|
||||
{
|
||||
"type": "button",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "Visit Uptime Kuma",
|
||||
},
|
||||
"value": "Uptime-Kuma",
|
||||
"url": notification.slackbutton
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
let res = await axios.post(notification.slackwebhookURL, data)
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error("Notification type is not supported")
|
||||
|
23
server/password-hash.js
Normal file
23
server/password-hash.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const passwordHashOld = require('password-hash');
|
||||
const bcrypt = require('bcrypt');
|
||||
const saltRounds = 10;
|
||||
|
||||
exports.generate = function (password) {
|
||||
return bcrypt.hashSync(password, saltRounds);
|
||||
}
|
||||
|
||||
exports.verify = function (password, hash) {
|
||||
if (isSHA1(hash)) {
|
||||
return passwordHashOld.verify(password, hash)
|
||||
} else {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
}
|
||||
}
|
||||
|
||||
function isSHA1(hash) {
|
||||
return (typeof hash === "string" && hash.startsWith("sha1"))
|
||||
}
|
||||
|
||||
exports.needRehash = function (hash) {
|
||||
return isSHA1(hash);
|
||||
}
|
112
server/server.js
112
server/server.js
@@ -1,25 +1,30 @@
|
||||
console.log("Welcome to Uptime Kuma ")
|
||||
console.log("Importing libraries")
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const http = require('http');
|
||||
const server = http.createServer(app);
|
||||
const { Server } = require("socket.io");
|
||||
const io = new Server(server);
|
||||
const dayjs = require("dayjs");
|
||||
const {R} = require("redbean-node");
|
||||
const passwordHash = require('password-hash');
|
||||
const passwordHash = require('./password-hash');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const Monitor = require("./model/monitor");
|
||||
const fs = require("fs");
|
||||
const {getSettings} = require("./util-server");
|
||||
const {Notification} = require("./notification")
|
||||
const gracefulShutdown = require('http-graceful-shutdown');
|
||||
const {sleep} = require("./util");
|
||||
const args = require('args-parser')(process.argv);
|
||||
|
||||
console.log("args:")
|
||||
console.log(args)
|
||||
|
||||
const version = require('../package.json').version;
|
||||
const hostname = args.host || "0.0.0.0"
|
||||
const port = args.port || 3001
|
||||
|
||||
console.log("Version: " + version)
|
||||
|
||||
console.log("Creating express and socket.io instance")
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server);
|
||||
app.use(express.json())
|
||||
|
||||
let totalClient = 0;
|
||||
@@ -32,18 +37,16 @@ let needSetup = false;
|
||||
|
||||
app.use('/', express.static("dist"));
|
||||
|
||||
app.post('/test-webhook', function(request, response, next) {
|
||||
console.log("Test Webhook (application/json only)")
|
||||
console.log("Content-Type: " + request.header("Content-Type"))
|
||||
console.log(request.body)
|
||||
response.end();
|
||||
});
|
||||
|
||||
app.get('*', function(request, response, next) {
|
||||
response.sendFile(process.cwd() + '/dist/index.html');
|
||||
});
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
|
||||
socket.emit("info", {
|
||||
version,
|
||||
})
|
||||
|
||||
console.log('a user connected');
|
||||
totalClient++;
|
||||
|
||||
@@ -100,6 +103,14 @@ let needSetup = false;
|
||||
|
||||
if (user && passwordHash.verify(data.password, user.password)) {
|
||||
|
||||
// Upgrade the hash to bcrypt
|
||||
if (passwordHash.needRehash(user.password)) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(data.password),
|
||||
user.id
|
||||
]);
|
||||
}
|
||||
|
||||
await afterLogin(socket, user)
|
||||
|
||||
callback({
|
||||
@@ -535,11 +546,11 @@ async function initDatabase() {
|
||||
const path = './data/kuma.db';
|
||||
|
||||
if (! fs.existsSync(path)) {
|
||||
console.log("Copy Database")
|
||||
console.log("Copying Database")
|
||||
fs.copyFileSync("./db/kuma.db", path);
|
||||
}
|
||||
|
||||
console.log("Connect to Database")
|
||||
console.log("Connecting to Database")
|
||||
|
||||
R.setup('sqlite', {
|
||||
filename: path
|
||||
@@ -656,3 +667,72 @@ async function sendImportantHeartbeatList(socket, monitorID) {
|
||||
|
||||
socket.emit("importantHeartbeatList", monitorID, list)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const startGracefulShutdown = async () => {
|
||||
console.log('Shutdown requested');
|
||||
|
||||
|
||||
await (new Promise((resolve) => {
|
||||
server.close(async function () {
|
||||
console.log('Stopped Express.');
|
||||
process.exit(0)
|
||||
setTimeout(async () =>{
|
||||
await R.close();
|
||||
console.log("Stopped DB")
|
||||
|
||||
resolve();
|
||||
}, 5000)
|
||||
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
|
||||
let noReject = true;
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
noReject = false;
|
||||
});
|
||||
|
||||
async function shutdownFunction(signal) {
|
||||
console.log('Called signal: ' + signal);
|
||||
|
||||
console.log("Stopping all monitors")
|
||||
for (let id in monitorList) {
|
||||
let monitor = monitorList[id]
|
||||
monitor.stop()
|
||||
}
|
||||
await sleep(2000)
|
||||
|
||||
console.log("Closing DB")
|
||||
|
||||
// Special handle, because tarn.js throw a promise reject that cannot be caught
|
||||
while (true) {
|
||||
noReject = true;
|
||||
await R.close()
|
||||
await sleep(2000)
|
||||
|
||||
if (noReject) {
|
||||
break;
|
||||
} else {
|
||||
console.log("Waiting...")
|
||||
}
|
||||
}
|
||||
|
||||
console.log("OK")
|
||||
}
|
||||
|
||||
function finalFunction() {
|
||||
console.log('Graceful Shutdown')
|
||||
}
|
||||
|
||||
gracefulShutdown(server, {
|
||||
signals: 'SIGINT SIGTERM',
|
||||
timeout: 30000, // timeout: 30 secs
|
||||
development: false, // not in dev mode
|
||||
forceExit: true, // triggers process.exit() at the end of shutdown process
|
||||
onShutdown: shutdownFunction, // shutdown function (async) - e.g. for cleanup DB, ...
|
||||
finally: finalFunction // finally function (sync) - e.g. for logging
|
||||
});
|
||||
|
@@ -56,7 +56,5 @@ exports.getSettings = async function (type) {
|
||||
result[row.key] = row.value;
|
||||
}
|
||||
|
||||
console.log(result)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -1,15 +1,11 @@
|
||||
/*
|
||||
* Common functions - can be used in frontend or backend
|
||||
*/
|
||||
// Common JS cannot be used in frontend sadly
|
||||
// sleep, ucfirst is duplicated in ../src/util-frontend.js
|
||||
|
||||
|
||||
|
||||
|
||||
export function sleep(ms) {
|
||||
exports.sleep = function (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function ucfirst(str) {
|
||||
exports.ucfirst = function (str) {
|
||||
if (! str) {
|
||||
return str;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
<script>
|
||||
|
||||
import {sleep} from "../../server/util";
|
||||
import {sleep} from '../util-frontend'
|
||||
|
||||
export default {
|
||||
|
||||
|
@@ -17,6 +17,9 @@
|
||||
<option value="webhook">Webhook</option>
|
||||
<option value="smtp">Email (SMTP)</option>
|
||||
<option value="discord">Discord</option>
|
||||
<option value="signal">Signal</option>
|
||||
<option value="gotify">Gotify</option>
|
||||
<option value="slack">Slack</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -105,12 +108,12 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" required v-model="notification.smtpUsername" autocomplete="false">
|
||||
<input type="text" class="form-control" id="username" v-model="notification.smtpUsername" autocomplete="false">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" required v-model="notification.smtpPassword" autocomplete="false">
|
||||
<input type="password" class="form-control" id="password" v-model="notification.smtpPassword" autocomplete="false">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -127,22 +130,79 @@
|
||||
|
||||
<template v-if="notification.type === 'discord'">
|
||||
<div class="mb-3">
|
||||
<label for="discord-token" class="form-label">Discord Bot Token</label>
|
||||
<input type="text" class="form-control" id="discord-token" required v-model="notification.discordToken" autocomplete="false">
|
||||
<div class="form-text">You should create a Discord app and create a bot from <a href="https://discord.com/developers/applications" target="_blank">here</a>.</div>
|
||||
<label for="discord-webhook-url" class="form-label">Discord Webhook URL</label>
|
||||
<input type="text" class="form-control" id="discord-webhook-url" required v-model="notification.discordWebhookUrl" autocomplete="false">
|
||||
<div class="form-text">You can get this by going to Server Settings -> Integrations -> Create Webhook</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'signal'">
|
||||
<div class="mb-3">
|
||||
<label for="signal-url" class="form-label">Post URL</label>
|
||||
<input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="discordChannelID" class="form-label">Channel ID</label>
|
||||
<input type="text" class="form-control" id="discordChannelID" required v-model="notification.discordChannelID" autocomplete="false">
|
||||
<label for="signal-number" class="form-label">Number</label>
|
||||
<input type="text" class="form-control" id="signal-number" required v-model="notification.signalNumber">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="signal-recipients" class="form-label">Recipients</label>
|
||||
<input type="text" class="form-control" id="signal-recipients" required v-model="notification.signalRecipients">
|
||||
|
||||
<div class="form-text">
|
||||
You should add the bot to your channel. <br />
|
||||
<a href="https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-" target="_blank">Where can I find the channel id?</a><br />
|
||||
<a href="https://discordapi.com/permissions.html#8" target="_blank">How to add a bot to your channel?</a>
|
||||
You need to have a signal client with REST API.
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
You can check this url to view how to setup one:
|
||||
</p>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
<a href="https://github.com/bbernhard/signal-cli-rest-api" target="_blank">https://github.com/bbernhard/signal-cli-rest-api</a>
|
||||
</p>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
IMPORTANT: You cannot mix groups and numbers in recipients!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'gotify'">
|
||||
<div class="mb-3">
|
||||
<label for="gotify-application-token" class="form-label">Application Token</label>
|
||||
<input type="text" class="form-control" id="gotify-application-token" required v-model="notification.gotifyapplicationToken">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="gotify-server-url" class="form-label">Server URL</label>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" id="gotify-server-url" required v-model="notification.gotifyserverurl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="gotify-priority" class="form-label">Priority</label>
|
||||
<input type="number" class="form-control" id="gotify-priority" v-model="notification.gotifyPriority" required min="0" max="10" step="1">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'slack'">
|
||||
<div class="mb-3">
|
||||
<label for="slack-webhook-url" class="form-label">Slack Webhook URL</label>
|
||||
<input type="text" class="form-control" id="slack-webhook-url" required v-model="notification.slackwebhookURL" autocomplete="false">
|
||||
<label for="gotify-server-url" class="form-label">Uptime Kuma URL</label>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" id="slack-button" required v-model="notification.slackbutton" autocomplete="false">
|
||||
</div>
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button>
|
||||
@@ -160,7 +220,7 @@
|
||||
|
||||
<script>
|
||||
import { Modal } from 'bootstrap'
|
||||
import { ucfirst } from "../../server/util";
|
||||
import { ucfirst } from '../util-frontend'
|
||||
import axios from "axios";
|
||||
import { useToast } from 'vue-toastification'
|
||||
import Confirm from "./Confirm.vue";
|
||||
@@ -179,6 +239,7 @@ export default {
|
||||
notification: {
|
||||
name: "",
|
||||
type: null,
|
||||
gotifyPriority: 8
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -218,6 +279,7 @@ export default {
|
||||
|
||||
// Default set to Telegram
|
||||
this.notification.type = "telegram"
|
||||
this.notification.gotifyPriority = 8
|
||||
}
|
||||
|
||||
this.modal.show()
|
||||
|
@@ -33,6 +33,14 @@
|
||||
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container-fluid">
|
||||
Uptime Kuma -
|
||||
Version: {{ $root.info.version }} -
|
||||
<a href="https://github.com/louislam/uptime-kuma/releases" target="_blank">Check Update On GitHub</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Mobile Only -->
|
||||
<div style="width: 100%;height: 60px;" v-if="$root.isMobile"></div>
|
||||
<nav class="bottom-nav" v-if="$root.isMobile">
|
||||
@@ -130,6 +138,14 @@ export default {
|
||||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 30px;
|
||||
|
||||
}
|
||||
|
||||
footer {
|
||||
color: #AAA;
|
||||
font-size: 13px;
|
||||
margin-bottom: 30px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -9,6 +9,7 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
info: { },
|
||||
socket: {
|
||||
token: null,
|
||||
firstConnect: true,
|
||||
@@ -44,6 +45,10 @@ export default {
|
||||
transports: ['websocket']
|
||||
});
|
||||
|
||||
socket.on('info', (info) => {
|
||||
this.info = info;
|
||||
});
|
||||
|
||||
socket.on('setup', (monitorID, data) => {
|
||||
this.$router.push("/setup")
|
||||
});
|
||||
@@ -280,6 +285,13 @@ export default {
|
||||
|
||||
watch: {
|
||||
|
||||
// Reload the SPA if the server version is changed.
|
||||
"info.version"(to, from) {
|
||||
if (from && from !== to) {
|
||||
window.location.reload()
|
||||
}
|
||||
},
|
||||
|
||||
remember() {
|
||||
localStorage.remember = (this.remember) ? "1" : "0"
|
||||
}
|
||||
|
@@ -5,6 +5,19 @@ import timezone from 'dayjs/plugin/timezone'
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
||||
export function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function ucfirst(str) {
|
||||
if (! str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const firstLetter = str.substr(0, 1);
|
||||
return firstLetter.toUpperCase() + str.substr(1);
|
||||
}
|
||||
|
||||
|
||||
function getTimezoneOffset(timeZone) {
|
||||
const now = new Date();
|
||||
@@ -24,7 +37,6 @@ const aryIannaTimeZones = [
|
||||
'Asia/Yerevan',
|
||||
'Antarctica/Casey',
|
||||
'Antarctica/Davis',
|
||||
'Antarctica/DumontDUrville', // https://bugs.chromium.org/p/chromium/issues/detail?id=928068
|
||||
'Antarctica/Mawson',
|
||||
'Antarctica/Palmer',
|
||||
'Antarctica/Rothera',
|
||||
@@ -195,7 +207,6 @@ const aryIannaTimeZones = [
|
||||
'Asia/Seoul',
|
||||
'Asia/Almaty',
|
||||
'Asia/Qyzylorda',
|
||||
'Asia/Qostanay', // https://bugs.chromium.org/p/chromium/issues/detail?id=928068
|
||||
'Asia/Aqtobe',
|
||||
'Asia/Aqtau',
|
||||
'Asia/Atyrau',
|
||||
@@ -364,22 +375,29 @@ const aryIannaTimeZones = [
|
||||
'Pacific/Efate',
|
||||
'Pacific/Wallis',
|
||||
'Pacific/Apia',
|
||||
'Africa/Johannesburg'
|
||||
'Africa/Johannesburg',
|
||||
];
|
||||
|
||||
|
||||
export function timezoneList() {
|
||||
|
||||
let result = [];
|
||||
|
||||
for (let timezone of aryIannaTimeZones) {
|
||||
|
||||
let display = dayjs().tz(timezone).format("Z");
|
||||
try {
|
||||
let display = dayjs().tz(timezone).format("Z");
|
||||
|
||||
result.push({
|
||||
name: `(UTC${display}) ${timezone}`,
|
||||
value: timezone,
|
||||
time: getTimezoneOffset(timezone),
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
console.log("Skip this timezone")
|
||||
}
|
||||
|
||||
result.push({
|
||||
name: `(UTC${display}) ${timezone}`,
|
||||
value: timezone,
|
||||
time: getTimezoneOffset(timezone),
|
||||
})
|
||||
}
|
||||
|
||||
result.sort((a, b) => {
|
||||
|
@@ -7,7 +7,7 @@ export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
legacy({
|
||||
targets: ['ie >= 11'],
|
||||
targets: ['ie > 11'],
|
||||
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
|
||||
})
|
||||
]
|
||||
|
Reference in New Issue
Block a user