Merge branch 'louislam:master' into add-mqtt-schemes

This commit is contained in:
twiggotronix 2023-01-11 10:34:24 +01:00 committed by GitHub
commit 8fab7112a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 336 additions and 115 deletions

View File

@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
node: [ 14, 16, 17, 18 ]
node: [ 14, 16, 18, 19 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@ -66,3 +66,19 @@ jobs:
- run: npm install
- run: npm run build
- run: npm run cy:test
frontend-unit-tests:
needs: [ check-linters ]
runs-on: ubuntu-latest
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- uses: actions/checkout@v3
- name: Use Node.js 14
uses: actions/setup-node@v3
with:
node-version: 14
cache: 'npm'
- run: npm install
- run: npm run build
- run: npm run cy:run:unit

View File

@ -1,6 +1,6 @@
# Project Info
First of all, thank you everyone who made pull requests for Uptime Kuma, I never thought GitHub Community can be that nice! And also because of this, I also never thought other people actually read my code and edit my code. It is not structured and commented so well, lol. Sorry about that.
First of all, I want to thank everyone who made pull requests for Uptime Kuma. I never thought the GitHub Community would be so nice! Because of this, I also never thought that other people would actually read and edit my code. It is not very well structured or commented, sorry about that.
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
@ -27,7 +27,7 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
## Can I create a pull request for Uptime Kuma?
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can discuss first**. Especially for a large pull request or you don't know it will be merged or not.
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**. Especially for a large pull request or you don't know it will be merged or not.
Here are some references:
@ -51,6 +51,10 @@ Here are some references:
- Convert existing code into other programming languages
- Unnecessary large code changes (Hard to review, causes code conflicts to other pull requests)
The above cases cannot cover all situations.
I (@louislam) have the final say. If your pull request does not meet my expectations, I will reject it, no matter how much time you spend on it. Therefore, it is essential to have a discussion beforehand.
I will mark your pull request in the [milestones](https://github.com/louislam/uptime-kuma/milestones), if I am plan to review and merge it.
Also, please don't rush or ask for ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
@ -73,9 +77,9 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
## Project Styles
I personally do not like something need to learn so much and need to config so much before you can finally start the app.
I personally do not like it when something requires so much learning and configuration before you can finally start the app.
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort to get it run
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort required to get it running
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`.
- Easy to use
@ -173,15 +177,11 @@ The data and socket logic are in `src/mixins/socket.js`.
## Unit Test
It is an end-to-end testing. It is using Jest and Puppeteer.
```bash
npm run build
npm test
```
By default, the Chromium window will be shown up during the test. Specifying `HEADLESS_TEST=1` for terminal environments.
## Dependencies
Both frontend and backend share the same package.json. However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into dist files. So:

View File

@ -0,0 +1,10 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
supportFile: false,
specPattern: [
"test/cypress/unit/**/*.js"
],
}
});

View File

@ -0,0 +1,16 @@
############################################
# Build in Golang
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
############################################
FROM golang:1.19.4-buster
WORKDIR /app
ARG TARGETPLATFORM
COPY ./extra/ ./extra/
# Compile healthcheck.go
RUN apt update && \
apt --yes --no-install-recommends install curl && \
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
apt --yes --no-install-recommends install nodejs && \
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
apt --yes remove nodejs

View File

@ -1,19 +1,9 @@
############################################
# Build in Golang
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
# Check file: builder-go.dockerfile
############################################
FROM golang:1.19.4-buster AS build_healthcheck
WORKDIR /app
ARG TARGETPLATFORM
COPY ./extra/ ./extra/
# Compile healthcheck.go
RUN apt update
RUN apt --yes --no-install-recommends install curl
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash
RUN apt --yes --no-install-recommends install nodejs
RUN node -v
RUN node ./extra/build-healthcheck.js $TARGETPLATFORM
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
############################################
# Build in Node.js
@ -22,10 +12,13 @@ FROM louislam/uptime-kuma:base-debian AS build
WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY .npmrc .npmrc
COPY package.json package.json
COPY package-lock.json package-lock.json
RUN npm ci --omit=dev
COPY . .
COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
RUN chmod +x /app/extra/entrypoint.sh
############################################
# ⭐ Main Image

View File

@ -3,10 +3,12 @@ WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY .npmrc .npmrc
COPY package.json package.json
COPY package-lock.json package-lock.json
RUN npm ci --omit=dev
COPY . .
RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
RUN chmod +x /app/extra/entrypoint.sh
FROM louislam/uptime-kuma:base-alpine AS release
WORKDIR /app

View File

@ -1,3 +1,7 @@
/*
* If changed, have to run `npm run build-docker-builder-go`.
* This script should be run after a period of time (180s), because the server may need some time to prepare.
*/
package main
import (

View File

@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "1.19.3",
"version": "1.19.4",
"license": "MIT",
"repository": {
"type": "git",
@ -31,6 +31,7 @@
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/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:$VERSION-alpine --target release . --push",
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
@ -38,7 +39,7 @@
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.19.3 && npm ci --production && npm run download-dist",
"setup": "git checkout 1.19.4 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js",
@ -60,6 +61,7 @@
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
"cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e",
"cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js",
"cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go"
},

View File

@ -25,15 +25,20 @@ const DEFAULT_KEEP_PERIOD = 180;
parsedPeriod = DEFAULT_KEEP_PERIOD;
}
log(`Clearing Data older than ${parsedPeriod} days...`);
if (parsedPeriod < 1) {
log(`Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
} else {
try {
await R.exec(
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
[ parsedPeriod ]
);
} catch (e) {
log(`Failed to clear old data: ${e.message}`);
log(`Clearing Data older than ${parsedPeriod} days...`);
try {
await R.exec(
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
[ parsedPeriod ]
);
} catch (e) {
log(`Failed to clear old data: ${e.message}`);
}
}
exit();

View File

@ -10,7 +10,7 @@ class Pushover extends NotificationProvider {
let pushoverlink = "https://api.pushover.net/1/messages.json";
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg,
"message": "<b>Message</b>:" + msg,
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,

View File

@ -7,6 +7,7 @@
settings.keepDataPeriodDays,
])
}}
{{ $t("infiniteRetention") }}
</label>
<input
id="keepDataPeriodDays"
@ -14,9 +15,12 @@
type="number"
class="form-control"
required
min="1"
min="0"
step="1"
/>
<div v-if="settings.keepDataPeriodDays < 0" class="form-text">
{{ $t("dataRetentionTimeError") }}
</div>
</div>
<div class="my-4">
<button class="btn btn-primary" type="button" @click="saveSettings()">

View File

@ -669,4 +669,10 @@ export default {
"General Monitor Type": "Общ тип монитор",
"Passive Monitor Type": "Пасивет тип монитор",
"Specific Monitor Type": "Специфичен тип монитор",
ZohoCliq: "ZohoCliq",
wayToGetZohoCliqURL: "Можете да научите как се създава URL адрес за уеб кука {0}.",
Kook: "Kook",
wayToGetKookBotToken: "Създайте приложение и вземете вашия бот токен на {0}",
wayToGetKookGuildID: "Превключете в 'Developer Mode' в 'Kook' настройките, след което десен клик върху 'guild' за да вземете неговото 'ID'",
"Guild ID": "Guild ID",
};

View File

@ -675,4 +675,6 @@ export default {
"General Monitor Type": "General Monitor Type",
"Passive Monitor Type": "Passive Monitor Type",
"Specific Monitor Type": "Specific Monitor Type",
dataRetentionTimeError: "Retention period must be 0 or greater",
infiniteRetention: "Set to 0 for infinite retention.",
};

View File

@ -209,6 +209,7 @@ export default {
here: "ici",
Required: "Requis",
telegram: "Telegram",
"ZohoCliq": "ZohoCliq",
"Bot Token": "Jeton du robot",
wayToGetTelegramToken: "Vous pouvez obtenir un token depuis {0}.",
"Chat ID": "Chat ID",
@ -240,7 +241,8 @@ export default {
"Hello @everyone is...": "Bonjour {'@'}everyone il...",
teams: "Microsoft Teams",
"Webhook URL": "URL vers le webhook",
wayToGetTeamsURL: "Vous pouvez apprendre comment créer un Webhook {0}.",
wayToGetTeamsURL: "Vous pouvez apprendre comment créer une URL Webhook {0}.",
wayToGetZohoCliqURL: "Vous pouvez apprendre comment créer une URL Webhook {0}.",
signal: "Signal",
Number: "Numéro",
Recipients: "Destinataires",
@ -270,6 +272,10 @@ export default {
apprise: "Apprise (prend en charge plus de 50 services de notification)",
GoogleChat: "Google Chat (Google Workspace uniquement)",
pushbullet: "Pushbullet",
Kook: "Kook",
wayToGetKookBotToken: "Créez une application et récupérer le jeton de robot à l'addresse {0}",
wayToGetKookGuildID: "Passez en « mode développeur » dans les paramètres de Kook, et cliquez droit sur le Guild pour obtenir son identifiant",
"Guild ID": "Identifiant de Guild",
line: "Line Messenger",
mattermost: "Mattermost",
"User Key": "Clé d'utilisateur",
@ -669,4 +675,6 @@ export default {
"General Monitor Type": "Type de sonde générale",
"Passive Monitor Type": "Type de sonde passive",
"Specific Monitor Type": "Type de sonde spécifique",
dataRetentionTimeError: "La durée de conservation doit être supérieure ou égale à 0",
infiniteRetention: "Définissez la valeur à 0 pour une durée de conservation infinie.",
};

View File

@ -2,17 +2,35 @@ export default {
languageName: "简体中文",
checkEverySecond: "检测频率 {0} 秒",
retryCheckEverySecond: "重试间隔 {0} 秒",
resendEveryXTimes: "每 {0} 次失败则重复发送一次",
resendDisabled: "为 0 时禁用重复发送",
retriesDescription: "服务被标记为故障并发送通知之前的最大重试次数",
ignoreTLSError: "忽略 HTTPS 站点的 TLS/SSL 错误",
upsideDownModeDescription: "反转状态监控,如果服务可访问,则认为是故障。",
maxRedirectDescription: "允许的最大重定向次数。设置为 0 禁用重定向。",
enableGRPCTls: "允许通过 TLS 连接发送 gRPC 请求",
grpcMethodDescription: "方法名会转换为小驼峰格式,例如 sayHello、check 等等",
acceptedStatusCodesDescription: "选择被视为成功响应的状态码。",
Maintenance: "维护",
statusMaintenance: "维护",
"Schedule maintenance": "计划维护",
"Affected Monitors": "受影响的监控项",
"Pick Affected Monitors...": "选择受影响的监控项…",
"Start of maintenance": "维护开始",
"All Status Pages": "所有状态页面",
"Select status pages...": "选择状态页面…",
recurringIntervalMessage: "每天一次 | 每 {0} 天一次",
affectedMonitorsDescription: "选择受当前维护影响的监控项",
affectedStatusPages: "在所选状态页面上显示此维护消息",
atLeastOneMonitor: "至少选择一个受影响的监控项",
passwordNotMatchMsg: "两次输入的密码不一致。",
notificationDescription: "通知必须被分配给监控项才能正常工作。",
keywordDescription: "在纯 HTML 或 JSON 响应中搜索关键字,区分大小写。",
pauseDashboardHome: "暂停",
deleteMonitorMsg: "确定要删除此监控项吗?",
deleteMaintenanceMsg: "确定要删除此维护吗?",
deleteNotificationMsg: "确定要为所有监控项删除此通知吗?",
dnsPortDescription: "DNS 服务器端口,默认为 53您可以在任何时候更改此端口.",
resolverserverDescription: "默认服务器是 Cloudflare。您随时可以修改解析服务器。",
rrtypeDescription: "选择要监控的资源记录类型",
pauseMonitorMsg: "确定要暂停吗?",
@ -34,7 +52,6 @@ export default {
Theme: "主题",
General: "常规",
"Primary Base URL": "站点主 URL",
About: "关于",
Version: "版本",
"Check Update On GitHub": "检查 GitHub 上的更新",
List: "列表",
@ -72,6 +89,7 @@ export default {
"Heartbeat Interval": "心跳间隔",
Retries: "重试次数",
"Heartbeat Retry Interval": "心跳重试间隔",
"Resend Notification if Down X times consequently": "连续失败时重复发送通知的间隔次数",
Advanced: "高级",
"Upside Down Mode": "反转监控",
"Max. Redirects": "最大重定向次数",
@ -125,7 +143,6 @@ export default {
"Last Result": "上次结果",
"Create your admin account": "创建管理员账户",
"Repeat Password": "重复密码",
Backup: "备份",
"Import Backup": "导入备份",
"Export Backup": "导出备份",
Export: "导出",
@ -160,7 +177,7 @@ export default {
Token: "令牌",
"Show URI": "显示 URI",
Tags: "标签",
"Add New below or Select...": "在下面添加或选择...",
"Add New below or Select...": "在下面添加或选择",
"Tag with this name already exist.": "相同名称的标签已存在。",
"Tag with this value already exist.": "相同内容的标签已存在。",
color: "颜色",
@ -173,7 +190,7 @@ export default {
Indigo: "靛蓝",
Purple: "紫色",
Pink: "粉色",
"Search...": "搜索...",
"Search...": "搜索",
"Avg. Ping": "平均 Ping",
"Avg. Response": "平均响应",
"Entry Page": "入口页面",
@ -192,6 +209,7 @@ export default {
here: "这里",
Required: "必填",
telegram: "Telegram",
"ZohoCliq": "ZohoCliq",
"Bot Token": "Bot Token",
wayToGetTelegramToken: "您可以从 {0} 获取 Token。",
"Chat ID": "Chat ID",
@ -204,6 +222,8 @@ export default {
"Content Type": "Content Type",
webhookJsonDesc: "{0} 适合现代的 HTTP 服务器,例如 Express.js",
webhookFormDataDesc: "{multipart} 适合 PHP其中 JSON 需要使用 {decodeFunction} 解码",
webhookAdditionalHeadersTitle: "额外 Header",
webhookAdditionalHeadersDesc: "设置通过此 Webhook 发送的额外 Header。",
smtp: "电子邮件SMTP",
secureOptionNone: "无 / STARTTLS常用端口 25、587",
secureOptionTLS: "TLS常用端口 465",
@ -221,7 +241,8 @@ export default {
"Hello @everyone is...": "{'@'}everyone……",
teams: "Microsoft Teams",
"Webhook URL": "Webhook URL",
wayToGetTeamsURL: "您可以在 {0} 了解如何获取 Webhook URL。",
wayToGetTeamsURL: "您可以在{0}了解如何获取 Webhook URL。",
wayToGetZohoCliqURL: "您可以在{0}了解如何创建 Webhook URL。",
signal: "Signal",
Number: "号码",
Recipients: "收件人",
@ -243,6 +264,7 @@ export default {
"rocket.chat": "Rocket.Chat",
pushover: "Pushover",
pushy: "Pushy",
PushByTechulus: "Push by Techulus",
octopush: "Octopush",
promosms: "PromoSMS",
clicksendsms: "ClickSend SMS",
@ -250,9 +272,10 @@ export default {
apprise: "Apprise (支持 50+ 种通知服务)",
GoogleChat: "Google Chat仅 Google Workspace",
pushbullet: "Pushbullet",
AliyunSMS: "阿里云短信服务",
Kook: "Kook",
wayToGetKookBotToken: "在 {0} 创建应用并获取机器人 Token",
wayToGetKookGuildID: "在Kook设置中打开 ‘开发者模式’,然后右键频道可获取其 ID",
wayToGetKookGuildID: "在 Kook 设置中打开“开发者模式”,然后右键点击频道可获取其 ID",
"Guild ID": "频道 ID",
line: "Line Messenger",
mattermost: "Mattermost",
@ -298,6 +321,7 @@ export default {
promosmsTypeSpeed: "SMS SPEED - 最高优先级。非常快速可靠,但更贵(大约两倍 SMS FULL 的价格)。",
promosmsPhoneNumber: "电话号码(波兰地区收信人可以不填区号)",
promosmsSMSSender: "短信发信人名称已注册的名称或以下默认值之一InfoSMS、SMS Info、MaxSMS、INFO、SMS",
Feishu: "飞书",
"Feishu WebHookUrl": "飞书 WebHook URL",
matrixHomeserverURL: "服务器 URL包含 http(s):// 和可选的端口号)",
"Internal Room Id": "内部房间 ID",
@ -316,14 +340,18 @@ export default {
"One record": "一条记录",
steamApiKeyDescription: "要监控 Steam 游戏服务器,您需要 Steam Web-API 密钥。您可以在这里注册您的 API 密钥: ",
"Current User": "当前用户",
topic: "Topic",
topicExplanation: "要监控的 MQTT Topic",
successMessage: "成功消息",
successMessageExplanation: "视为成功的 MQTT 消息",
recent: "最近",
Done: "完成",
Info: "信息",
Security: "安全性",
"Steam API Key": "Steam API 密钥",
"Shrink Database": "压缩数据库",
"Pick a RR-Type...": "选择资源记录类型...",
"Pick Accepted Status Codes...": "选择有效的状态码...",
"Pick a RR-Type...": "选择资源记录类型",
"Pick Accepted Status Codes...": "选择有效的状态码",
Default: "默认",
"HTTP Options": "HTTP 选项",
"Create Incident": "创建事件",
@ -333,6 +361,8 @@ export default {
info: "信息",
warning: "警告",
danger: "危险",
error: "错误",
critical: "关键",
primary: "主要",
light: "明亮",
dark: "黑暗",
@ -360,7 +390,20 @@ export default {
serwersmsAPIPassword: "API 密码",
serwersmsPhoneNumber: "电话号码",
serwersmsSenderName: "SMS 发信人名称(需要在客户中心注册)",
smseagle: "SMSEagle",
smseagleTo: "电话号码",
smseagleGroup: "通讯录群组名",
smseagleContact: "通讯录联系人",
smseagleRecipientType: "收信人类型",
smseagleRecipient: "收信人(多个需用半角逗号分隔)",
smseagleToken: "API Access token",
smseagleUrl: "您的 SMSEagle 设备 URL",
smseagleEncoding: "以 Unicode 发送",
smseaglePriority: "消息优先级0-9默认为 0",
stackfield: "Stackfield",
Customize: "自定义",
"Custom Footer": "自定义底部",
"Custom CSS": "自定义 CSS",
smtpDkimSettings: "DKIM 设置",
smtpDkimDesc: "请访问 Nodemailer DKIM {0} 了解配置方法。",
documentation: "文档",
@ -370,16 +413,13 @@ export default {
smtpDkimHashAlgo: "哈希算法(可选)",
smtpDkimheaderFieldNames: "包含在哈希计算对象内的 Header 列表(可选)",
smtpDkimskipFields: "不包含在哈希计算对象内的 Header 列表(可选)",
Feishu: "飞书",
AliyunSMS: "阿里云短信服务",
"Sms template must contain parameters: ": "短信模板必须包含以下变量:",
DingDing: "钉钉自定义机器人",
WebHookUrl: "钉钉自定义机器人 Webhook 地址",
SecretKey: "钉钉自定义机器人加签密钥",
"For safety, must use secret key": "出于安全考虑,必须使用加签密钥",
WeCom: "企业微信群机器人",
"WeCom Bot Key": "企业微信群机器人 Key",
PushByTechulus: "Push by Techulus",
wayToGetPagerDutyKey: "您可以在 Service -> Service Directory -> (选择一个 Service) -> Integrations -> Add integration 页面中搜索“Events API V2”以获取此 Integration Key更多信息请看{0}",
"Integration Key": "Integration Key",
"Integration URL": "Integration URL",
"Auto resolve or acknowledged": "自动标记为已解决或已读",
"do nothing": "不做任何操作",
"auto acknowledged": "自动标记为已读",
"auto resolve": "自动标记为已解决",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "API 接入点",
@ -396,9 +436,6 @@ export default {
proxyDescription: "代理必须配置到至少一个监控项后才会工作。",
enableProxyDescription: "此代理必须启用才能对监控项的网络请求起作用。您可以通过修改激活状态,临时在所有监控项中禁用此代理。",
setAsDefaultProxyDescription: "此代理会对新创建的监控项默认激活,您仍可以在监控项配置中单独禁用此代理。",
"Proxy Protocol": "代理协议",
"Proxy Server": "代理服务器",
"Server Address": "服务器地址",
"Certificate Chain": "证书链",
Valid: "有效",
Invalid: "无效",
@ -407,9 +444,14 @@ export default {
PhoneNumbers: "PhoneNumbers",
TemplateCode: "TemplateCode",
SignName: "SignName",
"Sms template must contain parameters: ": "短信模板必须包含以下变量:",
"Bark Endpoint": "Bark 接入点",
"Bark Group": "Bark 群组",
"Bark Sound": "Bark 铃声",
DingDing: "钉钉自定义机器人",
WebHookUrl: "钉钉自定义机器人 Webhook 地址",
SecretKey: "钉钉自定义机器人加签密钥",
"For safety, must use secret key": "出于安全考虑,必须使用加签密钥",
"Device Token": "Apple Device Token",
Platform: "平台",
iOS: "iOS",
@ -418,21 +460,18 @@ export default {
High: "高",
Retry: "重试次数",
Topic: "Gorush Topic",
WeCom: "企业微信群机器人",
"WeCom Bot Key": "企业微信群机器人 Key",
"Setup Proxy": "设置代理",
"Proxy Protocol": "代理协议",
"Proxy Server": "代理服务器",
"Server Address": "服务器地址",
"Proxy server has authentication": "代理服务器启用了身份验证功能",
User: "用户名",
Installed: "已安装",
"Not installed": "未安装",
Running: "运行中",
"Not running": "未运行",
"Message:": "信息:",
wayToGetCloudflaredURL: "(可从 {0} 下载 cloudflared",
cloudflareWebsite: "Cloudflare 网站",
"Don't know how to get the token? Please read the guide:": "不知道如何获取 Token请阅读指南",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "如果您正在通过 Cloudflare Tunnel 访问网站,则停止可能会导致当前连接断开。您确定要停止吗?请输入密码以确认。",
"Other Software": "其他软件",
"For example: nginx, Apache and Traefik.": "例如nginx、Apache 和 Traefik。",
"Please read": "请阅读",
"Remove Token": "移除 Token",
Start: "启动",
Stop: "停止",
@ -450,6 +489,18 @@ export default {
"New Status Page": "新的状态页",
"Page Not Found": "未找到该页面",
"Reverse Proxy": "反向代理",
Backup: "备份",
About: "关于",
wayToGetCloudflaredURL: "(可从 {0} 下载 cloudflared",
cloudflareWebsite: "Cloudflare 网站",
"Message:": "信息:",
"Don't know how to get the token? Please read the guide:": "不知道如何获取 Token请阅读指南",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "如果您正在通过 Cloudflare Tunnel 访问网站,则停止可能会导致当前连接断开。您确定要停止吗?请输入密码以确认。",
"HTTP Headers": "HTTP 头",
"Trust Proxy": "可信的代理类字段",
"Other Software": "其他软件",
"For example: nginx, Apache and Traefik.": "例如nginx、Apache 和 Traefik。",
"Please read": "请阅读",
"Subject:": "颁发给:",
"Valid To:": "有效期至:",
"Days Remaining:": "剩余有效天数:",
@ -459,26 +510,28 @@ export default {
"Domain Name Expiry Notification": "域名到期时通知",
Proxy: "代理",
"Date Created": "创建于",
HomeAssistant: "Home Assistant",
onebotHttpAddress: "OneBot HTTP 地址",
onebotMessageType: "OneBot 消息类型",
onebotGroupMessage: "群聊",
onebotPrivateMessage: "私聊",
onebotUserOrGroupId: "群组/用户ID",
onebotUserOrGroupId: "群组/用户 ID",
onebotSafetyTips: "出于安全原因,请务必设置 AccessToken",
topic: "Topic",
topicExplanation: "MQTT 传递给监控的 Topic",
successMessage: "成功时消息",
successMessageExplanation: "MQTT 成功时所传递的消息",
Customize: "自定义",
"Custom Footer": "自定义底部",
"Custom CSS": "自定义 CSS",
"PushDeer Key": "PushDeer Key",
"Footer Text": "底部自定义文本",
"Show Powered By": "显示 Powered By",
"Domain Names": "域名",
signedInDisp: "当前用户: {0}",
signedInDispDisabled: "已禁用身份验证",
RadiusSecret: "Radius 共享机密",
RadiusSecretDescription: "客户端和服务器之间共享的密钥",
RadiusCalledStationId: "NAS 网络访问服务器号码Called Station Id",
RadiusCalledStationIdDescription: "所访问的服务器的标识",
RadiusCallingStationId: "呼叫方号码Calling Station Id",
RadiusCallingStationIdDescription: "发出请求的设备的标识",
"Certificate Expiry Notification": "证书到期时通知",
"API Username": "API 凭证 Username",
"API Key": "API 凭证 Key",
"API Username": "API Username",
"API Key": "API Key",
"Recipient Number": "收件人手机号码",
"From Name/Number": "发件人名称/手机号码",
"Leave blank to use a shared sender number.": "留空以使用平台共享的发件人手机号码",
@ -526,38 +579,11 @@ export default {
"Retype the address.": "重新输入地址;",
"Go back to the previous page.": "返回到上一页面。",
"Coming Soon": "即将推出",
wayToGetClickSendSMSToken: "您可以从 {0} 获取 API 凭证 Username 和 凭证 Key。",
signedInDisp: "当前用户: {0}",
signedInDispDisabled: "已禁用身份验证",
dnsPortDescription: "DNS 服务器端口,默认为 53你可以在任何时候更改此端口.",
error: "错误",
critical: "关键",
wayToGetPagerDutyKey: "你可以在 Service -> Service Directory -> (选择一个 Service) -> Integrations -> Add integration 页面中搜索 \"Events API V2\" 以获取此 Integration Key更多信息请参见 {0}",
"Integration Key": "Integration Key",
"Integration URL": "Integration URL",
"Auto resolve or acknowledged": "自动标记为已解决或已读",
"do nothing": "不做任何操作",
"auto acknowledged": "自动标记为已读",
"auto resolve": "自动标记为已解决",
wayToGetClickSendSMSToken: "您可以在{0}获取 API Username 和 API Key。",
"Connection String": "连接字符串",
Query: "查询语句",
settingsCertificateExpiry: "TLS 证书过期通知",
certificationExpiryDescription: "HTTPS 监控项发现被监控目标的 TLS 证书剩余有效期少于以下天数时将发出通知:",
"ntfy Topic": "ntfy 主题",
Domain: "域名",
Workstation: "工作站",
resendEveryXTimes: "每 {0} 次失败则重复发送一次",
resendDisabled: "为 0 时禁用重复发送",
"Resend Notification if Down X times consequently": "连续失败时重复发送通知的间隔次数",
"HTTP Headers": "HTTP 头",
"Trust Proxy": "可信的代理类字段",
HomeAssistant: "Home Assistant",
RadiusSecret: "Radius 共享机密",
RadiusSecretDescription: "客户端和服务器之间共享的密钥",
RadiusCalledStationId: "NAS 网络访问服务器号码Called Station Id",
RadiusCalledStationIdDescription: "所访问的服务器的标识",
RadiusCallingStationId: "呼叫方号码Calling Station Id",
RadiusCallingStationIdDescription: "发出请求的设备的标识",
"Setup Docker Host": "配置 Docker 宿主信息",
"Connection Type": "连接方式",
"Docker Daemon": "Docker 守护进程",
@ -568,6 +594,9 @@ export default {
"Container Name / ID": "容器名称 / ID",
"Docker Host": "Docker 宿主",
"Docker Hosts": "Docker 宿主",
"ntfy Topic": "ntfy Topic",
Domain: "域名",
Workstation: "工作站",
disableCloudflaredNoAuthMsg: "您现在正处于 No Auth 模式,无需输入密码",
trustProxyDescription: "信任 'X-Forwarded-*' 头。如果您的 Uptime Kuma 是通过 Nginx 或 Apache 等反代服务对外提供访问的话,则您应当启用本功能以获取正确的客户端 IP。",
wayToGetLineNotifyToken: "您可以在 {0} 获取 Access token",
@ -584,7 +613,7 @@ export default {
"Event data:": "事件数据:",
"Then choose an action, for example switch the scene to where an RGB light is red.": "然后您可以选择关联操作,例如切换到 RGB 灯发出红光的场景",
"Frontend Version": "前端版本",
"Frontend Version do not match backend version!": "前端版本与后端版本不",
"Frontend Version do not match backend version!": "前端版本与后端版本不匹配",
"Base URL": "API 基础地址",
goAlertInfo: "GoAlert 是一个用于呼叫调度、自动汇报和通知(如 SMS 或语音呼叫)的开源应用程序。在正确的时间以正确的方式自动让正确的人参与!{0}",
goAlertIntegrationKeyInfo: "使用形如 aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee 的通用 API 集成密钥,通常是复制来的链接中的 token 参数值。",
@ -598,5 +627,57 @@ export default {
"Gateway Type": "网关类型",
SMSManager: "SMSManager",
"You can divide numbers with": "可用的分隔符:",
"or": "或",
or: "或",
recurringInterval: "时间间隔",
Recurring: "重复",
strategyManual: "手动启用/禁用",
warningTimezone: "使用服务器时区",
weekdayShortMon: "周一",
weekdayShortTue: "周二",
weekdayShortWed: "周三",
weekdayShortThu: "周四",
weekdayShortFri: "周五",
weekdayShortSat: "周六",
weekdayShortSun: "周日",
dayOfWeek: "每周计划",
dayOfMonth: "每月计划",
lastDay: "结束日",
lastDay1: "每月最后一天",
lastDay2: "每月倒数第二天",
lastDay3: "每月倒数第三天",
lastDay4: "每月倒数第四天",
"No Maintenance": "无维护计划",
pauseMaintenanceMsg: "确定要暂停吗?",
"maintenanceStatus-under-maintenance": "正在维护",
"maintenanceStatus-inactive": "未启用",
"maintenanceStatus-scheduled": "已计划",
"maintenanceStatus-ended": "已结束",
"maintenanceStatus-unknown": "未知",
"Display Timezone": "显示时区",
"Server Timezone": "服务器时区",
statusPageMaintenanceEndDate: "结束时间",
IconUrl: "图标 URL",
"Enable DNS Cache": "启用 DNS 缓存",
Enable: "启用",
Disable: "禁用",
dnsCacheDescription: "可能无法在某些 IPv6 环境工作,如果遇到问题请禁用。",
"Single Maintenance Window": "单一时间窗口",
"Maintenance Time Window of a Day": "每日维护时间窗口",
"Effective Date Range": "生效日期范围",
"Schedule Maintenance": "计划维护",
"Date and Time": "日期时间",
"DateTime Range": "日期时间范围",
Strategy: "策略",
"Free Mobile User Identifier": "Free Mobile User Identifier",
"Free Mobile API Key": "Free Mobile API Key",
"Enable TLS": "启用 TLS",
"Proto Service Name": "Proto 服务名称",
"Proto Method": "Proto 方法",
"Proto Content": "Proto 内容",
Economy: "经济",
Lowcost: "低价",
high: "高价",
"General Monitor Type": "常规监控类型",
"Passive Monitor Type": "被动监控类型",
"Specific Monitor Type": "针对监控类型",
};

View File

@ -473,6 +473,12 @@ table {
.dropdown-clear-data {
float: right;
ul {
width: 100%;
min-width: unset;
padding-left: 0;
}
}
.dark {

View File

@ -189,14 +189,36 @@ export default {
* @param {string} [currentPassword] Only need for disableAuth to true
*/
saveSettings(callback, currentPassword) {
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {
this.$root.toastRes(res);
this.loadSettings();
let valid = this.validateSettings();
if (valid.success) {
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {
this.$root.toastRes(res);
this.loadSettings();
if (callback) {
callback();
}
});
if (callback) {
callback();
}
});
} else {
this.$root.toastError(valid.msg);
}
},
/**
* Ensure settings are valid
* @returns {Object} Contains success state and error msg
*/
validateSettings() {
if (this.settings.keepDataPeriodDays < 0) {
return {
success: false,
msg: this.$t("dataRetentionTimeError"),
};
}
return {
success: true,
msg: "",
};
},
}
};

View File

@ -0,0 +1,44 @@
import { currentLocale } from "../../../src/i18n";
describe("Test i18n.js", () => {
it("currentLocale()", () => {
const setLanguage = (language) => {
Object.defineProperty(window.navigator, 'language', {
value: language,
writable: true
});
}
setLanguage('en-EN');
expect(currentLocale()).equal("en");
setLanguage('zh-HK');
expect(currentLocale()).equal("zh-HK");
// Note that in Safari on iOS prior to 10.2, the country code returned is lowercase: "en-us", "fr-fr" etc.
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language
setLanguage('zh-hk');
expect(currentLocale()).equal("en");
setLanguage('en-US');
expect(currentLocale()).equal("en");
setLanguage('ja-ZZ');
expect(currentLocale()).equal("ja");
setLanguage('zz-ZZ');
expect(currentLocale()).equal("en");
setLanguage('zz-ZZ');
expect(currentLocale()).equal("en");
setLanguage('en');
localStorage.locale = "en";
expect(currentLocale()).equal("en");
localStorage.locale = "zh-HK";
expect(currentLocale()).equal("zh-HK");
});
});