diff --git a/.github/workflows/conflict_labeler.yml b/.github/workflows/conflict_labeler.yml new file mode 100644 index 000000000..10dd831aa --- /dev/null +++ b/.github/workflows/conflict_labeler.yml @@ -0,0 +1,26 @@ +name: Merge Conflict Labeler + +on: + push: + branches: + - master + pull_request_target: + branches: + - master + types: [synchronize] + +jobs: + label: + name: Labeling + runs-on: ubuntu-latest + if: ${{ github.repository == 'louislam/uptime-kuma' }} + permissions: + contents: read + pull-requests: write + steps: + - name: Apply label + uses: eps1lon/actions-label-merge-conflict@v3 + with: + dirtyLabel: 'needs:resolve-merge-conflict' + removeOnDirtyLabel: 'needs:resolve-merge-conflict' + repoToken: '${{ secrets.GITHUB_TOKEN }}' diff --git a/db/knex_migrations/2023-12-20-0000-alter-status-page.js b/db/knex_migrations/2023-12-20-0000-alter-status-page.js new file mode 100644 index 000000000..61ef5d607 --- /dev/null +++ b/db/knex_migrations/2023-12-20-0000-alter-status-page.js @@ -0,0 +1,12 @@ +exports.up = function (knex) { + return knex.schema + .alterTable("status_page", function (table) { + table.integer("auto_refresh_interval").defaultTo(300).unsigned(); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("status_page", function (table) { + table.dropColumn("auto_refresh_interval"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index aef2e42ef..74ab764a1 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -824,15 +824,6 @@ class Monitor extends BeanModel { bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1", mysqlPassword); bean.status = UP; bean.ping = dayjs().valueOf() - startTime; - } else if (this.type === "mongodb") { - let startTime = dayjs().valueOf(); - - await mongodbPing(this.databaseConnectionString); - - bean.msg = ""; - bean.status = UP; - bean.ping = dayjs().valueOf() - startTime; - } else if (this.type === "radius") { let startTime = dayjs().valueOf(); @@ -863,7 +854,7 @@ class Monitor extends BeanModel { } else if (this.type === "redis") { let startTime = dayjs().valueOf(); - bean.msg = await redisPingAsync(this.databaseConnectionString); + bean.msg = await redisPingAsync(this.databaseConnectionString, !this.ignoreTls); bean.status = UP; bean.ping = dayjs().valueOf() - startTime; diff --git a/server/model/status_page.js b/server/model/status_page.js index 235582981..528d1dd49 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -238,6 +238,7 @@ class StatusPage extends BeanModel { description: this.description, icon: this.getIcon(), theme: this.theme, + autoRefreshInterval: this.autoRefreshInterval, published: !!this.published, showTags: !!this.show_tags, domainNameList: this.getDomainNameList(), @@ -260,6 +261,7 @@ class StatusPage extends BeanModel { title: this.title, description: this.description, icon: this.getIcon(), + autoRefreshInterval: this.autoRefreshInterval, theme: this.theme, published: !!this.published, showTags: !!this.show_tags, diff --git a/server/monitor-types/mongodb.js b/server/monitor-types/mongodb.js new file mode 100644 index 000000000..fd3a1dc98 --- /dev/null +++ b/server/monitor-types/mongodb.js @@ -0,0 +1,65 @@ +const { MonitorType } = require("./monitor-type"); +const { UP } = require("../../src/util"); +const { MongoClient } = require("mongodb"); +const jsonata = require("jsonata"); + +class MongodbMonitorType extends MonitorType { + + name = "mongodb"; + + /** + * @inheritdoc + */ + async check(monitor, heartbeat, _server) { + let command = { "ping": 1 }; + if (monitor.databaseQuery) { + command = JSON.parse(monitor.databaseQuery); + } + + let result = await this.runMongodbCommand(monitor.databaseConnectionString, command); + + if (result["ok"] !== 1) { + throw new Error("MongoDB command failed"); + } else { + heartbeat.msg = "Command executed successfully"; + } + + if (monitor.jsonPath) { + let expression = jsonata(monitor.jsonPath); + result = await expression.evaluate(result); + if (result) { + heartbeat.msg = "Command executed successfully and the jsonata expression produces a result."; + } else { + throw new Error("Queried value not found."); + } + } + + if (monitor.expectedValue) { + if (result.toString() === monitor.expectedValue) { + heartbeat.msg = "Command executed successfully and expected value was found"; + } else { + throw new Error("Query executed, but value is not equal to expected value, value was: [" + JSON.stringify(result) + "]"); + } + } + + heartbeat.status = UP; + } + + /** + * Connect to and run MongoDB command on a MongoDB database + * @param {string} connectionString The database connection string + * @param {object} command MongoDB command to run on the database + * @returns {Promise<(string[] | object[] | object)>} Response from + * server + */ + async runMongodbCommand(connectionString, command) { + let client = await MongoClient.connect(connectionString); + let result = await client.db().command(command); + await client.close(); + return result; + } +} + +module.exports = { + MongodbMonitorType, +}; diff --git a/server/notification-providers/bitrix24.js b/server/notification-providers/bitrix24.js new file mode 100644 index 000000000..ba12126c5 --- /dev/null +++ b/server/notification-providers/bitrix24.js @@ -0,0 +1,31 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { UP } = require("../../src/util"); + +class Bitrix24 extends NotificationProvider { + name = "Bitrix24"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + + try { + const params = { + user_id: notification.bitrix24UserID, + message: "[B]Uptime Kuma[/B]", + "ATTACH[COLOR]": (heartbeatJSON ?? {})["status"] === UP ? "#b73419" : "#67b518", + "ATTACH[BLOCKS][0][MESSAGE]": msg + }; + + await axios.get(`${notification.bitrix24WebhookURL}/im.notify.system.add.json`, { params }); + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Bitrix24; diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js index f24d47252..129e334b0 100644 --- a/server/notification-providers/discord.js +++ b/server/notification-providers/discord.js @@ -13,6 +13,10 @@ class Discord extends NotificationProvider { try { const discordDisplayName = notification.discordUsername || "Uptime Kuma"; + const webhookUrl = new URL(notification.discordWebhookUrl); + if (notification.discordChannelType === "postToThread") { + webhookUrl.searchParams.append("thread_id", notification.threadId); + } // If heartbeatJSON is null, assume we're testing. if (heartbeatJSON == null) { @@ -20,7 +24,12 @@ class Discord extends NotificationProvider { username: discordDisplayName, content: msg, }; - await axios.post(notification.discordWebhookUrl, discordtestdata); + + if (notification.discordChannelType === "createNewForumPost") { + discordtestdata.thread_name = notification.postName; + } + + await axios.post(webhookUrl.toString(), discordtestdata); return okMsg; } @@ -72,12 +81,14 @@ class Discord extends NotificationProvider { ], }], }; - + if (notification.discordChannelType === "createNewForumPost") { + discorddowndata.thread_name = notification.postName; + } if (notification.discordPrefixMessage) { discorddowndata.content = notification.discordPrefixMessage; } - await axios.post(notification.discordWebhookUrl, discorddowndata); + await axios.post(webhookUrl.toString(), discorddowndata); return okMsg; } else if (heartbeatJSON["status"] === UP) { @@ -108,11 +119,15 @@ class Discord extends NotificationProvider { }], }; + if (notification.discordChannelType === "createNewForumPost") { + discordupdata.thread_name = notification.postName; + } + if (notification.discordPrefixMessage) { discordupdata.content = notification.discordPrefixMessage; } - await axios.post(notification.discordWebhookUrl, discordupdata); + await axios.post(webhookUrl.toString(), discordupdata); return okMsg; } } catch (error) { diff --git a/server/notification-providers/flashduty.js b/server/notification-providers/flashduty.js index 83ef27b5c..c340ed06f 100644 --- a/server/notification-providers/flashduty.js +++ b/server/notification-providers/flashduty.js @@ -62,6 +62,15 @@ class FlashDuty extends NotificationProvider { * @returns {string} Success message */ async postNotification(notification, title, body, monitorInfo, eventStatus) { + let labels = { + resource: this.genMonitorUrl(monitorInfo), + check: monitorInfo.name, + }; + if (monitorInfo.tags && monitorInfo.tags.length > 0) { + for (let tag of monitorInfo.tags) { + labels[tag.name] = tag.value; + } + } const options = { method: "POST", url: "https://api.flashcat.cloud/event/push/alert/standard?integration_key=" + notification.flashdutyIntegrationKey, @@ -71,9 +80,7 @@ class FlashDuty extends NotificationProvider { title, event_status: eventStatus || "Info", alert_key: String(monitorInfo.id) || Math.random().toString(36).substring(7), - labels: monitorInfo?.tags?.reduce((acc, item) => ({ ...acc, - [item.name]: item.value - }), { resource: this.genMonitorUrl(monitorInfo) }), + labels, } }; diff --git a/server/notification.js b/server/notification.js index 03bd368ed..efb5e408a 100644 --- a/server/notification.js +++ b/server/notification.js @@ -5,6 +5,7 @@ const AlertNow = require("./notification-providers/alertnow"); const AliyunSms = require("./notification-providers/aliyun-sms"); const Apprise = require("./notification-providers/apprise"); const Bark = require("./notification-providers/bark"); +const Bitrix24 = require("./notification-providers/bitrix24"); const ClickSendSMS = require("./notification-providers/clicksendsms"); const CallMeBot = require("./notification-providers/call-me-bot"); const SMSC = require("./notification-providers/smsc"); @@ -83,6 +84,7 @@ class Notification { new AliyunSms(), new Apprise(), new Bark(), + new Bitrix24(), new ClickSendSMS(), new CallMeBot(), new SMSC(), diff --git a/server/server.js b/server/server.js index d64de2391..73d00c502 100644 --- a/server/server.js +++ b/server/server.js @@ -149,6 +149,7 @@ const apicache = require("./modules/apicache"); const { resetChrome } = require("./monitor-types/real-browser-monitor-type"); const { EmbeddedMariaDB } = require("./embedded-mariadb"); const { SetupDatabase } = require("./setup-database"); +const { chartSocketHandler } = require("./socket-handlers/chart-socket-handler"); app.use(express.json()); @@ -1530,6 +1531,7 @@ let needSetup = false; apiKeySocketHandler(socket); remoteBrowserSocketHandler(socket); generalSocketHandler(socket, server); + chartSocketHandler(socket); log.debug("server", "added all socket handlers"); diff --git a/server/socket-handlers/chart-socket-handler.js b/server/socket-handlers/chart-socket-handler.js new file mode 100644 index 000000000..654db0e73 --- /dev/null +++ b/server/socket-handlers/chart-socket-handler.js @@ -0,0 +1,38 @@ +const { checkLogin } = require("../util-server"); +const { UptimeCalculator } = require("../uptime-calculator"); +const { log } = require("../../src/util"); + +module.exports.chartSocketHandler = (socket) => { + socket.on("getMonitorChartData", async (monitorID, period, callback) => { + try { + checkLogin(socket); + + log.debug("monitor", `Get Monitor Chart Data: ${monitorID} User ID: ${socket.userID}`); + + if (period == null) { + throw new Error("Invalid period."); + } + + let uptimeCalculator = await UptimeCalculator.getUptimeCalculator(monitorID); + + let data; + if (period <= 24) { + data = uptimeCalculator.getDataArray(period * 60, "minute"); + } else if (period <= 720) { + data = uptimeCalculator.getDataArray(period, "hour"); + } else { + data = uptimeCalculator.getDataArray(period / 24, "day"); + } + + callback({ + ok: true, + data, + }); + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); +}; diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 53128914c..0804da15d 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -155,6 +155,7 @@ module.exports.statusPageSocketHandler = (socket) => { statusPage.title = config.title; statusPage.description = config.description; statusPage.icon = config.logo; + statusPage.autoRefreshInterval = config.autoRefreshInterval, statusPage.theme = config.theme; //statusPage.published = ; //statusPage.search_engine_index = ; @@ -280,6 +281,7 @@ module.exports.statusPageSocketHandler = (socket) => { statusPage.title = title; statusPage.theme = "auto"; statusPage.icon = ""; + statusPage.autoRefreshInterval = 300; await R.store(statusPage); callback({ diff --git a/server/uptime-calculator.js b/server/uptime-calculator.js index 3735a79a3..55059e960 100644 --- a/server/uptime-calculator.js +++ b/server/uptime-calculator.js @@ -290,7 +290,7 @@ class UptimeCalculator { dailyStatBean.pingMax = dailyData.maxPing; { // eslint-disable-next-line no-unused-vars - const { up, down, avgPing, minPing, maxPing, ...extras } = dailyData; + const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = dailyData; if (Object.keys(extras).length > 0) { dailyStatBean.extras = JSON.stringify(extras); } @@ -305,7 +305,7 @@ class UptimeCalculator { hourlyStatBean.pingMax = hourlyData.maxPing; { // eslint-disable-next-line no-unused-vars - const { up, down, avgPing, minPing, maxPing, ...extras } = hourlyData; + const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = hourlyData; if (Object.keys(extras).length > 0) { hourlyStatBean.extras = JSON.stringify(extras); } @@ -320,7 +320,7 @@ class UptimeCalculator { minutelyStatBean.pingMax = minutelyData.maxPing; { // eslint-disable-next-line no-unused-vars - const { up, down, avgPing, minPing, maxPing, ...extras } = minutelyData; + const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = minutelyData; if (Object.keys(extras).length > 0) { minutelyStatBean.extras = JSON.stringify(extras); } diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index bcf497b58..6ab5f6c26 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -113,6 +113,7 @@ class UptimeKumaServer { UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing(); UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType(); UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType(); + UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType(); // Allow all CORS origins (polling) in development let cors = undefined; @@ -516,3 +517,4 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor const { TailscalePing } = require("./monitor-types/tailscale-ping"); const { DnsMonitorType } = require("./monitor-types/dns"); const { MqttMonitorType } = require("./monitor-types/mqtt"); +const { MongodbMonitorType } = require("./monitor-types/mongodb"); diff --git a/server/util-server.js b/server/util-server.js index 098cd798d..9709e9e97 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -11,7 +11,6 @@ const mssql = require("mssql"); const { Client } = require("pg"); const postgresConParse = require("pg-connection-string").parse; const mysql = require("mysql2"); -const { MongoClient } = require("mongodb"); const { NtlmClient } = require("axios-ntlm"); const { Settings } = require("./settings"); const grpc = require("@grpc/grpc-js"); @@ -438,24 +437,6 @@ exports.mysqlQuery = function (connectionString, query, password = undefined) { }); }; -/** - * Connect to and ping a MongoDB database - * @param {string} connectionString The database connection string - * @returns {Promise<(string[] | object[] | object)>} Response from - * server - */ -exports.mongodbPing = async function (connectionString) { - let client = await MongoClient.connect(connectionString); - let dbPing = await client.db().command({ ping: 1 }); - await client.close(); - - if (dbPing["ok"] === 1) { - return "UP"; - } else { - throw Error("failed"); - } -}; - /** * Query radius server * @param {string} hostname Hostname of radius server @@ -506,12 +487,16 @@ exports.radius = function ( /** * Redis server ping * @param {string} dsn The redis connection string - * @returns {Promise} Response from redis server + * @param {boolean} rejectUnauthorized If false, allows unverified server certificates. + * @returns {Promise} Response from server */ -exports.redisPingAsync = function (dsn) { +exports.redisPingAsync = function (dsn, rejectUnauthorized) { return new Promise((resolve, reject) => { const client = redis.createClient({ - url: dsn + url: dsn, + socket: { + rejectUnauthorized + } }); client.on("error", (err) => { if (client.isOpen) { diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 09646d599..ebef7c9fb 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -114,6 +114,7 @@ export default { "AlertNow": "AlertNow", "apprise": this.$t("apprise"), "Bark": "Bark", + "Bitrix24": "Bitrix24", "clicksendsms": "ClickSend SMS", "CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)", "discord": "Discord", diff --git a/src/components/PingChart.vue b/src/components/PingChart.vue index c550b4b4f..60b0d3c9c 100644 --- a/src/components/PingChart.vue +++ b/src/components/PingChart.vue @@ -1,16 +1,24 @@ + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 52bebf83d..cb6a96bfb 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -3,6 +3,7 @@ import AlertNow from "./AlertNow.vue"; import AliyunSMS from "./AliyunSms.vue"; import Apprise from "./Apprise.vue"; import Bark from "./Bark.vue"; +import Bitrix24 from "./Bitrix24.vue"; import ClickSendSMS from "./ClickSendSMS.vue"; import CallMeBot from "./CallMeBot.vue"; import SMSC from "./SMSC.vue"; @@ -70,6 +71,7 @@ const NotificationFormList = { "AliyunSMS": AliyunSMS, "apprise": Apprise, "Bark": Bark, + "Bitrix24": Bitrix24, "clicksendsms": ClickSendSMS, "CallMeBot": CallMeBot, "smsc": SMSC, diff --git a/src/lang/en.json b/src/lang/en.json index c1af0fbc8..2b40c29a6 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -80,6 +80,7 @@ "resendDisabled": "Resend disabled", "retriesDescription": "Maximum retries before the service is marked as down and a notification is sent", "ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites", + "ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection", "upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.", "maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.", "Upside Down Mode": "Upside Down Mode", @@ -361,6 +362,8 @@ "Proxy": "Proxy", "Date Created": "Date Created", "Footer Text": "Footer Text", + "Refresh Interval": "Refresh Interval", + "Refresh Interval Description": "The status page will do a full site refresh every {0} seconds", "Show Powered By": "Show Powered By", "Domain Names": "Domain Names", "signedInDisp": "Signed in as {0}", @@ -527,6 +530,15 @@ "Bot Display Name": "Bot Display Name", "Prefix Custom Message": "Prefix Custom Message", "Hello @everyone is...": "Hello {'@'}everyone is…", + "Select message type": "Select message type", + "Send to channel": "Send to channel", + "Create new forum post": "Create new forum post", + "postToExistingThread": "Post to existing thread / forum post", + "forumPostName": "Forum post name", + "threadForumPostID": "Thread / Forum post ID", + "e.g. {discordThreadID}": "e.g. {discordThreadID}", + "whatHappensAtForumPost": "Create a new forum post. This does NOT post messages in existing post. To post in existing post use \"{option}\"", + "wayToGetDiscordThreadId": "Getting a thread / forum post id is similar to getting a channel id. Read more about how to get ids {0}", "wayToGetTeamsURL": "You can learn how to create a webhook URL {0}.", "wayToGetZohoCliqURL": "You can learn how to create a webhook URL {0}.", "needSignalAPI": "You need to have a signal client with REST API.", @@ -851,7 +863,7 @@ "noGroupMonitorMsg": "Not Available. Create a Group Monitor First.", "Close": "Close", "Request Body": "Request Body", - "wayToGetFlashDutyKey": "You can go to Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, add a 'Custom Event' to get a push address, copy the Integration Key in the address. For more information, please visit", + "wayToGetFlashDutyKey": "You can go to Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, add a 'Uptime Kuma' to get a push address, copy the Integration Key in the address. For more information, please visit", "FlashDuty Severity": "Severity", "nostrRelays": "Nostr relays", "nostrRelaysHelp": "One relay URL per line", @@ -862,6 +874,9 @@ "noOrBadCertificate": "No/Bad Certificate", "gamedigGuessPort": "Gamedig: Guess Port", "gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.", + "Bitrix24 Webhook URL": "Bitrix24 Webhook URL", + "wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}", + "bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.", "Saved.": "Saved.", "authUserInactiveOrDeleted": "The user is inactive or deleted.", "authInvalidToken": "Invalid Token.", @@ -893,6 +908,8 @@ "Browser Screenshot": "Browser Screenshot", "Zookeeper Host": "Zookeeper Host", "Zookeeper Timeout": "Zookeeper Connect Timeout (ms)", + "Command": "Command", + "mongodbCommandDescription": "Run a MongoDB command against the database. For information about the available commands check out the {documentation}", "wayToGetSevenIOApiKey": "Visit the dashboard under app.seven.io > developer > api key > the green add button", "senderSevenIO": "Sending number or name", "receiverSevenIO": "Receiving number", diff --git a/src/mixins/datetime.js b/src/mixins/datetime.js index 1946d1902..ca2d8f018 100644 --- a/src/mixins/datetime.js +++ b/src/mixins/datetime.js @@ -41,6 +41,33 @@ export default { return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss"); }, + /** + * Converts a Unix timestamp to a formatted date and time string. + * @param {number} value - The Unix timestamp to convert. + * @returns {string} The formatted date and time string. + */ + unixToDateTime(value) { + return dayjs.unix(value).tz(this.timezone).format("YYYY-MM-DD HH:mm:ss"); + }, + + /** + * Converts a Unix timestamp to a dayjs object. + * @param {number} value - The Unix timestamp to convert. + * @returns {dayjs.Dayjs} The dayjs object representing the given timestamp. + */ + unixToDayjs(value) { + return dayjs.unix(value).tz(this.timezone); + }, + + /** + * Converts the given value to a dayjs object. + * @param {string} value - the value to be converted + * @returns {dayjs.Dayjs} a dayjs object in the timezone of this instance + */ + toDayjs(value) { + return dayjs.utc(value).tz(this.timezone); + }, + /** * Get time for maintenance * @param {string | number | Date | dayjs.Dayjs} value Time to diff --git a/src/mixins/socket.js b/src/mixins/socket.js index a7a66fcab..150e39a25 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -673,6 +673,17 @@ export default { getMonitorBeats(monitorID, period, callback) { socket.emit("getMonitorBeats", monitorID, period, callback); }, + + /** + * Retrieves monitor chart data. + * @param {string} monitorID - The ID of the monitor. + * @param {number} period - The time period for the chart data, in hours. + * @param {socketCB} callback - The callback function to handle the chart data. + * @returns {void} + */ + getMonitorChartData(monitorID, period, callback) { + socket.emit("getMonitorChartData", monitorID, period, callback); + } }, computed: { diff --git a/src/pages/DashboardHome.vue b/src/pages/DashboardHome.vue index 3baf35a62..78bee91fc 100644 --- a/src/pages/DashboardHome.vue +++ b/src/pages/DashboardHome.vue @@ -9,15 +9,30 @@

{{ $t("Up") }}

- {{ $root.stats.up }} + + {{ $root.stats.up }} +

{{ $t("Down") }}

- {{ $root.stats.down }} + + {{ $root.stats.down }} +

{{ $t("Maintenance") }}

- {{ $root.stats.maintenance }} + + {{ $root.stats.maintenance }} +

{{ $t("Unknown") }}

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index be4f593cb..37b145923 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -450,6 +450,32 @@
+ + +
@@ -498,10 +524,10 @@
-
+
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index af2f028d7..1f67c22e2 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -34,6 +34,14 @@
+
+ + +
+ {{ $t("Refresh Interval Description", [config.autoRefreshInterval]) }} +
+
+