diff --git a/server/auth.js b/server/auth.js index b4d13d68..54182bde 100644 --- a/server/auth.js +++ b/server/auth.js @@ -1,6 +1,6 @@ -const basicAuth = require('express-basic-auth') -const passwordHash = require('./password-hash'); -const {R} = require("redbean-node"); +const basicAuth = require("express-basic-auth") +const passwordHash = require("./password-hash"); +const { R } = require("redbean-node"); /** * @@ -10,7 +10,7 @@ const {R} = require("redbean-node"); */ exports.login = async function (username, password) { let user = await R.findOne("user", " username = ? AND active = 1 ", [ - username + username, ]) if (user && passwordHash.verify(password, user.password)) { @@ -18,13 +18,13 @@ exports.login = async function (username, password) { if (passwordHash.needRehash(user.password)) { await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ passwordHash.generate(password), - user.id + user.id, ]); } return user; - } else { - return null; } + + return null; } function myAuthorizer(username, password, callback) { @@ -36,5 +36,5 @@ function myAuthorizer(username, password, callback) { exports.basicAuth = basicAuth({ authorizer: myAuthorizer, authorizeAsync: true, - challenge: true + challenge: true, }); diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index 01fb71ff..0e492ada 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -1,10 +1,9 @@ const dayjs = require("dayjs"); -const utc = require('dayjs/plugin/utc') -var timezone = require('dayjs/plugin/timezone') +const utc = require("dayjs/plugin/utc") +let timezone = require("dayjs/plugin/timezone") dayjs.extend(utc) dayjs.extend(timezone) -const {BeanModel} = require("redbean-node/dist/bean-model"); - +const { BeanModel } = require("redbean-node/dist/bean-model"); /** * status: diff --git a/server/notification.js b/server/notification.js index 9da8a0dc..f4cd08af 100644 --- a/server/notification.js +++ b/server/notification.js @@ -1,6 +1,6 @@ const axios = require("axios"); -const {R} = require("redbean-node"); -const FormData = require('form-data'); +const { R } = require("redbean-node"); +const FormData = require("form-data"); const nodemailer = require("nodemailer"); const child_process = require("child_process"); @@ -24,7 +24,7 @@ class Notification { params: { chat_id: notification.telegramChatID, text: msg, - } + }, }) return okMsg; @@ -41,7 +41,7 @@ class Notification { await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { "message": msg, "priority": notification.gotifyPriority || 8, - "title": "Uptime-Kuma" + "title": "Uptime-Kuma", }) return okMsg; @@ -62,10 +62,10 @@ class Notification { if (notification.webhookContentType === "form-data") { finalData = new FormData(); - finalData.append('data', JSON.stringify(data)); + finalData.append("data", JSON.stringify(data)); config = { - headers: finalData.getHeaders() + headers: finalData.getHeaders(), } } else { @@ -84,63 +84,68 @@ class Notification { } else if (notification.type === "discord") { try { - // If heartbeatJSON is null, assume we're testing. - if(heartbeatJSON == null) { + // If heartbeatJSON is null, assume we're testing. + if (heartbeatJSON == null) { + let data = { + username: "Uptime-Kuma", + content: msg, + } + await axios.post(notification.discordWebhookUrl, data) + return okMsg; + } + // If heartbeatJSON is not null, we go into the normal alerting loop. + if (heartbeatJSON["status"] == 0) { + var alertColor = "16711680"; + } else if (heartbeatJSON["status"] == 1) { + var alertColor = "65280"; + } let data = { - username: 'Uptime-Kuma', - content: msg + username: "Uptime-Kuma", + embeds: [{ + title: "Uptime-Kuma Alert", + color: alertColor, + fields: [ + { + name: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + name: "Message", + value: msg, + }, + ], + }], } await axios.post(notification.discordWebhookUrl, data) return okMsg; - } - // If heartbeatJSON is not null, we go into the normal alerting loop. - if(heartbeatJSON['status'] == 0) { - var alertColor = "16711680"; - } else if(heartbeatJSON['status'] == 1) { - var alertColor = "65280"; - } - let data = { - username: 'Uptime-Kuma', - embeds: [{ - title: "Uptime-Kuma Alert", - color: alertColor, - fields: [ - { - name: "Time (UTC)", - value: heartbeatJSON["time"] - }, - { - name: "Message", - value: msg - } - ] - }] - } - await axios.post(notification.discordWebhookUrl, data) - return okMsg; - } catch(error) { - throwGeneralAxiosError(error) + } catch (error) { + throwGeneralAxiosError(error) } } else if (notification.type === "signal") { - try { - let data = { - "message": msg, - "number": notification.signalNumber, - "recipients": notification.signalRecipients.replace(/\s/g, '').split(",") - }; - let config = {}; + try { + let data = { + "message": msg, + "number": notification.signalNumber, + "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), + }; + let config = {}; - await axios.post(notification.signalURL, data, config) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } + await axios.post(notification.signalURL, data, config) + return okMsg; + } catch (error) { + throwGeneralAxiosError(error) + } } else if (notification.type === "slack") { try { if (heartbeatJSON == null) { - let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo} + let data = { + "text": "Uptime Kuma Slack testing successful.", + "channel": notification.slackchannel, + "username": notification.slackusername, + "icon_emoji": notification.slackiconemo, + } await axios.post(notification.slackwebhookURL, data) return okMsg; } @@ -148,44 +153,42 @@ class Notification { const time = heartbeatJSON["time"]; let data = { "text": "Uptime Kuma Alert", - "channel":notification.slackchannel, + "channel": notification.slackchannel, "username": notification.slackusername, "icon_emoji": notification.slackiconemo, "blocks": [{ - "type": "header", - "text": { - "type": "plain_text", - "text": "Uptime Kuma Alert" - } + "type": "header", + "text": { + "type": "plain_text", + "text": "Uptime Kuma Alert", + }, + }, + { + "type": "section", + "fields": [{ + "type": "mrkdwn", + "text": "*Message*\n" + msg, }, { - "type": "section", - "fields": [{ - "type": "mrkdwn", - "text": '*Message*\n'+msg + "type": "mrkdwn", + "text": "*Time (UTC)*\n" + time, + }], + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Visit Uptime Kuma", }, - { - "type": "mrkdwn", - "text": "*Time (UTC)*\n"+time - } - ] - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Visit Uptime Kuma", - }, - "value": "Uptime-Kuma", - "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma" - } - ] - } - ] - } + "value": "Uptime-Kuma", + "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", + }, + ], + }], + } await axios.post(notification.slackwebhookURL, data) return okMsg; } catch (error) { @@ -193,27 +196,35 @@ class Notification { } } else if (notification.type === "pushover") { - var pushoverlink = 'https://api.pushover.net/1/messages.json' + let pushoverlink = "https://api.pushover.net/1/messages.json" try { if (heartbeatJSON == null) { - let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>", - 'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds, - 'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1} + let data = { + "message": "<b>Uptime Kuma Pushover testing successful.</b>", + "user": notification.pushoveruserkey, + "token": notification.pushoverapptoken, + "sound": notification.pushoversounds, + "priority": notification.pushoverpriority, + "title": notification.pushovertitle, + "retry": "30", + "expire": "3600", + "html": 1, + } await axios.post(pushoverlink, data) return okMsg; } let data = { - "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:"+msg+ '\n<b>Time (UTC)</b>:' +heartbeatJSON["time"], - "user":notification.pushoveruserkey, + "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"], + "user": notification.pushoveruserkey, "token": notification.pushoverapptoken, "sound": notification.pushoversounds, "priority": notification.pushoverpriority, "title": notification.pushovertitle, "retry": "30", "expire": "3600", - "html": 1 - } + "html": 1, + } await axios.post(pushoverlink, data) return okMsg; } catch (error) { @@ -291,24 +302,23 @@ class Notification { static async apprise(notification, msg) { let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) - - let output = (s.stdout) ? s.stdout.toString() : 'ERROR: maybe apprise not found'; + let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; if (output) { if (! output.includes("ERROR")) { return "Sent Successfully"; - } else { - throw new Error(output) } + throw new Error(output) + } else { return "" } } static checkApprise() { - let commandExistsSync = require('command-exists').sync; - let exists = commandExistsSync('apprise'); + let commandExistsSync = require("command-exists").sync; + let exists = commandExistsSync("apprise"); return exists; } diff --git a/server/password-hash.js b/server/password-hash.js index 39bc0c20..52e26b95 100644 --- a/server/password-hash.js +++ b/server/password-hash.js @@ -1,5 +1,5 @@ -const passwordHashOld = require('password-hash'); -const bcrypt = require('bcrypt'); +const passwordHashOld = require("password-hash"); +const bcrypt = require("bcrypt"); const saltRounds = 10; exports.generate = function (password) { @@ -9,9 +9,9 @@ exports.generate = function (password) { exports.verify = function (password, hash) { if (isSHA1(hash)) { return passwordHashOld.verify(password, hash) - } else { - return bcrypt.compareSync(password, hash); } + + return bcrypt.compareSync(password, hash); } function isSHA1(hash) { diff --git a/server/ping-lite.js b/server/ping-lite.js index e290d887..337bc0d3 100644 --- a/server/ping-lite.js +++ b/server/ping-lite.js @@ -1,9 +1,9 @@ // https://github.com/ben-bradley/ping-lite/blob/master/ping-lite.js // Fixed on Windows -var spawn = require('child_process').spawn, - events = require('events'), - fs = require('fs'), +let spawn = require("child_process").spawn, + events = require("events"), + fs = require("fs"), WIN = /^win/.test(process.platform), LIN = /^linux/.test(process.platform), MAC = /^darwin/.test(process.platform); @@ -11,8 +11,9 @@ var spawn = require('child_process').spawn, module.exports = Ping; function Ping(host, options) { - if (!host) - throw new Error('You must specify a host to ping!'); + if (!host) { + throw new Error("You must specify a host to ping!"); + } this._host = host; this._options = options = (options || {}); @@ -20,26 +21,24 @@ function Ping(host, options) { events.EventEmitter.call(this); if (WIN) { - this._bin = 'c:/windows/system32/ping.exe'; - this._args = (options.args) ? options.args : [ '-n', '1', '-w', '5000', host ]; + this._bin = "c:/windows/system32/ping.exe"; + this._args = (options.args) ? options.args : [ "-n", "1", "-w", "5000", host ]; this._regmatch = /[><=]([0-9.]+?)ms/; - } - else if (LIN) { - this._bin = '/bin/ping'; - this._args = (options.args) ? options.args : [ '-n', '-w', '2', '-c', '1', host ]; + } else if (LIN) { + this._bin = "/bin/ping"; + this._args = (options.args) ? options.args : [ "-n", "-w", "2", "-c", "1", host ]; this._regmatch = /=([0-9.]+?) ms/; // need to verify this - } - else if (MAC) { - this._bin = '/sbin/ping'; - this._args = (options.args) ? options.args : [ '-n', '-t', '2', '-c', '1', host ]; + } else if (MAC) { + this._bin = "/sbin/ping"; + this._args = (options.args) ? options.args : [ "-n", "-t", "2", "-c", "1", host ]; this._regmatch = /=([0-9.]+?) ms/; - } - else { - throw new Error('Could not detect your ping binary.'); + } else { + throw new Error("Could not detect your ping binary."); } - if (!fs.existsSync(this._bin)) - throw new Error('Could not detect '+this._bin+' on your system'); + if (!fs.existsSync(this._bin)) { + throw new Error("Could not detect " + this._bin + " on your system"); + } this._i = 0; @@ -51,48 +50,56 @@ Ping.prototype.__proto__ = events.EventEmitter.prototype; // SEND A PING // =========== Ping.prototype.send = function(callback) { - var self = this; + let self = this; callback = callback || function(err, ms) { - if (err) return self.emit('error', err); - else return self.emit('result', ms); + if (err) { + return self.emit("error", err); + } + return self.emit("result", ms); }; - var _ended, _exited, _errored; + let _ended, _exited, _errored; this._ping = spawn(this._bin, this._args); // spawn the binary - this._ping.on('error', function(err) { // handle binary errors + this._ping.on("error", function(err) { // handle binary errors _errored = true; callback(err); }); - this._ping.stdout.on('data', function(data) { // log stdout - this._stdout = (this._stdout || '') + data; + this._ping.stdout.on("data", function(data) { // log stdout + this._stdout = (this._stdout || "") + data; }); - this._ping.stdout.on('end', function() { + this._ping.stdout.on("end", function() { _ended = true; - if (_exited && !_errored) onEnd.call(self._ping); + if (_exited && !_errored) { + onEnd.call(self._ping); + } }); - this._ping.stderr.on('data', function(data) { // log stderr - this._stderr = (this._stderr || '') + data; + this._ping.stderr.on("data", function(data) { // log stderr + this._stderr = (this._stderr || "") + data; }); - this._ping.on('exit', function(code) { // handle complete + this._ping.on("exit", function(code) { // handle complete _exited = true; - if (_ended && !_errored) onEnd.call(self._ping); + if (_ended && !_errored) { + onEnd.call(self._ping); + } }); function onEnd() { - var stdout = this.stdout._stdout, + let stdout = this.stdout._stdout, stderr = this.stderr._stderr, ms; - if (stderr) + if (stderr) { return callback(new Error(stderr)); - else if (!stdout) - return callback(new Error('No stdout detected')); + } + if (!stdout) { + return callback(new Error("No stdout detected")); + } ms = stdout.match(self._regmatch); // parse out the ##ms response ms = (ms && ms[1]) ? Number(ms[1]) : ms; @@ -104,7 +111,7 @@ Ping.prototype.send = function(callback) { // CALL Ping#send(callback) ON A TIMER // =================================== Ping.prototype.start = function(callback) { - var self = this; + let self = this; this._i = setInterval(function() { self.send(callback); }, (self._options.interval || 5000)); diff --git a/server/server.js b/server/server.js index 2014d927..5b76b796 100644 --- a/server/server.js +++ b/server/server.js @@ -1,23 +1,23 @@ console.log("Welcome to Uptime Kuma ") console.log("Importing libraries") -const express = require('express'); -const http = require('http'); +const express = require("express"); +const http = require("http"); const { Server } = require("socket.io"); const dayjs = require("dayjs"); -const {R} = require("redbean-node"); -const jwt = require('jsonwebtoken'); +const { R } = require("redbean-node"); +const jwt = require("jsonwebtoken"); const Monitor = require("./model/monitor"); const fs = require("fs"); -const {getSettings} = require("./util-server"); -const {Notification} = require("./notification") -const gracefulShutdown = require('http-graceful-shutdown'); +const { getSettings } = require("./util-server"); +const { Notification } = require("./notification") +const gracefulShutdown = require("http-graceful-shutdown"); const Database = require("./database"); -const {sleep} = require("./util"); -const args = require('args-parser')(process.argv); -const prometheusAPIMetrics = require('prometheus-api-metrics'); +const { sleep } = require("./util"); +const args = require("args-parser")(process.argv); +const prometheusAPIMetrics = require("prometheus-api-metrics"); const { basicAuth } = require("./auth"); -const {login} = require("./auth"); -const version = require('../package.json').version; +const { login } = require("./auth"); +const version = require("../package.json").version; const hostname = args.host || "0.0.0.0" const port = args.port || 3001 @@ -63,12 +63,12 @@ let needSetup = false; // Normal Router here - app.use('/', express.static("dist")); + app.use("/", express.static("dist")); // Basic Auth Router here // For testing - basicAuthRouter.get('/test-auth', (req, res) => { + basicAuthRouter.get("/test-auth", (req, res) => { res.end("OK") }); @@ -77,12 +77,12 @@ let needSetup = false; basicAuthRouter.use(prometheusAPIMetrics()) // Universal Route Handler, must be at the end - app.get('*', function(request, response, next) { - response.sendFile(process.cwd() + '/dist/index.html'); + app.get("*", function(request, response, next) { + response.sendFile(process.cwd() + "/dist/index.html"); }); console.log("Adding socket handler") - io.on('connection', async (socket) => { + io.on("connection", async (socket) => { socket.emit("info", { version, @@ -95,7 +95,7 @@ let needSetup = false; socket.emit("setup") } - socket.on('disconnect', () => { + socket.on("disconnect", () => { totalClient--; }); @@ -109,7 +109,7 @@ let needSetup = false; console.log("Username from JWT: " + decoded.username) let user = await R.findOne("user", " username = ? AND active = 1 ", [ - decoded.username + decoded.username, ]) if (user) { @@ -121,13 +121,13 @@ let needSetup = false; } else { callback({ ok: false, - msg: "The user is inactive or deleted." + msg: "The user is inactive or deleted.", }) } } catch (error) { callback({ ok: false, - msg: "Invalid token." + msg: "Invalid token.", }) } @@ -144,13 +144,13 @@ let needSetup = false; callback({ ok: true, token: jwt.sign({ - username: data.username - }, jwtSecret) + username: data.username, + }, jwtSecret), }) } else { callback({ ok: false, - msg: "Incorrect username or password." + msg: "Incorrect username or password.", }) } @@ -181,13 +181,13 @@ let needSetup = false; callback({ ok: true, - msg: "Added Successfully." + msg: "Added Successfully.", }); } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -214,13 +214,13 @@ let needSetup = false; callback({ ok: true, msg: "Added Successfully.", - monitorID: bean.id + monitorID: bean.id, }); } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -257,14 +257,14 @@ let needSetup = false; callback({ ok: true, msg: "Saved.", - monitorID: bean.id + monitorID: bean.id, }); } catch (e) { console.error(e) callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -288,7 +288,7 @@ let needSetup = false; } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -302,13 +302,13 @@ let needSetup = false; callback({ ok: true, - msg: "Resumed Successfully." + msg: "Resumed Successfully.", }); } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -321,14 +321,13 @@ let needSetup = false; callback({ ok: true, - msg: "Paused Successfully." + msg: "Paused Successfully.", }); - } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -346,12 +345,12 @@ let needSetup = false; await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [ monitorID, - socket.userID + socket.userID, ]); callback({ ok: true, - msg: "Deleted Successfully." + msg: "Deleted Successfully.", }); await sendMonitorList(socket); @@ -359,7 +358,7 @@ let needSetup = false; } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -373,19 +372,19 @@ let needSetup = false; } let user = await R.findOne("user", " id = ? AND active = 1 ", [ - socket.userID + socket.userID, ]) if (user && passwordHash.verify(password.currentPassword, user.password)) { await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ passwordHash.generate(password.newPassword), - socket.userID + socket.userID, ]); callback({ ok: true, - msg: "Password has been updated successfully." + msg: "Password has been updated successfully.", }) } else { throw new Error("Incorrect current password") @@ -394,7 +393,7 @@ let needSetup = false; } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -403,7 +402,6 @@ let needSetup = false; try { checkLogin(socket) - callback({ ok: true, data: await getSettings(type), @@ -412,7 +410,7 @@ let needSetup = false; } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -433,7 +431,7 @@ let needSetup = false; } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -453,7 +451,7 @@ let needSetup = false; } catch (e) { callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -466,7 +464,7 @@ let needSetup = false; callback({ ok: true, - msg + msg, }); } catch (e) { @@ -474,7 +472,7 @@ let needSetup = false; callback({ ok: false, - msg: e.message + msg: e.message, }); } }); @@ -499,7 +497,7 @@ let needSetup = false; async function updateMonitorNotification(monitorID, notificationIDList) { R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ - monitorID + monitorID, ]) for (let notificationID in notificationIDList) { @@ -532,7 +530,7 @@ async function sendMonitorList(socket) { async function sendNotificationList(socket) { let result = []; let list = await R.find("notification", " user_id = ? ", [ - socket.userID + socket.userID, ]); for (let bean of list) { @@ -562,7 +560,7 @@ async function getMonitorJSONList(userID) { let result = {}; let monitorList = await R.find("monitor", " user_id = ? ", [ - userID + userID, ]) for (let monitor of monitorList) { @@ -585,8 +583,8 @@ async function initDatabase() { } console.log("Connecting to Database") - R.setup('sqlite', { - filename: Database.path + R.setup("sqlite", { + filename: Database.path, }); console.log("Connected") @@ -598,7 +596,7 @@ async function initDatabase() { await R.autoloadModels("./server/model"); let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [ - "jwtSecret" + "jwtSecret", ]); if (! jwtSecretBean) { @@ -629,11 +627,11 @@ async function startMonitor(userID, monitorID) { await R.exec("UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? ", [ monitorID, - userID + userID, ]); let monitor = await R.findOne("monitor", " id = ? ", [ - monitorID + monitorID, ]) if (monitor.id in monitorList) { @@ -655,7 +653,7 @@ async function pauseMonitor(userID, monitorID) { await R.exec("UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? ", [ monitorID, - userID + userID, ]); if (monitorID in monitorList) { @@ -684,13 +682,13 @@ async function sendHeartbeatList(socket, monitorID) { ORDER BY time DESC LIMIT 100 `, [ - monitorID + monitorID, ]) let result = []; for (let bean of list) { - result.unshift(bean.toJSON()) + result.unshift(bean.toJSON()) } socket.emit("heartbeatList", monitorID, result) @@ -703,23 +701,20 @@ async function sendImportantHeartbeatList(socket, monitorID) { ORDER BY time DESC LIMIT 500 `, [ - monitorID + monitorID, ]) socket.emit("importantHeartbeatList", monitorID, list) } - - const startGracefulShutdown = async () => { - console.log('Shutdown requested'); - + console.log("Shutdown requested"); await (new Promise((resolve) => { server.close(async function () { - console.log('Stopped Express.'); + console.log("Stopped Express."); process.exit(0) - setTimeout(async () =>{ + setTimeout(async () => { await R.close(); console.log("Stopped DB") @@ -729,11 +724,10 @@ const startGracefulShutdown = async () => { }); })); - } async function shutdownFunction(signal) { - console.log('Called signal: ' + signal); + console.log("Called signal: " + signal); console.log("Stopping all monitors") for (let id in monitorList) { @@ -745,14 +739,14 @@ async function shutdownFunction(signal) { } function finalFunction() { - console.log('Graceful Shutdown') + console.log("Graceful Shutdown") } gracefulShutdown(server, { - signals: 'SIGINT SIGTERM', + signals: "SIGINT SIGTERM", timeout: 30000, // timeout: 30 secs development: false, // not in dev mode forceExit: true, // triggers process.exit() at the end of shutdown process onShutdown: shutdownFunction, // shutdown function (async) - e.g. for cleanup DB, ... - finally: finalFunction // finally function (sync) - e.g. for logging + finally: finalFunction, // finally function (sync) - e.g. for logging }); diff --git a/server/util-server.js b/server/util-server.js index f03823d3..43aa5ccf 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -1,6 +1,6 @@ -const tcpp = require('tcp-ping'); +const tcpp = require("tcp-ping"); const Ping = require("./ping-lite"); -const {R} = require("redbean-node"); +const { R } = require("redbean-node"); exports.tcping = function (hostname, port) { return new Promise((resolve, reject) => { @@ -41,13 +41,13 @@ exports.ping = function (hostname) { exports.setting = async function (key) { return await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [ - key + key, ]) } exports.setSetting = async function (key, value) { let bean = await R.findOne("setting", " `key` = ? ", [ - key + key, ]) if (! bean) { bean = R.dispense("setting") @@ -59,7 +59,7 @@ exports.setSetting = async function (key, value) { exports.getSettings = async function (type) { let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ - type + type, ]) let result = {}; @@ -71,7 +71,6 @@ exports.getSettings = async function (type) { return result; } - // ssl-checker by @dyaa // param: res - response object from axios // return an object containing the certificate information @@ -97,7 +96,9 @@ exports.checkCertificate = function (res) { } = res.request.res.socket.getPeerCertificate(false); if (!valid_from || !valid_to || !subjectaltname) { - throw { message: 'No TLS certificate in response' }; + throw { + message: "No TLS certificate in response", + }; } const valid = res.request.res.socket.authorized || false; @@ -118,4 +119,4 @@ exports.checkCertificate = function (res) { issuer, fingerprint, }; -} \ No newline at end of file +} diff --git a/server/util.js b/server/util.js index 081561bf..6e90dc42 100644 --- a/server/util.js +++ b/server/util.js @@ -23,4 +23,3 @@ exports.debug = (msg) => { console.log(msg) } } - diff --git a/src/App.vue b/src/App.vue index 1f05560e..974757a6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,9 +3,7 @@ </template> <script> -export default { - -} +export default {} </script> <style lang="scss"> diff --git a/src/components/Confirm.vue b/src/components/Confirm.vue index 063ece25..ecf165a0 100644 --- a/src/components/Confirm.vue +++ b/src/components/Confirm.vue @@ -1,17 +1,23 @@ <template> - <div class="modal fade" tabindex="-1" ref="modal"> + <div ref="modal" class="modal fade" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> - <h5 class="modal-title" id="exampleModalLabel">Confirm</h5> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + <h5 id="exampleModalLabel" class="modal-title"> + Confirm + </h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> </div> <div class="modal-body"> - <slot></slot> + <slot /> </div> <div class="modal-footer"> - <button type="button" class="btn" :class="btnStyle" @click="yes" data-bs-dismiss="modal">Yes</button> - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <button type="button" class="btn" :class="btnStyle" data-bs-dismiss="modal" @click="yes"> + Yes + </button> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> + No + </button> </div> </div> </div> @@ -19,17 +25,17 @@ </template> <script> -import { Modal } from 'bootstrap' +import { Modal } from "bootstrap" export default { props: { btnStyle: { type: String, - default: "btn-primary" - } + default: "btn-primary", + }, }, data: () => ({ - modal: null + modal: null, }), mounted() { this.modal = new Modal(this.$refs.modal) @@ -39,9 +45,9 @@ export default { this.modal.show() }, yes() { - this.$emit('yes'); - } - } + this.$emit("yes"); + }, + }, } </script> diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue index 33904b6a..1d2a4c55 100644 --- a/src/components/CountUp.vue +++ b/src/components/CountUp.vue @@ -5,7 +5,7 @@ <script> -import {sleep} from '../util-frontend' +import { sleep } from "../util-frontend" export default { @@ -18,11 +18,7 @@ export default { unit: { String, default: "ms", - } - }, - - mounted() { - this.output = this.value; + }, }, data() { @@ -32,14 +28,10 @@ export default { } }, - methods: { - - }, - computed: { isNum() { - return typeof this.value === 'number' - } + return typeof this.value === "number" + }, }, watch: { @@ -61,6 +53,12 @@ export default { }, }, + mounted() { + this.output = this.value; + }, + + methods: {}, + } </script> diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue index 3e551659..365e9fca 100644 --- a/src/components/Datetime.vue +++ b/src/components/Datetime.vue @@ -5,8 +5,8 @@ <script> import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime" -import utc from 'dayjs/plugin/utc' -import timezone from 'dayjs/plugin/timezone' // dependent on utc plugin +import utc from "dayjs/plugin/utc" +import timezone from "dayjs/plugin/timezone" // dependent on utc plugin dayjs.extend(utc) dayjs.extend(timezone) dayjs.extend(relativeTime) @@ -28,11 +28,11 @@ export default { format = "YYYY-MM-DD"; } return dayjs.utc(this.value).tz(this.$root.timezone).format(format); - } else { - return ""; } + + return ""; }, - } + }, } </script> diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index 03cdceca..7ee94317 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -1,28 +1,27 @@ <template> - <div class="wrap" :style="wrapStyle" ref="wrap"> + <div ref="wrap" class="wrap" :style="wrapStyle"> <div class="hp-bar-big" :style="barStyle"> <div + v-for="(beat, index) in shortBeatList" + :key="index" class="beat" :class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }" :style="beatStyle" - v-for="(beat, index) in shortBeatList" - :key="index" - :title="beat.msg"> - </div> + :title="beat.msg" + /> </div> </div> </template> <script> - export default { props: { size: { type: String, - default: "big" + default: "big", }, - monitorId: Number + monitorId: Number, }, data() { return { @@ -34,26 +33,6 @@ export default { maxBeat: -1, } }, - unmounted() { - window.removeEventListener("resize", this.resize); - }, - mounted() { - if (this.size === "small") { - this.beatWidth = 5.6; - this.beatMargin = 2.4; - this.beatHeight = 16 - } - - window.addEventListener("resize", this.resize); - this.resize(); - }, - methods: { - resize() { - if (this.$refs.wrap) { - this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)) - } - } - }, computed: { beatList() { @@ -80,8 +59,6 @@ export default { start = 0; } - - return placeholders.concat(this.beatList.slice(start)) }, @@ -98,7 +75,7 @@ export default { return { padding: `${topBottom}px ${leftRight}px`, - width: width + width: width, } }, @@ -111,11 +88,11 @@ export default { transform: `translateX(${width}px)`, } - } else { - return { - transform: `translateX(0)`, - } } + return { + transform: "translateX(0)", + } + }, beatStyle() { @@ -125,7 +102,7 @@ export default { margin: this.beatMargin + "px", "--hover-scale": this.hoverScale, } - } + }, }, watch: { @@ -138,8 +115,28 @@ export default { }, 300) }, deep: true, + }, + }, + unmounted() { + window.removeEventListener("resize", this.resize); + }, + mounted() { + if (this.size === "small") { + this.beatWidth = 5.6; + this.beatMargin = 2.4; + this.beatHeight = 16 } - } + + window.addEventListener("resize", this.resize); + this.resize(); + }, + methods: { + resize() { + if (this.$refs.wrap) { + this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)) + } + }, + }, } </script> diff --git a/src/components/Login.vue b/src/components/Login.vue index 017261cc..4b08de06 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -2,31 +2,32 @@ <div class="form-container"> <div class="form"> <form @submit.prevent="submit"> - - <h1 class="h3 mb-3 fw-normal"></h1> + <h1 class="h3 mb-3 fw-normal" /> <div class="form-floating"> - <input type="text" class="form-control" id="floatingInput" placeholder="Username" v-model="username"> + <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username"> <label for="floatingInput">Username</label> </div> <div class="form-floating mt-3"> - <input type="password" class="form-control" id="floatingPassword" placeholder="Password" v-model="password"> + <input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password"> <label for="floatingPassword">Password</label> </div> <div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4"> <div class="form-check"> - <input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember"> + <input id="remember" v-model="$root.remember" type="checkbox" value="remember-me" class="form-check-input"> <label class="form-check-label" for="remember"> Remember me </label> </div> </div> - <button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button> + <button class="w-100 btn btn-primary" type="submit" :disabled="processing"> + Login + </button> - <div class="alert alert-danger mt-3" role="alert" v-if="res && !res.ok"> + <div v-if="res && !res.ok" class="alert alert-danger mt-3" role="alert"> {{ res.msg }} </div> </form> @@ -52,8 +53,8 @@ export default { this.processing = false; this.res = res; }) - } - } + }, + }, } </script> diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index afbfc0d4..3adbe4af 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -1,48 +1,70 @@ <template> <form @submit.prevent="submit"> - - <div class="modal fade" tabindex="-1" ref="modal" data-bs-backdrop="static"> + <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 class="modal-title" id="exampleModalLabel">Setup Notification</h5> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + <h5 id="exampleModalLabel" class="modal-title"> + Setup Notification + </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="type" class="form-label">Notification Type</label> - <select class="form-select" id="type" v-model="notification.type"> - <option value="telegram">Telegram</option> - <option value="webhook">Webhook</option> - <option value="smtp">Email (SMTP)</option> - <option value="discord">Discord</option> - <option value="signal">Signal</option> - <option value="gotify">Gotify</option> - <option value="slack">Slack</option> - <option value="pushover">Pushover</option> - <option value="apprise">Apprise (Support 50+ Notification services)</option> + <select id="type" v-model="notification.type" class="form-select"> + <option value="telegram"> + Telegram + </option> + <option value="webhook"> + Webhook + </option> + <option value="smtp"> + Email (SMTP) + </option> + <option value="discord"> + Discord + </option> + <option value="signal"> + Signal + </option> + <option value="gotify"> + Gotify + </option> + <option value="slack"> + Slack + </option> + <option value="pushover"> + Pushover + </option> + <option value="apprise"> + Apprise (Support 50+ Notification services) + </option> </select> </div> <div class="mb-3"> <label for="name" class="form-label">Friendly Name</label> - <input type="text" class="form-control" id="name" required v-model="notification.name"> + <input id="name" v-model="notification.name" type="text" class="form-control" required> </div> <template v-if="notification.type === 'telegram'"> <div class="mb-3"> <label for="telegram-bot-token" class="form-label">Bot Token</label> - <input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken"> - <div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div> + <input id="telegram-bot-token" v-model="notification.telegramBotToken" type="text" class="form-control" required> + <div class="form-text"> + You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. + </div> </div> <div class="mb-3"> <label for="telegram-chat-id" class="form-label">Chat ID</label> <div class="input-group mb-3"> - <input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID"> - <button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button> + <input id="telegram-chat-id" v-model="notification.telegramChatID" type="text" class="form-control" required> + <button v-if="notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID"> + Auto Get + </button> </div> <div class="form-text"> @@ -53,7 +75,6 @@ </p> <p style="margin-top: 8px;"> - <template v-if="notification.telegramBotToken"> <a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> </template> @@ -69,15 +90,18 @@ <template v-if="notification.type === 'webhook'"> <div class="mb-3"> <label for="webhook-url" class="form-label">Post URL</label> - <input type="url" pattern="https?://.+" class="form-control" id="webhook-url" required v-model="notification.webhookURL"> - + <input id="webhook-url" v-model="notification.webhookURL" type="url" pattern="https?://.+" class="form-control" required> </div> <div class="mb-3"> <label for="webhook-content-type" class="form-label">Content Type</label> - <select class="form-select" id="webhook-content-type" v-model="notification.webhookContentType" required> - <option value="json">application/json</option> - <option value="form-data">multipart/form-data</option> + <select id="webhook-content-type" v-model="notification.webhookContentType" class="form-select" required> + <option value="json"> + application/json + </option> + <option value="form-data"> + multipart/form-data + </option> </select> <div class="form-text"> @@ -90,70 +114,71 @@ <template v-if="notification.type === 'smtp'"> <div class="mb-3"> <label for="hostname" class="form-label">Hostname</label> - <input type="text" class="form-control" id="hostname" required v-model="notification.smtpHost"> + <input id="hostname" v-model="notification.smtpHost" type="text" class="form-control" required> </div> <div class="mb-3"> <label for="port" class="form-label">Port</label> - <input type="number" class="form-control" id="port" v-model="notification.smtpPort" required min="0" max="65535" step="1"> + <input id="port" v-model="notification.smtpPort" type="number" class="form-control" required min="0" max="65535" step="1"> </div> <div class="mb-3"> <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" id="secure" v-model="notification.smtpSecure"> + <input id="secure" v-model="notification.smtpSecure" class="form-check-input" type="checkbox" value=""> <label class="form-check-label" for="secure"> Secure </label> </div> - <div class="form-text">Generally, true for 465, false for other ports.</div> + <div class="form-text"> + Generally, true for 465, false for other ports. + </div> </div> <div class="mb-3"> <label for="username" class="form-label">Username</label> - <input type="text" class="form-control" id="username" v-model="notification.smtpUsername" autocomplete="false"> + <input id="username" v-model="notification.smtpUsername" type="text" class="form-control" autocomplete="false"> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> - <input type="password" class="form-control" id="password" v-model="notification.smtpPassword" autocomplete="false"> + <input id="password" v-model="notification.smtpPassword" type="password" class="form-control" autocomplete="false"> </div> <div class="mb-3"> <label for="from-email" class="form-label">From Email</label> - <input type="email" class="form-control" id="from-email" required v-model="notification.smtpFrom" autocomplete="false"> + <input id="from-email" v-model="notification.smtpFrom" type="email" class="form-control" required autocomplete="false"> </div> <div class="mb-3"> <label for="to-email" class="form-label">To Email</label> - <input type="email" class="form-control" id="to-email" required v-model="notification.smtpTo" autocomplete="false"> + <input id="to-email" v-model="notification.smtpTo" type="email" class="form-control" required autocomplete="false"> </div> - </template> <template v-if="notification.type === 'discord'"> <div class="mb-3"> <label for="discord-webhook-url" class="form-label">Discord Webhook URL</label> - <input type="text" class="form-control" id="discord-webhook-url" required v-model="notification.discordWebhookUrl" autocomplete="false"> - <div class="form-text">You can get this by going to Server Settings -> Integrations -> Create Webhook</div> + <input id="discord-webhook-url" v-model="notification.discordWebhookUrl" type="text" class="form-control" required autocomplete="false"> + <div class="form-text"> + You can get this by going to Server Settings -> Integrations -> Create Webhook + </div> </div> </template> <template v-if="notification.type === 'signal'"> <div class="mb-3"> <label for="signal-url" class="form-label">Post URL</label> - <input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL"> - + <input id="signal-url" v-model="notification.signalURL" type="url" pattern="https?://.+" class="form-control" required> </div> <div class="mb-3"> <label for="signal-number" class="form-label">Number</label> - <input type="text" class="form-control" id="signal-number" required v-model="notification.signalNumber"> - + <input id="signal-number" v-model="notification.signalNumber" type="text" class="form-control" required> </div> <div class="mb-3"> <label for="signal-recipients" class="form-label">Recipients</label> - <input type="text" class="form-control" id="signal-recipients" required v-model="notification.signalRecipients"> + <input id="signal-recipients" v-model="notification.signalRecipients" type="text" class="form-control" required> <div class="form-text"> You need to have a signal client with REST API. @@ -174,37 +199,37 @@ </template> <template v-if="notification.type === 'gotify'"> - <div class="mb-3"> - <label for="gotify-application-token" class="form-label">Application Token</label> - <input type="text" class="form-control" id="gotify-application-token" required v-model="notification.gotifyapplicationToken"> - </div> - <div class="mb-3"> - <label for="gotify-server-url" class="form-label">Server URL</label> - <div class="input-group mb-3"> - <input type="text" class="form-control" id="gotify-server-url" required v-model="notification.gotifyserverurl"> - </div> + <div class="mb-3"> + <label for="gotify-application-token" class="form-label">Application Token</label> + <input id="gotify-application-token" v-model="notification.gotifyapplicationToken" type="text" class="form-control" required> + </div> + <div class="mb-3"> + <label for="gotify-server-url" class="form-label">Server URL</label> + <div class="input-group mb-3"> + <input id="gotify-server-url" v-model="notification.gotifyserverurl" type="text" class="form-control" required> </div> + </div> - <div class="mb-3"> - <label for="gotify-priority" class="form-label">Priority</label> - <input type="number" class="form-control" id="gotify-priority" v-model="notification.gotifyPriority" required min="0" max="10" step="1"> - </div> - </template> + <div class="mb-3"> + <label for="gotify-priority" class="form-label">Priority</label> + <input id="gotify-priority" v-model="notification.gotifyPriority" type="number" class="form-control" required min="0" max="10" step="1"> + </div> + </template> <template v-if="notification.type === 'slack'"> <div class="mb-3"> <label for="slack-webhook-url" class="form-label">Webhook URL<span style="color:red;"><sup>*</sup></span></label> - <input type="text" class="form-control" id="slack-webhook-url" required v-model="notification.slackwebhookURL"> + <input id="slack-webhook-url" v-model="notification.slackwebhookURL" type="text" class="form-control" required> <label for="slack-username" class="form-label">Username</label> - <input type="text" class="form-control" id="slack-username" v-model="notification.slackusername"> + <input id="slack-username" v-model="notification.slackusername" type="text" class="form-control"> <label for="slack-iconemo" class="form-label">Icon Emoji</label> - <input type="text" class="form-control" id="slack-iconemo" v-model="notification.slackiconemo"> + <input id="slack-iconemo" v-model="notification.slackiconemo" type="text" class="form-control"> <label for="slack-channel" class="form-label">Channel Name</label> - <input type="text" class="form-control" id="slack-channel-name" v-model="notification.slackchannel"> + <input id="slack-channel-name" v-model="notification.slackchannel" type="text" class="form-control"> <label for="slack-button-url" class="form-label">Uptime Kuma URL</label> - <input type="text" class="form-control" id="slack-button" v-model="notification.slackbutton"> + <input id="slack-button" v-model="notification.slackbutton" type="text" class="form-control"> <div class="form-text"> - <span style="color:red;"><sup>*</sup></span>Required + <span style="color:red;"><sup>*</sup></span>Required <p style="margin-top: 8px;"> More info about webhooks on: <a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a> </p> @@ -224,15 +249,15 @@ <template v-if="notification.type === 'pushover'"> <div class="mb-3"> <label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label> - <input type="text" class="form-control" id="pushover-user" required v-model="notification.pushoveruserkey"> + <input id="pushover-user" v-model="notification.pushoveruserkey" type="text" class="form-control" required> <label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label> - <input type="text" class="form-control" id="pushover-app-token" required v-model="notification.pushoverapptoken"> + <input id="pushover-app-token" v-model="notification.pushoverapptoken" type="text" class="form-control" required> <label for="pushover-device" class="form-label">Device</label> - <input type="text" class="form-control" id="pushover-device" v-model="notification.pushoverdevice"> + <input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control"> <label for="pushover-device" class="form-label">Message Title</label> - <input type="text" class="form-control" id="pushover-title" v-model="notification.pushovertitle"> + <input id="pushover-title" v-model="notification.pushovertitle" type="text" class="form-control"> <label for="pushover-priority" class="form-label">Priority</label> - <select class="form-select" id="pushover-priority" v-model="notification.pushoverpriority"> + <select id="pushover-priority" v-model="notification.pushoverpriority" class="form-select"> <option>-2</option> <option>-1</option> <option>0</option> @@ -240,7 +265,7 @@ <option>2</option> </select> <label for="pushover-sound" class="form-label">Notification Sound</label> - <select class="form-select" id="pushover-sound" v-model="notification.pushoversounds"> + <select id="pushover-sound" v-model="notification.pushoversounds" class="form-select"> <option>pushover</option> <option>bike</option> <option>bugle</option> @@ -265,16 +290,16 @@ <option>none</option> </select> <div class="form-text"> - <span style="color:red;"><sup>*</sup></span>Required - <p style="margin-top: 8px;"> + <span style="color:red;"><sup>*</sup></span>Required + <p style="margin-top: 8px;"> More info on: <a href="https://pushover.net/api" target="_blank">https://pushover.net/api</a> - </p> - <p style="margin-top: 8px;"> + </p> + <p style="margin-top: 8px;"> Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour. - </p> - <p style="margin-top: 8px;"> + </p> + <p style="margin-top: 8px;"> If you want to send notifications to different devices, fill out Device field. - </p> + </p> </div> </div> </template> @@ -282,7 +307,7 @@ <template v-if="notification.type === 'apprise'"> <div class="mb-3"> <label for="apprise-url" class="form-label">Apprise URL</label> - <input type="text" class="form-control" id="apprise-url" required v-model="notification.appriseURL"> + <input id="apprise-url" v-model="notification.appriseURL" type="text" class="form-control" required> <div class="form-text"> <p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p> <p> @@ -293,40 +318,46 @@ <div class="mb-3"> <p> Status: - <span class="text-primary" v-if="appriseInstalled">Apprise is installed</span> - <span class="text-danger" v-else>Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span> + <span v-if="appriseInstalled" class="text-primary">Apprise is installed</span> + <span v-else class="text-danger">Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span> </p> </div> </template> - </div> <div class="modal-footer"> - <button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button> - <button type="button" class="btn btn-warning" @click="test" :disabled="processing">Test</button> - <button type="submit" class="btn btn-primary" :disabled="processing">Save</button> + <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> + Delete + </button> + <button type="button" class="btn btn-warning" :disabled="processing" @click="test"> + Test + </button> + <button type="submit" class="btn btn-primary" :disabled="processing"> + Save + </button> </div> </div> </div> </div> - </form> - <Confirm ref="confirmDelete" @yes="deleteNotification" btn-style="btn-danger">Are you sure want to delete this notification for all monitors?</Confirm> + <Confirm ref="confirmDelete" btn-style="btn-danger" @yes="deleteNotification"> + Are you sure want to delete this notification for all monitors? + </Confirm> </template> <script> -import { Modal } from 'bootstrap' -import { ucfirst } from '../util-frontend' +import { Modal } from "bootstrap" +import { ucfirst } from "../util-frontend" import axios from "axios"; -import { useToast } from 'vue-toastification' +import { useToast } from "vue-toastification" import Confirm from "./Confirm.vue"; const toast = useToast() export default { - components: {Confirm}, - props: { - + components: { + Confirm, }, + props: {}, data() { return { model: null, @@ -335,11 +366,37 @@ export default { notification: { name: "", type: null, - gotifyPriority: 8 + gotifyPriority: 8, }, appriseInstalled: false, } }, + computed: { + telegramGetUpdatesURL() { + let token = "<YOUR BOT TOKEN HERE>" + + if (this.notification.telegramBotToken) { + token = this.notification.telegramBotToken; + } + + return `https://api.telegram.org/bot${token}/getUpdates`; + }, + }, + watch: { + "notification.type"(to, from) { + let oldName; + + if (from) { + oldName = `My ${ucfirst(from)} Alert (1)`; + } else { + oldName = ""; + } + + if (! this.notification.name || this.notification.name === oldName) { + this.notification.name = `My ${ucfirst(to)} Alert (1)` + } + }, + }, mounted() { this.modal = new Modal(this.$refs.modal) @@ -437,32 +494,6 @@ export default { }, }, - computed: { - telegramGetUpdatesURL() { - let token = "<YOUR BOT TOKEN HERE>" - - if (this.notification.telegramBotToken) { - token = this.notification.telegramBotToken; - } - - return `https://api.telegram.org/bot${token}/getUpdates`; - }, - }, - watch: { - "notification.type"(to, from) { - let oldName; - - if (from) { - oldName = `My ${ucfirst(from)} Alert (1)`; - } else { - oldName = ""; - } - - if (! this.notification.name || this.notification.name === oldName) { - this.notification.name = `My ${ucfirst(to)} Alert (1)` - } - } - } } </script> diff --git a/src/components/Status.vue b/src/components/Status.vue index 1eaf17f2..c0549821 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -5,34 +5,34 @@ <script> export default { props: { - status: Number + status: Number, }, computed: { color() { if (this.status === 0) { return "danger" - } else if (this.status === 1) { + } if (this.status === 1) { return "primary" - } else if (this.status === 2) { + } if (this.status === 2) { return "warning" - } else { - return "secondary" } + return "secondary" + }, text() { if (this.status === 0) { return "Down" - } else if (this.status === 1) { + } if (this.status === 1) { return "Up" - } else if (this.status === 2) { + } if (this.status === 2) { return "Pending" - } else { - return "Unknown" } + return "Unknown" + }, - } + }, } </script> diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue index 322b35f7..e9c6f583 100644 --- a/src/components/Uptime.vue +++ b/src/components/Uptime.vue @@ -5,7 +5,7 @@ <script> export default { props: { - monitor : Object, + monitor: Object, type: String, pill: { Boolean, @@ -20,41 +20,41 @@ export default { if (this.$root.uptimeList[key] !== undefined) { return Math.round(this.$root.uptimeList[key] * 10000) / 100 + "%"; - } else { - return "N/A" } + + return "N/A" }, color() { if (this.lastHeartBeat.status === 0) { return "danger" - } else if (this.lastHeartBeat.status === 1) { + } if (this.lastHeartBeat.status === 1) { return "primary" - } else if (this.lastHeartBeat.status === 2) { + } if (this.lastHeartBeat.status === 2) { return "warning" - } else { - return "secondary" } + + return "secondary" }, lastHeartBeat() { if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { return this.$root.lastHeartbeatList[this.monitor.id] - } else { - return { status: -1 } + } + + return { + status: -1, } }, className() { if (this.pill) { return `badge rounded-pill bg-${this.color}`; - } else { - return ""; } + + return ""; }, - }, - } </script> diff --git a/src/icon.js b/src/icon.js index 02c5d818..d8ea36d6 100644 --- a/src/icon.js +++ b/src/icon.js @@ -1,12 +1,10 @@ -import { library } from '@fortawesome/fontawesome-svg-core' +import { library } from "@fortawesome/fontawesome-svg-core" +import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" //import { fa } from '@fortawesome/free-regular-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' -import { faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" // Add Free Font Awesome Icons here // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList) -export { - FontAwesomeIcon -} +export { FontAwesomeIcon } diff --git a/src/layouts/EmptyLayout.vue b/src/layouts/EmptyLayout.vue index 7b720447..6f996587 100644 --- a/src/layouts/EmptyLayout.vue +++ b/src/layouts/EmptyLayout.vue @@ -3,9 +3,7 @@ </template> <script> -export default { - -} +export default {} </script> <style scoped> diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index bc99854a..546c0ffe 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -1,28 +1,35 @@ <template> - - <div class="lost-connection" v-if="! $root.socket.connected && ! $root.socket.firstConnect"> + <div v-if="! $root.socket.connected && ! $root.socket.firstConnect" class="lost-connection"> <div class="container-fluid"> Lost connection to the socket server. Reconnecting... </div> </div> <!-- Desktop header --> - <header class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom" v-if="! $root.isMobile"> + <header v-if="! $root.isMobile" class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom"> <router-link to="/dashboard" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none"> - <object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" alt="Logo"></object> + <object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" alt="Logo" /> <span class="fs-4 title">Uptime Kuma</span> </router-link> - <ul class="nav nav-pills" > - <li class="nav-item"><router-link to="/dashboard" class="nav-link"><font-awesome-icon icon="tachometer-alt" /> Dashboard</router-link></li> - <li class="nav-item"><router-link to="/settings" class="nav-link"><font-awesome-icon icon="cog" /> Settings</router-link></li> + <ul class="nav nav-pills"> + <li class="nav-item"> + <router-link to="/dashboard" class="nav-link"> + <font-awesome-icon icon="tachometer-alt" /> Dashboard + </router-link> + </li> + <li class="nav-item"> + <router-link to="/settings" class="nav-link"> + <font-awesome-icon icon="cog" /> Settings + </router-link> + </li> </ul> </header> <!-- Mobile header --> - <header class="d-flex flex-wrap justify-content-center mt-3 mb-3" v-else> + <header v-else class="d-flex flex-wrap justify-content-center mt-3 mb-3"> <router-link to="/dashboard" class="d-flex align-items-center text-dark text-decoration-none"> - <object class="bi" width="40" height="40" data="/icon.svg"></object> + <object class="bi" width="40" height="40" data="/icon.svg" /> <span class="fs-4 title ms-2">Uptime Kuma</span> </router-link> </header> @@ -42,9 +49,8 @@ </footer> <!-- Mobile Only --> - <div style="width: 100%;height: 60px;" v-if="$root.isMobile"></div> - <nav class="bottom-nav" v-if="$root.isMobile"> - + <div v-if="$root.isMobile" style="width: 100%;height: 60px;" /> + <nav v-if="$root.isMobile" class="bottom-nav"> <router-link to="/dashboard" class="nav-link" @click="$root.cancelActiveList"> <div><font-awesome-icon icon="tachometer-alt" /></div> Dashboard @@ -64,7 +70,6 @@ <div><font-awesome-icon icon="cog" /></div> Settings </router-link> - </nav> </template> @@ -73,23 +78,19 @@ import Login from "../components/Login.vue"; export default { components: { - Login + Login, }, data() { - return { - - } - }, - computed: { - - }, - mounted() { - this.init(); + return {} }, + computed: {}, watch: { $route (to, from) { this.init(); - } + }, + }, + mounted() { + this.init(); }, methods: { init() { @@ -98,7 +99,7 @@ export default { } }, - } + }, } </script> diff --git a/src/main.js b/src/main.js index 59672f8d..e76db1ed 100644 --- a/src/main.js +++ b/src/main.js @@ -1,59 +1,58 @@ -import {createApp, h} from "vue"; -import {createRouter, createWebHistory} from 'vue-router' - -import App from './App.vue' -import Layout from './layouts/Layout.vue' -import EmptyLayout from './layouts/EmptyLayout.vue' -import Settings from "./pages/Settings.vue"; +import "bootstrap"; +import { createApp, h } from "vue"; +import { createRouter, createWebHistory } from "vue-router"; +import Toast from "vue-toastification"; +import "vue-toastification/dist/index.css"; +import App from "./App.vue"; +import "./assets/app.scss"; +import { FontAwesomeIcon } from "./icon.js"; +import EmptyLayout from "./layouts/EmptyLayout.vue"; +import Layout from "./layouts/Layout.vue"; +import socket from "./mixins/socket"; import Dashboard from "./pages/Dashboard.vue"; import DashboardHome from "./pages/DashboardHome.vue"; import Details from "./pages/Details.vue"; -import socket from "./mixins/socket" -import "./assets/app.scss" import EditMonitor from "./pages/EditMonitor.vue"; -import Toast from "vue-toastification"; -import "vue-toastification/dist/index.css"; -import "bootstrap" +import Settings from "./pages/Settings.vue"; import Setup from "./pages/Setup.vue"; -import {FontAwesomeIcon} from "./icon.js" const routes = [ { - path: '/', + path: "/", component: Layout, children: [ { name: "root", - path: '', + path: "", component: Dashboard, children: [ { name: "DashboardHome", - path: '/dashboard', + path: "/dashboard", component: DashboardHome, children: [ { - path: '/dashboard/:id', + path: "/dashboard/:id", component: EmptyLayout, children: [ { - path: '', + path: "", component: Details, }, { - path: '/edit/:id', + path: "/edit/:id", component: EditMonitor, }, - ] + ], }, { - path: '/add', + path: "/add", component: EditMonitor, }, - ] + ], }, { - path: '/settings', + path: "/settings", component: Settings, }, ], @@ -63,13 +62,13 @@ const routes = [ }, { - path: '/setup', + path: "/setup", component: Setup, }, ] const router = createRouter({ - linkActiveClass: 'active', + linkActiveClass: "active", history: createWebHistory(), routes, }) @@ -78,18 +77,18 @@ const app = createApp({ mixins: [ socket, ], - render: ()=>h(App) + render: () => h(App), }) app.use(router) const options = { - position: "bottom-right" + position: "bottom-right", }; app.use(Toast, options); -app.component('font-awesome-icon', FontAwesomeIcon) +app.component("FontAwesomeIcon", FontAwesomeIcon) -app.mount('#app') +app.mount("#app") diff --git a/src/mixins/socket.js b/src/mixins/socket.js index f36a770e..85cb4e2c 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -1,6 +1,6 @@ -import {io} from "socket.io-client"; -import { useToast } from 'vue-toastification' import dayjs from "dayjs"; +import { io } from "socket.io-client"; +import { useToast } from "vue-toastification"; const toast = useToast() let socket; @@ -33,7 +33,7 @@ export default { }, created() { - window.addEventListener('resize', this.onResize); + window.addEventListener("resize", this.onResize); let wsHost; const env = process.env.NODE_ENV || "production"; @@ -44,18 +44,18 @@ export default { } socket = io(wsHost, { - transports: ['websocket'] + transports: ["websocket"], }); socket.on("connect_error", (err) => { console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`); }); - socket.on('info', (info) => { + socket.on("info", (info) => { this.info = info; }); - socket.on('setup', (monitorID, data) => { + socket.on("setup", (monitorID, data) => { this.$router.push("/setup") }); @@ -73,11 +73,11 @@ export default { this.monitorList = data; }); - socket.on('notificationList', (data) => { + socket.on("notificationList", (data) => { this.notificationList = data; }); - socket.on('heartbeat', (data) => { + socket.on("heartbeat", (data) => { if (! (data.monitorID in this.heartbeatList)) { this.heartbeatList[data.monitorID] = []; } @@ -100,7 +100,6 @@ export default { toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`); } - if (! (data.monitorID in this.importantHeartbeatList)) { this.importantHeartbeatList[data.monitorID] = []; } @@ -109,7 +108,7 @@ export default { } }); - socket.on('heartbeatList', (monitorID, data) => { + socket.on("heartbeatList", (monitorID, data) => { if (! (monitorID in this.heartbeatList)) { this.heartbeatList[monitorID] = data; } else { @@ -117,19 +116,19 @@ export default { } }); - socket.on('avgPing', (monitorID, data) => { + socket.on("avgPing", (monitorID, data) => { this.avgPingList[monitorID] = data }); - socket.on('uptime', (monitorID, type, data) => { + socket.on("uptime", (monitorID, type, data) => { this.uptimeList[`${monitorID}_${type}`] = data }); - socket.on('certInfo', (monitorID, data) => { + socket.on("certInfo", (monitorID, data) => { this.certInfoList[monitorID] = JSON.parse(data) }); - socket.on('importantHeartbeatList', (monitorID, data) => { + socket.on("importantHeartbeatList", (monitorID, data) => { if (! (monitorID in this.importantHeartbeatList)) { this.importantHeartbeatList[monitorID] = data; } else { @@ -137,12 +136,12 @@ export default { } }); - socket.on('disconnect', () => { + socket.on("disconnect", () => { console.log("disconnect") this.socket.connected = false; }); - socket.on('connect', () => { + socket.on("connect", () => { console.log("connect") this.socket.connectCount++; this.socket.connected = true; @@ -201,7 +200,7 @@ export default { this.loggedIn = true; // Trigger Chrome Save Password - history.pushState({}, '') + history.pushState({}, "") } callback(res) @@ -254,9 +253,8 @@ export default { if (this.userTimezone === "auto") { return dayjs.tz.guess() - } else { - return this.userTimezone } + return this.userTimezone }, @@ -276,7 +274,7 @@ export default { let unknown = { text: "Unknown", - color: "secondary" + color: "secondary", } for (let monitorID in this.lastHeartbeatList) { @@ -287,17 +285,17 @@ export default { } else if (lastHeartBeat.status === 1) { result[monitorID] = { text: "Up", - color: "primary" + color: "primary", }; } else if (lastHeartBeat.status === 0) { result[monitorID] = { text: "Down", - color: "danger" + color: "danger", }; } else if (lastHeartBeat.status === 2) { result[monitorID] = { text: "Pending", - color: "warning" + color: "warning", }; } else { result[monitorID] = unknown; @@ -305,23 +303,23 @@ export default { } return result; - } + }, }, watch: { // Reload the SPA if the server version is changed. "info.version"(to, from) { - if (from && from !== to) { - window.location.reload() - } + if (from && from !== to) { + window.location.reload() + } }, remember() { localStorage.remember = (this.remember) ? "1" : "0" - } + }, - } + }, } diff --git a/src/pages/Dashboard.vue b/src/pages/Dashboard.vue index d24d9fc0..7df7ec7d 100644 --- a/src/pages/Dashboard.vue +++ b/src/pages/Dashboard.vue @@ -1,36 +1,33 @@ <template> - <div class="container-fluid"> <div class="row"> <div class="col-12 col-md-5 col-xl-4"> <div v-if="! $root.isMobile"> - <router-link to="/add" class="btn btn-primary"><font-awesome-icon icon="plus" /> Add New Monitor</router-link> + <router-link to="/add" class="btn btn-primary"> + <font-awesome-icon icon="plus" /> Add New Monitor + </router-link> </div> - <div class="shadow-box list mb-4" v-if="showList"> - - <div class="text-center mt-3" v-if="Object.keys($root.monitorList).length === 0"> - No Monitors, please <router-link to="/add">add one</router-link>. + <div v-if="showList" class="shadow-box list mb-4"> + <div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3"> + No Monitors, please <router-link to="/add"> + add one + </router-link>. </div> - <router-link :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" v-for="(item, index) in sortedMonitorList" @click="$root.cancelActiveList" :key="index"> - + <router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" @click="$root.cancelActiveList"> <div class="row"> - <div class="col-6 col-md-8 small-padding"> - + <div class="col-6 col-md-8 small-padding"> <div class="info"> <Uptime :monitor="item" type="24" :pill="true" /> {{ item.name }} </div> - </div> - <div class="col-6 col-md-4"> + <div class="col-6 col-md-4"> <HeartbeatBar size="small" :monitor-id="item.id" /> </div> </div> - </router-link> - </div> </div> <div class="col-12 col-md-7 col-xl-8"> @@ -38,7 +35,6 @@ </div> </div> </div> - </template> <script> @@ -49,12 +45,10 @@ import Uptime from "../components/Uptime.vue"; export default { components: { Uptime, - HeartbeatBar + HeartbeatBar, }, data() { - return { - - } + return {} }, computed: { sortedMonitorList() { @@ -94,8 +88,8 @@ export default { methods: { monitorURL(id) { return "/dashboard/" + id; - } - } + }, + }, } </script> diff --git a/src/pages/DashboardHome.vue b/src/pages/DashboardHome.vue index 1fa784ff..657a3a49 100644 --- a/src/pages/DashboardHome.vue +++ b/src/pages/DashboardHome.vue @@ -1,15 +1,16 @@ <template> - <div v-if="$route.name === 'DashboardHome'"> - <h1 class="mb-3">Quick Stats</h1> + <h1 class="mb-3"> + Quick Stats + </h1> <div class="shadow-box big-padding text-center"> <div class="row"> - <div class="col"> + <div class="col"> <h3>Up</h3> <span class="num">{{ stats.up }}</span> </div> - <div class="col"> + <div class="col"> <h3>Down</h3> <span class="num text-danger">{{ stats.down }}</span> </div> @@ -22,16 +23,16 @@ <span class="num text-secondary">{{ stats.pause }}</span> </div> </div> - <div class="row" v-if="false"> + <div v-if="false" class="row"> <div class="col-3"> <h3>Uptime</h3> <p>(24-hour)</p> - <span class="num"></span> + <span class="num" /> </div> <div class="col-3"> <h3>Uptime</h3> <p>(30-day)</p> - <span class="num"></span> + <span class="num" /> </div> </div> </div> @@ -39,32 +40,35 @@ <div class="shadow-box" style="margin-top: 25px;"> <table class="table table-borderless table-hover"> <thead> - <tr> - <th>Name</th> - <th>Status</th> - <th>DateTime</th> - <th>Message</th> - </tr> + <tr> + <th>Name</th> + <th>Status</th> + <th>DateTime</th> + <th>Message</th> + </tr> </thead> <tbody> - <tr v-for="(beat, index) in displayedRecords" :key="index"> - <td>{{ beat.name }}</td> - <td><Status :status="beat.status" /></td> - <td><Datetime :value="beat.time" /></td> - <td>{{ beat.msg }}</td> - </tr> + <tr v-for="(beat, index) in displayedRecords" :key="index"> + <td>{{ beat.name }}</td> + <td><Status :status="beat.status" /></td> + <td><Datetime :value="beat.time" /></td> + <td>{{ beat.msg }}</td> + </tr> - <tr v-if="importantHeartBeatList.length === 0"> - <td colspan="4">No important events</td> - </tr> + <tr v-if="importantHeartBeatList.length === 0"> + <td colspan="4"> + No important events + </td> + </tr> </tbody> </table> <div class="d-flex justify-content-center kuma_pagination"> <pagination v-model="page" - :records=importantHeartBeatList.length - :per-page="perPage" /> + :records="importantHeartBeatList.length" + :per-page="perPage" + /> </div> </div> </div> @@ -111,7 +115,7 @@ export default { } else if (beat.status === 0) { result.down++; } else if (beat.status === 2) { - result.up++; + result.up++; } else { result.unknown++; } @@ -127,7 +131,7 @@ export default { let result = []; for (let monitorID in this.$root.importantHeartbeatList) { - let list = this.$root.importantHeartbeatList[monitorID] + let list = this.$root.importantHeartbeatList[monitorID] result = result.concat(list); } @@ -142,11 +146,11 @@ export default { result.sort((a, b) => { if (a.time > b.time) { return -1; - } else if (a.time < b.time) { + } if (a.time < b.time) { return 1; - } else { - return 0; } + + return 0; }); this.heartBeatList = result; @@ -159,7 +163,7 @@ export default { const endIndex = startIndex + this.perPage; return this.heartBeatList.slice(startIndex, endIndex); }, - } + }, } </script> diff --git a/src/pages/Details.vue b/src/pages/Details.vue index b1f39613..a83e238f 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -1,20 +1,28 @@ <template> <h1> {{ monitor.name }}</h1> <p class="url"> - <a :href="monitor.url" target="_blank" v-if="monitor.type === 'http' || monitor.type === 'keyword' ">{{ monitor.url }}</a> + <a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a> <span v-if="monitor.type === 'port'">TCP Ping {{ monitor.hostname }}:{{ monitor.port }}</span> <span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span> <span v-if="monitor.type === 'keyword'"> - <br /> + <br> <span>Keyword:</span> <span style="color: black">{{ monitor.keyword }}</span> </span> </p> <div class="functions"> - <button class="btn btn-light" @click="pauseDialog" v-if="monitor.active"><font-awesome-icon icon="pause" /> Pause</button> - <button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active"><font-awesome-icon icon="pause" /> Resume</button> - <router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary"><font-awesome-icon icon="edit" /> Edit</router-link> - <button class="btn btn-danger" @click="deleteDialog"><font-awesome-icon icon="trash" /> Delete</button> + <button v-if="monitor.active" class="btn btn-light" @click="pauseDialog"> + <font-awesome-icon icon="pause" /> Pause + </button> + <button v-if="! monitor.active" class="btn btn-primary" @click="resumeMonitor"> + <font-awesome-icon icon="pause" /> Resume + </button> + <router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary"> + <font-awesome-icon icon="edit" /> Edit + </router-link> + <button class="btn btn-danger" @click="deleteDialog"> + <font-awesome-icon icon="trash" /> Delete + </button> </div> <div class="shadow-box"> @@ -52,40 +60,50 @@ <span class="num"><Uptime :monitor="monitor" type="720" /></span> </div> - <div class="col" v-if="certInfo"> + <div v-if="certInfo" class="col"> <h4>CertExp.</h4> <p>(<Datetime :value="certInfo.validTo" date-only />)</p> - <span class="num" > - <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{certInfo.daysRemaining}} days</a> + <span class="num"> + <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ certInfo.daysRemaining }} days</a> </span> </div> </div> </div> - <div class="shadow-box big-padding text-center" v-if="showCertInfoBox"> + <div v-if="showCertInfoBox" class="shadow-box big-padding text-center"> <div class="row"> <div class="col"> <h4>Certificate Info</h4> <table class="text-start"> <tbody> <tr class="my-3"> - <td class="px-3">Valid: </td> + <td class="px-3"> + Valid: + </td> <td>{{ certInfo.valid }}</td> </tr> <tr class="my-3"> - <td class="px-3">Valid To: </td> + <td class="px-3"> + Valid To: + </td> <td><Datetime :value="certInfo.validTo" /></td> </tr> <tr class="my-3"> - <td class="px-3">Days Remaining: </td> + <td class="px-3"> + Days Remaining: + </td> <td>{{ certInfo.daysRemaining }}</td> </tr> <tr class="my-3"> - <td class="px-3">Issuer: </td> + <td class="px-3"> + Issuer: + </td> <td>{{ certInfo.issuer }}</td> </tr> <tr class="my-3"> - <td class="px-3">Fingerprint: </td> + <td class="px-3"> + Fingerprint: + </td> <td>{{ certInfo.fingerprint }}</td> </tr> </tbody> @@ -111,7 +129,9 @@ </tr> <tr v-if="importantHeartBeatList.length === 0"> - <td colspan="3">No important events</td> + <td colspan="3"> + No important events + </td> </tr> </tbody> </table> @@ -119,8 +139,9 @@ <div class="d-flex justify-content-center kuma_pagination"> <pagination v-model="page" - :records=importantHeartBeatList.length - :per-page="perPage" /> + :records="importantHeartBeatList.length" + :per-page="perPage" + /> </div> </div> @@ -128,13 +149,13 @@ Are you sure want to pause? </Confirm> - <Confirm ref="confirmDelete" btnStyle="btn-danger" @yes="deleteMonitor"> + <Confirm ref="confirmDelete" btn-style="btn-danger" @yes="deleteMonitor"> Are you sure want to delete this monitor? </Confirm> </template> <script> -import { useToast } from 'vue-toastification' +import { useToast } from "vue-toastification" const toast = useToast() import Confirm from "../components/Confirm.vue"; import HeartbeatBar from "../components/HeartbeatBar.vue"; @@ -153,9 +174,6 @@ export default { Confirm, Status, Pagination, - }, - mounted() { - }, data() { return { @@ -170,9 +188,9 @@ export default { pingTitle() { if (this.monitor.type === "http") { return "Response" - } else { - return "Ping" } + + return "Ping" }, monitor() { @@ -183,50 +201,52 @@ export default { lastHeartBeat() { if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { return this.$root.lastHeartbeatList[this.monitor.id] - } else { - return { status: -1 } + } + + return { + status: -1, } }, ping() { if (this.lastHeartBeat.ping || this.lastHeartBeat.ping === 0) { return this.lastHeartBeat.ping; - } else { - return "N/A" } + + return "N/A" }, avgPing() { if (this.$root.avgPingList[this.monitor.id] || this.$root.avgPingList[this.monitor.id] === 0) { return this.$root.avgPingList[this.monitor.id]; - } else { - return "N/A" } + + return "N/A" }, importantHeartBeatList() { if (this.$root.importantHeartbeatList[this.monitor.id]) { this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id]; return this.$root.importantHeartbeatList[this.monitor.id] - } else { - return []; } + + return []; }, status() { if (this.$root.statusList[this.monitor.id]) { return this.$root.statusList[this.monitor.id] - } else { - return { } } + + return { } }, certInfo() { if (this.$root.certInfoList[this.monitor.id]) { return this.$root.certInfoList[this.monitor.id] - } else { - return null } + + return null }, showCertInfoBox() { @@ -238,6 +258,9 @@ export default { const endIndex = startIndex + this.perPage; return this.heartBeatList.slice(startIndex, endIndex); }, + }, + mounted() { + }, methods: { testNotification() { @@ -274,9 +297,9 @@ export default { toast.error(res.msg); } }) - } + }, - } + }, } </script> diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 75d7d4b9..b79c6cb1 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1,85 +1,102 @@ <template> - <h1 class="mb-3">{{ pageName }}</h1> + <h1 class="mb-3"> + {{ pageName }} + </h1> <form @submit.prevent="submit"> - - <div class="shadow-box"> - <div class="row"> - <div class="col-md-6"> - <h2>General</h2> + <div class="shadow-box"> + <div class="row"> + <div class="col-md-6"> + <h2>General</h2> <div class="mb-3"> <label for="type" class="form-label">Monitor Type</label> - <select class="form-select" aria-label="Default select example" id="type" v-model="monitor.type"> - <option value="http">HTTP(s)</option> - <option value="port">TCP Port</option> - <option value="ping">Ping</option> - <option value="keyword">HTTP(s) - Keyword</option> + <select id="type" v-model="monitor.type" class="form-select" aria-label="Default select example"> + <option value="http"> + HTTP(s) + </option> + <option value="port"> + TCP Port + </option> + <option value="ping"> + Ping + </option> + <option value="keyword"> + HTTP(s) - Keyword + </option> </select> </div> <div class="mb-3"> <label for="name" class="form-label">Friendly Name</label> - <input type="text" class="form-control" id="name" v-model="monitor.name" required> + <input id="name" v-model="monitor.name" type="text" class="form-control" required> </div> - <div class="mb-3" v-if="monitor.type === 'http' || monitor.type === 'keyword' "> + <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="mb-3"> <label for="url" class="form-label">URL</label> - <input type="url" class="form-control" id="url" v-model="monitor.url" pattern="https?://.+" required> + <input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required> </div> - <div class="mb-3" v-if="monitor.type === 'keyword' "> + <div v-if="monitor.type === 'keyword' " class="mb-3"> <label for="keyword" class="form-label">Keyword</label> - <input type="text" class="form-control" id="keyword" v-model="monitor.keyword" required> - <div class="form-text">Search keyword in plain html or JSON response and it is case-sensitive</div> + <input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required> + <div class="form-text"> + Search keyword in plain html or JSON response and it is case-sensitive + </div> </div> - <div class="mb-3" v-if="monitor.type === 'port' || monitor.type === 'ping' "> + <div v-if="monitor.type === 'port' || monitor.type === 'ping' " class="mb-3"> <label for="hostname" class="form-label">Hostname</label> - <input type="text" class="form-control" id="hostname" v-model="monitor.hostname" required> + <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required> </div> - <div class="mb-3" v-if="monitor.type === 'port' "> + <div v-if="monitor.type === 'port' " class="mb-3"> <label for="port" class="form-label">Port</label> - <input type="number" class="form-control" id="port" v-model="monitor.port" required min="0" max="65535" step="1"> + <input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1"> </div> <div class="mb-3"> <label for="interval" class="form-label">Heartbeat Interval (Every {{ monitor.interval }} seconds)</label> - <input type="number" class="form-control" id="interval" v-model="monitor.interval" required min="20" step="1"> + <input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1"> </div> <div class="mb-3"> <label for="maxRetries" class="form-label">Retries</label> - <input type="number" class="form-control" id="maxRetries" v-model="monitor.maxretries" required min="0" step="1"> - <div class="form-text">Maximum retries before the service is marked as down and a notification is sent</div> + <input id="maxRetries" v-model="monitor.maxretries" type="number" class="form-control" required min="0" step="1"> + <div class="form-text"> + Maximum retries before the service is marked as down and a notification is sent + </div> </div> <div> - <button class="btn btn-primary" type="submit" :disabled="processing">Save</button> + <button class="btn btn-primary" type="submit" :disabled="processing"> + Save + </button> </div> - - </div> - - <div class="col-md-6"> - - <div class="mt-3" v-if="$root.isMobile"></div> - - <h2>Notifications</h2> - <p v-if="$root.notificationList.length === 0">Not available, please setup.</p> - - <div class="form-check form-switch mb-3" :key="notification.id" v-for="notification in $root.notificationList"> - <input class="form-check-input" type="checkbox" :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]"> - - <label class="form-check-label" :for=" 'notification' + notification.id"> - {{ notification.name }} - <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a> - </label> </div> - <button class="btn btn-primary me-2" @click="$refs.notificationDialog.show()" type="button">Setup Notification</button> + <div class="col-md-6"> + <div v-if="$root.isMobile" class="mt-3" /> + + <h2>Notifications</h2> + <p v-if="$root.notificationList.length === 0"> + Not available, please setup. + </p> + + <div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch mb-3"> + <input :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]" class="form-check-input" type="checkbox"> + + <label class="form-check-label" :for=" 'notification' + notification.id"> + {{ notification.name }} + <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a> + </label> + </div> + + <button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()"> + Setup Notification + </button> + </div> </div> </div> - </div> </form> <NotificationDialog ref="notificationDialog" /> @@ -87,15 +104,12 @@ <script> import NotificationDialog from "../components/NotificationDialog.vue"; -import { useToast } from 'vue-toastification' +import { useToast } from "vue-toastification" const toast = useToast() export default { components: { - NotificationDialog - }, - mounted() { - this.init(); + NotificationDialog, }, data() { return { @@ -114,7 +128,15 @@ export default { }, isEdit() { return this.$route.path.startsWith("/edit"); - } + }, + }, + watch: { + "$route.fullPath" () { + this.init(); + }, + }, + mounted() { + this.init(); }, methods: { init() { @@ -161,12 +183,7 @@ export default { this.$root.toastRes(res) }) } - } - }, - watch: { - '$route.fullPath' () { - this.init(); - } + }, }, } </script> diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index f4bf156a..d1d3599b 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -1,22 +1,29 @@ <template> - <h1 class="mb-3">Settings</h1> + <h1 class="mb-3"> + Settings + </h1> <div class="shadow-box"> <div class="row"> - <div class="col-md-6"> <h2>General</h2> <form class="mb-3" @submit.prevent="saveGeneral"> <div class="mb-3"> <label for="timezone" class="form-label">Timezone</label> - <select class="form-select" id="timezone" v-model="$root.userTimezone"> - <option value="auto">Auto: {{ guessTimezone }}</option> - <option v-for="(timezone, index) in timezoneList" :value="timezone.value" :key="index">{{ timezone.name }}</option> + <select id="timezone" v-model="$root.userTimezone" class="form-select"> + <option value="auto"> + Auto: {{ guessTimezone }} + </option> + <option v-for="(timezone, index) in timezoneList" :key="index" :value="timezone.value"> + {{ timezone.name }} + </option> </select> </div> <div> - <button class="btn btn-primary" type="submit">Save</button> + <button class="btn btn-primary" type="submit"> + Save + </button> </div> </form> @@ -24,51 +31,58 @@ <form class="mb-3" @submit.prevent="savePassword"> <div class="mb-3"> <label for="current-password" class="form-label">Current Password</label> - <input type="password" class="form-control" id="current-password" required v-model="password.currentPassword"> + <input id="current-password" v-model="password.currentPassword" type="password" class="form-control" required> </div> <div class="mb-3"> <label for="new-password" class="form-label">New Password</label> - <input type="password" class="form-control" id="new-password" required v-model="password.newPassword"> + <input id="new-password" v-model="password.newPassword" type="password" class="form-control" required> </div> <div class="mb-3"> <label for="repeat-new-password" class="form-label">Repeat New Password</label> - <input type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" id="repeat-new-password" required v-model="password.repeatNewPassword"> + <input id="repeat-new-password" v-model="password.repeatNewPassword" type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" required> <div class="invalid-feedback"> The repeat password does not match. </div> </div> <div> - <button class="btn btn-primary" type="submit">Update Password</button> + <button class="btn btn-primary" type="submit"> + Update Password + </button> </div> </form> <div> - <button class="btn btn-danger" @click="$root.logout">Logout</button> + <button class="btn btn-danger" @click="$root.logout"> + Logout + </button> </div> </div> <div class="col-md-6"> - - <div class="mt-3" v-if="$root.isMobile"></div> + <div v-if="$root.isMobile" class="mt-3" /> <h2>Notifications</h2> - <p v-if="$root.notificationList.length === 0">Not available, please setup.</p> - <p v-else>Please assign a notification to monitor(s) to get it to work.</p> + <p v-if="$root.notificationList.length === 0"> + Not available, please setup. + </p> + <p v-else> + Please assign a notification to monitor(s) to get it to work. + </p> <ul class="list-group mb-3" style="border-radius: 1rem;"> - <li class="list-group-item" v-for="(notification, index) in $root.notificationList" :key="index"> - {{ notification.name }}<br /> + <li v-for="(notification, index) in $root.notificationList" :key="index" class="list-group-item"> + {{ notification.name }}<br> <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a> </li> </ul> - <button class="btn btn-primary me-2" @click="$refs.notificationDialog.show()" type="button">Setup Notification</button> + <button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()"> + Setup Notification + </button> </div> - - </div> </div> @@ -77,18 +91,18 @@ <script> import dayjs from "dayjs"; -import utc from 'dayjs/plugin/utc' -import timezone from 'dayjs/plugin/timezone' +import utc from "dayjs/plugin/utc" +import timezone from "dayjs/plugin/timezone" import NotificationDialog from "../components/NotificationDialog.vue"; dayjs.extend(utc) dayjs.extend(timezone) -import {timezoneList} from "../util-frontend"; -import { useToast } from 'vue-toastification' +import { timezoneList } from "../util-frontend"; +import { useToast } from "vue-toastification" const toast = useToast() export default { components: { - NotificationDialog + NotificationDialog, }, data() { return { @@ -100,9 +114,14 @@ export default { currentPassword: "", newPassword: "", repeatNewPassword: "", - } + }, } }, + watch: { + "password.repeatNewPassword"() { + this.invalidPassword = false; + }, + }, mounted() { @@ -130,11 +149,6 @@ export default { } }, }, - watch: { - "password.repeatNewPassword"() { - this.invalidPassword = false; - } - } } </script> diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index fa68945d..85a8147c 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -2,38 +2,42 @@ <div class="form-container"> <div class="form"> <form @submit.prevent="submit"> - <div> - <object width="64" height="64" data="/icon.svg"></object> - <div style="font-size: 28px; font-weight: bold; margin-top: 5px;">Uptime Kuma</div> + <object width="64" height="64" data="/icon.svg" /> + <div style="font-size: 28px; font-weight: bold; margin-top: 5px;"> + Uptime Kuma + </div> </div> - <p class="mt-3">Create your admin account</p> + <p class="mt-3"> + Create your admin account + </p> <div class="form-floating"> - <input type="text" class="form-control" id="floatingInput" placeholder="Username" v-model="username" required> + <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username" required> <label for="floatingInput">Username</label> </div> <div class="form-floating mt-3"> - <input type="password" class="form-control" id="floatingPassword" placeholder="Password" v-model="password" required> + <input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password" required> <label for="floatingPassword">Password</label> </div> <div class="form-floating mt-3"> - <input type="password" class="form-control" id="repeat" placeholder="Repeat Password" v-model="repeatPassword" required> + <input id="repeat" v-model="repeatPassword" type="password" class="form-control" placeholder="Repeat Password" required> <label for="repeat">Repeat Password</label> </div> - <button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing">Create</button> - + <button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing"> + Create + </button> </form> </div> </div> </template> <script> -import { useToast } from 'vue-toastification' +import { useToast } from "vue-toastification" const toast = useToast() export default { @@ -70,8 +74,8 @@ export default { this.$router.push("/") } }) - } - } + }, + }, } </script> diff --git a/src/util-frontend.js b/src/util-frontend.js index 3cfab2df..d80d4385 100644 --- a/src/util-frontend.js +++ b/src/util-frontend.js @@ -1,6 +1,6 @@ import dayjs from "dayjs"; -import utc from 'dayjs/plugin/utc' -import timezone from 'dayjs/plugin/timezone' +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; dayjs.extend(utc) dayjs.extend(timezone) @@ -18,11 +18,12 @@ export function ucfirst(str) { return firstLetter.toUpperCase() + str.substr(1); } - function getTimezoneOffset(timeZone) { const now = new Date(); - const tzString = now.toLocaleString('en-US', { timeZone }); - const localString = now.toLocaleString('en-US'); + const tzString = now.toLocaleString("en-US", { + timeZone, + }); + const localString = now.toLocaleString("en-US"); const diff = (Date.parse(localString) - Date.parse(tzString)) / 3600000; const offset = diff + now.getTimezoneOffset() / 60; return -offset; @@ -31,355 +32,354 @@ function getTimezoneOffset(timeZone) { // From: https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript // TODO: Move to separate file const aryIannaTimeZones = [ - 'Europe/Andorra', - 'Asia/Dubai', - 'Asia/Kabul', - 'Europe/Tirane', - 'Asia/Yerevan', - 'Antarctica/Casey', - 'Antarctica/Davis', - 'Antarctica/Mawson', - 'Antarctica/Palmer', - 'Antarctica/Rothera', - 'Antarctica/Syowa', - 'Antarctica/Troll', - 'Antarctica/Vostok', - 'America/Argentina/Buenos_Aires', - 'America/Argentina/Cordoba', - 'America/Argentina/Salta', - 'America/Argentina/Jujuy', - 'America/Argentina/Tucuman', - 'America/Argentina/Catamarca', - 'America/Argentina/La_Rioja', - 'America/Argentina/San_Juan', - 'America/Argentina/Mendoza', - 'America/Argentina/San_Luis', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Ushuaia', - 'Pacific/Pago_Pago', - 'Europe/Vienna', - 'Australia/Lord_Howe', - 'Antarctica/Macquarie', - 'Australia/Hobart', - 'Australia/Currie', - 'Australia/Melbourne', - 'Australia/Sydney', - 'Australia/Broken_Hill', - 'Australia/Brisbane', - 'Australia/Lindeman', - 'Australia/Adelaide', - 'Australia/Darwin', - 'Australia/Perth', - 'Australia/Eucla', - 'Asia/Baku', - 'America/Barbados', - 'Asia/Dhaka', - 'Europe/Brussels', - 'Europe/Sofia', - 'Atlantic/Bermuda', - 'Asia/Brunei', - 'America/La_Paz', - 'America/Noronha', - 'America/Belem', - 'America/Fortaleza', - 'America/Recife', - 'America/Araguaina', - 'America/Maceio', - 'America/Bahia', - 'America/Sao_Paulo', - 'America/Campo_Grande', - 'America/Cuiaba', - 'America/Santarem', - 'America/Porto_Velho', - 'America/Boa_Vista', - 'America/Manaus', - 'America/Eirunepe', - 'America/Rio_Branco', - 'America/Nassau', - 'Asia/Thimphu', - 'Europe/Minsk', - 'America/Belize', - 'America/St_Johns', - 'America/Halifax', - 'America/Glace_Bay', - 'America/Moncton', - 'America/Goose_Bay', - 'America/Blanc-Sablon', - 'America/Toronto', - 'America/Nipigon', - 'America/Thunder_Bay', - 'America/Iqaluit', - 'America/Pangnirtung', - 'America/Atikokan', - 'America/Winnipeg', - 'America/Rainy_River', - 'America/Resolute', - 'America/Rankin_Inlet', - 'America/Regina', - 'America/Swift_Current', - 'America/Edmonton', - 'America/Cambridge_Bay', - 'America/Yellowknife', - 'America/Inuvik', - 'America/Creston', - 'America/Dawson_Creek', - 'America/Fort_Nelson', - 'America/Vancouver', - 'America/Whitehorse', - 'America/Dawson', - 'Indian/Cocos', - 'Europe/Zurich', - 'Africa/Abidjan', - 'Pacific/Rarotonga', - 'America/Santiago', - 'America/Punta_Arenas', - 'Pacific/Easter', - 'Asia/Shanghai', - 'Asia/Urumqi', - 'America/Bogota', - 'America/Costa_Rica', - 'America/Havana', - 'Atlantic/Cape_Verde', - 'America/Curacao', - 'Indian/Christmas', - 'Asia/Nicosia', - 'Asia/Famagusta', - 'Europe/Prague', - 'Europe/Berlin', - 'Europe/Copenhagen', - 'America/Santo_Domingo', - 'Africa/Algiers', - 'America/Guayaquil', - 'Pacific/Galapagos', - 'Europe/Tallinn', - 'Africa/Cairo', - 'Africa/El_Aaiun', - 'Europe/Madrid', - 'Africa/Ceuta', - 'Atlantic/Canary', - 'Europe/Helsinki', - 'Pacific/Fiji', - 'Atlantic/Stanley', - 'Pacific/Chuuk', - 'Pacific/Pohnpei', - 'Pacific/Kosrae', - 'Atlantic/Faroe', - 'Europe/Paris', - 'Europe/London', - 'Asia/Tbilisi', - 'America/Cayenne', - 'Africa/Accra', - 'Europe/Gibraltar', - 'America/Godthab', - 'America/Danmarkshavn', - 'America/Scoresbysund', - 'America/Thule', - 'Europe/Athens', - 'Atlantic/South_Georgia', - 'America/Guatemala', - 'Pacific/Guam', - 'Africa/Bissau', - 'America/Guyana', - 'Asia/Hong_Kong', - 'America/Tegucigalpa', - 'America/Port-au-Prince', - 'Europe/Budapest', - 'Asia/Jakarta', - 'Asia/Pontianak', - 'Asia/Makassar', - 'Asia/Jayapura', - 'Europe/Dublin', - 'Asia/Jerusalem', - 'Asia/Kolkata', - 'Indian/Chagos', - 'Asia/Baghdad', - 'Asia/Tehran', - 'Atlantic/Reykjavik', - 'Europe/Rome', - 'America/Jamaica', - 'Asia/Amman', - 'Asia/Tokyo', - 'Africa/Nairobi', - 'Asia/Bishkek', - 'Pacific/Tarawa', - 'Pacific/Enderbury', - 'Pacific/Kiritimati', - 'Asia/Pyongyang', - 'Asia/Seoul', - 'Asia/Almaty', - 'Asia/Qyzylorda', - 'Asia/Aqtobe', - 'Asia/Aqtau', - 'Asia/Atyrau', - 'Asia/Oral', - 'Asia/Beirut', - 'Asia/Colombo', - 'Africa/Monrovia', - 'Europe/Vilnius', - 'Europe/Luxembourg', - 'Europe/Riga', - 'Africa/Tripoli', - 'Africa/Casablanca', - 'Europe/Monaco', - 'Europe/Chisinau', - 'Pacific/Majuro', - 'Pacific/Kwajalein', - 'Asia/Yangon', - 'Asia/Ulaanbaatar', - 'Asia/Hovd', - 'Asia/Choibalsan', - 'Asia/Macau', - 'America/Martinique', - 'Europe/Malta', - 'Indian/Mauritius', - 'Indian/Maldives', - 'America/Mexico_City', - 'America/Cancun', - 'America/Merida', - 'America/Monterrey', - 'America/Matamoros', - 'America/Mazatlan', - 'America/Chihuahua', - 'America/Ojinaga', - 'America/Hermosillo', - 'America/Tijuana', - 'America/Bahia_Banderas', - 'Asia/Kuala_Lumpur', - 'Asia/Kuching', - 'Africa/Maputo', - 'Africa/Windhoek', - 'Pacific/Noumea', - 'Pacific/Norfolk', - 'Africa/Lagos', - 'America/Managua', - 'Europe/Amsterdam', - 'Europe/Oslo', - 'Asia/Kathmandu', - 'Pacific/Nauru', - 'Pacific/Niue', - 'Pacific/Auckland', - 'Pacific/Chatham', - 'America/Panama', - 'America/Lima', - 'Pacific/Tahiti', - 'Pacific/Marquesas', - 'Pacific/Gambier', - 'Pacific/Port_Moresby', - 'Pacific/Bougainville', - 'Asia/Manila', - 'Asia/Karachi', - 'Europe/Warsaw', - 'America/Miquelon', - 'Pacific/Pitcairn', - 'America/Puerto_Rico', - 'Asia/Gaza', - 'Asia/Hebron', - 'Europe/Lisbon', - 'Atlantic/Madeira', - 'Atlantic/Azores', - 'Pacific/Palau', - 'America/Asuncion', - 'Asia/Qatar', - 'Indian/Reunion', - 'Europe/Bucharest', - 'Europe/Belgrade', - 'Europe/Kaliningrad', - 'Europe/Moscow', - 'Europe/Simferopol', - 'Europe/Kirov', - 'Europe/Astrakhan', - 'Europe/Volgograd', - 'Europe/Saratov', - 'Europe/Ulyanovsk', - 'Europe/Samara', - 'Asia/Yekaterinburg', - 'Asia/Omsk', - 'Asia/Novosibirsk', - 'Asia/Barnaul', - 'Asia/Tomsk', - 'Asia/Novokuznetsk', - 'Asia/Krasnoyarsk', - 'Asia/Irkutsk', - 'Asia/Chita', - 'Asia/Yakutsk', - 'Asia/Khandyga', - 'Asia/Vladivostok', - 'Asia/Ust-Nera', - 'Asia/Magadan', - 'Asia/Sakhalin', - 'Asia/Srednekolymsk', - 'Asia/Kamchatka', - 'Asia/Anadyr', - 'Asia/Riyadh', - 'Pacific/Guadalcanal', - 'Indian/Mahe', - 'Africa/Khartoum', - 'Europe/Stockholm', - 'Asia/Singapore', - 'America/Paramaribo', - 'Africa/Juba', - 'Africa/Sao_Tome', - 'America/El_Salvador', - 'Asia/Damascus', - 'America/Grand_Turk', - 'Africa/Ndjamena', - 'Indian/Kerguelen', - 'Asia/Bangkok', - 'Asia/Dushanbe', - 'Pacific/Fakaofo', - 'Asia/Dili', - 'Asia/Ashgabat', - 'Africa/Tunis', - 'Pacific/Tongatapu', - 'Europe/Istanbul', - 'America/Port_of_Spain', - 'Pacific/Funafuti', - 'Asia/Taipei', - 'Europe/Kiev', - 'Europe/Uzhgorod', - 'Europe/Zaporozhye', - 'Pacific/Wake', - 'America/New_York', - 'America/Detroit', - 'America/Kentucky/Louisville', - 'America/Kentucky/Monticello', - 'America/Indiana/Indianapolis', - 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', - 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', - 'America/Indiana/Vevay', - 'America/Chicago', - 'America/Indiana/Tell_City', - 'America/Indiana/Knox', - 'America/Menominee', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/North_Dakota/Beulah', - 'America/Denver', - 'America/Boise', - 'America/Phoenix', - 'America/Los_Angeles', - 'America/Anchorage', - 'America/Juneau', - 'America/Sitka', - 'America/Metlakatla', - 'America/Yakutat', - 'America/Nome', - 'America/Adak', - 'Pacific/Honolulu', - 'America/Montevideo', - 'Asia/Samarkand', - 'Asia/Tashkent', - 'America/Caracas', - 'Asia/Ho_Chi_Minh', - 'Pacific/Efate', - 'Pacific/Wallis', - 'Pacific/Apia', - 'Africa/Johannesburg', + "Europe/Andorra", + "Asia/Dubai", + "Asia/Kabul", + "Europe/Tirane", + "Asia/Yerevan", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/Mawson", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "America/Argentina/Buenos_Aires", + "America/Argentina/Cordoba", + "America/Argentina/Salta", + "America/Argentina/Jujuy", + "America/Argentina/Tucuman", + "America/Argentina/Catamarca", + "America/Argentina/La_Rioja", + "America/Argentina/San_Juan", + "America/Argentina/Mendoza", + "America/Argentina/San_Luis", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Ushuaia", + "Pacific/Pago_Pago", + "Europe/Vienna", + "Australia/Lord_Howe", + "Antarctica/Macquarie", + "Australia/Hobart", + "Australia/Currie", + "Australia/Melbourne", + "Australia/Sydney", + "Australia/Broken_Hill", + "Australia/Brisbane", + "Australia/Lindeman", + "Australia/Adelaide", + "Australia/Darwin", + "Australia/Perth", + "Australia/Eucla", + "Asia/Baku", + "America/Barbados", + "Asia/Dhaka", + "Europe/Brussels", + "Europe/Sofia", + "Atlantic/Bermuda", + "Asia/Brunei", + "America/La_Paz", + "America/Noronha", + "America/Belem", + "America/Fortaleza", + "America/Recife", + "America/Araguaina", + "America/Maceio", + "America/Bahia", + "America/Sao_Paulo", + "America/Campo_Grande", + "America/Cuiaba", + "America/Santarem", + "America/Porto_Velho", + "America/Boa_Vista", + "America/Manaus", + "America/Eirunepe", + "America/Rio_Branco", + "America/Nassau", + "Asia/Thimphu", + "Europe/Minsk", + "America/Belize", + "America/St_Johns", + "America/Halifax", + "America/Glace_Bay", + "America/Moncton", + "America/Goose_Bay", + "America/Blanc-Sablon", + "America/Toronto", + "America/Nipigon", + "America/Thunder_Bay", + "America/Iqaluit", + "America/Pangnirtung", + "America/Atikokan", + "America/Winnipeg", + "America/Rainy_River", + "America/Resolute", + "America/Rankin_Inlet", + "America/Regina", + "America/Swift_Current", + "America/Edmonton", + "America/Cambridge_Bay", + "America/Yellowknife", + "America/Inuvik", + "America/Creston", + "America/Dawson_Creek", + "America/Fort_Nelson", + "America/Vancouver", + "America/Whitehorse", + "America/Dawson", + "Indian/Cocos", + "Europe/Zurich", + "Africa/Abidjan", + "Pacific/Rarotonga", + "America/Santiago", + "America/Punta_Arenas", + "Pacific/Easter", + "Asia/Shanghai", + "Asia/Urumqi", + "America/Bogota", + "America/Costa_Rica", + "America/Havana", + "Atlantic/Cape_Verde", + "America/Curacao", + "Indian/Christmas", + "Asia/Nicosia", + "Asia/Famagusta", + "Europe/Prague", + "Europe/Berlin", + "Europe/Copenhagen", + "America/Santo_Domingo", + "Africa/Algiers", + "America/Guayaquil", + "Pacific/Galapagos", + "Europe/Tallinn", + "Africa/Cairo", + "Africa/El_Aaiun", + "Europe/Madrid", + "Africa/Ceuta", + "Atlantic/Canary", + "Europe/Helsinki", + "Pacific/Fiji", + "Atlantic/Stanley", + "Pacific/Chuuk", + "Pacific/Pohnpei", + "Pacific/Kosrae", + "Atlantic/Faroe", + "Europe/Paris", + "Europe/London", + "Asia/Tbilisi", + "America/Cayenne", + "Africa/Accra", + "Europe/Gibraltar", + "America/Godthab", + "America/Danmarkshavn", + "America/Scoresbysund", + "America/Thule", + "Europe/Athens", + "Atlantic/South_Georgia", + "America/Guatemala", + "Pacific/Guam", + "Africa/Bissau", + "America/Guyana", + "Asia/Hong_Kong", + "America/Tegucigalpa", + "America/Port-au-Prince", + "Europe/Budapest", + "Asia/Jakarta", + "Asia/Pontianak", + "Asia/Makassar", + "Asia/Jayapura", + "Europe/Dublin", + "Asia/Jerusalem", + "Asia/Kolkata", + "Indian/Chagos", + "Asia/Baghdad", + "Asia/Tehran", + "Atlantic/Reykjavik", + "Europe/Rome", + "America/Jamaica", + "Asia/Amman", + "Asia/Tokyo", + "Africa/Nairobi", + "Asia/Bishkek", + "Pacific/Tarawa", + "Pacific/Enderbury", + "Pacific/Kiritimati", + "Asia/Pyongyang", + "Asia/Seoul", + "Asia/Almaty", + "Asia/Qyzylorda", + "Asia/Aqtobe", + "Asia/Aqtau", + "Asia/Atyrau", + "Asia/Oral", + "Asia/Beirut", + "Asia/Colombo", + "Africa/Monrovia", + "Europe/Vilnius", + "Europe/Luxembourg", + "Europe/Riga", + "Africa/Tripoli", + "Africa/Casablanca", + "Europe/Monaco", + "Europe/Chisinau", + "Pacific/Majuro", + "Pacific/Kwajalein", + "Asia/Yangon", + "Asia/Ulaanbaatar", + "Asia/Hovd", + "Asia/Choibalsan", + "Asia/Macau", + "America/Martinique", + "Europe/Malta", + "Indian/Mauritius", + "Indian/Maldives", + "America/Mexico_City", + "America/Cancun", + "America/Merida", + "America/Monterrey", + "America/Matamoros", + "America/Mazatlan", + "America/Chihuahua", + "America/Ojinaga", + "America/Hermosillo", + "America/Tijuana", + "America/Bahia_Banderas", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Africa/Maputo", + "Africa/Windhoek", + "Pacific/Noumea", + "Pacific/Norfolk", + "Africa/Lagos", + "America/Managua", + "Europe/Amsterdam", + "Europe/Oslo", + "Asia/Kathmandu", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Auckland", + "Pacific/Chatham", + "America/Panama", + "America/Lima", + "Pacific/Tahiti", + "Pacific/Marquesas", + "Pacific/Gambier", + "Pacific/Port_Moresby", + "Pacific/Bougainville", + "Asia/Manila", + "Asia/Karachi", + "Europe/Warsaw", + "America/Miquelon", + "Pacific/Pitcairn", + "America/Puerto_Rico", + "Asia/Gaza", + "Asia/Hebron", + "Europe/Lisbon", + "Atlantic/Madeira", + "Atlantic/Azores", + "Pacific/Palau", + "America/Asuncion", + "Asia/Qatar", + "Indian/Reunion", + "Europe/Bucharest", + "Europe/Belgrade", + "Europe/Kaliningrad", + "Europe/Moscow", + "Europe/Simferopol", + "Europe/Kirov", + "Europe/Astrakhan", + "Europe/Volgograd", + "Europe/Saratov", + "Europe/Ulyanovsk", + "Europe/Samara", + "Asia/Yekaterinburg", + "Asia/Omsk", + "Asia/Novosibirsk", + "Asia/Barnaul", + "Asia/Tomsk", + "Asia/Novokuznetsk", + "Asia/Krasnoyarsk", + "Asia/Irkutsk", + "Asia/Chita", + "Asia/Yakutsk", + "Asia/Khandyga", + "Asia/Vladivostok", + "Asia/Ust-Nera", + "Asia/Magadan", + "Asia/Sakhalin", + "Asia/Srednekolymsk", + "Asia/Kamchatka", + "Asia/Anadyr", + "Asia/Riyadh", + "Pacific/Guadalcanal", + "Indian/Mahe", + "Africa/Khartoum", + "Europe/Stockholm", + "Asia/Singapore", + "America/Paramaribo", + "Africa/Juba", + "Africa/Sao_Tome", + "America/El_Salvador", + "Asia/Damascus", + "America/Grand_Turk", + "Africa/Ndjamena", + "Indian/Kerguelen", + "Asia/Bangkok", + "Asia/Dushanbe", + "Pacific/Fakaofo", + "Asia/Dili", + "Asia/Ashgabat", + "Africa/Tunis", + "Pacific/Tongatapu", + "Europe/Istanbul", + "America/Port_of_Spain", + "Pacific/Funafuti", + "Asia/Taipei", + "Europe/Kiev", + "Europe/Uzhgorod", + "Europe/Zaporozhye", + "Pacific/Wake", + "America/New_York", + "America/Detroit", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Indiana/Indianapolis", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Vevay", + "America/Chicago", + "America/Indiana/Tell_City", + "America/Indiana/Knox", + "America/Menominee", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/North_Dakota/Beulah", + "America/Denver", + "America/Boise", + "America/Phoenix", + "America/Los_Angeles", + "America/Anchorage", + "America/Juneau", + "America/Sitka", + "America/Metlakatla", + "America/Yakutat", + "America/Nome", + "America/Adak", + "Pacific/Honolulu", + "America/Montevideo", + "Asia/Samarkand", + "Asia/Tashkent", + "America/Caracas", + "Asia/Ho_Chi_Minh", + "Pacific/Efate", + "Pacific/Wallis", + "Pacific/Apia", + "Africa/Johannesburg", ]; - export function timezoneList() { let result = []; @@ -404,12 +404,14 @@ export function timezoneList() { result.sort((a, b) => { if (a.time > b.time) { return 1; - } else if (b.time > a.time) { - return -1; - } else { - return 0; } + + if (b.time > a.time) { + return -1; + } + + return 0; }) return result; -}; +}