Compare commits

..

No commits in common. "master" and "1.20.X" have entirely different histories.

66 changed files with 380 additions and 3883 deletions

View File

@ -18,7 +18,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-latest, ubuntu-latest, windows-latest]
node: [ 14, 16, 18, 19 ] node: [ 14, 16, 17, 18 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:
@ -66,19 +66,3 @@ jobs:
- run: npm install - run: npm install
- run: npm run build - run: npm run build
- run: npm run cy:test - run: npm run cy:test
frontend-unit-tests:
needs: [ check-linters ]
runs-on: ubuntu-latest
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- uses: actions/checkout@v3
- name: Use Node.js 14
uses: actions/setup-node@v3
with:
node-version: 14
cache: 'npm'
- run: npm install
- run: npm run build
- run: npm run cy:run:unit

View File

@ -7,9 +7,9 @@
<img src="./public/icon.svg" width="128" alt="" /> <img src="./public/icon.svg" width="128" alt="" />
</div> </div>
Uptime Kuma is an easy-to-use self-hosted monitoring tool. It is a self-hosted monitoring tool like "Uptime Robot".
<img src="https://user-images.githubusercontent.com/1336778/212262296-e6205815-ad62-488c-83ec-a5b0d0689f7c.jpg" width="700" alt="" /> <img src="https://uptime.kuma.pet/img/dark.jpg" width="700" alt="" />
## 🥔 Live Demo ## 🥔 Live Demo

View File

@ -1,10 +0,0 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
supportFile: false,
specPattern: [
"test/cypress/unit/**/*.js"
],
}
});

View File

@ -1,16 +0,0 @@
############################################
# 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
############################################
FROM golang:1.19.4-buster
WORKDIR /app
ARG TARGETPLATFORM
COPY ./extra/ ./extra/
# Compile healthcheck.go
RUN apt update && \
apt --yes --no-install-recommends install curl && \
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
apt --yes --no-install-recommends install nodejs && \
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
apt --yes remove nodejs

View File

@ -1,9 +1,19 @@
############################################ ############################################
# 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
# Check file: builder-go.dockerfile
############################################ ############################################
FROM louislam/uptime-kuma:builder-go AS build_healthcheck FROM golang:1.19.4-buster AS build_healthcheck
WORKDIR /app
ARG TARGETPLATFORM
COPY ./extra/ ./extra/
# Compile healthcheck.go
RUN apt update
RUN apt --yes --no-install-recommends install curl
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash
RUN apt --yes --no-install-recommends install nodejs
RUN node -v
RUN node ./extra/build-healthcheck.js $TARGETPLATFORM
############################################ ############################################
# Build in Node.js # Build in Node.js
@ -12,13 +22,10 @@ FROM louislam/uptime-kuma:base-debian AS build
WORKDIR /app WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY .npmrc .npmrc
COPY package.json package.json
COPY package-lock.json package-lock.json
RUN npm ci --omit=dev
COPY . . COPY . .
COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
RUN chmod +x /app/extra/entrypoint.sh RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
############################################ ############################################
# ⭐ Main Image # ⭐ Main Image

View File

@ -3,12 +3,10 @@ WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY .npmrc .npmrc
COPY package.json package.json
COPY package-lock.json package-lock.json
RUN npm ci --omit=dev
COPY . . COPY . .
RUN chmod +x /app/extra/entrypoint.sh RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
FROM louislam/uptime-kuma:base-alpine AS release FROM louislam/uptime-kuma:base-alpine AS release
WORKDIR /app WORKDIR /app

View File

@ -32,10 +32,6 @@ if (! exists) {
process.exit(1); process.exit(1);
} }
/**
* Commit updated files
* @param {string} version Version to update to
*/
function commit(version) { function commit(version) {
let msg = "Update to " + version; let msg = "Update to " + version;
@ -51,10 +47,6 @@ function commit(version) {
console.log(res.stdout.toString().trim()); console.log(res.stdout.toString().trim());
} }
/**
* Create a tag with the specified version
* @param {string} version Tag to create
*/
function tag(version) { function tag(version) {
let res = childProcess.spawnSync("git", [ "tag", version ]); let res = childProcess.spawnSync("git", [ "tag", version ]);
console.log(res.stdout.toString().trim()); console.log(res.stdout.toString().trim());
@ -63,11 +55,6 @@ function tag(version) {
console.log(res.stdout.toString().trim()); console.log(res.stdout.toString().trim());
} }
/**
* Check if a tag exists for the specified version
* @param {string} version Version to check
* @returns {boolean} Does the tag already exist
*/
function tagExists(version) { function tagExists(version) {
if (! version) { if (! version) {
throw new Error("invalid version"); throw new Error("invalid version");

View File

@ -25,10 +25,6 @@ if (platform === "linux/amd64") {
const file = fs.createWriteStream("cloudflared.deb"); const file = fs.createWriteStream("cloudflared.deb");
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb"); get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
/**
* Download specified file
* @param {string} url URL to request
*/
function get(url) { function get(url) {
http.get(url, function (res) { http.get(url, function (res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {

View File

@ -1,7 +1,3 @@
/*
* If changed, have to run `npm run build-docker-builder-go`.
* This script should be run after a period of time (180s), because the server may need some time to prepare.
*/
package main package main
import ( import (

View File

@ -43,11 +43,6 @@ const main = async () => {
console.log("Finished."); console.log("Finished.");
}; };
/**
* Ask question of user
* @param {string} question Question to ask
* @returns {Promise<string>} Users response
*/
function question(question) { function question(question) {
return new Promise((resolve) => { return new Promise((resolve) => {
rl.question(question, (answer) => { rl.question(question, (answer) => {

View File

@ -53,11 +53,6 @@ const main = async () => {
console.log("Finished."); console.log("Finished.");
}; };
/**
* Ask question of user
* @param {string} question Question to ask
* @returns {Promise<string>} Users response
*/
function question(question) { function question(question) {
return new Promise((resolve) => { return new Promise((resolve) => {
rl.question(question, (answer) => { rl.question(question, (answer) => {

View File

@ -135,11 +135,6 @@ server.listen({
udp: 5300 udp: 5300
}); });
/**
* Get human readable request type from request code
* @param {number} code Request code to translate
* @returns {string} Human readable request type
*/
function type(code) { function type(code) {
for (let name in Packet.TYPE) { for (let name in Packet.TYPE) {
if (Packet.TYPE[name] === code) { if (Packet.TYPE[name] === code) {

View File

@ -11,7 +11,6 @@ class SimpleMqttServer {
this.port = port; this.port = port;
} }
/** Start the MQTT server */
start() { start() {
this.server.listen(this.port, () => { this.server.listen(this.port, () => {
console.log("server started and listening on port ", this.port); console.log("server started and listening on port ", this.port);

View File

@ -36,8 +36,10 @@ if (! exists) {
} }
/** /**
* Commit updated files * Updates the version number in package.json and commits it to git.
* @param {string} version Version to update to * @param {string} version - The new version number
*
* Generated by Trelent
*/ */
function commit(version) { function commit(version) {
let msg = "Update to " + version; let msg = "Update to " + version;
@ -51,19 +53,16 @@ function commit(version) {
} }
} }
/**
* Create a tag with the specified version
* @param {string} version Tag to create
*/
function tag(version) { function tag(version) {
let res = childProcess.spawnSync("git", [ "tag", version ]); let res = childProcess.spawnSync("git", [ "tag", version ]);
console.log(res.stdout.toString().trim()); console.log(res.stdout.toString().trim());
} }
/** /**
* Check if a tag exists for the specified version * Checks if a given version is already tagged in the git repository.
* @param {string} version Version to check * @param {string} version - The version to check for.
* @returns {boolean} Does the tag already exist *
* Generated by Trelent
*/ */
function tagExists(version) { function tagExists(version) {
if (! version) { if (! version) {

View File

@ -10,10 +10,6 @@ if (!newVersion) {
updateWiki(newVersion); updateWiki(newVersion);
/**
* Update the wiki with new version number
* @param {string} newVersion Version to update to
*/
function updateWiki(newVersion) { function updateWiki(newVersion) {
const wikiDir = "./tmp/wiki"; const wikiDir = "./tmp/wiki";
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md"; const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
@ -43,10 +39,6 @@ function updateWiki(newVersion) {
safeDelete(wikiDir); safeDelete(wikiDir);
} }
/**
* Check if a directory exists and then delete it
* @param {string} dir Directory to delete
*/
function safeDelete(dir) { function safeDelete(dir) {
if (fs.existsSync(dir)) { if (fs.existsSync(dir)) {
fs.rm(dir, { fs.rm(dir, {

2733
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.19.6", "version": "1.19.3",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@ -31,7 +31,6 @@
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine", "build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push", "build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push", "build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push", "build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push", "build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", "build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
@ -39,7 +38,7 @@
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.19.6 && npm ci --production && npm run download-dist", "setup": "git checkout 1.19.3 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js", "download-dist": "node extra/download-dist.js",
"mark-as-nightly": "node extra/mark-as-nightly.js", "mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js", "reset-password": "node extra/reset-password.js",
@ -61,13 +60,11 @@
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev", "start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
"cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e", "cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e",
"cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js", "cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js",
"cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"", "cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go" "build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go"
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "~1.7.3", "@grpc/grpc-js": "~1.7.3",
"@louislam/ping": "~0.4.2-mod.1",
"@louislam/sqlite3": "15.1.2", "@louislam/sqlite3": "15.1.2",
"args-parser": "~1.3.0", "args-parser": "~1.3.0",
"axios": "~0.27.0", "axios": "~0.27.0",
@ -96,7 +93,6 @@
"jsonwebtoken": "~9.0.0", "jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2", "jwt-decode": "~3.1.2",
"limiter": "~2.1.0", "limiter": "~2.1.0",
"mongodb": "~4.13.0",
"mqtt": "~4.3.7", "mqtt": "~4.3.7",
"mssql": "~8.1.4", "mssql": "~8.1.4",
"mysql2": "~2.3.3", "mysql2": "~2.3.3",
@ -107,11 +103,11 @@
"password-hash": "~1.2.2", "password-hash": "~1.2.2",
"pg": "~8.8.0", "pg": "~8.8.0",
"pg-connection-string": "~2.5.0", "pg-connection-string": "~2.5.0",
"ping": "~0.4.2",
"prom-client": "~13.2.0", "prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.1", "prometheus-api-metrics": "~3.2.1",
"protobufjs": "~7.1.1", "protobufjs": "~7.1.1",
"redbean-node": "~0.2.0", "redbean-node": "0.1.4",
"redis": "~4.5.1",
"socket.io": "~4.5.3", "socket.io": "~4.5.3",
"socket.io-client": "~4.5.3", "socket.io-client": "~4.5.3",
"socks-proxy-agent": "6.1.1", "socks-proxy-agent": "6.1.1",

View File

@ -63,12 +63,6 @@ function myAuthorizer(username, password, callback) {
}); });
} }
/**
* Use basic auth if auth is not disabled
* @param {express.Request} req Express request object
* @param {express.Response} res Express response object
* @param {express.NextFunction} next
*/
exports.basicAuth = async function (req, res, next) { exports.basicAuth = async function (req, res, next) {
const middleware = basicAuth({ const middleware = basicAuth({
authorizer: myAuthorizer, authorizer: myAuthorizer,

View File

@ -37,10 +37,6 @@ class CacheableDnsHttpAgent {
this.enable = isEnable; this.enable = isEnable;
} }
/**
* Attach cacheable to HTTP agent
* @param {http.Agent} agent Agent to install
*/
static install(agent) { static install(agent) {
this.cacheable.install(agent); this.cacheable.install(agent);
} }

View File

@ -32,7 +32,6 @@ const initBackgroundJobs = function (args) {
return bree; return bree;
}; };
/** Stop all background jobs if running */
const stopBackgroundJobs = function () { const stopBackgroundJobs = function () {
if (bree) { if (bree) {
bree.stop(); bree.stop();

View File

@ -25,20 +25,15 @@ const DEFAULT_KEEP_PERIOD = 180;
parsedPeriod = DEFAULT_KEEP_PERIOD; parsedPeriod = DEFAULT_KEEP_PERIOD;
} }
if (parsedPeriod < 1) { log(`Clearing Data older than ${parsedPeriod} days...`);
log(`Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
} else {
log(`Clearing Data older than ${parsedPeriod} days...`); try {
await R.exec(
try { "DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
await R.exec( [ parsedPeriod ]
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ", );
[ parsedPeriod ] } catch (e) {
); log(`Failed to clear old data: ${e.message}`);
} catch (e) {
log(`Failed to clear old data: ${e.message}`);
}
} }
exit(); exit();

View File

@ -112,11 +112,6 @@ class Maintenance extends BeanModel {
return this.toPublicJSON(timezone); return this.toPublicJSON(timezone);
} }
/**
* Get a list of weekdays that the maintenance is active for
* Monday=1, Tuesday=2 etc.
* @returns {number[]} Array of active weekdays
*/
getDayOfWeekList() { getDayOfWeekList() {
log.debug("timeslot", "List: " + this.weekdays); log.debug("timeslot", "List: " + this.weekdays);
return JSON.parse(this.weekdays).sort(function (a, b) { return JSON.parse(this.weekdays).sort(function (a, b) {
@ -124,20 +119,12 @@ class Maintenance extends BeanModel {
}); });
} }
/**
* Get a list of days in month that maintenance is active for
* @returns {number[]} Array of active days in month
*/
getDayOfMonthList() { getDayOfMonthList() {
return JSON.parse(this.days_of_month).sort(function (a, b) { return JSON.parse(this.days_of_month).sort(function (a, b) {
return a - b; return a - b;
}); });
} }
/**
* Get the start date and time for maintenance
* @returns {dayjs.Dayjs} Start date and time
*/
getStartDateTime() { getStartDateTime() {
let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm"); let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
log.debug("timeslot", "startOfTheDay: " + startOfTheDay); log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
@ -150,10 +137,6 @@ class Maintenance extends BeanModel {
return dayjs.utc(this.start_date).add(startTimeSecond, "second"); return dayjs.utc(this.start_date).add(startTimeSecond, "second");
} }
/**
* Get the duraction of maintenance in seconds
* @returns {number} Duration of maintenance
*/
getDuration() { getDuration() {
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second"); let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
// Add 24hours if it is across day // Add 24hours if it is across day
@ -163,12 +146,6 @@ class Maintenance extends BeanModel {
return duration; return duration;
} }
/**
* Convert data from socket to bean
* @param {Bean} bean Bean to fill in
* @param {Object} obj Data to fill bean with
* @returns {Bean} Filled bean
*/
static jsonToBean(bean, obj) { static jsonToBean(bean, obj) {
if (obj.id) { if (obj.id) {
bean.id = obj.id; bean.id = obj.id;

View File

@ -6,11 +6,6 @@ const { UptimeKumaServer } = require("../uptime-kuma-server");
class MaintenanceTimeslot extends BeanModel { class MaintenanceTimeslot extends BeanModel {
/**
* Return an object that ready to parse to JSON for public
* Only show necessary data to public
* @returns {Object}
*/
async toPublicJSON() { async toPublicJSON() {
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset(); const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
@ -26,10 +21,6 @@ class MaintenanceTimeslot extends BeanModel {
return obj; return obj;
} }
/**
* Return an object that ready to parse to JSON
* @returns {Object}
*/
async toJSON() { async toJSON() {
return await this.toPublicJSON(); return await this.toPublicJSON();
} }

View File

@ -3,9 +3,7 @@ const dayjs = require("dayjs");
const axios = require("axios"); const axios = require("axios");
const { Prometheus } = require("../prometheus"); const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } = require("../../src/util"); const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery, const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery } = require("../util-server");
redisPingAsync, mongodbPing,
} = require("../util-server");
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");
@ -38,6 +36,7 @@ class Monitor extends BeanModel {
id: this.id, id: this.id,
name: this.name, name: this.name,
sendUrl: this.sendUrl, sendUrl: this.sendUrl,
maintenance: await Monitor.isUnderMaintenance(this.id),
}; };
if (this.sendUrl) { if (this.sendUrl) {
@ -495,17 +494,13 @@ class Monitor extends BeanModel {
const options = { const options = {
url: `/containers/${this.docker_container}/json`, url: `/containers/${this.docker_container}/json`,
timeout: this.interval * 1000 * 0.8,
headers: { headers: {
"Accept": "*/*", "Accept": "*/*",
"User-Agent": "Uptime-Kuma/" + version, "User-Agent": "Uptime-Kuma/" + version,
}, },
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({ httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: !this.getIgnoreTls(), rejectUnauthorized: ! this.getIgnoreTls(),
}),
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
maxCachedSessions: 0,
}), }),
}; };
@ -586,15 +581,6 @@ class Monitor extends BeanModel {
bean.msg = ""; bean.msg = "";
bean.status = UP; bean.status = UP;
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "mongodb") {
let startTime = dayjs().valueOf();
await mongodbPing(this.databaseConnectionString);
bean.msg = "";
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "radius") { } else if (this.type === "radius") {
let startTime = dayjs().valueOf(); let startTime = dayjs().valueOf();
@ -631,12 +617,6 @@ class Monitor extends BeanModel {
} }
} }
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "redis") {
let startTime = dayjs().valueOf();
bean.msg = await redisPingAsync(this.databaseConnectionString);
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else { } else {
bean.msg = "Unknown Monitor Type"; bean.msg = "Unknown Monitor Type";
bean.status = PENDING; bean.status = PENDING;
@ -768,13 +748,6 @@ class Monitor extends BeanModel {
} }
} }
/**
* Make a request using axios
* @param {Object} options Options for Axios
* @param {boolean} finalCall Should this be the final call i.e
* don't retry on faliure
* @returns {Object} Axios response
*/
async makeAxiosRequest(options, finalCall = false) { async makeAxiosRequest(options, finalCall = false) {
try { try {
let res; let res;
@ -1256,7 +1229,6 @@ class Monitor extends BeanModel {
return maintenance.count !== 0; return maintenance.count !== 0;
} }
/** Make sure monitor interval is between bounds */
validate() { validate() {
if (this.interval > MAX_INTERVAL_SECOND) { if (this.interval > MAX_INTERVAL_SECOND) {
throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`); throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`);

View File

@ -8,14 +8,6 @@ class PromoSMS extends NotificationProvider {
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully."; let okMsg = "Sent Successfully.";
if (notification.promosmsAllowLongSMS === undefined) {
notification.promosmsAllowLongSMS = false;
}
//TODO: Add option for enabling special characters. It will decrese message max length from 160 to 70 chars.
//Lets remove non ascii char
let cleanMsg = msg.replace(/[^\x00-\x7F]/g, "");
try { try {
let config = { let config = {
headers: { headers: {
@ -26,9 +18,8 @@ class PromoSMS extends NotificationProvider {
}; };
let data = { let data = {
"recipients": [ notification.promosmsPhoneNumber ], "recipients": [ notification.promosmsPhoneNumber ],
//Trim message to maximum length of 1 SMS or 4 if we allowed long messages //Lets remove non ascii char
"text": notification.promosmsAllowLongSMS ? cleanMsg.substring(0, 639) : cleanMsg.substring(0, 159), "text": msg.replace(/[^\x00-\x7F]/g, ""),
"long-sms": notification.promosmsAllowLongSMS,
"type": Number(notification.promosmsSMSType), "type": Number(notification.promosmsSMSType),
"sender": notification.promosmsSenderName "sender": notification.promosmsSenderName
}; };

View File

@ -10,7 +10,7 @@ class Pushover extends NotificationProvider {
let pushoverlink = "https://api.pushover.net/1/messages.json"; let pushoverlink = "https://api.pushover.net/1/messages.json";
let data = { let data = {
"message": "<b>Message</b>:" + msg, "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg,
"user": notification.pushoveruserkey, "user": notification.pushoveruserkey,
"token": notification.pushoverapptoken, "token": notification.pushoverapptoken,
"sound": notification.pushoversounds, "sound": notification.pushoversounds,

View File

@ -21,12 +21,6 @@ class ServerChan extends NotificationProvider {
} }
} }
/**
* Get the formatted title for message
* @param {?Object} monitorJSON Monitor details (For Up/Down only)
* @param {?Object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {string} Formatted title
*/
checkStatus(heartbeatJSON, monitorJSON) { checkStatus(heartbeatJSON, monitorJSON) {
let title = "UptimeKuma Message"; let title = "UptimeKuma Message";
if (heartbeatJSON != null && heartbeatJSON["status"] === UP) { if (heartbeatJSON != null && heartbeatJSON["status"] === UP) {

View File

@ -1,113 +0,0 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
let successMessage = "Sent Successfully.";
class Splunk extends NotificationProvider {
name = "Splunk";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
try {
if (heartbeatJSON == null) {
const title = "Uptime Kuma Alert";
const monitor = {
type: "ping",
url: "Uptime Kuma Test Button",
};
return this.postNotification(notification, title, msg, monitor, "trigger");
}
if (heartbeatJSON.status === UP) {
const title = "Uptime Kuma Monitor ✅ Up";
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "recovery");
}
if (heartbeatJSON.status === DOWN) {
const title = "Uptime Kuma Monitor 🔴 Down";
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "trigger");
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* Check if result is successful, result code should be in range 2xx
* @param {Object} result Axios response object
* @throws {Error} The status code is not in range 2xx
*/
checkResult(result) {
if (result.status == null) {
throw new Error("Splunk notification failed with invalid response!");
}
if (result.status < 200 || result.status >= 300) {
throw new Error("Splunk notification failed with status code " + result.status);
}
}
/**
* Send the message
* @param {BeanModel} notification Message title
* @param {string} title Message title
* @param {string} body Message
* @param {Object} monitorInfo Monitor details (For Up/Down only)
* @param {?string} eventAction Action event for PagerDuty (trigger, acknowledge, resolve)
* @returns {string}
*/
async postNotification(notification, title, body, monitorInfo, eventAction = "trigger") {
let monitorUrl;
if (monitorInfo.type === "port") {
monitorUrl = monitorInfo.hostname;
if (monitorInfo.port) {
monitorUrl += ":" + monitorInfo.port;
}
} else if (monitorInfo.hostname != null) {
monitorUrl = monitorInfo.hostname;
} else {
monitorUrl = monitorInfo.url;
}
if (eventAction === "recovery") {
if (notification.splunkAutoResolve === "0") {
return "No action required";
}
eventAction = notification.splunkAutoResolve;
} else {
eventAction = notification.splunkSeverity;
}
const options = {
method: "POST",
url: notification.splunkRestURL,
headers: { "Content-Type": "application/json" },
data: {
message_type: eventAction,
state_message: `[${title}] [${monitorUrl}] ${body}`,
entity_display_name: "Uptime Kuma Alert: " + monitorInfo.name,
routing_key: notification.pagerdutyIntegrationKey,
entity_id: "Uptime Kuma/" + monitorInfo.id,
}
};
const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
}
let result = await axios.request(options);
this.checkResult(result);
if (result.statusText != null) {
return "Splunk notification succeed: " + result.statusText;
}
return successMessage;
}
}
module.exports = Splunk;

View File

@ -40,7 +40,6 @@ const Stackfield = require("./notification-providers/stackfield");
const Teams = require("./notification-providers/teams"); const Teams = require("./notification-providers/teams");
const TechulusPush = require("./notification-providers/techulus-push"); const TechulusPush = require("./notification-providers/techulus-push");
const Telegram = require("./notification-providers/telegram"); const Telegram = require("./notification-providers/telegram");
const Splunk = require("./notification-providers/splunk");
const Webhook = require("./notification-providers/webhook"); const Webhook = require("./notification-providers/webhook");
const WeCom = require("./notification-providers/wecom"); const WeCom = require("./notification-providers/wecom");
const GoAlert = require("./notification-providers/goalert"); const GoAlert = require("./notification-providers/goalert");
@ -101,7 +100,6 @@ class Notification {
new Teams(), new Teams(),
new TechulusPush(), new TechulusPush(),
new Telegram(), new Telegram(),
new Splunk(),
new Webhook(), new Webhook(),
new WeCom(), new WeCom(),
new GoAlert(), new GoAlert(),

View File

@ -99,7 +99,6 @@ class Prometheus {
} }
} }
/** Remove monitor from prometheus */
remove() { remove() {
try { try {
monitorCertDaysRemaining.remove(this.monitorLabelValues); monitorCertDaysRemaining.remove(this.monitorLabelValues);

View File

@ -941,21 +941,13 @@ let needSetup = false;
try { try {
checkLogin(socket); checkLogin(socket);
let bean = await R.findOne("tag", " id = ? ", [ tag.id ]); let bean = await R.findOne("monitor", " id = ? ", [ tag.id ]);
if (bean == null) {
callback({
ok: false,
msg: "Tag not found",
});
return;
}
bean.name = tag.name; bean.name = tag.name;
bean.color = tag.color; bean.color = tag.color;
await R.store(bean); await R.store(bean);
callback({ callback({
ok: true, ok: true,
msg: "Saved",
tag: await bean.toJSON(), tag: await bean.toJSON(),
}); });

View File

@ -6,10 +6,10 @@ class UptimeCacheList {
static list = {}; static list = {};
/** /**
* Get the uptime for a specific period *
* @param {number} monitorID * @param monitorID
* @param {number} duration * @param duration
* @return {number} * @return number
*/ */
static getUptime(monitorID, duration) { static getUptime(monitorID, duration) {
if (UptimeCacheList.list[monitorID] && UptimeCacheList.list[monitorID][duration]) { if (UptimeCacheList.list[monitorID] && UptimeCacheList.list[monitorID][duration]) {
@ -20,12 +20,6 @@ class UptimeCacheList {
} }
} }
/**
* Add uptime for specified monitor
* @param {number} monitorID
* @param {number} duration
* @param {number} uptime Uptime to add
*/
static addUptime(monitorID, duration, uptime) { static addUptime(monitorID, duration, uptime) {
log.debug("UptimeCacheList", "addUptime: " + monitorID + " " + duration); log.debug("UptimeCacheList", "addUptime: " + monitorID + " " + duration);
if (!UptimeCacheList.list[monitorID]) { if (!UptimeCacheList.list[monitorID]) {
@ -34,10 +28,6 @@ class UptimeCacheList {
UptimeCacheList.list[monitorID][duration] = uptime; UptimeCacheList.list[monitorID][duration] = uptime;
} }
/**
* Clear cache for specified monitor
* @param {number} monitorID
*/
static clearCache(monitorID) { static clearCache(monitorID) {
log.debug("UptimeCacheList", "clearCache: " + monitorID); log.debug("UptimeCacheList", "clearCache: " + monitorID);
delete UptimeCacheList.list[monitorID]; delete UptimeCacheList.list[monitorID];

View File

@ -86,7 +86,6 @@ class UptimeKumaServer {
this.io = new Server(this.httpServer); this.io = new Server(this.httpServer);
} }
/** Initialise app after the database has been set up */
async initAfterDatabaseReady() { async initAfterDatabaseReady() {
await CacheableDnsHttpAgent.update(); await CacheableDnsHttpAgent.update();
@ -99,11 +98,6 @@ class UptimeKumaServer {
this.generateMaintenanceTimeslotsInterval = setInterval(this.generateMaintenanceTimeslots, 60 * 1000); this.generateMaintenanceTimeslotsInterval = setInterval(this.generateMaintenanceTimeslots, 60 * 1000);
} }
/**
* Send list of monitors to client
* @param {Socket} socket
* @returns {Object} List of monitors
*/
async sendMonitorList(socket) { async sendMonitorList(socket) {
let list = await this.getMonitorJSONList(socket.userID); let list = await this.getMonitorJSONList(socket.userID);
this.io.to(socket.userID).emit("monitorList", list); this.io.to(socket.userID).emit("monitorList", list);
@ -140,11 +134,6 @@ class UptimeKumaServer {
return await this.sendMaintenanceListByUserID(socket.userID); return await this.sendMaintenanceListByUserID(socket.userID);
} }
/**
* Send list of maintenances to user
* @param {number} userID
* @returns {Object}
*/
async sendMaintenanceListByUserID(userID) { async sendMaintenanceListByUserID(userID) {
let list = await this.getMaintenanceJSONList(userID); let list = await this.getMaintenanceJSONList(userID);
this.io.to(userID).emit("maintenanceList", list); this.io.to(userID).emit("maintenanceList", list);
@ -196,11 +185,6 @@ class UptimeKumaServer {
errorLogStream.end(); errorLogStream.end();
} }
/**
* Get the IP of the client connected to the socket
* @param {Socket} socket
* @returns {string}
*/
async getClientIP(socket) { async getClientIP(socket) {
let clientIP = socket.client.conn.remoteAddress; let clientIP = socket.client.conn.remoteAddress;
@ -219,12 +203,6 @@ class UptimeKumaServer {
} }
} }
/**
* Attempt to get the current server timezone
* If this fails, fall back to environment variables and then make a
* guess.
* @returns {string}
*/
async getTimezone() { async getTimezone() {
let timezone = await Settings.get("serverTimezone"); let timezone = await Settings.get("serverTimezone");
if (timezone) { if (timezone) {
@ -236,25 +214,16 @@ class UptimeKumaServer {
} }
} }
/**
* Get the current offset
* @returns {string}
*/
getTimezoneOffset() { getTimezoneOffset() {
return dayjs().format("Z"); return dayjs().format("Z");
} }
/**
* Set the current server timezone and environment variables
* @param {string} timezone
*/
async setTimezone(timezone) { async setTimezone(timezone) {
await Settings.set("serverTimezone", timezone, "general"); await Settings.set("serverTimezone", timezone, "general");
process.env.TZ = timezone; process.env.TZ = timezone;
dayjs.tz.setDefault(timezone); dayjs.tz.setDefault(timezone);
} }
/** Load the timeslots for maintenance */
async generateMaintenanceTimeslots() { async generateMaintenanceTimeslots() {
let list = await R.find("maintenance_timeslot", " generated_next = 0 AND start_date <= DATETIME('now') "); let list = await R.find("maintenance_timeslot", " generated_next = 0 AND start_date <= DATETIME('now') ");
@ -268,7 +237,6 @@ class UptimeKumaServer {
} }
/** Stop the server */
async stop() { async stop() {
clearTimeout(this.generateMaintenanceTimeslotsInterval); clearTimeout(this.generateMaintenanceTimeslotsInterval);
} }

View File

@ -1,5 +1,5 @@
const tcpp = require("tcp-ping"); const tcpp = require("tcp-ping");
const ping = require("@louislam/ping"); const ping = require("ping");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { log, genSecret } = require("../src/util"); const { log, genSecret } = require("../src/util");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
@ -14,13 +14,11 @@ const mssql = require("mssql");
const { Client } = require("pg"); const { Client } = require("pg");
const postgresConParse = require("pg-connection-string").parse; const postgresConParse = require("pg-connection-string").parse;
const mysql = require("mysql2"); const mysql = require("mysql2");
const { MongoClient } = require("mongodb");
const { NtlmClient } = require("axios-ntlm"); const { NtlmClient } = require("axios-ntlm");
const { Settings } = require("./settings"); const { Settings } = require("./settings");
const grpc = require("@grpc/grpc-js"); const grpc = require("@grpc/grpc-js");
const protojs = require("protobufjs"); const protojs = require("protobufjs");
const radiusClient = require("node-radius-client"); const radiusClient = require("node-radius-client");
const redis = require("redis");
const { const {
dictionaries: { dictionaries: {
rfc2865: { file, attributes }, rfc2865: { file, attributes },
@ -105,7 +103,7 @@ exports.pingAsync = function (hostname, ipv6 = false) {
ping.promise.probe(hostname, { ping.promise.probe(hostname, {
v6: ipv6, v6: ipv6,
min_reply: 1, min_reply: 1,
deadline: 10, timeout: 10,
}).then((res) => { }).then((res) => {
// If ping failed, it will set field to unknown // If ping failed, it will set field to unknown
if (res.alive) { if (res.alive) {
@ -137,7 +135,7 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
const { port, username, password, interval = 20 } = options; const { port, username, password, interval = 20 } = options;
// Adds MQTT protocol to the hostname if not already present // Adds MQTT protocol to the hostname if not already present
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) { if (!/^(?:http|mqtt)s?:\/\//.test(hostname)) {
hostname = "mqtt://" + hostname; hostname = "mqtt://" + hostname;
} }
@ -147,11 +145,10 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
reject(new Error("Timeout")); reject(new Error("Timeout"));
}, interval * 1000 * 0.8); }, interval * 1000 * 0.8);
const mqttUrl = `${hostname}:${port}`; log.debug("mqtt", "MQTT connecting");
log.debug("mqtt", `MQTT connecting to ${mqttUrl}`); let client = mqtt.connect(hostname, {
port,
let client = mqtt.connect(mqttUrl, {
username, username,
password password
}); });
@ -283,23 +280,18 @@ exports.postgresQuery = function (connectionString, query) {
const client = new Client({ connectionString }); const client = new Client({ connectionString });
client.connect((err) => { client.connect();
if (err) {
reject(err);
client.end();
} else {
// Connected here
client.query(query, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
client.end();
});
}
});
return client.query(query)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
})
.finally(() => {
client.end();
});
}); });
}; };
@ -325,23 +317,6 @@ exports.mysqlQuery = function (connectionString, query) {
}); });
}; };
/**
* Connect to and Ping a MongoDB database
* @param {string} connectionString The database connection string
* @returns {Promise<(string[]|Object[]|Object)>}
*/
exports.mongodbPing = async function (connectionString) {
let client = await MongoClient.connect(connectionString);
let dbPing = await client.db().command({ ping: 1 });
await client.close();
if (dbPing["ok"] === 1) {
return "UP";
} else {
throw Error("failed");
}
};
/** /**
* Query radius server * Query radius server
* @param {string} hostname Hostname of radius server * @param {string} hostname Hostname of radius server
@ -379,30 +354,6 @@ exports.radius = function (
}); });
}; };
/**
* Redis server ping
* @param {string} dsn The redis connection string
*/
exports.redisPingAsync = function (dsn) {
return new Promise((resolve, reject) => {
const client = redis.createClient({
url: dsn,
});
client.on("error", (err) => {
reject(err);
});
client.connect().then(() => {
client.ping().then((res, err) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
});
};
/** /**
* Retrieve value of setting based on key * Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve * @param {string} key Key of setting to retrieve

View File

@ -3,7 +3,3 @@ html[lang='fa'] {
font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji; font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
} }
} }
ul.multiselect__content {
padding-left: 0 !important;
}

View File

@ -73,7 +73,7 @@ export default {
emits: [ "added" ], emits: [ "added" ],
data() { data() {
return { return {
modal: null, model: null,
processing: false, processing: false,
id: null, id: null,
connectionTypes: [ "socket", "tcp" ], connectionTypes: [ "socket", "tcp" ],
@ -91,16 +91,11 @@ export default {
}, },
methods: { methods: {
/** Confirm deletion of docker host */
deleteConfirm() { deleteConfirm() {
this.modal.hide(); this.modal.hide();
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/**
* Show specified docker host
* @param {number} dockerHostID
*/
show(dockerHostID) { show(dockerHostID) {
if (dockerHostID) { if (dockerHostID) {
let found = false; let found = false;
@ -131,7 +126,6 @@ export default {
this.modal.show(); this.modal.show();
}, },
/** Add docker host */
submit() { submit() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("addDockerHost", this.dockerHost, this.id, (res) => { this.$root.getSocket().emit("addDockerHost", this.dockerHost, this.id, (res) => {
@ -150,7 +144,6 @@ export default {
}); });
}, },
/** Test the docker host */
test() { test() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("testDockerHost", this.dockerHost, (res) => { this.$root.getSocket().emit("testDockerHost", this.dockerHost, (res) => {
@ -159,7 +152,6 @@ export default {
}); });
}, },
/** Delete this docker host */
deleteDockerHost() { deleteDockerHost() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("deleteDockerHost", this.id, (res) => { this.$root.getSocket().emit("deleteDockerHost", this.id, (res) => {

View File

@ -41,7 +41,7 @@ export default {
}, },
computed: { computed: {
displayText() { displayText() {
if (this.item.value === "" || this.item.value === undefined) { if (this.item.value === "") {
return this.item.name; return this.item.name;
} else { } else {
return `${this.item.name}: ${this.item.value}`; return `${this.item.name}: ${this.item.value}`;

View File

@ -1,376 +0,0 @@
<template>
<form @submit.prevent="submit">
<div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="exampleModalLabel" class="modal-title">
{{ $t("Edit Tag") }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
</div>
<div class="modal-body">
<div class="mb-3">
<label for="tag-name" class="form-label">{{ $t("Name") }}</label>
<input id="tag-name" v-model="tag.name" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="tag-color" class="form-label">{{ $t("Color") }}</label>
<div class="d-flex">
<div class="col-8 pe-1">
<vue-multiselect
v-model="selectedColor"
:options="colorOptions"
:multiple="false"
:searchable="true"
:placeholder="$t('color')"
track-by="color"
label="name"
select-label=""
deselect-label=""
>
<template #option="{ option }">
<div
class="mx-2 py-1 px-3 rounded d-inline-flex"
style="height: 24px; color: white;"
:style="{ backgroundColor: option.color + ' !important' }"
>
<span>{{ option.name }}</span>
</div>
</template>
<template #singleLabel="{ option }">
<div
class="py-1 px-3 rounded d-inline-flex"
style="height: 24px; color: white;"
:style="{ backgroundColor: option.color + ' !important' }"
>
<span>{{ option.name }}</span>
</div>
</template>
</vue-multiselect>
</div>
<div class="col-4 ps-1">
<input id="tag-color-hex" v-model="tag.color" type="text" class="form-control">
</div>
</div>
</div>
<div class="mb-3">
<label for="tag-monitors" class="form-label">{{ $tc("Monitor", selectedMonitors.length) }}</label>
<div class="tag-monitors-list">
<router-link v-for="monitor in selectedMonitors" :key="monitor.id" class="d-flex align-items-center justify-content-between text-decoration-none tag-monitors-list-row py-2 px-3" :to="monitorURL(monitor.id)" @click="modal.hide()">
<span>{{ monitor.name }}</span>
<button type="button" class="btn-rm-monitor btn btn-outline-danger ms-2 py-1" @click.stop.prevent="removeMonitor(monitor.id)">
<font-awesome-icon class="" icon="times" />
</button>
</router-link>
</div>
<div v-if="allMonitorList.length > 0" class="pt-3 px-3">
<label class="form-label">{{ $t("Add a monitor") }}:</label>
<select v-model="selectedAddMonitor" class="form-control">
<option v-for="monitor in allMonitorList" :key="monitor.id" :value="monitor">{{ monitor.name }}</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button v-if="tag" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
{{ $t("Delete") }}
</button>
<button type="submit" class="btn btn-primary" :disabled="processing">
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
{{ $t("Save") }}
</button>
</div>
</div>
</div>
</div>
</form>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteTag">
{{ $t("confirmDeleteTagMsg") }}
</Confirm>
</template>
<script>
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
import VueMultiselect from "vue-multiselect";
import { colorOptions } from "../util-frontend";
import { useToast } from "vue-toastification";
import { getMonitorRelativeURL } from "../util.ts";
const toast = useToast();
export default {
components: {
VueMultiselect,
Confirm,
},
props: {
updated: {
type: Function,
default: () => {},
}
},
data() {
return {
modal: null,
processing: false,
selectedColor: {
name: null,
color: null,
},
tag: {
id: null,
name: "",
color: "",
// Do not set default value here, please scroll to show()
},
monitors: [],
removingMonitor: [],
addingMonitor: [],
selectedAddMonitor: null,
};
},
computed: {
colorOptions() {
if (!colorOptions(this).find(option => option.color === this.tag.color)) {
return colorOptions(this).concat(
{
name: "custom",
color: this.tag.color
});
} else {
return colorOptions(this);
}
},
selectedMonitors() {
return this.monitors
.concat(Object.values(this.$root.monitorList).filter(monitor => this.addingMonitor.includes(monitor.id)))
.filter(monitor => !this.removingMonitor.includes(monitor.id));
},
allMonitorList() {
return Object.values(this.$root.monitorList).filter(monitor => !this.selectedMonitors.includes(monitor));
},
},
watch: {
// Set color option to "Custom" when a unknown color is entered
"tag.color"(to, from) {
if (colorOptions(this).find(x => x.color === to) == null) {
this.selectedColor.name = this.$t("Custom");
this.selectedColor.color = to;
}
},
selectedColor(to, from) {
if (to != null) {
this.tag.color = to.color;
}
},
/**
* Selected a monitor and add to the list.
*/
selectedAddMonitor(monitor) {
if (monitor) {
if (this.removingMonitor.includes(monitor.id)) {
this.removingMonitor = this.removingMonitor.filter(id => id !== monitor.id);
} else {
this.addingMonitor.push(monitor.id);
}
this.selectedAddMonitor = null;
}
},
},
mounted() {
this.modal = new Modal(this.$refs.modal);
},
methods: {
/**
* Show confirmation for deleting a tag
*/
deleteConfirm() {
this.$refs.confirmDelete.show();
},
/**
* Load tag information for display in the edit dialog
* @param {Object} tag tag object to edit
* @returns {void}
*/
show(tag) {
if (tag) {
this.selectedColor = this.colorOptions.find(x => x.color === tag.color) ?? {
name: this.$t("Custom"),
color: tag.color
};
this.tag.id = tag.id;
this.tag.name = tag.name;
this.tag.color = tag.color;
this.monitors = this.monitorsByTag(tag.id);
this.removingMonitor = [];
this.addingMonitor = [];
this.selectedAddMonitor = null;
}
this.modal.show();
},
/**
* Submit tag and monitorTag changes to server
* @returns {void}
*/
async submit() {
this.processing = true;
let editResult = true;
for (let addId of this.addingMonitor) {
await this.addMonitorTagAsync(this.tag.id, addId, "").then((res) => {
if (!res.ok) {
toast.error(res.msg);
editResult = false;
}
});
}
for (let removeId of this.removingMonitor) {
this.monitors.find(monitor => monitor.id === removeId)?.tags.forEach(async (monitorTag) => {
await this.deleteMonitorTagAsync(this.tag.id, removeId, monitorTag.value).then((res) => {
if (!res.ok) {
toast.error(res.msg);
editResult = false;
}
});
});
}
this.$root.getSocket().emit("editTag", this.tag, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok && editResult) {
this.updated();
this.modal.hide();
}
});
},
/**
* Delete the editing tag from server
* @returns {void}
*/
deleteTag() {
this.processing = true;
this.$root.getSocket().emit("deleteTag", this.tag.id, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok) {
this.updated();
this.modal.hide();
}
});
},
/**
* Remove a monitor from the monitors list locally
* @param {number} id id of the tag to remove
* @returns {void}
*/
removeMonitor(id) {
if (this.addingMonitor.includes(id)) {
this.addingMonitor = this.addingMonitor.filter(x => x !== id);
} else {
this.removingMonitor.push(id);
}
},
/**
* Get monitors which has a specific tag locally
* @param {number} tagId id of the tag to filter
* @returns {Object[]} list of monitors which has a specific tag
*/
monitorsByTag(tagId) {
return Object.values(this.$root.monitorList).filter((monitor) => {
return monitor.tags.find(monitorTag => monitorTag.tag_id === tagId);
});
},
/**
* Get URL of monitor
* @param {number} id ID of monitor
* @returns {string} Relative URL of monitor
*/
monitorURL(id) {
return getMonitorRelativeURL(id);
},
/**
* Add a tag to a monitor asynchronously
* @param {number} tagId ID of tag to add
* @param {number} monitorId ID of monitor to add tag to
* @param {string} value Value of tag
* @returns {Promise<void>}
*/
addMonitorTagAsync(tagId, monitorId, value) {
return new Promise((resolve) => {
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve);
});
},
/**
* Delete a tag from a monitor asynchronously
* @param {number} tagId ID of tag to remove
* @param {number} monitorId ID of monitor to remove tag from
* @param {string} value Value of tag
* @returns {Promise<void>}
*/
deleteMonitorTagAsync(tagId, monitorId, value) {
return new Promise((resolve) => {
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.dark {
.modal-dialog .form-text, .modal-dialog p {
color: $dark-font-color;
}
}
.btn-rm-monitor {
padding-left: 11px;
padding-right: 11px;
}
.tag-monitors-list {
max-height: 40vh;
overflow-y: scroll;
}
.tag-monitors-list .tag-monitors-list-row {
cursor: pointer;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
.dark & {
border-bottom: 1px solid $dark-border-color;
}
&:hover {
background-color: $highlight-white;
}
.dark &:hover {
background-color: $dark-bg2;
}
}
</style>

View File

@ -130,7 +130,6 @@
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import VueMultiselect from "vue-multiselect"; import VueMultiselect from "vue-multiselect";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import { colorOptions } from "../util-frontend";
import Tag from "../components/Tag.vue"; import Tag from "../components/Tag.vue";
const toast = useToast(); const toast = useToast();
@ -177,7 +176,24 @@ export default {
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id === tag.id)); return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id === tag.id));
}, },
colorOptions() { colorOptions() {
return colorOptions(this); return [
{ name: this.$t("Gray"),
color: "#4B5563" },
{ name: this.$t("Red"),
color: "#DC2626" },
{ name: this.$t("Orange"),
color: "#D97706" },
{ name: this.$t("Green"),
color: "#059669" },
{ name: this.$t("Blue"),
color: "#2563EB" },
{ name: this.$t("Indigo"),
color: "#4F46E5" },
{ name: this.$t("Purple"),
color: "#7C3AED" },
{ name: this.$t("Pink"),
color: "#DB2777" },
];
}, },
validateDraftTag() { validateDraftTag() {
let nameInvalid = false; let nameInvalid = false;

View File

@ -3,8 +3,6 @@
</template> </template>
<script> <script>
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
export default { export default {
props: { props: {
/** Monitor this represents */ /** Monitor this represents */
@ -26,6 +24,7 @@ export default {
computed: { computed: {
uptime() { uptime() {
if (this.type === "maintenance") { if (this.type === "maintenance") {
return this.$t("statusMaintenance"); return this.$t("statusMaintenance");
} }
@ -40,19 +39,19 @@ export default {
}, },
color() { color() {
if (this.lastHeartBeat.status === MAINTENANCE) { if (this.type === "maintenance" || this.monitor.maintenance) {
return "maintenance"; return "maintenance";
} }
if (this.lastHeartBeat.status === DOWN) { if (this.lastHeartBeat.status === 0) {
return "danger"; return "danger";
} }
if (this.lastHeartBeat.status === UP) { if (this.lastHeartBeat.status === 1) {
return "primary"; return "primary";
} }
if (this.lastHeartBeat.status === PENDING) { if (this.lastHeartBeat.status === 2) {
return "warning"; return "warning";
} }

View File

@ -26,10 +26,6 @@
<label for="promosms-sender-name" class="form-label">{{ $t("promosmsSMSSender") }}</label> <label for="promosms-sender-name" class="form-label">{{ $t("promosmsSMSSender") }}</label>
<input id="promosms-sender-name" v-model="$parent.notification.promosmsSenderName" type="text" minlength="3" maxlength="11" class="form-control"> <input id="promosms-sender-name" v-model="$parent.notification.promosmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
</div> </div>
<div class="form-check form-switch">
<input id="promosms-allow-long" v-model="$parent.notification.promosmsAllowLongSMS" type="checkbox" class="form-check-input">
<label for="promosms-allow-long" class="form-label">{{ $t("promosmsAllowLongSMS") }}</label>
</div>
</template> </template>
<script> <script>

View File

@ -1,32 +0,0 @@
<template>
<div class="mb-3">
<label for="splunk-rest-url" class="form-label">{{ $t("Splunk Rest URL") }}</label>
<HiddenInput id="splunk-rest-url" v-model="$parent.notification.splunkRestURL" :required="true" autocomplete="false"></HiddenInput>
</div>
<div class="mb-3">
<label for="splunk-severity" class="form-label">{{ $t("Severity") }}</label>
<select id="splunk-severity" v-model="$parent.notification.splunkSeverity" class="form-select">
<option value="INFO">{{ $t("info") }}</option>
<option value="WARNING">{{ $t("warning") }}</option>
<option value="CRITICAL" selected="selected">{{ $t("critical") }}</option>
</select>
</div>
<div class="mb-3">
<label for="splunk-resolve" class="form-label">{{ $t("Auto resolve or acknowledged") }}</label>
<select id="splunk-resolve" v-model="$parent.notification.splunkAutoResolve" class="form-select">
<option value="0" selected="selected">{{ $t("do nothing") }}</option>
<option value="ACKNOWLEDGEMENT">{{ $t("auto acknowledged") }}</option>
<option value="RECOVERY">{{ $t("auto resolve") }}</option>
</select>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@ -42,11 +42,6 @@ export default {
HiddenInput, HiddenInput,
}, },
methods: { methods: {
/**
* Get the URL for telegram updates
* @param {string} [mode=masked] Should the token be masked?
* @returns {string} formatted URL
*/
telegramGetUpdatesURL(mode = "masked") { telegramGetUpdatesURL(mode = "masked") {
let token = `<${this.$t("YOUR BOT TOKEN HERE")}>`; let token = `<${this.$t("YOUR BOT TOKEN HERE")}>`;
@ -60,8 +55,6 @@ export default {
return `https://api.telegram.org/bot${token}/getUpdates`; return `https://api.telegram.org/bot${token}/getUpdates`;
}, },
/** Get the telegram chat ID */
async autoGetTelegramChatID() { async autoGetTelegramChatID() {
try { try {
let res = await axios.get(this.telegramGetUpdatesURL("withToken")); let res = await axios.get(this.telegramGetUpdatesURL("withToken"));

View File

@ -44,7 +44,6 @@ import Webhook from "./Webhook.vue";
import WeCom from "./WeCom.vue"; import WeCom from "./WeCom.vue";
import GoAlert from "./GoAlert.vue"; import GoAlert from "./GoAlert.vue";
import ZohoCliq from "./ZohoCliq.vue"; import ZohoCliq from "./ZohoCliq.vue";
import Splunk from "./Splunk.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -93,7 +92,6 @@ const NotificationFormList = {
"stackfield": Stackfield, "stackfield": Stackfield,
"teams": Teams, "teams": Teams,
"telegram": Telegram, "telegram": Telegram,
"Splunk": Splunk,
"webhook": Webhook, "webhook": Webhook,
"WeCom": WeCom, "WeCom": WeCom,
"GoAlert": GoAlert, "GoAlert": GoAlert,

View File

@ -7,7 +7,6 @@
settings.keepDataPeriodDays, settings.keepDataPeriodDays,
]) ])
}} }}
{{ $t("infiniteRetention") }}
</label> </label>
<input <input
id="keepDataPeriodDays" id="keepDataPeriodDays"
@ -15,12 +14,9 @@
type="number" type="number"
class="form-control" class="form-control"
required required
min="0" min="1"
step="1" step="1"
/> />
<div v-if="settings.keepDataPeriodDays < 0" class="form-text">
{{ $t("dataRetentionTimeError") }}
</div>
</div> </div>
<div class="my-4"> <div class="my-4">
<button class="btn btn-primary" type="button" @click="saveSettings()"> <button class="btn btn-primary" type="button" @click="saveSettings()">

View File

@ -191,7 +191,6 @@ export default {
location.reload(); location.reload();
}, },
/** Show confirmation dialog for disable auth */
confirmDisableAuth() { confirmDisableAuth() {
this.$refs.confirmDisableAuth.show(); this.$refs.confirmDisableAuth.show();
}, },

View File

@ -1,171 +0,0 @@
<template>
<div>
<div class="tags-list my-3">
<div v-for="(tag, index) in tagsList" :key="tag.id" class="d-flex align-items-center mx-4 py-1 tags-list-row" :disabled="processing" @click="editTag(index)">
<div class="col-5 ps-1">
<Tag :item="tag" />
</div>
<div class="col-5 px-1">
<div>{{ monitorsByTag(tag.id).length }} {{ $tc("Monitor", monitorsByTag(tag.id).length) }}</div>
</div>
<div class="col-2 pe-3 d-flex justify-content-end">
<button type="button" class="btn ms-2 py-1">
<font-awesome-icon class="" icon="edit" />
</button>
<button type="button" class="btn-rm-tag btn btn-outline-danger ms-2 py-1" :disabled="processing" @click.stop="deleteConfirm(index)">
<font-awesome-icon class="" icon="trash" />
</button>
</div>
</div>
</div>
<TagEditDialog ref="tagEditDialog" :updated="tagsUpdated" />
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteTag">
{{ $t("confirmDeleteTagMsg") }}
</Confirm>
</div>
</template>
<script>
import { useToast } from "vue-toastification";
import TagEditDialog from "../../components/TagEditDialog.vue";
import Tag from "../Tag.vue";
import Confirm from "../Confirm.vue";
const toast = useToast();
export default {
components: {
Confirm,
TagEditDialog,
Tag,
},
data() {
return {
processing: false,
tagsList: null,
deletingTag: null,
};
},
computed: {
settings() {
return this.$parent.$parent.$parent.settings;
},
saveSettings() {
return this.$parent.$parent.$parent.saveSettings;
},
settingsLoaded() {
return this.$parent.$parent.$parent.settingsLoaded;
},
},
mounted() {
this.getExistingTags();
},
methods: {
/**
* Reflect tag changes in the UI by fetching data. Callback for the edit tag dialog.
* @returns {void}
*/
tagsUpdated() {
this.getExistingTags();
this.$root.getMonitorList();
},
/**
* Get list of tags from server
* @returns {void}
*/
getExistingTags() {
this.processing = true;
this.$root.getSocket().emit("getTags", (res) => {
this.processing = false;
if (res.ok) {
this.tagsList = res.tags;
} else {
toast.error(res.msg);
}
});
},
/**
* Show confirmation for deleting a tag
* @param {number} index index of the tag to delete in the local tagsList
* @returns {void}
*/
deleteConfirm(index) {
this.deletingTag = this.tagsList[index];
this.$refs.confirmDelete.show();
},
/**
* Show dialog for editing a tag
* @param {number} index index of the tag to edit in the local tagsList
* @returns {void}
*/
editTag(index) {
this.$refs.tagEditDialog.show(this.tagsList[index]);
},
/**
* Delete the tag "deletingTag" from server
* @returns {void}
*/
deleteTag() {
this.processing = true;
this.$root.getSocket().emit("deleteTag", this.deletingTag.id, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok) {
this.tagsUpdated();
}
});
},
/**
* Get monitors which has a specific tag locally
* @param {number} tagId id of the tag to filter
* @returns {Object[]} list of monitors which has a specific tag
*/
monitorsByTag(tagId) {
return Object.values(this.$root.monitorList).filter((monitor) => {
return monitor.tags.find(monitorTag => monitorTag.tag_id === tagId);
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../../assets/vars.scss";
.btn-rm-tag {
padding-left: 11px;
padding-right: 11px;
}
.tags-list .tags-list-row {
cursor: pointer;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
.dark & {
border-bottom: 1px solid $dark-border-color;
}
&:hover {
background-color: $highlight-white;
}
.dark &:hover {
background-color: $dark-bg2;
}
}
.tags-list .tags-list-row:last-child {
border: none;
}
</style>

View File

@ -44,7 +44,6 @@ import {
faWrench, faWrench,
faHeartbeat, faHeartbeat,
faFilter, faFilter,
faInfoCircle,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
library.add( library.add(
@ -89,7 +88,6 @@ library.add(
faWrench, faWrench,
faHeartbeat, faHeartbeat,
faFilter, faFilter,
faInfoCircle,
); );
export { FontAwesomeIcon }; export { FontAwesomeIcon };

View File

@ -591,7 +591,7 @@ export default {
"You can divide numbers with": "Čísla můžete dělit pomocí", "You can divide numbers with": "Čísla můžete dělit pomocí",
"or": "nebo", "or": "nebo",
recurringInterval: "Interval", recurringInterval: "Interval",
"Recurring": "Opakující se", "Recurring": "Recurring",
strategyManual: "Aktivní/Neaktivní Ručně", strategyManual: "Aktivní/Neaktivní Ručně",
warningTimezone: "Používá se časové pásmo serveru", warningTimezone: "Používá se časové pásmo serveru",
weekdayShortMon: "Po", weekdayShortMon: "Po",

View File

@ -74,7 +74,6 @@ export default {
Current: "Current", Current: "Current",
Uptime: "Uptime", Uptime: "Uptime",
"Cert Exp.": "Cert Exp.", "Cert Exp.": "Cert Exp.",
Monitor: "Monitor | Monitors",
day: "day | days", day: "day | days",
"-day": "-day", "-day": "-day",
hour: "hour", hour: "hour",
@ -191,7 +190,6 @@ export default {
Indigo: "Indigo", Indigo: "Indigo",
Purple: "Purple", Purple: "Purple",
Pink: "Pink", Pink: "Pink",
Custom: "Custom",
"Search...": "Search...", "Search...": "Search...",
"Avg. Ping": "Avg. Ping", "Avg. Ping": "Avg. Ping",
"Avg. Response": "Avg. Response", "Avg. Response": "Avg. Response",
@ -322,7 +320,6 @@ export default {
promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).", promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).",
promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)", promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)",
promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS", promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
promosmsAllowLongSMS: "Allow long SMS",
"Feishu WebHookUrl": "Feishu WebHookURL", "Feishu WebHookUrl": "Feishu WebHookURL",
matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)", matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)",
"Internal Room Id": "Internal Room ID", "Internal Room Id": "Internal Room ID",
@ -678,7 +675,4 @@ export default {
"General Monitor Type": "General Monitor Type", "General Monitor Type": "General Monitor Type",
"Passive Monitor Type": "Passive Monitor Type", "Passive Monitor Type": "Passive Monitor Type",
"Specific Monitor Type": "Specific Monitor Type", "Specific Monitor Type": "Specific Monitor Type",
dataRetentionTimeError: "Retention period must be 0 or greater",
infiniteRetention: "Set to 0 for infinite retention.",
confirmDeleteTagMsg: "Are you sure you want to delete this tag? Monitors associated with this tag will not be deleted.",
}; };

View File

@ -675,6 +675,4 @@ export default {
"General Monitor Type": "Type de sonde générale", "General Monitor Type": "Type de sonde générale",
"Passive Monitor Type": "Type de sonde passive", "Passive Monitor Type": "Type de sonde passive",
"Specific Monitor Type": "Type de sonde spécifique", "Specific Monitor Type": "Type de sonde spécifique",
dataRetentionTimeError: "La durée de conservation doit être supérieure ou égale à 0",
infiniteRetention: "Définissez la valeur à 0 pour une durée de conservation infinie.",
}; };

View File

@ -284,7 +284,6 @@ export default {
promosmsTypeSpeed: "SMS SPEED - wysyłka priorytetowa, ma wszystkie zalety SMS FULL", promosmsTypeSpeed: "SMS SPEED - wysyłka priorytetowa, ma wszystkie zalety SMS FULL",
promosmsPhoneNumber: "Numer odbiorcy", promosmsPhoneNumber: "Numer odbiorcy",
promosmsSMSSender: "Nadawca SMS (wcześniej zatwierdzone nazwy z panelu PromoSMS)", promosmsSMSSender: "Nadawca SMS (wcześniej zatwierdzone nazwy z panelu PromoSMS)",
promosmsAllowLongSMS: "Zezwól na długie SMSy",
"Primary Base URL": "Główny URL", "Primary Base URL": "Główny URL",
"Push URL": "Push URL", "Push URL": "Push URL",
needPushEvery: "Powinieneś wywoływać ten URL co {0} sekund", needPushEvery: "Powinieneś wywoływać ten URL co {0} sekund",

View File

@ -63,12 +63,6 @@
</router-link> </router-link>
</li> </li>
<li>
<a href="https://github.com/louislam/uptime-kuma/wiki" class="dropdown-item" target="_blank">
<font-awesome-icon icon="info-circle" /> {{ $t("Help") }}
</a>
</li>
<li v-if="$root.loggedIn && $root.socket.token !== 'autoLogin'"> <li v-if="$root.loggedIn && $root.socket.token !== 'autoLogin'">
<button class="dropdown-item" @click="$root.logout"> <button class="dropdown-item" @click="$root.logout">
<font-awesome-icon icon="sign-out-alt" /> <font-awesome-icon icon="sign-out-alt" />

View File

@ -12,11 +12,6 @@ export default {
}, },
methods: { methods: {
/**
* Convert value to UTC
* @param {string | number | Date | dayjs.Dayjs} value
* @returns {dayjs.Dayjs}
*/
toUTC(value) { toUTC(value) {
return dayjs.tz(value, this.timezone).utc().format(); return dayjs.tz(value, this.timezone).utc().format();
}, },
@ -39,11 +34,6 @@ export default {
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss"); return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss");
}, },
/**
* Get time for maintenance
* @param {string | number | Date | dayjs.Dayjs} value
* @returns {string}
*/
datetimeMaintenance(value) { datetimeMaintenance(value) {
const inputDate = new Date(value); const inputDate = new Date(value);
const now = new Date(Date.now()); const now = new Date(Date.now());

View File

@ -3,7 +3,6 @@ import { useToast } from "vue-toastification";
import jwtDecode from "jwt-decode"; import jwtDecode from "jwt-decode";
import Favico from "favico.js"; import Favico from "favico.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
const toast = useToast(); const toast = useToast();
let socket; let socket;
@ -455,10 +454,6 @@ export default {
socket.emit("getMonitorList", callback); socket.emit("getMonitorList", callback);
}, },
/**
* Get list of maintenances
* @param {socketCB} callback
*/
getMaintenanceList(callback) { getMaintenanceList(callback) {
if (! callback) { if (! callback) {
callback = () => { }; callback = () => { };
@ -475,49 +470,22 @@ export default {
socket.emit("add", monitor, callback); socket.emit("add", monitor, callback);
}, },
/**
* Adds a maintenace
* @param {Object} maintenance
* @param {socketCB} callback
*/
addMaintenance(maintenance, callback) { addMaintenance(maintenance, callback) {
socket.emit("addMaintenance", maintenance, callback); socket.emit("addMaintenance", maintenance, callback);
}, },
/**
* Add monitors to maintenance
* @param {number} maintenanceID
* @param {number[]} monitors
* @param {socketCB} callback
*/
addMonitorMaintenance(maintenanceID, monitors, callback) { addMonitorMaintenance(maintenanceID, monitors, callback) {
socket.emit("addMonitorMaintenance", maintenanceID, monitors, callback); socket.emit("addMonitorMaintenance", maintenanceID, monitors, callback);
}, },
/**
* Add status page to maintenance
* @param {number} maintenanceID
* @param {number} statusPages
* @param {socketCB} callback
*/
addMaintenanceStatusPage(maintenanceID, statusPages, callback) { addMaintenanceStatusPage(maintenanceID, statusPages, callback) {
socket.emit("addMaintenanceStatusPage", maintenanceID, statusPages, callback); socket.emit("addMaintenanceStatusPage", maintenanceID, statusPages, callback);
}, },
/**
* Get monitors affected by maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
*/
getMonitorMaintenance(maintenanceID, callback) { getMonitorMaintenance(maintenanceID, callback) {
socket.emit("getMonitorMaintenance", maintenanceID, callback); socket.emit("getMonitorMaintenance", maintenanceID, callback);
}, },
/**
* Get status pages where maintenance is shown
* @param {number} maintenanceID
* @param {socketCB} callback
*/
getMaintenanceStatusPage(maintenanceID, callback) { getMaintenanceStatusPage(maintenanceID, callback) {
socket.emit("getMaintenanceStatusPage", maintenanceID, callback); socket.emit("getMaintenanceStatusPage", maintenanceID, callback);
}, },
@ -531,11 +499,6 @@ export default {
socket.emit("deleteMonitor", monitorID, callback); socket.emit("deleteMonitor", monitorID, callback);
}, },
/**
* Delete specified maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
*/
deleteMaintenance(maintenanceID, callback) { deleteMaintenance(maintenanceID, callback) {
socket.emit("deleteMaintenance", maintenanceID, callback); socket.emit("deleteMaintenance", maintenanceID, callback);
}, },
@ -627,28 +590,28 @@ export default {
for (let monitorID in this.lastHeartbeatList) { for (let monitorID in this.lastHeartbeatList) {
let lastHeartBeat = this.lastHeartbeatList[monitorID]; let lastHeartBeat = this.lastHeartbeatList[monitorID];
if (! lastHeartBeat) { if (this.monitorList[monitorID] && this.monitorList[monitorID].maintenance) {
result[monitorID] = {
text: this.$t("statusMaintenance"),
color: "maintenance",
};
} else if (! lastHeartBeat) {
result[monitorID] = unknown; result[monitorID] = unknown;
} else if (lastHeartBeat.status === UP) { } else if (lastHeartBeat.status === 1) {
result[monitorID] = { result[monitorID] = {
text: this.$t("Up"), text: this.$t("Up"),
color: "primary", color: "primary",
}; };
} else if (lastHeartBeat.status === DOWN) { } else if (lastHeartBeat.status === 0) {
result[monitorID] = { result[monitorID] = {
text: this.$t("Down"), text: this.$t("Down"),
color: "danger", color: "danger",
}; };
} else if (lastHeartBeat.status === PENDING) { } else if (lastHeartBeat.status === 2) {
result[monitorID] = { result[monitorID] = {
text: this.$t("Pending"), text: this.$t("Pending"),
color: "warning", color: "warning",
}; };
} else if (lastHeartBeat.status === MAINTENANCE) {
result[monitorID] = {
text: this.$t("statusMaintenance"),
color: "maintenance",
};
} else { } else {
result[monitorID] = unknown; result[monitorID] = unknown;
} }
@ -670,17 +633,17 @@ export default {
let beat = this.$root.lastHeartbeatList[monitorID]; let beat = this.$root.lastHeartbeatList[monitorID];
let monitor = this.$root.monitorList[monitorID]; let monitor = this.$root.monitorList[monitorID];
if (monitor && ! monitor.active) { if (monitor && monitor.maintenance) {
result.maintenance++;
} else if (monitor && ! monitor.active) {
result.pause++; result.pause++;
} else if (beat) { } else if (beat) {
if (beat.status === UP) { if (beat.status === 1) {
result.up++; result.up++;
} else if (beat.status === DOWN) { } else if (beat.status === 0) {
result.down++; result.down++;
} else if (beat.status === PENDING) { } else if (beat.status === 2) {
result.up++; result.up++;
} else if (beat.status === MAINTENANCE) {
result.maintenance++;
} else { } else {
result.unknown++; result.unknown++;
} }

View File

@ -356,7 +356,6 @@ export default {
}); });
}, },
methods: { methods: {
/** Initialise page */
init() { init() {
this.affectedMonitors = []; this.affectedMonitors = [];
this.selectedStatusPages = []; this.selectedStatusPages = [];
@ -415,7 +414,6 @@ export default {
} }
}, },
/** Create new maintenance */
async submit() { async submit() {
this.processing = true; this.processing = true;
@ -460,11 +458,6 @@ export default {
} }
}, },
/**
* Add monitor to maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
*/
async addMonitorMaintenance(maintenanceID, callback) { async addMonitorMaintenance(maintenanceID, callback) {
await this.$root.addMonitorMaintenance(maintenanceID, this.affectedMonitors, async (res) => { await this.$root.addMonitorMaintenance(maintenanceID, this.affectedMonitors, async (res) => {
if (!res.ok) { if (!res.ok) {
@ -477,11 +470,6 @@ export default {
}); });
}, },
/**
* Add status page to maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
*/
async addMaintenanceStatusPage(maintenanceID, callback) { async addMaintenanceStatusPage(maintenanceID, callback) {
await this.$root.addMaintenanceStatusPage(maintenanceID, (this.showOnAllPages) ? this.selectedStatusPagesOptions : this.selectedStatusPages, async (res) => { await this.$root.addMaintenanceStatusPage(maintenanceID, (this.showOnAllPages) ? this.selectedStatusPagesOptions : this.selectedStatusPages, async (res) => {
if (!res.ok) { if (!res.ok) {

View File

@ -57,15 +57,9 @@
<option value="mysql"> <option value="mysql">
MySQL/MariaDB MySQL/MariaDB
</option> </option>
<option value="mongodb">
MongoDB
</option>
<option value="radius"> <option value="radius">
Radius Radius
</option> </option>
<option value="redis">
Redis
</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>
@ -111,7 +105,7 @@
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius only --> <!-- TCP Port / Ping / DNS / Steam / MQTT / Radius only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3"> <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label> <label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`" required> <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
</div> </div>
<!-- Port --> <!-- Port -->
@ -273,24 +267,6 @@
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea> <textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
</div> </div>
</template> </template>
<!-- Redis -->
<template v-if="monitor.type === 'redis'">
<div class="my-3">
<label for="redisConnectionString" class="form-label">{{ $t("Connection String") }}</label>
<input id="redisConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="redis://user:password@host:port">
</div>
</template>
<!-- MongoDB -->
<template v-if="monitor.type === 'mongodb'">
<div class="my-3">
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
<template v-if="monitor.type === 'mongodb'">
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="mongodb://username:password@host:port/database">
</template>
</div>
</template>
<!-- Interval --> <!-- Interval -->
<div class="my-3"> <div class="my-3">
@ -600,7 +576,6 @@ import DockerHostDialog from "../components/DockerHostDialog.vue";
import ProxyDialog from "../components/ProxyDialog.vue"; import ProxyDialog from "../components/ProxyDialog.vue";
import TagsManager from "../components/TagsManager.vue"; import TagsManager from "../components/TagsManager.vue";
import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts"; import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts";
import { hostNameRegexPattern } from "../util-frontend";
const toast = useToast(); const toast = useToast();
@ -625,8 +600,11 @@ export default {
}, },
acceptedStatusCodeOptions: [], acceptedStatusCodeOptions: [],
dnsresolvetypeOptions: [], dnsresolvetypeOptions: [],
ipOrHostnameRegexPattern: hostNameRegexPattern(),
mqttIpOrHostnameRegexPattern: hostNameRegexPattern(true) // Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
ipRegexPattern: "((^\\s*((([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*$))",
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
hostnameRegexPattern: "^(([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_])$"
}; };
}, },

View File

@ -65,7 +65,6 @@ export default {
this.init(); this.init();
}, },
methods: { methods: {
/** Initialise page */
init() { init() {
this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => { this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => {
if (res.ok) { if (res.ok) {
@ -84,12 +83,10 @@ export default {
}); });
}, },
/** Confirm deletion */
deleteDialog() { deleteDialog() {
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/** Delete maintenance after showing confirmation */
deleteMaintenance() { deleteMaintenance() {
this.$root.deleteMaintenance(this.maintenance.id, (res) => { this.$root.deleteMaintenance(this.maintenance.id, (res) => {
if (res.ok) { if (res.ok) {

View File

@ -133,25 +133,15 @@ export default {
} }
}, },
/**
* Get maintenance URL
* @param {number} id
* @returns {string} Relative URL
*/
maintenanceURL(id) { maintenanceURL(id) {
return getMaintenanceRelativeURL(id); return getMaintenanceRelativeURL(id);
}, },
/**
* Show delete confirmation
* @param {number} maintenanceID
*/
deleteDialog(maintenanceID) { deleteDialog(maintenanceID) {
this.selectedMaintenanceID = maintenanceID; this.selectedMaintenanceID = maintenanceID;
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/** Delete maintenance after showing confirmation dialog */
deleteMaintenance() { deleteMaintenance() {
this.$root.deleteMaintenance(this.selectedMaintenanceID, (res) => { this.$root.deleteMaintenance(this.selectedMaintenanceID, (res) => {
if (res.ok) { if (res.ok) {

View File

@ -95,9 +95,6 @@ export default {
"reverse-proxy": { "reverse-proxy": {
title: this.$t("Reverse Proxy"), title: this.$t("Reverse Proxy"),
}, },
tags: {
title: this.$t("Tags"),
},
"monitor-history": { "monitor-history": {
title: this.$t("Monitor History"), title: this.$t("Monitor History"),
}, },
@ -192,36 +189,14 @@ export default {
* @param {string} [currentPassword] Only need for disableAuth to true * @param {string} [currentPassword] Only need for disableAuth to true
*/ */
saveSettings(callback, currentPassword) { saveSettings(callback, currentPassword) {
let valid = this.validateSettings(); this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {
if (valid.success) { this.$root.toastRes(res);
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => { this.loadSettings();
this.$root.toastRes(res);
this.loadSettings();
if (callback) { if (callback) {
callback(); callback();
} }
}); });
} else {
this.$root.toastError(valid.msg);
}
},
/**
* Ensure settings are valid
* @returns {Object} Contains success state and error msg
*/
validateSettings() {
if (this.settings.keepDataPeriodDays < 0) {
return {
success: false,
msg: this.$t("dataRetentionTimeError"),
};
}
return {
success: true,
msg: "",
};
}, },
} }
}; };

View File

@ -24,7 +24,6 @@ import Appearance from "./components/settings/Appearance.vue";
import General from "./components/settings/General.vue"; import General from "./components/settings/General.vue";
const Notifications = () => import("./components/settings/Notifications.vue"); const Notifications = () => import("./components/settings/Notifications.vue");
import ReverseProxy from "./components/settings/ReverseProxy.vue"; import ReverseProxy from "./components/settings/ReverseProxy.vue";
import Tags from "./components/settings/Tags.vue";
import MonitorHistory from "./components/settings/MonitorHistory.vue"; import MonitorHistory from "./components/settings/MonitorHistory.vue";
const Security = () => import("./components/settings/Security.vue"); const Security = () => import("./components/settings/Security.vue");
import Proxies from "./components/settings/Proxies.vue"; import Proxies from "./components/settings/Proxies.vue";
@ -96,10 +95,6 @@ const routes = [
path: "reverse-proxy", path: "reverse-proxy",
component: ReverseProxy, component: ReverseProxy,
}, },
{
path: "tags",
component: Tags,
},
{ {
path: "monitor-history", path: "monitor-history",
component: MonitorHistory, component: MonitorHistory,

View File

@ -78,45 +78,3 @@ export function getResBaseURL() {
return ""; return "";
} }
} }
/**
*
* @param {} mqtt wheather or not the regex should take into account the fact that it is an mqtt uri
* @returns RegExp The requested regex
*/
export function hostNameRegexPattern(mqtt = false) {
// mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect)
const mqttSchemeRegexPattern = "((mqtt|ws)s?:\\/\\/)?";
// 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*$))`;
// 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_])$`;
return `${ipRegexPattern}|${hostNameRegexPattern}`;
}
/**
* Get the tag color options
* Shared between components
* @returns {Object[]}
*/
export function colorOptions(self) {
return [
{ name: self.$t("Gray"),
color: "#4B5563" },
{ name: self.$t("Red"),
color: "#DC2626" },
{ name: self.$t("Orange"),
color: "#D97706" },
{ name: self.$t("Green"),
color: "#059669" },
{ name: self.$t("Blue"),
color: "#2563EB" },
{ name: self.$t("Indigo"),
color: "#4F46E5" },
{ name: self.$t("Purple"),
color: "#7C3AED" },
{ name: self.$t("Pink"),
color: "#DB2777" },
];
}

View File

@ -315,11 +315,6 @@ function getMonitorRelativeURL(id) {
return "/dashboard/" + id; return "/dashboard/" + id;
} }
exports.getMonitorRelativeURL = getMonitorRelativeURL; exports.getMonitorRelativeURL = getMonitorRelativeURL;
/**
* Get relative path for maintenance
* @param id ID of maintenance
* @returns Formatted relative path
*/
function getMaintenanceRelativeURL(id) { function getMaintenanceRelativeURL(id) {
return "/maintenance/" + id; return "/maintenance/" + id;
} }
@ -366,11 +361,6 @@ function parseTimeFromTimeObject(obj) {
return result; return result;
} }
exports.parseTimeFromTimeObject = parseTimeFromTimeObject; exports.parseTimeFromTimeObject = parseTimeFromTimeObject;
/**
* Convert ISO date to UTC
* @param input Date
* @returns ISO Date time
*/
function isoToUTCDateTime(input) { function isoToUTCDateTime(input) {
return dayjs(input).utc().format(exports.SQL_DATETIME_FORMAT); return dayjs(input).utc().format(exports.SQL_DATETIME_FORMAT);
} }
@ -389,12 +379,6 @@ function utcToLocal(input, format = exports.SQL_DATETIME_FORMAT) {
return dayjs.utc(input).local().format(format); return dayjs.utc(input).local().format(format);
} }
exports.utcToLocal = utcToLocal; exports.utcToLocal = utcToLocal;
/**
* Convert local datetime to UTC
* @param input Local date
* @param format Format to return
* @returns Date in requested format
*/
function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) { function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) {
return dayjs(input).utc().format(format); return dayjs(input).utc().format(format);
} }

View File

@ -352,11 +352,6 @@ export function getMonitorRelativeURL(id: string) {
return "/dashboard/" + id; return "/dashboard/" + id;
} }
/**
* Get relative path for maintenance
* @param id ID of maintenance
* @returns Formatted relative path
*/
export function getMaintenanceRelativeURL(id: string) { export function getMaintenanceRelativeURL(id: string) {
return "/maintenance/" + id; return "/maintenance/" + id;
} }
@ -410,11 +405,7 @@ export function parseTimeFromTimeObject(obj : any) {
return result; return result;
} }
/**
* Convert ISO date to UTC
* @param input Date
* @returns ISO Date time
*/
export function isoToUTCDateTime(input : string) { export function isoToUTCDateTime(input : string) {
return dayjs(input).utc().format(SQL_DATETIME_FORMAT); return dayjs(input).utc().format(SQL_DATETIME_FORMAT);
} }
@ -433,12 +424,6 @@ export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) {
return dayjs.utc(input).local().format(format); return dayjs.utc(input).local().format(format);
} }
/**
* Convert local datetime to UTC
* @param input Local date
* @param format Format to return
* @returns Date in requested format
*/
export function localToUTC(input : string, format = SQL_DATETIME_FORMAT) { export function localToUTC(input : string, format = SQL_DATETIME_FORMAT) {
return dayjs(input).utc().format(format); return dayjs(input).utc().format(format);
} }

View File

@ -1,44 +0,0 @@
import { currentLocale } from "../../../src/i18n";
describe("Test i18n.js", () => {
it("currentLocale()", () => {
const setLanguage = (language) => {
Object.defineProperty(window.navigator, 'language', {
value: language,
writable: true
});
}
setLanguage('en-EN');
expect(currentLocale()).equal("en");
setLanguage('zh-HK');
expect(currentLocale()).equal("zh-HK");
// Note that in Safari on iOS prior to 10.2, the country code returned is lowercase: "en-us", "fr-fr" etc.
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language
setLanguage('zh-hk');
expect(currentLocale()).equal("en");
setLanguage('en-US');
expect(currentLocale()).equal("en");
setLanguage('ja-ZZ');
expect(currentLocale()).equal("ja");
setLanguage('zz-ZZ');
expect(currentLocale()).equal("en");
setLanguage('zz-ZZ');
expect(currentLocale()).equal("en");
setLanguage('en');
localStorage.locale = "en";
expect(currentLocale()).equal("en");
localStorage.locale = "zh-HK";
expect(currentLocale()).equal("zh-HK");
});
});

View File

@ -1,33 +0,0 @@
import { hostNameRegexPattern } from "../../../src/util-frontend";
describe("Test util-frontend.js", () => {
describe("hostNameRegexPattern()", () => {
it('should return a valid regex for non mqtt hostnames', () => {
const regex = new RegExp(hostNameRegexPattern(false));
expect(regex.test("www.test.com")).to.be.true;
expect(regex.test("127.0.0.1")).to.be.true;
expect(regex.test("192.168.1.156")).to.be.true;
["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
expect(regex.test(`${schema}://www.test.com`)).to.be.false;
expect(regex.test(`${schema}://127.0.0.1`)).to.be.false;
});
});
it('should return a valid regex for mqtt hostnames', () => {
const hostnameString = hostNameRegexPattern(false);
console.log('*********', hostnameString, '***********');
const regex = new RegExp(hostNameRegexPattern(true));
expect(regex.test("www.test.com")).to.be.true;
expect(regex.test("127.0.0.1")).to.be.true;
expect(regex.test("192.168.1.156")).to.be.true;
["mqtt", "mqtts", "ws", "wss"].forEach(schema => {
expect(regex.test(`${schema}://www.test.com`)).to.be.true;
expect(regex.test(`${schema}://127.0.0.1`)).to.be.true;
});
});
});
});