mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-18 04:04:41 -05:00
Merge branch 'louislam:master' into group-monitors
This commit is contained in:
commit
2c581ade90
@ -17,8 +17,11 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
|||||||
|
|
||||||
## Directories
|
## Directories
|
||||||
|
|
||||||
|
- config (dev config files)
|
||||||
- data (App data)
|
- data (App data)
|
||||||
|
- db (Base database and migration scripts)
|
||||||
- dist (Frontend build)
|
- dist (Frontend build)
|
||||||
|
- docker (Dockerfiles)
|
||||||
- extra (Extra useful scripts)
|
- extra (Extra useful scripts)
|
||||||
- public (Frontend resources for dev only)
|
- public (Frontend resources for dev only)
|
||||||
- server (Server source code)
|
- server (Server source code)
|
||||||
@ -80,13 +83,13 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
|
|||||||
|
|
||||||
## Project Styles
|
## Project Styles
|
||||||
|
|
||||||
I personally do not like it when something requires so much learning and configuration before you can finally start the app.
|
I personally do not like something that requires so many configurations before you can finally start the app. I hope Uptime Kuma installation could be as easy as like installing a mobile app.
|
||||||
|
|
||||||
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort required to get it running
|
- Easy to install for non-Docker users, no native build dependency is needed (for x86_64/armv7/arm64), no extra config, no extra effort required to get it running
|
||||||
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
||||||
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`.
|
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`
|
||||||
- Easy to use
|
- Easy to use
|
||||||
- The web UI styling should be consistent and nice.
|
- The web UI styling should be consistent and nice
|
||||||
|
|
||||||
## Coding Styles
|
## Coding Styles
|
||||||
|
|
||||||
@ -95,7 +98,7 @@ I personally do not like it when something requires so much learning and configu
|
|||||||
- Follow ESLint
|
- Follow ESLint
|
||||||
- Methods and functions should be documented with JSDoc
|
- Methods and functions should be documented with JSDoc
|
||||||
|
|
||||||
## Name convention
|
## Name Conventions
|
||||||
|
|
||||||
- Javascript/Typescript: camelCaseType
|
- Javascript/Typescript: camelCaseType
|
||||||
- SQLite: snake_case (Underscore)
|
- SQLite: snake_case (Underscore)
|
||||||
@ -109,7 +112,7 @@ I personally do not like it when something requires so much learning and configu
|
|||||||
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
||||||
- A SQLite GUI tool (SQLite Expert Personal is suggested)
|
- A SQLite GUI tool (SQLite Expert Personal is suggested)
|
||||||
|
|
||||||
## Install dependencies
|
## Install Dependencies for Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm ci
|
npm ci
|
||||||
@ -127,6 +130,12 @@ Port `3000` and port `3001` will be used.
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
But sometimes, you would like to keep restart the server, but not the frontend, you can run these command in two terminals:
|
||||||
|
```
|
||||||
|
npm run start-frontend-dev
|
||||||
|
npm run start-server-dev
|
||||||
|
```
|
||||||
|
|
||||||
## Backend Server
|
## Backend Server
|
||||||
|
|
||||||
It binds to `0.0.0.0:3001` by default.
|
It binds to `0.0.0.0:3001` by default.
|
||||||
@ -142,12 +151,15 @@ express.js is used for:
|
|||||||
|
|
||||||
### Structure in /server/
|
### Structure in /server/
|
||||||
|
|
||||||
|
- jobs/ (Jobs that are running in another process)
|
||||||
- model/ (Object model, auto mapping to the database table name)
|
- model/ (Object model, auto mapping to the database table name)
|
||||||
- modules/ (Modified 3rd-party modules)
|
- modules/ (Modified 3rd-party modules)
|
||||||
|
- monitor_types (Monitor Types)
|
||||||
- notification-providers/ (individual notification logic)
|
- notification-providers/ (individual notification logic)
|
||||||
- routers/ (Express Routers)
|
- routers/ (Express Routers)
|
||||||
- socket-handler (Socket.io Handlers)
|
- socket-handler (Socket.io Handlers)
|
||||||
- server.js (Server entry point and main logic)
|
- server.js (Server entry point)
|
||||||
|
- uptime-kuma-server.js (UptimeKumaServer class, main logic should be here, but some still in `server.js`)
|
||||||
|
|
||||||
## Frontend Dev Server
|
## Frontend Dev Server
|
||||||
|
|
||||||
@ -198,18 +210,12 @@ Both frontend and backend share the same package.json. However, the frontend dep
|
|||||||
|
|
||||||
### Update Dependencies
|
### Update Dependencies
|
||||||
|
|
||||||
Install `ncu`
|
|
||||||
https://github.com/raineorshine/npm-check-updates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ncu -u -t patch
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
|
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
|
||||||
|
|
||||||
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
|
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
|
||||||
|
|
||||||
|
If for maybe security reasons, a library must be updated. Then you must need to check if there are any breaking changes.
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
||||||
|
@ -3,6 +3,6 @@ FROM node:16-alpine3.12
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install apprise, iputils for non-root ping, setpriv
|
# Install apprise, iputils for non-root ping, setpriv
|
||||||
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
|
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib git && \
|
||||||
pip3 --no-cache-dir install apprise==1.2.1 && \
|
pip3 --no-cache-dir install apprise==1.2.1 && \
|
||||||
rm -rf /root/.cache
|
rm -rf /root/.cache
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# Build in Golang
|
# Build in Golang
|
||||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
||||||
############################################
|
############################################
|
||||||
FROM golang:1.19.4-buster
|
FROM golang:1.19-buster
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
COPY ./extra/ ./extra/
|
COPY ./extra/ ./extra/
|
||||||
|
@ -10,7 +10,7 @@ WORKDIR /app
|
|||||||
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||||
sqlite3 iputils-ping util-linux dumb-init && \
|
sqlite3 iputils-ping util-linux dumb-init git && \
|
||||||
pip3 --no-cache-dir install apprise==1.2.1 && \
|
pip3 --no-cache-dir install apprise==1.2.1 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt --yes autoremove
|
apt --yes autoremove
|
||||||
|
@ -11,12 +11,17 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
isFreeBSD := runtime.GOOS == "freebsd"
|
isFreeBSD := runtime.GOOS == "freebsd"
|
||||||
|
|
||||||
|
// Is K8S + uptime-kuma as the container name
|
||||||
|
// See #2083
|
||||||
|
isK8s := strings.HasPrefix(os.Getenv("UPTIME_KUMA_PORT"), "tcp://")
|
||||||
|
|
||||||
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@ -44,7 +49,11 @@ func main() {
|
|||||||
hostname = "127.0.0.1"
|
hostname = "127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
port := os.Getenv("UPTIME_KUMA_PORT")
|
port := ""
|
||||||
|
// UPTIME_KUMA_PORT is override by K8S unexpectedly,
|
||||||
|
if !isK8s {
|
||||||
|
port = os.Getenv("UPTIME_KUMA_PORT")
|
||||||
|
}
|
||||||
if len(port) == 0 {
|
if len(port) == 0 {
|
||||||
port = os.Getenv("PORT")
|
port = os.Getenv("PORT")
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,17 @@ if (sslKey && sslCert) {
|
|||||||
|
|
||||||
// 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.
|
// 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 (::)
|
// Dual-stack support for (::)
|
||||||
let hostname = process.env.UPTIME_KUMA_HOST;
|
let hostname = process.env.UPTIME_KUMA_SERVICE_HOST || process.env.UPTIME_KUMA_HOST || "::";
|
||||||
|
|
||||||
// Also read HOST if not *BSD, as HOST is a system environment variable in FreeBSD
|
// Also read HOST if not *BSD, as HOST is a system environment variable in FreeBSD
|
||||||
if (!hostname && !FBSD) {
|
if (!hostname && !FBSD) {
|
||||||
hostname = process.env.HOST;
|
hostname = process.env.HOST;
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || 3001);
|
const port = parseInt(process.env.UPTIME_KUMA_SERVICE_PORT || process.env.UPTIME_KUMA_PORT || process.env.PORT || 3001);
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
host: hostname || "127.0.0.1",
|
host: hostname,
|
||||||
port: port,
|
port: port,
|
||||||
timeout: 28 * 1000,
|
timeout: 28 * 1000,
|
||||||
};
|
};
|
||||||
|
18
package-lock.json
generated
18
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.19.5",
|
"version": "1.19.6",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.19.5",
|
"version": "1.19.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.7.3",
|
"@grpc/grpc-js": "~1.7.3",
|
||||||
@ -27,6 +27,7 @@
|
|||||||
"compare-versions": "~3.6.0",
|
"compare-versions": "~3.6.0",
|
||||||
"compression": "~1.7.4",
|
"compression": "~1.7.4",
|
||||||
"dayjs": "~1.11.5",
|
"dayjs": "~1.11.5",
|
||||||
|
"dotenv": "~16.0.3",
|
||||||
"express": "~4.17.3",
|
"express": "~4.17.3",
|
||||||
"express-basic-auth": "~1.2.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
"express-static-gzip": "~2.1.7",
|
"express-static-gzip": "~2.1.7",
|
||||||
@ -7813,6 +7814,14 @@
|
|||||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||||
|
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/duplexify": {
|
"node_modules/duplexify": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
||||||
@ -24881,6 +24890,11 @@
|
|||||||
"domhandler": "^5.0.1"
|
"domhandler": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dotenv": {
|
||||||
|
"version": "16.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||||
|
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
|
||||||
|
},
|
||||||
"duplexify": {
|
"duplexify": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
||||||
|
@ -84,6 +84,7 @@
|
|||||||
"compare-versions": "~3.6.0",
|
"compare-versions": "~3.6.0",
|
||||||
"compression": "~1.7.4",
|
"compression": "~1.7.4",
|
||||||
"dayjs": "~1.11.5",
|
"dayjs": "~1.11.5",
|
||||||
|
"dotenv": "~16.0.3",
|
||||||
"express": "~4.17.3",
|
"express": "~4.17.3",
|
||||||
"express-basic-auth": "~1.2.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
"express-static-gzip": "~2.1.7",
|
"express-static-gzip": "~2.1.7",
|
||||||
|
@ -5,6 +5,8 @@ const badgeConstants = {
|
|||||||
naColor: "#999",
|
naColor: "#999",
|
||||||
defaultUpColor: "#66c20a",
|
defaultUpColor: "#66c20a",
|
||||||
defaultDownColor: "#c2290a",
|
defaultDownColor: "#c2290a",
|
||||||
|
defaultPendingColor: "#f8a306",
|
||||||
|
defaultMaintenanceColor: "#1747f5",
|
||||||
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
||||||
defaultStyle: "flat",
|
defaultStyle: "flat",
|
||||||
defaultPingValueSuffix: "ms",
|
defaultPingValueSuffix: "ms",
|
||||||
|
@ -72,6 +72,12 @@ class PluginsManager {
|
|||||||
* @param {string} name Directory name, also known as plugin unique name
|
* @param {string} name Directory name, also known as plugin unique name
|
||||||
*/
|
*/
|
||||||
downloadPlugin(repoURL, name) {
|
downloadPlugin(repoURL, name) {
|
||||||
|
if (fs.existsSync(this.pluginsDir + name)) {
|
||||||
|
log.info("plugin", "Plugin folder already exists? Removing...");
|
||||||
|
fs.rmSync(this.pluginsDir + name, {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
}
|
||||||
log.info("plugin", "Installing plugin: " + name + " " + repoURL);
|
log.info("plugin", "Installing plugin: " + name + " " + repoURL);
|
||||||
let result = Git.clone(repoURL, this.pluginsDir, name);
|
let result = Git.clone(repoURL, this.pluginsDir, name);
|
||||||
log.info("plugin", "Install result: " + result);
|
log.info("plugin", "Install result: " + result);
|
||||||
@ -115,13 +121,19 @@ class PluginsManager {
|
|||||||
* @returns {Promise<[]>}
|
* @returns {Promise<[]>}
|
||||||
*/
|
*/
|
||||||
async fetchPluginList() {
|
async fetchPluginList() {
|
||||||
|
let remotePluginList;
|
||||||
|
try {
|
||||||
const res = await axios.get("https://uptime.kuma.pet/c/plugins.json");
|
const res = await axios.get("https://uptime.kuma.pet/c/plugins.json");
|
||||||
const list = res.data.pluginList;
|
remotePluginList = res.data.pluginList;
|
||||||
|
} catch (e) {
|
||||||
|
log.error("plugin", "Failed to fetch plugin list: " + e.message);
|
||||||
|
remotePluginList = [];
|
||||||
|
}
|
||||||
|
|
||||||
for (let plugin of this.pluginList) {
|
for (let plugin of this.pluginList) {
|
||||||
let find = false;
|
let find = false;
|
||||||
// Try to merge
|
// Try to merge
|
||||||
for (let remotePlugin of list) {
|
for (let remotePlugin of remotePluginList) {
|
||||||
if (remotePlugin.name === plugin.info.name) {
|
if (remotePlugin.name === plugin.info.name) {
|
||||||
find = true;
|
find = true;
|
||||||
remotePlugin.installed = true;
|
remotePlugin.installed = true;
|
||||||
@ -136,12 +148,12 @@ class PluginsManager {
|
|||||||
// Local plugin
|
// Local plugin
|
||||||
if (!find) {
|
if (!find) {
|
||||||
plugin.info.local = true;
|
plugin.info.local = true;
|
||||||
list.push(plugin.info);
|
remotePluginList.push(plugin.info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort Installed first, then sort by name
|
// Sort Installed first, then sort by name
|
||||||
return list.sort((a, b) => {
|
return remotePluginList.sort((a, b) => {
|
||||||
if (a.installed === b.installed) {
|
if (a.installed === b.installed) {
|
||||||
if (a.fullName < b.fullName) {
|
if (a.fullName < b.fullName) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -191,15 +203,24 @@ class PluginWrapper {
|
|||||||
let indexFile = this.pluginDir + "/index.js";
|
let indexFile = this.pluginDir + "/index.js";
|
||||||
let packageJSON = this.pluginDir + "/package.json";
|
let packageJSON = this.pluginDir + "/package.json";
|
||||||
|
|
||||||
|
log.info("plugin", "Installing dependencies");
|
||||||
|
|
||||||
if (fs.existsSync(indexFile)) {
|
if (fs.existsSync(indexFile)) {
|
||||||
// Install dependencies
|
// Install dependencies
|
||||||
childProcess.execSync("npm install", {
|
let result = childProcess.spawnSync("npm", [ "install" ], {
|
||||||
cwd: this.pluginDir,
|
cwd: this.pluginDir,
|
||||||
env: {
|
env: {
|
||||||
|
...process.env,
|
||||||
PLAYWRIGHT_BROWSERS_PATH: "../../browsers", // Special handling for read-browser-monitor
|
PLAYWRIGHT_BROWSERS_PATH: "../../browsers", // Special handling for read-browser-monitor
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.stdout) {
|
||||||
|
log.info("plugin", "Install dependencies result: " + result.stdout.toString("utf-8"));
|
||||||
|
} else {
|
||||||
|
log.warn("plugin", "Install dependencies result: no output");
|
||||||
|
}
|
||||||
|
|
||||||
this.pluginClass = require(path.join(process.cwd(), indexFile));
|
this.pluginClass = require(path.join(process.cwd(), indexFile));
|
||||||
|
|
||||||
let pluginClassType = typeof this.pluginClass;
|
let pluginClassType = typeof this.pluginClass;
|
||||||
|
@ -4,7 +4,7 @@ const { R } = require("redbean-node");
|
|||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const Monitor = require("../model/monitor");
|
const Monitor = require("../model/monitor");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { UP, MAINTENANCE, DOWN, flipStatus, log } = require("../../src/util");
|
const { UP, MAINTENANCE, DOWN, PENDING, flipStatus, log } = require("../../src/util");
|
||||||
const StatusPage = require("../model/status_page");
|
const StatusPage = require("../model/status_page");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { makeBadge } = require("badge-maker");
|
const { makeBadge } = require("badge-maker");
|
||||||
@ -111,8 +111,12 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
|
|||||||
label,
|
label,
|
||||||
upLabel = "Up",
|
upLabel = "Up",
|
||||||
downLabel = "Down",
|
downLabel = "Down",
|
||||||
|
pendingLabel = "Pending",
|
||||||
|
maintenanceLabel = "Maintenance",
|
||||||
upColor = badgeConstants.defaultUpColor,
|
upColor = badgeConstants.defaultUpColor,
|
||||||
downColor = badgeConstants.defaultDownColor,
|
downColor = badgeConstants.defaultDownColor,
|
||||||
|
pendingColor = badgeConstants.defaultPendingColor,
|
||||||
|
maintenanceColor = badgeConstants.defaultMaintenanceColor,
|
||||||
style = badgeConstants.defaultStyle,
|
style = badgeConstants.defaultStyle,
|
||||||
value, // for demo purpose only
|
value, // for demo purpose only
|
||||||
} = request.query;
|
} = request.query;
|
||||||
@ -139,11 +143,30 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
|
|||||||
badgeValues.color = badgeConstants.naColor;
|
badgeValues.color = badgeConstants.naColor;
|
||||||
} else {
|
} else {
|
||||||
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
||||||
const state = overrideValue !== undefined ? overrideValue : heartbeat.status === 1;
|
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
|
||||||
|
|
||||||
badgeValues.label = label ? label : "";
|
badgeValues.label = label ?? "";
|
||||||
badgeValues.color = state ? upColor : downColor;
|
switch (state) {
|
||||||
badgeValues.message = label ?? state ? upLabel : downLabel;
|
case DOWN:
|
||||||
|
badgeValues.color = downColor;
|
||||||
|
badgeValues.message = downLabel;
|
||||||
|
break;
|
||||||
|
case UP:
|
||||||
|
badgeValues.color = upColor;
|
||||||
|
badgeValues.message = upLabel;
|
||||||
|
break;
|
||||||
|
case PENDING:
|
||||||
|
badgeValues.color = pendingColor;
|
||||||
|
badgeValues.message = pendingLabel;
|
||||||
|
break;
|
||||||
|
case MAINTENANCE:
|
||||||
|
badgeValues.color = maintenanceColor;
|
||||||
|
badgeValues.message = maintenanceLabel;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
badgeValues.color = badgeConstants.naColor;
|
||||||
|
badgeValues.message = "N/A";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the svg based on given values
|
// build the svg based on given values
|
||||||
|
@ -11,6 +11,9 @@ dayjs.extend(require("dayjs/plugin/utc"));
|
|||||||
dayjs.extend(require("./modules/dayjs/plugin/timezone"));
|
dayjs.extend(require("./modules/dayjs/plugin/timezone"));
|
||||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||||
|
|
||||||
|
// Load environment variables from `.env`
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
// Check Node.js Version
|
// Check Node.js Version
|
||||||
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
|
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
|
||||||
const requiredVersion = 14;
|
const requiredVersion = 14;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const { checkLogin } = require("../util-server");
|
const { checkLogin } = require("../util-server");
|
||||||
const { PluginManager } = require("../plugins-manager");
|
const { PluginsManager } = require("../plugins-manager");
|
||||||
|
const { log } = require("../../src/util.js");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handlers for plugins
|
* Handlers for plugins
|
||||||
@ -15,7 +16,9 @@ module.exports.pluginsHandler = (socket, server) => {
|
|||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
if (PluginManager.disable) {
|
log.debug("plugin", "PluginManager.disable: " + PluginsManager.disable);
|
||||||
|
|
||||||
|
if (PluginsManager.disable) {
|
||||||
throw new Error("Plugin Disabled: In order to enable plugin feature, you need to use the default data directory: ./data/");
|
throw new Error("Plugin Disabled: In order to enable plugin feature, you need to use the default data directory: ./data/");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +28,7 @@ module.exports.pluginsHandler = (socket, server) => {
|
|||||||
pluginList,
|
pluginList,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
log.warn("plugin", "Error: " + error.message);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
|
@ -48,7 +48,7 @@ export default {
|
|||||||
this.remotePluginList = res.pluginList;
|
this.remotePluginList = res.pluginList;
|
||||||
this.remotePluginListMsg = "";
|
this.remotePluginListMsg = "";
|
||||||
} else {
|
} else {
|
||||||
this.remotePluginListMsg = this.$t("loadingError") + " " + res.message;
|
this.remotePluginListMsg = this.$t("loadingError") + " " + res.msg;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,16 @@
|
|||||||
</option>
|
</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Hidden for now: Reason refer to Setting.vue
|
||||||
<optgroup :label="$t('Custom Monitor Type')">
|
<optgroup :label="$t('Custom Monitor Type')">
|
||||||
<option value="browser">
|
<option value="browser">
|
||||||
(Beta) HTTP(s) - Browser Engine (Chrome/Firefox)
|
(Beta) HTTP(s) - Browser Engine (Chrome/Firefox)
|
||||||
</option>
|
</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
|
-->
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Friendly Name -->
|
<!-- Friendly Name -->
|
||||||
@ -425,10 +429,6 @@
|
|||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
|
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-5 mb-1">
|
|
||||||
<button id="monitor-submit-btn" class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -618,6 +618,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12 mt-5 mb-1">
|
||||||
|
<button id="monitor-submit-btn" class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -1000,6 +1004,14 @@ message HealthCheckResponse {
|
|||||||
this.monitor.headers = JSON.stringify(JSON.parse(this.monitor.headers), null, 4);
|
this.monitor.headers = JSON.stringify(JSON.parse(this.monitor.headers), null, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.monitor.hostname) {
|
||||||
|
this.monitor.hostname = this.monitor.hostname.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.monitor.url) {
|
||||||
|
this.monitor.url = this.monitor.url.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
this.$root.add(this.monitor, async (res) => {
|
this.$root.add(this.monitor, async (res) => {
|
||||||
|
|
||||||
|
@ -113,9 +113,12 @@ export default {
|
|||||||
backup: {
|
backup: {
|
||||||
title: this.$t("Backup"),
|
title: this.$t("Backup"),
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
|
Hidden for now: Unfortunately, after some test, I found that Playwright requires a lot of libraries to be installed on the Linux host in order to start Chrome or Firefox.
|
||||||
|
It will be hard to install, so I hide this feature for now. But it still accessible via URL: /settings/plugins.
|
||||||
plugins: {
|
plugins: {
|
||||||
title: this.$tc("plugin", 2),
|
title: this.$tc("plugin", 2),
|
||||||
},
|
},*/
|
||||||
about: {
|
about: {
|
||||||
title: this.$t("About"),
|
title: this.$t("About"),
|
||||||
},
|
},
|
||||||
|
@ -32,7 +32,6 @@ import Proxies from "./components/settings/Proxies.vue";
|
|||||||
import Backup from "./components/settings/Backup.vue";
|
import Backup from "./components/settings/Backup.vue";
|
||||||
import About from "./components/settings/About.vue";
|
import About from "./components/settings/About.vue";
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
|
@ -88,7 +88,7 @@ export function hostNameRegexPattern(mqtt = false) {
|
|||||||
// mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect)
|
// mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect)
|
||||||
const mqttSchemeRegexPattern = "((mqtt|ws)s?:\\/\\/)?";
|
const mqttSchemeRegexPattern = "((mqtt|ws)s?:\\/\\/)?";
|
||||||
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
||||||
const ipRegexPattern = `((^\\s*${mqtt ? mqttSchemeRegexPattern : ""}((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))`;
|
const ipRegexPattern = `((^${mqtt ? mqttSchemeRegexPattern : ""}((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$))`;
|
||||||
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
||||||
const hostNameRegexPattern = `^${mqtt ? mqttSchemeRegexPattern : ""}([a-zA-Z0-9])?(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\\-_]*[a-zA-Z0-9_])\\.)*([A-Za-z0-9_]|[A-Za-z0-9_][A-Za-z0-9\\-_]*[A-Za-z0-9_])$`;
|
const hostNameRegexPattern = `^${mqtt ? mqttSchemeRegexPattern : ""}([a-zA-Z0-9])?(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\\-_]*[a-zA-Z0-9_])\\.)*([A-Za-z0-9_]|[A-Za-z0-9_][A-Za-z0-9\\-_]*[A-Za-z0-9_])$`;
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ describe("Test util-frontend.js", () => {
|
|||||||
expect(regex.test("www.test.com")).to.be.true;
|
expect(regex.test("www.test.com")).to.be.true;
|
||||||
expect(regex.test("127.0.0.1")).to.be.true;
|
expect(regex.test("127.0.0.1")).to.be.true;
|
||||||
expect(regex.test("192.168.1.156")).to.be.true;
|
expect(regex.test("192.168.1.156")).to.be.true;
|
||||||
|
expect(regex.test(" 192.168.1.145")).to.be.false;
|
||||||
|
expect(regex.test("192.168.1.145 ")).to.be.false;
|
||||||
|
expect(regex.test(" fe80::3282:3ff:ae28:592")).to.be.false;
|
||||||
|
expect(regex.test("fe80::3282:3ff:ae28:592 ")).to.be.false;
|
||||||
|
|
||||||
["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
|
["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
|
||||||
expect(regex.test(`${schema}://www.test.com`)).to.be.false;
|
expect(regex.test(`${schema}://www.test.com`)).to.be.false;
|
||||||
@ -23,6 +27,10 @@ describe("Test util-frontend.js", () => {
|
|||||||
expect(regex.test("www.test.com")).to.be.true;
|
expect(regex.test("www.test.com")).to.be.true;
|
||||||
expect(regex.test("127.0.0.1")).to.be.true;
|
expect(regex.test("127.0.0.1")).to.be.true;
|
||||||
expect(regex.test("192.168.1.156")).to.be.true;
|
expect(regex.test("192.168.1.156")).to.be.true;
|
||||||
|
expect(regex.test(" 192.168.1.145")).to.be.false;
|
||||||
|
expect(regex.test("192.168.1.145 ")).to.be.false;
|
||||||
|
expect(regex.test(" fe80::3282:3ff:ae28:592")).to.be.false;
|
||||||
|
expect(regex.test("fe80::3282:3ff:ae28:592 ")).to.be.false;
|
||||||
|
|
||||||
["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
|
["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
|
||||||
expect(regex.test(`${schema}://www.test.com`)).to.be.true;
|
expect(regex.test(`${schema}://www.test.com`)).to.be.true;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2020",
|
"es2020",
|
||||||
"DOM",
|
"DOM"
|
||||||
],
|
],
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user