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..6dfdb23f0 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 * 100, }; -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/notification.js b/server/notification.js index 472012af7..f78401d9b 100644 --- a/server/notification.js +++ b/server/notification.js @@ -4,6 +4,8 @@ const FormData = require("form-data"); const nodemailer = require("nodemailer"); const child_process = require("child_process"); +const { UP, DOWN } = require("../src/util"); + class Notification { /** @@ -80,7 +82,7 @@ class Notification { } } else if (notification.type === "smtp") { - return await Notification.smtp(notification, msg) + return await Notification.smtp(notification, msg, heartbeatJSON) } else if (notification.type === "discord") { try { @@ -109,7 +111,7 @@ class Notification { } // If heartbeatJSON is not null, we go into the normal alerting loop. - if (heartbeatJSON["status"] == 0) { + if (heartbeatJSON["status"] == DOWN) { let discorddowndata = { username: discordDisplayName, embeds: [{ @@ -139,7 +141,7 @@ class Notification { await axios.post(notification.discordWebhookUrl, discorddowndata) return okMsg; - } else if (heartbeatJSON["status"] == 1) { + } else if (heartbeatJSON["status"] == UP) { let discordupdata = { username: discordDisplayName, embeds: [{ @@ -343,7 +345,7 @@ class Notification { const mattermostIconEmoji = notification.mattermosticonemo; const mattermostIconUrl = notification.mattermosticonurl; - if (heartbeatJSON["status"] == 0) { + if (heartbeatJSON["status"] == DOWN) { let mattermostdowndata = { username: mattermostUserName, text: "Uptime Kuma Alert", @@ -387,7 +389,7 @@ class Notification { mattermostdowndata ); return okMsg; - } else if (heartbeatJSON["status"] == 1) { + } else if (heartbeatJSON["status"] == UP) { let mattermostupdata = { username: mattermostUserName, text: "Uptime Kuma Alert", @@ -489,19 +491,19 @@ class Notification { return okMsg; } - if (heartbeatJSON["status"] == 0) { + if (heartbeatJSON["status"] == DOWN) { let downdata = { - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], } await axios.post(lunaseadevice, downdata) return okMsg; } - if (heartbeatJSON["status"] == 1) { + if (heartbeatJSON["status"] == UP) { let updata = { - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], } await axios.post(lunaseadevice, updata) return okMsg; @@ -527,18 +529,18 @@ class Notification { "body": "Testing Successful.", } await axios.post(pushbulletUrl, testdata, config) - } else if (heartbeatJSON["status"] == 0) { + } else if (heartbeatJSON["status"] == DOWN) { let downdata = { "type": "note", - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], } await axios.post(pushbulletUrl, downdata, config) - } else if (heartbeatJSON["status"] == 1) { + } else if (heartbeatJSON["status"] == UP) { let updata = { "type": "note", - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], } await axios.post(pushbulletUrl, updata, config) } @@ -566,7 +568,7 @@ class Notification { ] } await axios.post(lineAPIUrl, testMessage, config) - } else if (heartbeatJSON["status"] == 0) { + } else if (heartbeatJSON["status"] == DOWN) { let downMessage = { "to": notification.lineUserID, "messages": [ @@ -577,7 +579,7 @@ class Notification { ] } await axios.post(lineAPIUrl, downMessage, config) - } else if (heartbeatJSON["status"] == 1) { + } else if (heartbeatJSON["status"] == UP) { let upMessage = { "to": notification.lineUserID, "messages": [ @@ -634,7 +636,7 @@ class Notification { await R.trash(bean) } - static async smtp(notification, msg) { + static async smtp(notification, msg, heartbeatJSON = null) { const config = { host: notification.smtpHost, @@ -652,12 +654,17 @@ class Notification { let transporter = nodemailer.createTransport(config); + let bodyTextContent = msg; + if(heartbeatJSON) { + bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; + } + // send mail with defined transport object await transporter.sendMail({ from: `"Uptime Kuma" <${notification.smtpFrom}>`, to: notification.smtpTo, subject: msg, - text: msg, + text: bodyTextContent, }); return "Sent Successfully."; 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 @@ +<template> + <div class="input-group mb-3"> + <!-- + Hack - Disable Chrome save password + readonly + onfocus + https://stackoverflow.com/questions/41217019/how-to-prevent-a-browser-from-storing-passwords + --> + <input + v-model="model" + :type="visibility" + class="form-control" + :placeholder="placeholder" + :maxlength="maxlength" + :autocomplete="autocomplete" + :required="required" + :readonly="isReadOnly" + @focus="removeReadOnly" + > + + <a v-if="visibility == 'password'" class="btn btn-outline-primary" @click="showInput()"> + <font-awesome-icon icon="eye" /> + </a> + <a v-if="visibility == 'text'" class="btn btn-outline-primary" @click="hideInput()"> + <font-awesome-icon icon="eye-slash" /> + </a> + </div> +</template> + +<script> +export default { + props: { + modelValue: { + type: String, + default: "" + }, + placeholder: { + type: String, + default: "" + }, + maxlength: { + type: Number, + default: 255 + }, + autocomplete: { + type: Boolean, + }, + required: { + type: Boolean + }, + readonly: { + type: Boolean, + default: false, + }, + }, + data() { + return { + visibility: "password", + readOnlyValue: false, + } + }, + computed: { + model: { + get() { + return this.modelValue + }, + set(value) { + this.$emit("update:modelValue", value) + } + }, + isReadOnly() { + // Actually readonly from prop + if (this.readonly) { + return true; + } + + // Hack - Disable Chrome save password + return this.readOnlyValue; + } + }, + created() { + // Hack - Disable Chrome save password + if (this.autocomplete) { + this.readOnlyValue = "readonly"; + } + }, + methods: { + showInput() { + this.visibility = "text"; + }, + hideInput() { + this.visibility = "password"; + }, + + // Hack - Disable Chrome save password + removeReadOnly() { + if (this.autocomplete) { + this.readOnlyValue = false; + } + } + } +} +</script> diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 2fdb8fe18..080fb859e 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -40,7 +40,7 @@ <template v-if="notification.type === 'telegram'"> <div class="mb-3"> <label for="telegram-bot-token" class="form-label">Bot Token</label> - <input id="telegram-bot-token" v-model="notification.telegramBotToken" type="text" class="form-control" required> + <HiddenInput id="telegram-bot-token" v-model="notification.telegramBotToken" :required="true" :readonly="true"></HiddenInput> <div class="form-text"> You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. </div> @@ -130,7 +130,7 @@ <div class="mb-3"> <label for="password" class="form-label">Password</label> - <input id="password" v-model="notification.smtpPassword" type="password" class="form-control" autocomplete="false"> + <HiddenInput id="password" v-model="notification.smtpPassword" :required="true" autocomplete="false"></HiddenInput> </div> <div class="mb-3"> @@ -195,7 +195,7 @@ <template v-if="notification.type === 'gotify'"> <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> + <HiddenInput id="gotify-application-token" v-model="notification.gotifyapplicationToken" :required="true"></HiddenInput> </div> <div class="mb-3"> <label for="gotify-server-url" class="form-label">Server URL</label> @@ -306,13 +306,13 @@ <template v-if="notification.type === 'pushy'"> <div class="mb-3"> <label for="pushy-app-token" class="form-label">API_KEY</label> - <input id="pushy-app-token" v-model="notification.pushyAPIKey" type="text" class="form-control" required> + <HiddenInput id="pushy-app-token" v-model="notification.pushyAPIKey" :required="true"></HiddenInput> </div> <div class="mb-3"> <label for="pushy-user-key" class="form-label">USER_TOKEN</label> <div class="input-group mb-3"> - <input id="pushy-user-key" v-model="notification.pushyToken" type="text" class="form-control" required> + <HiddenInput id="pushy-user-key" v-model="notification.pushyToken" :required="true"></HiddenInput> </div> </div> <p style="margin-top: 8px;"> @@ -323,7 +323,7 @@ <template v-if="notification.type === 'octopush'"> <div class="mb-3"> <label for="octopush-key" class="form-label">API KEY</label> - <input id="octopush-key" v-model="notification.octopushAPIKey" type="text" class="form-control" required> + <HiddenInput id="octopush-key" v-model="notification.octopushAPIKey" :required="true"></HiddenInput> <label for="octopush-login" class="form-label">API LOGIN</label> <input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required> </div> @@ -354,9 +354,9 @@ <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 id="pushover-user" v-model="notification.pushoveruserkey" type="text" class="form-control" required> + <HiddenInput id="pushover-user" v-model="notification.pushoveruserkey" :required="true"></HiddenInput> <label for="pushover-app-token" class="form-label">Application Token<span style="color: red;"><sup>*</sup></span></label> - <input id="pushover-app-token" v-model="notification.pushoverapptoken" type="text" class="form-control" required> + <HiddenInput id="pushover-app-token" v-model="notification.pushoverapptoken" :required="true"></HiddenInput> <label for="pushover-device" class="form-label">Device</label> <input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control"> <label for="pushover-device" class="form-label">Message Title</label> @@ -442,7 +442,7 @@ <template v-if="notification.type === 'pushbullet'"> <div class="mb-3"> <label for="pushbullet-access-token" class="form-label">Access Token</label> - <input id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" type="text" class="form-control" required> + <HiddenInput id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" :required="true"></HiddenInput> </div> <p style="margin-top: 8px;"> @@ -453,7 +453,7 @@ <template v-if="notification.type === 'line'"> <div class="mb-3"> <label for="line-channel-access-token" class="form-label">Channel access token</label> - <input id="line-channel-access-token" v-model="notification.lineChannelAccessToken" type="text" class="form-control" required> + <HiddenInput id="line-channel-access-token" v-model="notification.lineChannelAccessToken" :required="true"></HiddenInput> </div> <div class="form-text"> Line Developers Console - <b>Basic Settings</b> @@ -497,11 +497,13 @@ import { ucfirst } from "../util.ts" import axios from "axios"; import { useToast } from "vue-toastification" import Confirm from "./Confirm.vue"; +import HiddenInput from "./HiddenInput.vue"; const toast = useToast() export default { components: { Confirm, + HiddenInput, }, props: {}, data() { diff --git a/src/icon.js b/src/icon.js index 960fa8c18..58583f0f8 100644 --- a/src/icon.js +++ b/src/icon.js @@ -1,10 +1,10 @@ import { library } from "@fortawesome/fontawesome-svg-core" -import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp } from "@fortawesome/free-solid-svg-icons" +import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons" //import { fa } from '@fortawesome/free-regular-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, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp); +library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash); export { FontAwesomeIcon } diff --git a/src/languages/da-DK.js b/src/languages/da-DK.js index 1f4f74faf..14444e190 100644 --- a/src/languages/da-DK.js +++ b/src/languages/da-DK.js @@ -109,6 +109,8 @@ export default { "Resource Record Type": "Resource Record Type", respTime: "Resp. Time (ms)", notAvailableShort: "N/A", + Create: "Create", + notAvailableShort: "N/A", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index 0db25023c..9945bdff6 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -114,5 +114,6 @@ export default { "Repeat Password": "Wiederhole das Passwort", "Resource Record Type": "Resource Record Type", respTime: "Antw. Zeit (ms)", - notAvailableShort: "N/A" + notAvailableShort: "N/A", + Create: "Erstellen", } diff --git a/src/languages/en.js b/src/languages/en.js index 58089b9ea..ff71164a5 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -112,6 +112,8 @@ export default { "Repeat Password": "Repeat Password", respTime: "Resp. Time (ms)", notAvailableShort: "N/A", + Create: "Create", + notAvailableShort: "N/A", "Clear Data": "Clear Data", Events: "Events", Heartbeats: "Heartbeats" diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js index 0f1183271..7c1ba094c 100644 --- a/src/languages/es-ES.js +++ b/src/languages/es-ES.js @@ -109,6 +109,8 @@ export default { "Repeat Password": "Repetir contraseña", respTime: "Tiempo de resp. (ms)", notAvailableShort: "N/A", + Create: "Create", + notAvailableShort: "N/A", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/fr.js b/src/languages/fr-FR.js similarity index 98% rename from src/languages/fr.js rename to src/languages/fr-FR.js index 7bfb4f9d3..efcdc2798 100644 --- a/src/languages/fr.js +++ b/src/languages/fr-FR.js @@ -66,7 +66,7 @@ export default { "Theme - Heartbeat Bar": "Voir les services surveillés", Normal: "Général", Bottom: "En dessous", - None: "Non", + None: "Rien", Timezone: "Fuseau Horaire", "Search Engine Visibility": "Visibilité par les moteurs de recherche", "Allow indexing": "Autoriser l'indexation par des moteurs de recherche", @@ -109,6 +109,8 @@ export default { "Repeat Password": "Répéter le mot de passe", respTime: "Temps de réponse (ms)", notAvailableShort: "N/A", + Create: "Créer", + notAvailableShort: "N/A", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/ja.js b/src/languages/ja.js index 86c5b2030..b029280cb 100644 --- a/src/languages/ja.js +++ b/src/languages/ja.js @@ -109,6 +109,8 @@ export default { "Repeat Password": "Repeat Password", respTime: "Resp. Time (ms)", notAvailableShort: "N/A", + Create: "Create", + notAvailableShort: "N/A", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js index 4a3c84a4a..8956de072 100644 --- a/src/languages/ko-KR.js +++ b/src/languages/ko-KR.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "비밀번호 재입력", respTime: "응답 시간 (ms)", notAvailableShort: "N/A", + Create: "Create", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/nl-NL.js b/src/languages/nl-NL.js index 24e72910b..647d136b2 100644 --- a/src/languages/nl-NL.js +++ b/src/languages/nl-NL.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "Herhaal wachtwoord", respTime: "resp. tijd (ms)", notAvailableShort: "N.v.t.", + Create: "Create", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/pl.js b/src/languages/pl.js new file mode 100644 index 000000000..cc625d6de --- /dev/null +++ b/src/languages/pl.js @@ -0,0 +1,113 @@ +export default { + languageName: "Polski", + checkEverySecond: "Sprawdzaj co {0} sekund.", + "Avg.": "Średnia ", + retriesDescription: "Maksymalna liczba powtórzeń, zanim usługa zostanie oznaczona jako wyłączona i zostanie wysłane powiadomienie", + ignoreTLSError: "Ignoruj błąd TLS/SSL dla stron HTTPS", + upsideDownModeDescription: "Odwróć status do góry nogami. Jeśli usługa jest osiągalna, to jest oznaczona jako niedostępna.", + maxRedirectDescription: "Maksymalna liczba przekierowań do wykonania. Ustaw na 0, aby wyłączyć przekierowania.", + acceptedStatusCodesDescription: "Wybierz kody stanu, które są uważane za udaną odpowiedź.", + passwordNotMatchMsg: "Powtórzone hasło nie pasuje.", + notificationDescription: "Proszę przypisać powiadomienie do monitora(ów), aby zadziałało.", + keywordDescription: "Wyszukiwanie słów kluczowych w zwykłym html lub odpowiedzi JSON. Wielkość liter ma znaczenie.", + pauseDashboardHome: "Pauza", + deleteMonitorMsg: "Czy na pewno chcesz usunąć ten monitor?", + deleteNotificationMsg: "Czy na pewno chcesz usunąć to powiadomienie dla wszystkich monitorów?", + resoverserverDescription: "Cloudflare jest domyślnym serwerem, możesz zmienić serwer resolver w każdej chwili.", + rrtypeDescription: "Wybierz RR-Type który chcesz monitorować", + pauseMonitorMsg: "Czy na pewno chcesz wstrzymać?", + Settings: "Ustawienia", + Dashboard: "Panel", + "New Update": "Nowa aktualizacja", + Language: "Język", + Appearance: "Wygląd", + Theme: "Motyw", + General: "Ogólne", + Version: "Wersja", + "Check Update On GitHub": "Sprawdź aktualizację na GitHub.", + List: "Lista", + Add: "Dodaj", + "Add New Monitor": "Dodaj nowy monitor", + "Quick Stats": "Szybkie statystyki", + Up: "Online", + Down: "Offline", + Pending: "Oczekujący", + Unknown: "Nieznane", + Pause: "Pauza", + Name: "Nazwa", + Status: "Status", + DateTime: "Data i godzina", + Message: "Wiadomość", + "No important events": "Brak ważnych wydarzeń", + Resume: "Wznów", + Edit: "Edytuj", + Delete: "Usuń", + Current: "aktualny", + Uptime: "Czas pracy", + "Cert Exp.": "Wygaśnięcie certyfikatu", + days: "dni", + day: "dzień", + "-day": " dni", + hour: "godzina", + "-hour": " godziny", + Response: "Odpowiedź", + Ping: "Ping", + "Monitor Type": "Typ monitora", + Keyword: "Słowo kluczowe", + "Friendly Name": "Przyjazna nazwa", + URL: "URL", + Hostname: "Nazwa hosta", + Port: "Port", + "Heartbeat Interval": "Interwał bicia serca", + Retries: "Prób", + Advanced: "Zaawansowane", + "Upside Down Mode": "Tryb do góry nogami", + "Max. Redirects": "Maks. przekierowania", + "Accepted Status Codes": "Akceptowane kody statusu", + Save: "Zapisz", + Notifications: "Powiadomienia", + "Not available, please setup.": "Niedostępne, proszę skonfigurować.", + "Setup Notification": "Konfiguracja powiadomień", + Light: "Jasny", + Dark: "Ciemny", + Auto: "Automatyczny", + "Theme - Heartbeat Bar": "Motyw - pasek bicia serca", + Normal: "Normalne", + Bottom: "Na dole", + None: "Brak", + Timezone: "Strefa czasowa", + "Search Engine Visibility": "Widoczność w wyszukiwarce", + "Allow indexing": "Pozwól na indeksowanie", + "Discourage search engines from indexing site": "Zniechęcaj wyszukiwarki do indeksowania strony", + "Change Password": "Zmień hasło", + "Current Password": "Aktualne hasło", + "New Password": "Nowe hasło", + "Repeat New Password": "Powtórz nowe hasło", + "Update Password": "Zaktualizuj hasło", + "Disable Auth": "Wyłącz autoryzację", + "Enable Auth": "Włącz autoryzację ", + Logout: "Wyloguj się", + Leave: "Zostaw", + "I understand, please disable": "Rozumiem, proszę wyłączyć", + Confirm: "Potwierdź", + Yes: "Tak", + No: "Nie", + Username: "Nazwa użytkownika", + Password: "Hasło", + "Remember me": "Zapamiętaj mnie", + Login: "Zaloguj się", + "No Monitors, please": "Brak monitorów, proszę", + "add one": "dodaj jeden", + "Notification Type": "Typ powiadomienia", + Email: "Email", + Test: "Test", + "Certificate Info": "Informacje o certyfikacie", + "Resolver Server": "Server resolver", + "Resource Record Type": "Typ rekordu zasobów", + "Last Result": "Ostatni wynik", + "Create your admin account": "Utwórz swoje konto administratora", + "Repeat Password": "Powtórz hasło", + respTime: "Czas odp. (ms)", + notAvailableShort: "N/A", + Create: "Stwórz" +} diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js index f2c7a0eee..e3835816b 100644 --- a/src/languages/ru-RU.js +++ b/src/languages/ru-RU.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "Повторите пароль", respTime: "Resp. Time (ms)", notAvailableShort: "N/A", + Create: "Create", clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/sr-latn.js b/src/languages/sr-latn.js index 60a90d859..18fdd004d 100644 --- a/src/languages/sr-latn.js +++ b/src/languages/sr-latn.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "Ponovite lozinku", respTime: "Vreme odg. (ms)", notAvailableShort: "N/A", + Create: "Create" clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/sr.js b/src/languages/sr.js index dfcee7cee..449b191e2 100644 --- a/src/languages/sr.js +++ b/src/languages/sr.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "Поновите лозинку", respTime: "Време одг. (мс)", notAvailableShort: "N/A", + Create: "Create" clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/sv-SE.js b/src/languages/sv-SE.js index dc76babd9..174e33882 100644 --- a/src/languages/sv-SE.js +++ b/src/languages/sv-SE.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "Upprepa Lösenord", respTime: "Svarstid (ms)", notAvailableShort: "Ej Tillg.", + Create: "Create" clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js index d04eabdf4..1aaec2f66 100644 --- a/src/languages/zh-CN.js +++ b/src/languages/zh-CN.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "重复密码", respTime: "Resp. Time (ms)", notAvailableShort: "N/A", + Create: "Create" clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js index 39532dfe2..b76bdfc85 100644 --- a/src/languages/zh-HK.js +++ b/src/languages/zh-HK.js @@ -109,6 +109,7 @@ export default { "Repeat Password": "重複密碼", respTime: "反應時間 (ms)", notAvailableShort: "N/A", + Create: "建立", clearEventsMsg: "是否確定刪除這個監測器的所有事件?", clearHeartbeatsMsg: "是否確定刪除這個監測器的所有脈搏資料?", confirmClearStatisticsMsg: "是否確定刪除所有監測器的脈搏資料?(您的監測器會繼續正常運作)", diff --git a/src/main.js b/src/main.js index 8f5e0a916..f34e9c43d 100644 --- a/src/main.js +++ b/src/main.js @@ -28,7 +28,7 @@ import zhHK from "./languages/zh-HK"; import deDE from "./languages/de-DE"; import nlNL from "./languages/nl-NL"; import esEs from "./languages/es-ES"; -import fr from "./languages/fr"; +import frFR from "./languages/fr-FR"; import ja from "./languages/ja"; import daDK from "./languages/da-DK"; import sr from "./languages/sr"; @@ -37,6 +37,7 @@ import svSE from "./languages/sv-SE"; import koKR from "./languages/ko-KR"; import ruRU from "./languages/ru-RU"; import zhCN from "./languages/zh-CN"; +import pl from "./languages/pl" const routes = [ { @@ -105,7 +106,7 @@ const languageList = { "de-DE": deDE, "nl-NL": nlNL, "es-ES": esEs, - "fr": fr, + "fr-FR": frFR, "ja": ja, "da-DK": daDK, "sr": sr, @@ -114,6 +115,7 @@ const languageList = { "ko-KR": koKR, "ru-RU": ruRU, "zh-CN": zhCN, + "pl": pl, }; const i18n = createI18n({ diff --git a/src/mixins/theme.js b/src/mixins/theme.js index 44e375327..b149e1113 100644 --- a/src/mixins/theme.js +++ b/src/mixins/theme.js @@ -11,7 +11,7 @@ export default { mounted() { // Default Light if (! this.userTheme) { - this.userTheme = "light"; + this.userTheme = "auto"; } // Default Heartbeat Bar diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index 95d15db6f..151df2c4d 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -214,6 +214,11 @@ <p>이 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong>을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.</p> <p>신중하게 사용하세요.</p> </template> + <template v-if="$i18n.locale === 'pl' "> + <p>Czy na pewno chcesz <strong>wyłączyć autoryzację</strong>?</p> + <p>Jest przeznaczony dla <strong>kogoś, kto ma autoryzację zewnętrzną</strong> przed Uptime Kuma, taką jak Cloudflare Access.</p> + <p>Proszę używać ostrożnie.</p> + </template> </Confirm> <Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics"> diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index 756aff365..dbff86837 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -14,6 +14,15 @@ </p> <div class="form-floating"> + <select id="language" v-model="$i18n.locale" class="form-select"> + <option v-for="(lang, i) in $i18n.availableLocales" :key="`Lang${i}`" :value="lang"> + {{ $i18n.messages[lang].languageName }} + </option> + </select> + <label for="language" class="form-label">{{ $t("Language") }}</label> + </div> + + <div class="form-floating mt-3"> <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username" required> <label for="floatingInput">{{ $t("Username") }}</label> </div> @@ -29,7 +38,7 @@ </div> <button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing"> - Create + {{ $t("Create") }} </button> </form> </div> @@ -49,6 +58,11 @@ export default { repeatPassword: "", } }, + watch: { + "$i18n.locale"() { + localStorage.locale = this.$i18n.locale; + }, + }, mounted() { this.$root.getSocket().emit("needSetup", (needSetup) => { if (! needSetup) {