diff --git a/README.md b/README.md index bb6589c43..c15d79db0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec * Fancy, Reactive, Fast UI/UX. * Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284). * 20 seconds interval. +* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages) ## 🔧 How to Install diff --git a/dockerfile b/dockerfile index 8674b9993..3dabf513e 100644 --- a/dockerfile +++ b/dockerfile @@ -1,25 +1,30 @@ -# DON'T UPDATE TO alpine3.13, 1.14, see #41. -FROM node:14-alpine3.12 AS release +FROM node:14-bullseye-slim AS release WORKDIR /app +# install dependencies +RUN apt update && apt --yes install python3 python3-pip python3-dev git g++ make iputils-ping +RUN ln -s /usr/bin/python3 /usr/bin/python + # split the sqlite install here, so that it can caches the arm prebuilt -RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - npm install mapbox/node-sqlite3#593c9d && \ - apk del .build-deps && \ - rm -f /usr/bin/python +RUN npm install mapbox/node-sqlite3#593c9d # Install apprise -RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib +RUN apt --yes install python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib RUN pip3 --no-cache-dir install apprise && \ - rm -rf /root/.cache + rm -rf /root/.cache + +# additional package should be added here, since we don't want to re-compile the arm prebuilt again + +# add sqlite3 cli for debugging in the future +RUN apt --yes install sqlite3 + COPY . . RUN npm install --legacy-peer-deps && npm run build && npm prune EXPOSE 3001 VOLUME ["/app/data"] -HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js +HEALTHCHECK --interval=600s --timeout=130s --start-period=300s CMD node extra/healthcheck.js CMD ["node", "server/server.js"] FROM release AS nightly diff --git a/dockerfile-alpine b/dockerfile-alpine new file mode 100644 index 000000000..1dd6b6b53 --- /dev/null +++ b/dockerfile-alpine @@ -0,0 +1,26 @@ +# DON'T UPDATE TO alpine3.13, 1.14, see #41. +FROM node:14-alpine3.12 AS release +WORKDIR /app + +# split the sqlite install here, so that it can caches the arm prebuilt +RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + npm install mapbox/node-sqlite3#593c9d && \ + apk del .build-deps && \ + rm -f /usr/bin/python + +# Install apprise +RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib +RUN pip3 --no-cache-dir install apprise && \ + rm -rf /root/.cache + +COPY . . +RUN npm install --legacy-peer-deps && npm run build && npm prune + +EXPOSE 3001 +VOLUME ["/app/data"] +HEALTHCHECK --interval=600s --timeout=130s --start-period=300s CMD node extra/healthcheck.js +CMD ["node", "server/server.js"] + +FROM release AS nightly +RUN npm run mark-as-nightly diff --git a/dockerfile-debian b/dockerfile-debian deleted file mode 100644 index 7e0f79890..000000000 --- a/dockerfile-debian +++ /dev/null @@ -1,28 +0,0 @@ -# DON'T UPDATE TO alpine3.13, 1.14, see #41. -FROM node:14-bullseye AS release -WORKDIR /app - -RUN apt update -RUN apt --yes install python3 python3-pip python3-dev git g++ make -RUN ln -s /usr/bin/python3 /usr/bin/python - -# split the sqlite install here, so that it can caches the arm prebuilt -RUN npm install mapbox/node-sqlite3#593c9d - -# Install apprise -RUN apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib -RUN pip3 --no-cache-dir install apprise && \ - rm -rf /root/.cache - -RUN apt --yes install iputils-ping - -COPY . . -RUN npm install --legacy-peer-deps && npm run build && npm prune - -EXPOSE 3001 -VOLUME ["/app/data"] -HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js -CMD ["node", "server/server.js"] - -FROM release AS nightly -RUN npm run mark-as-nightly diff --git a/extra/healthcheck.js b/extra/healthcheck.js index c0b33b6ec..ed4e3eb23 100644 --- a/extra/healthcheck.js +++ b/extra/healthcheck.js @@ -1,19 +1,31 @@ -let http = require("http"); +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +let client; + +if (process.env.SSL_KEY && process.env.SSL_CERT) { + client = require("https"); +} else { + client = require("http"); +} + let options = { - host: "localhost", - port: "3001", - timeout: 2000, + host: process.env.HOST || "127.0.0.1", + port: parseInt(process.env.PORT) || 3001, + timeout: 120 * 1000, }; -let request = http.request(options, (res) => { - console.log(`STATUS: ${res.statusCode}`); - if (res.statusCode == 200) { + +let request = client.request(options, (res) => { + console.log(`Health Check OK [Res Code: ${res.statusCode}]`); + if (res.statusCode === 200) { process.exit(0); } else { process.exit(1); } }); + request.on("error", function (err) { - console.log("ERROR"); + console.error("Health Check ERROR"); process.exit(1); }); + request.end(); diff --git a/package.json b/package.json index 8bb962904..c04be03f4 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "build": "vite build", "vite-preview-dist": "vite preview --host", "build-docker": "npm run build-docker-alpine && npm run build-docker-debian", - "build-docker-alpine": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.3 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.5.3-alpine --target release . --push", - "build-docker-debian": "docker buildx build -f dockerfile-debian --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.5.3-debian --target release . --push", + "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.5.3-alpine --target release . --push", + "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.5.3-debian --target release . --push", "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", "build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "setup": "git checkout 1.5.3 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", diff --git a/server/client.js b/server/client.js new file mode 100644 index 000000000..4f28a2fa0 --- /dev/null +++ b/server/client.js @@ -0,0 +1,91 @@ +/* + * For Client Socket + */ +const { TimeLogger } = require("../src/util"); +const { R } = require("redbean-node"); +const { io } = require("./server"); + +async function sendNotificationList(socket) { + const timeLogger = new TimeLogger(); + + let result = []; + let list = await R.find("notification", " user_id = ? ", [ + socket.userID, + ]); + + for (let bean of list) { + result.push(bean.export()) + } + + io.to(socket.userID).emit("notificationList", result) + + timeLogger.print("Send Notification List"); + + return list; +} + +/** + * Send Heartbeat History list to socket + * @param toUser True = send to all browsers with the same user id, False = send to the current browser only + * @param overwrite Overwrite client-side's heartbeat list + */ +async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { + const timeLogger = new TimeLogger(); + + let list = await R.find("heartbeat", ` + monitor_id = ? + ORDER BY time DESC + LIMIT 100 + `, [ + monitorID, + ]) + + let result = []; + + for (let bean of list) { + result.unshift(bean.toJSON()); + } + + if (toUser) { + io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); + } else { + socket.emit("heartbeatList", monitorID, result, overwrite); + } + + timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`); +} + +/** + * Important Heart beat list (aka event list) + * @param socket + * @param monitorID + * @param toUser True = send to all browsers with the same user id, False = send to the current browser only + * @param overwrite Overwrite client-side's heartbeat list + */ +async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { + const timeLogger = new TimeLogger(); + + let list = await R.find("heartbeat", ` + monitor_id = ? + AND important = 1 + ORDER BY time DESC + LIMIT 500 + `, [ + monitorID, + ]) + + timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); + + if (toUser) { + io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite); + } else { + socket.emit("importantHeartbeatList", monitorID, list, overwrite); + } + +} + +module.exports = { + sendNotificationList, + sendImportantHeartbeatList, + sendHeartbeatList, +} diff --git a/server/server.js b/server/server.js index e4cd06eb5..7d9295354 100644 --- a/server/server.js +++ b/server/server.js @@ -82,7 +82,12 @@ if (sslKey && sslCert) { } const io = new Server(server); -app.use(express.json()) +module.exports.io = io; + +// Must be after io instantiation +const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList } = require("./client"); + +app.use(express.json()); /** * Total WebSocket client connected to server currently, no actual use @@ -585,6 +590,76 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); } }); + socket.on("clearEvents", async (monitorID, callback) => { + try { + checkLogin(socket) + + console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`) + + await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [ + "", + "0", + monitorID, + ]); + + await sendImportantHeartbeatList(socket, monitorID, true, true); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("clearHeartbeats", async (monitorID, callback) => { + try { + checkLogin(socket) + + console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`) + + await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [ + monitorID + ]); + + await sendHeartbeatList(socket, monitorID, true, true); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("clearStatistics", async (callback) => { + try { + checkLogin(socket) + + console.log(`Clear Statistics User ID: ${socket.userID}`) + + await R.exec("DELETE FROM heartbeat"); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + debug("added all socket handlers") // *************************** @@ -653,25 +728,6 @@ async function sendMonitorList(socket) { return list; } -async function sendNotificationList(socket) { - const timeLogger = new TimeLogger(); - - let result = []; - let list = await R.find("notification", " user_id = ? ", [ - socket.userID, - ]); - - for (let bean of list) { - result.push(bean.export()) - } - - io.to(socket.userID).emit("notificationList", result) - - timeLogger.print("Send Notification List"); - - return list; -} - async function afterLogin(socket, user) { socket.userID = user.id; socket.join(user.id) @@ -806,48 +862,6 @@ async function startMonitors() { } } -/** - * Send Heartbeat History list to socket - */ -async function sendHeartbeatList(socket, monitorID) { - const timeLogger = new TimeLogger(); - - let list = await R.find("heartbeat", ` - monitor_id = ? - ORDER BY time DESC - LIMIT 100 - `, [ - monitorID, - ]) - - let result = []; - - for (let bean of list) { - result.unshift(bean.toJSON()) - } - - socket.emit("heartbeatList", monitorID, result) - - timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`) -} - -async function sendImportantHeartbeatList(socket, monitorID) { - const timeLogger = new TimeLogger(); - - let list = await R.find("heartbeat", ` - monitor_id = ? - AND important = 1 - ORDER BY time DESC - LIMIT 500 - `, [ - monitorID, - ]) - - timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); - - socket.emit("importantHeartbeatList", monitorID, list) -} - async function shutdownFunction(signal) { console.log("Shutdown requested"); console.log("Called signal: " + signal); diff --git a/src/assets/vars.scss b/src/assets/vars.scss index 9d487eb26..2f4369832 100644 --- a/src/assets/vars.scss +++ b/src/assets/vars.scss @@ -16,3 +16,5 @@ $dark-border-color: #1d2634; $easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97); $easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); $easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86); + +$dropdown-border-radius: 0.5rem; diff --git a/src/components/HiddenInput.vue b/src/components/HiddenInput.vue new file mode 100644 index 000000000..7ec9f2e49 --- /dev/null +++ b/src/components/HiddenInput.vue @@ -0,0 +1,102 @@ + + + diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index fa63a376c..f289a7aa9 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -40,7 +40,7 @@