Browse Source

Merge branch 'master' into clear-monitor-data

# Conflicts:
#	src/languages/da-DK.js
#	src/languages/en.js
#	src/languages/es-ES.js
#	src/languages/fr-FR.js
#	src/languages/ja.js
#	src/languages/ko-KR.js
#	src/languages/nl-NL.js
#	src/languages/ru-RU.js
#	src/languages/sr-latn.js
#	src/languages/sr.js
#	src/languages/sv-SE.js
#	src/languages/zh-CN.js
#	src/languages/zh-HK.js
pull/353/head
LouisLam 3 years ago
parent
commit
da131a5156
  1. 25
      dockerfile
  2. 26
      dockerfile-alpine
  3. 28
      dockerfile-debian
  4. 28
      extra/healthcheck.js
  5. 4
      package.json
  6. 49
      server/notification.js
  7. 102
      src/components/HiddenInput.vue
  8. 22
      src/components/NotificationDialog.vue
  9. 4
      src/icon.js
  10. 2
      src/languages/da-DK.js
  11. 3
      src/languages/de-DE.js
  12. 2
      src/languages/en.js
  13. 2
      src/languages/es-ES.js
  14. 4
      src/languages/fr-FR.js
  15. 2
      src/languages/ja.js
  16. 1
      src/languages/ko-KR.js
  17. 1
      src/languages/nl-NL.js
  18. 113
      src/languages/pl.js
  19. 1
      src/languages/ru-RU.js
  20. 1
      src/languages/sr-latn.js
  21. 1
      src/languages/sr.js
  22. 1
      src/languages/sv-SE.js
  23. 1
      src/languages/zh-CN.js
  24. 1
      src/languages/zh-HK.js
  25. 6
      src/main.js
  26. 2
      src/mixins/theme.js
  27. 5
      src/pages/Settings.vue
  28. 16
      src/pages/Setup.vue

25
dockerfile

@ -1,25 +1,30 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41. FROM node:14-bullseye-slim AS release
FROM node:14-alpine3.12 AS release
WORKDIR /app 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 # 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 && \ RUN npm install mapbox/node-sqlite3#593c9d
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 # 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 && \ 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 . . COPY . .
RUN npm install --legacy-peer-deps && npm run build && npm prune RUN npm install --legacy-peer-deps && npm run build && npm prune
EXPOSE 3001 EXPOSE 3001
VOLUME ["/app/data"] 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"] CMD ["node", "server/server.js"]
FROM release AS nightly FROM release AS nightly

26
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

28
dockerfile-debian

@ -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

28
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 = { let options = {
host: "localhost", host: process.env.HOST || "127.0.0.1",
port: "3001", port: parseInt(process.env.PORT) || 3001,
timeout: 2000, timeout: 120 * 100,
}; };
let request = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`); let request = client.request(options, (res) => {
if (res.statusCode == 200) { console.log(`Health Check OK [Res Code: ${res.statusCode}]`);
if (res.statusCode === 200) {
process.exit(0); process.exit(0);
} else { } else {
process.exit(1); process.exit(1);
} }
}); });
request.on("error", function (err) { request.on("error", function (err) {
console.log("ERROR"); console.error("Health Check ERROR");
process.exit(1); process.exit(1);
}); });
request.end(); request.end();

4
package.json

@ -19,8 +19,8 @@
"build": "vite build", "build": "vite build",
"vite-preview-dist": "vite preview --host", "vite-preview-dist": "vite preview --host",
"build-docker": "npm run build-docker-alpine && npm run build-docker-debian", "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-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 -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-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": "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", "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", "setup": "git checkout 1.5.3 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune",

49
server/notification.js

@ -4,6 +4,8 @@ const FormData = require("form-data");
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const child_process = require("child_process"); const child_process = require("child_process");
const { UP, DOWN } = require("../src/util");
class Notification { class Notification {
/** /**
@ -80,7 +82,7 @@ class Notification {
} }
} else if (notification.type === "smtp") { } else if (notification.type === "smtp") {
return await Notification.smtp(notification, msg) return await Notification.smtp(notification, msg, heartbeatJSON)
} else if (notification.type === "discord") { } else if (notification.type === "discord") {
try { try {
@ -109,7 +111,7 @@ class Notification {
} }
// If heartbeatJSON is not null, we go into the normal alerting loop. // If heartbeatJSON is not null, we go into the normal alerting loop.
if (heartbeatJSON["status"] == 0) { if (heartbeatJSON["status"] == DOWN) {
let discorddowndata = { let discorddowndata = {
username: discordDisplayName, username: discordDisplayName,
embeds: [{ embeds: [{
@ -139,7 +141,7 @@ class Notification {
await axios.post(notification.discordWebhookUrl, discorddowndata) await axios.post(notification.discordWebhookUrl, discorddowndata)
return okMsg; return okMsg;
} else if (heartbeatJSON["status"] == 1) { } else if (heartbeatJSON["status"] == UP) {
let discordupdata = { let discordupdata = {
username: discordDisplayName, username: discordDisplayName,
embeds: [{ embeds: [{
@ -343,7 +345,7 @@ class Notification {
const mattermostIconEmoji = notification.mattermosticonemo; const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl; const mattermostIconUrl = notification.mattermosticonurl;
if (heartbeatJSON["status"] == 0) { if (heartbeatJSON["status"] == DOWN) {
let mattermostdowndata = { let mattermostdowndata = {
username: mattermostUserName, username: mattermostUserName,
text: "Uptime Kuma Alert", text: "Uptime Kuma Alert",
@ -387,7 +389,7 @@ class Notification {
mattermostdowndata mattermostdowndata
); );
return okMsg; return okMsg;
} else if (heartbeatJSON["status"] == 1) { } else if (heartbeatJSON["status"] == UP) {
let mattermostupdata = { let mattermostupdata = {
username: mattermostUserName, username: mattermostUserName,
text: "Uptime Kuma Alert", text: "Uptime Kuma Alert",
@ -489,19 +491,19 @@ class Notification {
return okMsg; return okMsg;
} }
if (heartbeatJSON["status"] == 0) { if (heartbeatJSON["status"] == DOWN) {
let downdata = { let downdata = {
"title": "UptimeKuma Alert:" + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} }
await axios.post(lunaseadevice, downdata) await axios.post(lunaseadevice, downdata)
return okMsg; return okMsg;
} }
if (heartbeatJSON["status"] == 1) { if (heartbeatJSON["status"] == UP) {
let updata = { let updata = {
"title": "UptimeKuma Alert:" + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} }
await axios.post(lunaseadevice, updata) await axios.post(lunaseadevice, updata)
return okMsg; return okMsg;
@ -527,18 +529,18 @@ class Notification {
"body": "Testing Successful.", "body": "Testing Successful.",
} }
await axios.post(pushbulletUrl, testdata, config) await axios.post(pushbulletUrl, testdata, config)
} else if (heartbeatJSON["status"] == 0) { } else if (heartbeatJSON["status"] == DOWN) {
let downdata = { let downdata = {
"type": "note", "type": "note",
"title": "UptimeKuma Alert:" + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} }
await axios.post(pushbulletUrl, downdata, config) await axios.post(pushbulletUrl, downdata, config)
} else if (heartbeatJSON["status"] == 1) { } else if (heartbeatJSON["status"] == UP) {
let updata = { let updata = {
"type": "note", "type": "note",
"title": "UptimeKuma Alert:" + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} }
await axios.post(pushbulletUrl, updata, config) await axios.post(pushbulletUrl, updata, config)
} }
@ -566,7 +568,7 @@ class Notification {
] ]
} }
await axios.post(lineAPIUrl, testMessage, config) await axios.post(lineAPIUrl, testMessage, config)
} else if (heartbeatJSON["status"] == 0) { } else if (heartbeatJSON["status"] == DOWN) {
let downMessage = { let downMessage = {
"to": notification.lineUserID, "to": notification.lineUserID,
"messages": [ "messages": [
@ -577,7 +579,7 @@ class Notification {
] ]
} }
await axios.post(lineAPIUrl, downMessage, config) await axios.post(lineAPIUrl, downMessage, config)
} else if (heartbeatJSON["status"] == 1) { } else if (heartbeatJSON["status"] == UP) {
let upMessage = { let upMessage = {
"to": notification.lineUserID, "to": notification.lineUserID,
"messages": [ "messages": [
@ -634,7 +636,7 @@ class Notification {
await R.trash(bean) await R.trash(bean)
} }
static async smtp(notification, msg) { static async smtp(notification, msg, heartbeatJSON = null) {
const config = { const config = {
host: notification.smtpHost, host: notification.smtpHost,
@ -652,12 +654,17 @@ class Notification {
let transporter = nodemailer.createTransport(config); let transporter = nodemailer.createTransport(config);
let bodyTextContent = msg;
if(heartbeatJSON) {
bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`;
}
// send mail with defined transport object // send mail with defined transport object
await transporter.sendMail({ await transporter.sendMail({
from: `"Uptime Kuma" <${notification.smtpFrom}>`, from: `"Uptime Kuma" <${notification.smtpFrom}>`,
to: notification.smtpTo, to: notification.smtpTo,
subject: msg, subject: msg,
text: msg, text: bodyTextContent,
}); });
return "Sent Successfully."; return "Sent Successfully.";

102
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>

22
src/components/NotificationDialog.vue

@ -40,7 +40,7 @@
<template v-if="notification.type === 'telegram'"> <template v-if="notification.type === 'telegram'">
<div class="mb-3"> <div class="mb-3">
<label for="telegram-bot-token" class="form-label">Bot Token</label> <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"> <div class="form-text">
You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.
</div> </div>
@ -130,7 +130,7 @@
<div class="mb-3"> <div class="mb-3">
<label for="password" class="form-label">Password</label> <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>
<div class="mb-3"> <div class="mb-3">
@ -195,7 +195,7 @@
<template v-if="notification.type === 'gotify'"> <template v-if="notification.type === 'gotify'">
<div class="mb-3"> <div class="mb-3">
<label for="gotify-application-token" class="form-label">Application Token</label> <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>
<div class="mb-3"> <div class="mb-3">
<label for="gotify-server-url" class="form-label">Server URL</label> <label for="gotify-server-url" class="form-label">Server URL</label>
@ -306,13 +306,13 @@
<template v-if="notification.type === 'pushy'"> <template v-if="notification.type === 'pushy'">
<div class="mb-3"> <div class="mb-3">
<label for="pushy-app-token" class="form-label">API_KEY</label> <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>
<div class="mb-3"> <div class="mb-3">
<label for="pushy-user-key" class="form-label">USER_TOKEN</label> <label for="pushy-user-key" class="form-label">USER_TOKEN</label>
<div class="input-group mb-3"> <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>
</div> </div>
<p style="margin-top: 8px;"> <p style="margin-top: 8px;">
@ -323,7 +323,7 @@
<template v-if="notification.type === 'octopush'"> <template v-if="notification.type === 'octopush'">
<div class="mb-3"> <div class="mb-3">
<label for="octopush-key" class="form-label">API KEY</label> <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> <label for="octopush-login" class="form-label">API LOGIN</label>
<input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required> <input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required>
</div> </div>
@ -354,9 +354,9 @@
<template v-if="notification.type === 'pushover'"> <template v-if="notification.type === 'pushover'">
<div class="mb-3"> <div class="mb-3">
<label for="pushover-user" class="form-label">User Key<span style="color: red;"><sup>*</sup></span></label> <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> <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> <label for="pushover-device" class="form-label">Device</label>
<input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control"> <input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control">
<label for="pushover-device" class="form-label">Message Title</label> <label for="pushover-device" class="form-label">Message Title</label>
@ -442,7 +442,7 @@
<template v-if="notification.type === 'pushbullet'"> <template v-if="notification.type === 'pushbullet'">
<div class="mb-3"> <div class="mb-3">
<label for="pushbullet-access-token" class="form-label">Access Token</label> <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> </div>
<p style="margin-top: 8px;"> <p style="margin-top: 8px;">
@ -453,7 +453,7 @@
<template v-if="notification.type === 'line'"> <template v-if="notification.type === 'line'">
<div class="mb-3"> <div class="mb-3">
<label for="line-channel-access-token" class="form-label">Channel access token</label> <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>
<div class="form-text"> <div class="form-text">
Line Developers Console - <b>Basic Settings</b> Line Developers Console - <b>Basic Settings</b>
@ -497,11 +497,13 @@ import { ucfirst } from "../util.ts"
import axios from "axios"; import axios from "axios";
import { useToast } from "vue-toastification" import { useToast } from "vue-toastification"
import Confirm from "./Confirm.vue"; import Confirm from "./Confirm.vue";
import HiddenInput from "./HiddenInput.vue";
const toast = useToast() const toast = useToast()
export default { export default {
components: { components: {
Confirm, Confirm,
HiddenInput,
}, },
props: {}, props: {},
data() { data() {

4
src/icon.js

@ -1,10 +1,10 @@
import { library } from "@fortawesome/fontawesome-svg-core" 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 { fa } from '@fortawesome/free-regular-svg-icons'
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
// Add Free Font Awesome Icons here // Add Free Font Awesome Icons here
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free // 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 } export { FontAwesomeIcon }

2
src/languages/da-DK.js

@ -109,6 +109,8 @@ export default {
"Resource Record Type": "Resource Record Type", "Resource Record Type": "Resource Record Type",
respTime: "Resp. Time (ms)", respTime: "Resp. Time (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create",
notAvailableShort: "N/A",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

3
src/languages/de-DE.js

@ -114,5 +114,6 @@ export default {
"Repeat Password": "Wiederhole das Passwort", "Repeat Password": "Wiederhole das Passwort",
"Resource Record Type": "Resource Record Type", "Resource Record Type": "Resource Record Type",
respTime: "Antw. Zeit (ms)", respTime: "Antw. Zeit (ms)",
notAvailableShort: "N/A" notAvailableShort: "N/A",
Create: "Erstellen",
} }

2
src/languages/en.js

@ -112,6 +112,8 @@ export default {
"Repeat Password": "Repeat Password", "Repeat Password": "Repeat Password",
respTime: "Resp. Time (ms)", respTime: "Resp. Time (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create",
notAvailableShort: "N/A",
"Clear Data": "Clear Data", "Clear Data": "Clear Data",
Events: "Events", Events: "Events",
Heartbeats: "Heartbeats" Heartbeats: "Heartbeats"

2
src/languages/es-ES.js

@ -109,6 +109,8 @@ export default {
"Repeat Password": "Repetir contraseña", "Repeat Password": "Repetir contraseña",
respTime: "Tiempo de resp. (ms)", respTime: "Tiempo de resp. (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create",
notAvailableShort: "N/A",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

4
src/languages/fr.js → src/languages/fr-FR.js

@ -66,7 +66,7 @@ export default {
"Theme - Heartbeat Bar": "Voir les services surveillés", "Theme - Heartbeat Bar": "Voir les services surveillés",
Normal: "Général", Normal: "Général",
Bottom: "En dessous", Bottom: "En dessous",
None: "Non", None: "Rien",
Timezone: "Fuseau Horaire", Timezone: "Fuseau Horaire",
"Search Engine Visibility": "Visibilité par les moteurs de recherche", "Search Engine Visibility": "Visibilité par les moteurs de recherche",
"Allow indexing": "Autoriser l'indexation par des 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", "Repeat Password": "Répéter le mot de passe",
respTime: "Temps de réponse (ms)", respTime: "Temps de réponse (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Créer",
notAvailableShort: "N/A",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

2
src/languages/ja.js

@ -109,6 +109,8 @@ export default {
"Repeat Password": "Repeat Password", "Repeat Password": "Repeat Password",
respTime: "Resp. Time (ms)", respTime: "Resp. Time (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create",
notAvailableShort: "N/A",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/ko-KR.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "비밀번호 재입력", "Repeat Password": "비밀번호 재입력",
respTime: "응답 시간 (ms)", respTime: "응답 시간 (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/nl-NL.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "Herhaal wachtwoord", "Repeat Password": "Herhaal wachtwoord",
respTime: "resp. tijd (ms)", respTime: "resp. tijd (ms)",
notAvailableShort: "N.v.t.", notAvailableShort: "N.v.t.",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

113
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"
}

1
src/languages/ru-RU.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "Повторите пароль", "Repeat Password": "Повторите пароль",
respTime: "Resp. Time (ms)", respTime: "Resp. Time (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/sr-latn.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "Ponovite lozinku", "Repeat Password": "Ponovite lozinku",
respTime: "Vreme odg. (ms)", respTime: "Vreme odg. (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create"
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/sr.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "Поновите лозинку", "Repeat Password": "Поновите лозинку",
respTime: "Време одг. (мс)", respTime: "Време одг. (мс)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create"
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/sv-SE.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "Upprepa Lösenord", "Repeat Password": "Upprepa Lösenord",
respTime: "Svarstid (ms)", respTime: "Svarstid (ms)",
notAvailableShort: "Ej Tillg.", notAvailableShort: "Ej Tillg.",
Create: "Create"
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/zh-CN.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "重复密码", "Repeat Password": "重复密码",
respTime: "Resp. Time (ms)", respTime: "Resp. Time (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "Create"
clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",

1
src/languages/zh-HK.js

@ -109,6 +109,7 @@ export default {
"Repeat Password": "重複密碼", "Repeat Password": "重複密碼",
respTime: "反應時間 (ms)", respTime: "反應時間 (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
Create: "建立",
clearEventsMsg: "是否確定刪除這個監測器的所有事件?", clearEventsMsg: "是否確定刪除這個監測器的所有事件?",
clearHeartbeatsMsg: "是否確定刪除這個監測器的所有脈搏資料?", clearHeartbeatsMsg: "是否確定刪除這個監測器的所有脈搏資料?",
confirmClearStatisticsMsg: "是否確定刪除所有監測器的脈搏資料?(您的監測器會繼續正常運作)", confirmClearStatisticsMsg: "是否確定刪除所有監測器的脈搏資料?(您的監測器會繼續正常運作)",

6
src/main.js

@ -28,7 +28,7 @@ import zhHK from "./languages/zh-HK";
import deDE from "./languages/de-DE"; import deDE from "./languages/de-DE";
import nlNL from "./languages/nl-NL"; import nlNL from "./languages/nl-NL";
import esEs from "./languages/es-ES"; import esEs from "./languages/es-ES";
import fr from "./languages/fr"; import frFR from "./languages/fr-FR";
import ja from "./languages/ja"; import ja from "./languages/ja";
import daDK from "./languages/da-DK"; import daDK from "./languages/da-DK";
import sr from "./languages/sr"; import sr from "./languages/sr";
@ -37,6 +37,7 @@ import svSE from "./languages/sv-SE";
import koKR from "./languages/ko-KR"; import koKR from "./languages/ko-KR";
import ruRU from "./languages/ru-RU"; import ruRU from "./languages/ru-RU";
import zhCN from "./languages/zh-CN"; import zhCN from "./languages/zh-CN";
import pl from "./languages/pl"
const routes = [ const routes = [
{ {
@ -105,7 +106,7 @@ const languageList = {
"de-DE": deDE, "de-DE": deDE,
"nl-NL": nlNL, "nl-NL": nlNL,
"es-ES": esEs, "es-ES": esEs,
"fr": fr, "fr-FR": frFR,
"ja": ja, "ja": ja,
"da-DK": daDK, "da-DK": daDK,
"sr": sr, "sr": sr,
@ -114,6 +115,7 @@ const languageList = {
"ko-KR": koKR, "ko-KR": koKR,
"ru-RU": ruRU, "ru-RU": ruRU,
"zh-CN": zhCN, "zh-CN": zhCN,
"pl": pl,
}; };
const i18n = createI18n({ const i18n = createI18n({

2
src/mixins/theme.js

@ -11,7 +11,7 @@ export default {
mounted() { mounted() {
// Default Light // Default Light
if (! this.userTheme) { if (! this.userTheme) {
this.userTheme = "light"; this.userTheme = "auto";
} }
// Default Heartbeat Bar // Default Heartbeat Bar

5
src/pages/Settings.vue

@ -214,6 +214,11 @@
<p> 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong> Uptime Kuma 앞에 사용자를 위한 기능이에요.</p> <p> 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong> Uptime Kuma 앞에 사용자를 위한 기능이에요.</p>
<p>신중하게 사용하세요.</p> <p>신중하게 사용하세요.</p>
</template> </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>
<Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics"> <Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics">

16
src/pages/Setup.vue

@ -14,6 +14,15 @@
</p> </p>
<div class="form-floating"> <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> <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username" required>
<label for="floatingInput">{{ $t("Username") }}</label> <label for="floatingInput">{{ $t("Username") }}</label>
</div> </div>
@ -29,7 +38,7 @@
</div> </div>
<button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing"> <button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing">
Create {{ $t("Create") }}
</button> </button>
</form> </form>
</div> </div>
@ -49,6 +58,11 @@ export default {
repeatPassword: "", repeatPassword: "",
} }
}, },
watch: {
"$i18n.locale"() {
localStorage.locale = this.$i18n.locale;
},
},
mounted() { mounted() {
this.$root.getSocket().emit("needSetup", (needSetup) => { this.$root.getSocket().emit("needSetup", (needSetup) => {
if (! needSetup) { if (! needSetup) {

Loading…
Cancel
Save