mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-06 13:12:33 +02:00
Merge remote-tracking branch 'louislam/master' into feature/monitor-checks
This commit is contained in:
commit
fc261825a9
64 changed files with 10497 additions and 1039 deletions
|
@ -2,6 +2,8 @@
|
|||
/dist
|
||||
/node_modules
|
||||
/data
|
||||
/test
|
||||
/kubernetes
|
||||
/.do
|
||||
**/.dockerignore
|
||||
**/.git
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
|
|
1
.github/ISSUE_TEMPLATE/ask-for-help.md
vendored
1
.github/ISSUE_TEMPLATE/ask-for-help.md
vendored
|
@ -16,4 +16,3 @@ Docker Version:
|
|||
Node.js Version (Without Docker only):
|
||||
OS:
|
||||
Browser:
|
||||
|
||||
|
|
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -15,6 +15,7 @@ A clear and concise description of what the bug is.
|
|||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
|
@ -23,7 +24,6 @@ Steps to reproduce the behavior:
|
|||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
|
||||
**Info**
|
||||
Uptime Kuma Version:
|
||||
Using Docker?: Yes/No
|
||||
|
@ -32,13 +32,11 @@ Node.js Version (Without Docker only):
|
|||
OS:
|
||||
Browser:
|
||||
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Error Log**
|
||||
It is easier for us to find out the problem.
|
||||
|
||||
Docker: "docker logs <container id>"
|
||||
PM2: "~/.pm2/logs/" (e.g. /home/ubuntu/.pm2/logs)
|
||||
|
||||
Docker: `docker logs <container id>`
|
||||
PM2: `~/.pm2/logs/` (e.g. `/home/ubuntu/.pm2/logs`)
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,4 +7,4 @@ dist-ssr
|
|||
|
||||
/data
|
||||
!/data/.gitkeep
|
||||
.vscode
|
||||
.vscode
|
||||
|
|
|
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
|||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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 structed and commented so well, lol. Sorry about that.
|
||||
|
||||
The project was created with vite.js (vue3). Then I created a sub-directory called "server" for server part. Both frontend and backend share the same package.json.
|
||||
The project was created with vite.js (vue3). Then I created a sub-directory called "server" for server part. Both frontend and backend share the same package.json.
|
||||
|
||||
The frontend code build into "dist" directory. The server uses "dist" as root. This is how production is working.
|
||||
|
||||
|
@ -20,13 +20,13 @@ If you are not sure, feel free to create an empty pull request draft first.
|
|||
- Add a chart
|
||||
- Fix a bug
|
||||
|
||||
### *️⃣ Requires one more reviewer
|
||||
### *️⃣ Requires one more reviewer
|
||||
|
||||
I do not have such knowledge to test it.
|
||||
|
||||
- Add k8s supports
|
||||
- Add k8s supports
|
||||
|
||||
### *️⃣ Low Priority
|
||||
### *️⃣ Low Priority
|
||||
|
||||
It changed my current workflow and require further studies.
|
||||
|
||||
|
@ -41,9 +41,9 @@ It changed my current workflow and require further studies.
|
|||
|
||||
# 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 something need to learn so much and need to config so much before you can finally start the app.
|
||||
|
||||
For example, recently, because I am not a python expert, I spent a 2 hours to resolve all problems in order to install and use the Apprise cli. Apprise requires so many hidden requirements, I have to figure out myself how to solve the problems by Google search for my OS. That is painful. I do not want Uptime Kuma to be like this way, so:
|
||||
For example, recently, because I am not a python expert, I spent a 2 hours to resolve all problems in order to install and use the Apprise cli. Apprise requires so many hidden requirements, I have to figure out myself how to solve the problems by Google search for my OS. That is painful. I do not want Uptime Kuma to be like this way, so:
|
||||
|
||||
- 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
|
||||
- Single container for Docker users, no very complex docker-composer file. Just map the volume and expose the port, then good to go
|
||||
|
@ -52,8 +52,8 @@ For example, recently, because I am not a python expert, I spent a 2 hours to re
|
|||
|
||||
# Coding Styles
|
||||
|
||||
- Follow .editorconfig
|
||||
- Follow eslint
|
||||
- Follow `.editorconfig`
|
||||
- Follow ESLint
|
||||
|
||||
## Name convention
|
||||
|
||||
|
@ -62,12 +62,13 @@ For example, recently, because I am not a python expert, I spent a 2 hours to re
|
|||
- CSS/SCSS: dash-type
|
||||
|
||||
# Tools
|
||||
|
||||
- Node.js >= 14
|
||||
- Git
|
||||
- IDE that supports .editorconfig and eslint (I am using Intellji Idea)
|
||||
- IDE that supports EditorConfig and ESLint (I am using Intellji Idea)
|
||||
- A SQLite tool (I am using SQLite Expert Personal)
|
||||
|
||||
# Install dependencies
|
||||
# Install dependencies
|
||||
|
||||
```bash
|
||||
npm install --dev
|
||||
|
@ -75,7 +76,7 @@ npm install --dev
|
|||
|
||||
For npm@7, you need --legacy-peer-deps
|
||||
|
||||
```
|
||||
```bash
|
||||
npm install --legacy-peer-deps --dev
|
||||
```
|
||||
|
||||
|
@ -84,23 +85,22 @@ npm install --legacy-peer-deps --dev
|
|||
```bash
|
||||
npm run start-server
|
||||
|
||||
# Or
|
||||
# Or
|
||||
|
||||
node server/server.js
|
||||
```
|
||||
|
||||
It binds to 0.0.0.0:3001 by default.
|
||||
|
||||
It binds to `0.0.0.0:3001` by default.
|
||||
|
||||
## Backend Details
|
||||
|
||||
It is mainly a socket.io app + express.js.
|
||||
|
||||
express.js is just used for serving the frontend built files (index.html, .js and .css etc.)
|
||||
express.js is just used for serving the frontend built files (index.html, .js and .css etc.)
|
||||
|
||||
# Frontend Dev
|
||||
|
||||
Start frontend dev server. Hot-reload enabled in this way. It binds to 0.0.0.0:3000.
|
||||
Start frontend dev server. Hot-reload enabled in this way. It binds to `0.0.0.0:3000` by default.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
@ -108,7 +108,7 @@ npm run dev
|
|||
|
||||
PS: You can ignore those scss warnings, those warnings are from Bootstrap that I cannot fix.
|
||||
|
||||
You can use Vue Devtool Chrome extension for debugging.
|
||||
You can use Vue.js devtools Chrome extension for debugging.
|
||||
|
||||
After the frontend server started. It cannot connect to the websocket server even you have started the server. You need to tell the frontend that is a dev env by running this in DevTool console and refresh:
|
||||
|
||||
|
@ -118,8 +118,7 @@ localStorage.dev = "dev";
|
|||
|
||||
So that the frontend will try to connect websocket server in 3001.
|
||||
|
||||
Alternately, you can specific NODE_ENV to "development".
|
||||
|
||||
Alternately, you can specific `NODE_ENV` to "development".
|
||||
|
||||
## Build the frontend
|
||||
|
||||
|
@ -131,22 +130,17 @@ npm run build
|
|||
|
||||
Uptime Kuma Frontend is a single page application (SPA). Most paths are handled by Vue Router.
|
||||
|
||||
The router in "src/main.js"
|
||||
The router is in `src/router.js`
|
||||
|
||||
As you can see, most data in frontend is stored in root level, even though you changed the current router to any other pages.
|
||||
|
||||
The data and socket logic in "src/mixins/socket.js"
|
||||
The data and socket logic are in `src/mixins/socket.js`.
|
||||
|
||||
# Database Migration
|
||||
|
||||
1. create `patch{num}.sql` in `./db/`
|
||||
1. update `latestVersion` in `./server/database.js`
|
||||
1. Create `patch{num}.sql` in `./db/`
|
||||
2. Update `latestVersion` in `./server/database.js`
|
||||
|
||||
# Unit Test
|
||||
|
||||
Yes, no unit test for now. I know it is very important, but at the same time my spare time is very limited. I want to implement my ideas first. I will go back to this in some points.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
17
README.md
17
README.md
|
@ -20,12 +20,11 @@ It is a 5 minutes live demo, all data will be deleted after that. The server is
|
|||
|
||||
VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much!
|
||||
|
||||
|
||||
## ⭐ Features
|
||||
|
||||
* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record.
|
||||
* Fancy, Reactive, Fast UI/UX.
|
||||
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284).
|
||||
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284).
|
||||
* 20 seconds interval.
|
||||
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
|
||||
|
||||
|
@ -65,7 +64,6 @@ If you need more options or need to browse via a reserve proxy, please read:
|
|||
|
||||
https://github.com/louislam/uptime-kuma/wiki/%F0%9F%94%A7-How-to-Install
|
||||
|
||||
|
||||
## 🆙 How to Update
|
||||
|
||||
Please read:
|
||||
|
@ -107,15 +105,15 @@ Telegram Notification Sample:
|
|||
|
||||
If you love this project, please consider giving me a ⭐.
|
||||
|
||||
|
||||
## 🗣️ Discussion
|
||||
|
||||
You can also discuss or ask for help in [Issues](https://github.com/louislam/uptime-kuma/issues).
|
||||
|
||||
Alternatively, you can discuss in my original post on reddit: https://www.reddit.com/r/selfhosted/comments/oi7dc7/uptime_kuma_a_fancy_selfhosted_monitoring_tool_an/
|
||||
|
||||
I think the real "Discussion" tab is hard to use, as it is reddit-like flow, I always missed new comments.
|
||||
### Issues Page
|
||||
You can discuss or ask for help in [Issues](https://github.com/louislam/uptime-kuma/issues).
|
||||
|
||||
### Subreddit
|
||||
My Reddit account: louislamlam
|
||||
You can mention me if you ask question on Reddit.
|
||||
https://www.reddit.com/r/UptimeKuma/
|
||||
|
||||
## Contribute
|
||||
|
||||
|
@ -126,4 +124,3 @@ If you want to translate Uptime Kuma into your langauge, please read: https://gi
|
|||
If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||
|
||||
English proofreading is needed too because my grammar is not that great sadly. Feel free to correct my grammar in this readme, source code, or wiki.
|
||||
|
||||
|
|
|
@ -10,5 +10,6 @@ currently being supported with security updates.
|
|||
| 1.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
Please report security issues to uptime@kuma.pet.
|
||||
|
||||
https://github.com/louislam/uptime-kuma/issues
|
||||
Do not use the issue tracker or discuss it in the public as it will cause more damage.
|
||||
|
|
7
db/patch-add-retry-interval-monitor.sql
Normal file
7
db/patch-add-retry-interval-monitor.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD retry_interval INTEGER default 0 not null;
|
||||
|
||||
COMMIT;
|
19
db/patch10.sql
Normal file
19
db/patch10.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
CREATE TABLE tag (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
color VARCHAR(255) NOT NULL,
|
||||
created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE monitor_tag (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
monitor_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
value TEXT,
|
||||
CONSTRAINT FK_tag FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX monitor_tag_monitor_id_index ON monitor_tag (monitor_id);
|
||||
CREATE INDEX monitor_tag_tag_id_index ON monitor_tag (tag_id);
|
28
dockerfile
28
dockerfile
|
@ -5,32 +5,34 @@ WORKDIR /app
|
|||
# split the sqlite install here, so that it can caches the arm prebuilt
|
||||
# do not modify it, since we don't want to re-compile the arm prebuilt again
|
||||
RUN apt update && \
|
||||
apt --yes install python3 python3-pip python3-dev git g++ make && \
|
||||
ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
npm install mapbox/node-sqlite3#593c9d --build-from-source
|
||||
apt --yes install python3 python3-pip python3-dev git g++ make && \
|
||||
ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
npm install mapbox/node-sqlite3#593c9d --build-from-source
|
||||
|
||||
COPY . .
|
||||
RUN npm install --legacy-peer-deps && npm run build && npm prune --production
|
||||
RUN npm install --legacy-peer-deps && \
|
||||
npm run build && \
|
||||
npm prune --production && \
|
||||
chmod +x /app/extra/entrypoint.sh
|
||||
|
||||
|
||||
FROM node:14-bullseye-slim AS release
|
||||
WORKDIR /app
|
||||
|
||||
# Install Apprise,
|
||||
# add sqlite3 cli for debugging in the future
|
||||
# iputils-ping for ping
|
||||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
|
||||
RUN apt update && \
|
||||
apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||
sqlite3 \
|
||||
iputils-ping && \
|
||||
pip3 --no-cache-dir install apprise && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||
sqlite3 iputils-ping util-linux && \
|
||||
pip3 --no-cache-dir install apprise && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy app files from build layer
|
||||
COPY --from=build /app /app
|
||||
COPY --from=build /app /app
|
||||
|
||||
EXPOSE 3001
|
||||
VOLUME ["/app/data"]
|
||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
||||
ENTRYPOINT ["extra/entrypoint.sh"]
|
||||
CMD ["node", "server/server.js"]
|
||||
|
||||
FROM release AS nightly
|
||||
|
|
|
@ -4,29 +4,33 @@ 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
|
||||
ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
npm install mapbox/node-sqlite3#593c9d && \
|
||||
apk del .build-deps && \
|
||||
rm -f /usr/bin/python
|
||||
|
||||
COPY . .
|
||||
RUN npm install --legacy-peer-deps && npm run build && npm prune --production
|
||||
RUN npm install --legacy-peer-deps && \
|
||||
npm run build && \
|
||||
npm prune --production && \
|
||||
chmod +x /app/extra/entrypoint.sh
|
||||
|
||||
|
||||
FROM node:14-alpine3.12 AS release
|
||||
WORKDIR /app
|
||||
|
||||
# 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 && \
|
||||
pip3 --no-cache-dir install apprise && \
|
||||
rm -rf /root/.cache
|
||||
# Install apprise, iputils for non-root ping, setpriv
|
||||
RUN apk add --no-cache iputils setpriv python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
|
||||
pip3 --no-cache-dir install apprise && \
|
||||
rm -rf /root/.cache
|
||||
|
||||
# Copy app files from build layer
|
||||
COPY --from=build /app /app
|
||||
COPY --from=build /app /app
|
||||
|
||||
EXPOSE 3001
|
||||
VOLUME ["/app/data"]
|
||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
||||
ENTRYPOINT ["extra/entrypoint.sh"]
|
||||
CMD ["node", "server/server.js"]
|
||||
|
||||
FROM release AS nightly
|
||||
|
|
21
extra/entrypoint.sh
Normal file
21
extra/entrypoint.sh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# set -e Exit the script if an error happens
|
||||
set -e
|
||||
PUID=${PUID=1000}
|
||||
PGID=${PGID=1000}
|
||||
|
||||
files_ownership () {
|
||||
# -h Changes the ownership of an encountered symbolic link and not that of the file or directory pointed to by the symbolic link.
|
||||
# -R Recursively descends the specified directories
|
||||
# -c Like verbose but report only when a change is made
|
||||
chown -hRc "$PUID":"$PGID" /app/data
|
||||
}
|
||||
|
||||
echo "==> Performing startup jobs and maintenance tasks"
|
||||
files_ownership
|
||||
|
||||
echo "==> Starting application with user $PUID group $PGID"
|
||||
|
||||
# --clear-groups Clear supplementary groups.
|
||||
exec setpriv --reuid "$PUID" --regid "$PGID" --clear-groups "$@"
|
|
@ -1,4 +1,4 @@
|
|||
// Need to use es6 to read language files
|
||||
// Need to use ES6 to read language files
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
@ -14,6 +14,7 @@ const copyRecursiveSync = function (src, dest) {
|
|||
let exists = fs.existsSync(src);
|
||||
let stats = exists && fs.statSync(src);
|
||||
let isDirectory = exists && stats.isDirectory();
|
||||
|
||||
if (isDirectory) {
|
||||
fs.mkdirSync(dest);
|
||||
fs.readdirSync(src).forEach(function (childItemName) {
|
||||
|
@ -24,8 +25,9 @@ const copyRecursiveSync = function (src, dest) {
|
|||
fs.copyFileSync(src, dest);
|
||||
}
|
||||
};
|
||||
console.log(process.argv)
|
||||
const baseLangCode = process.argv[2] || "zh-HK";
|
||||
|
||||
console.log("Arguments:", process.argv)
|
||||
const baseLangCode = process.argv[2] || "en";
|
||||
console.log("Base Lang: " + baseLangCode);
|
||||
fs.rmdirSync("./languages", { recursive: true });
|
||||
copyRecursiveSync("../../src/languages", "./languages");
|
||||
|
@ -33,46 +35,50 @@ copyRecursiveSync("../../src/languages", "./languages");
|
|||
const en = (await import("./languages/en.js")).default;
|
||||
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
|
||||
const files = fs.readdirSync("./languages");
|
||||
console.log(files);
|
||||
console.log("Files:", files);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith(".js")) {
|
||||
console.log("Processing " + file);
|
||||
const lang = await import("./languages/" + file);
|
||||
if (!file.endsWith(".js")) {
|
||||
console.log("Skipping " + file)
|
||||
continue;
|
||||
}
|
||||
|
||||
let obj;
|
||||
console.log("Processing " + file);
|
||||
const lang = await import("./languages/" + file);
|
||||
|
||||
if (lang.default) {
|
||||
console.log("is js module");
|
||||
obj = lang.default;
|
||||
} else {
|
||||
console.log("empty file");
|
||||
obj = {
|
||||
languageName: "<Your Language name in your language (not in English)>"
|
||||
};
|
||||
}
|
||||
|
||||
// En first
|
||||
for (const key in en) {
|
||||
if (! obj[key]) {
|
||||
obj[key] = en[key];
|
||||
}
|
||||
let obj;
|
||||
|
||||
if (lang.default) {
|
||||
obj = lang.default;
|
||||
} else {
|
||||
console.log("Empty file");
|
||||
obj = {
|
||||
languageName: "<Your Language name in your language (not in English)>"
|
||||
};
|
||||
}
|
||||
|
||||
// En first
|
||||
for (const key in en) {
|
||||
if (! obj[key]) {
|
||||
obj[key] = en[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (baseLang !== en) {
|
||||
// Base second
|
||||
for (const key in baseLang) {
|
||||
if (! obj[key]) {
|
||||
obj[key] = key;
|
||||
}
|
||||
}
|
||||
|
||||
const code = "export default " + util.inspect(obj, {
|
||||
depth: null,
|
||||
});
|
||||
|
||||
fs.writeFileSync(`../../src/languages/${file}`, code);
|
||||
|
||||
}
|
||||
|
||||
const code = "export default " + util.inspect(obj, {
|
||||
depth: null,
|
||||
});
|
||||
|
||||
fs.writeFileSync(`../../src/languages/${file}`, code);
|
||||
}
|
||||
|
||||
fs.rmdirSync("./languages", { recursive: true });
|
||||
console.log("Done, fix the format by eslint now");
|
||||
console.log("Done. Fixing formatting by ESLint...");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<meta name="theme-color" id="theme-color" content="" />
|
||||
<meta name="description" content="Uptime Kuma monitoring tool" />
|
||||
<title>Uptime Kuma</title>
|
||||
|
|
|
@ -8,24 +8,25 @@ Kustomize is a tool which builds a complete deployment file for all config eleme
|
|||
You can edit the files in the ```uptime-kuma``` folder except the ```kustomization.yml``` until you know what you're doing.
|
||||
If you want to choose another namespace you can edit the ```kustomization.yml``` in the ```kubernetes```-Folder and change the ```namespace: uptime-kuma``` to something you like.
|
||||
|
||||
It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service
|
||||
It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service.
|
||||
|
||||
## What do I have to edit?
|
||||
|
||||
## What do i have to edit?
|
||||
You have to edit the ```ingressroute.yml``` to your needs.
|
||||
This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.github.io/ingress-nginx/) in combination with the [cert-manager](https://cert-manager.io/).
|
||||
|
||||
- host
|
||||
- secrets and secret names
|
||||
- Host
|
||||
- Secrets and secret names
|
||||
- (Cluster)Issuer (optional)
|
||||
- the Version in the Deployment-File
|
||||
- update:
|
||||
- change to newer version and run the above commands, it will update the pods one after another
|
||||
- The Version in the Deployment-File
|
||||
- Update:
|
||||
- Change to newer version and run the above commands, it will update the pods one after another
|
||||
|
||||
## How To use:
|
||||
## How To use
|
||||
|
||||
- install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/)
|
||||
- Install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/)
|
||||
- Edit files mentioned above to your needs
|
||||
- run ```kustomize build > apply.yml```
|
||||
- run ```kubectl apply -f apply.yml```
|
||||
- Run ```kustomize build > apply.yml```
|
||||
- Run ```kubectl apply -f apply.yml```
|
||||
|
||||
Now you should see some k8s magic and Uptime-Kuma should be available at the specified address.
|
||||
|
|
8337
package-lock.json
generated
8337
package-lock.json
generated
File diff suppressed because it is too large
Load diff
33
package.json
33
package.json
|
@ -34,24 +34,26 @@
|
|||
"test-install-script-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
|
||||
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
|
||||
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
||||
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
|
||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||
"update-language-files": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix"
|
||||
"update-language-files_old": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix",
|
||||
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"@popperjs/core": "^2.10.1",
|
||||
"args-parser": "^1.3.0",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.21.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bootstrap": "^5.1.0",
|
||||
"bootstrap": "^5.1.1",
|
||||
"chart.js": "^3.5.1",
|
||||
"chartjs-adapter-dayjs": "^1.0.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"compare-versions": "^3.6.0",
|
||||
"dayjs": "^1.10.6",
|
||||
"dayjs": "^1.10.7",
|
||||
"express": "^4.17.1",
|
||||
"express-basic-auth": "^1.2.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
@ -69,10 +71,11 @@
|
|||
"socket.io-client": "^4.2.0",
|
||||
"sqlite3": "github:mapbox/node-sqlite3#593c9d",
|
||||
"tcp-ping": "^0.1.1",
|
||||
"timezones-list": "^3.0.1",
|
||||
"thirty-two": "^1.0.2",
|
||||
"v-pagination-3": "^0.1.6",
|
||||
"vue": "^3.2.8",
|
||||
"vue-chart-3": "^0.5.7",
|
||||
"vue-chart-3": "^0.5.8",
|
||||
"vue-confirm-dialog": "^1.0.2",
|
||||
"vue-i18n": "^9.1.7",
|
||||
"vue-multiselect": "^3.0.0-alpha.2",
|
||||
|
@ -81,19 +84,19 @@
|
|||
"vue-toastification": "^2.0.0-rc.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.15.0",
|
||||
"@types/bootstrap": "^5.1.2",
|
||||
"@vitejs/plugin-legacy": "^1.5.2",
|
||||
"@vitejs/plugin-vue": "^1.6.0",
|
||||
"@vue/compiler-sfc": "^3.2.6",
|
||||
"core-js": "^3.17.0",
|
||||
"@babel/eslint-parser": "^7.15.4",
|
||||
"@types/bootstrap": "^5.1.4",
|
||||
"@vitejs/plugin-legacy": "^1.5.3",
|
||||
"@vitejs/plugin-vue": "^1.6.2",
|
||||
"@vue/compiler-sfc": "^3.2.11",
|
||||
"core-js": "^3.17.3",
|
||||
"dns2": "^2.0.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^7.17.0",
|
||||
"sass": "^1.38.2",
|
||||
"sass": "^1.39.2",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"typescript": "^4.4.2",
|
||||
"vite": "^2.5.3"
|
||||
"typescript": "^4.4.3",
|
||||
"vite": "^2.5.7"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/icon-192x192.png
Normal file
BIN
public/icon-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
public/icon-512x512.png
Normal file
BIN
public/icon-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
19
public/manifest.json
Normal file
19
public/manifest.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Uptime Kuma",
|
||||
"short_name": "Uptime Kuma",
|
||||
"start_url": "/",
|
||||
"background_color": "#fff",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -31,6 +31,7 @@ class Database {
|
|||
"patch-setting-value-type.sql": true,
|
||||
"patch-improve-performance.sql": true,
|
||||
"patch-2fa.sql": true,
|
||||
"patch-add-retry-interval-monitor.sql": true,
|
||||
"patch-add-monitor-checks-table.sql": true,
|
||||
}
|
||||
|
||||
|
@ -38,7 +39,7 @@ class Database {
|
|||
* The finally version should be 10 after merged tag feature
|
||||
* @deprecated Use patchList for any new feature
|
||||
*/
|
||||
static latestVersion = 9;
|
||||
static latestVersion = 10;
|
||||
|
||||
static noReject = true;
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ class Monitor extends BeanModel {
|
|||
notificationIDList[bean.notification_id] = true;
|
||||
}
|
||||
|
||||
const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]);
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
|
@ -44,6 +46,7 @@ class Monitor extends BeanModel {
|
|||
active: this.active,
|
||||
type: this.type,
|
||||
interval: this.interval,
|
||||
retryInterval: this.retryInterval,
|
||||
ignoreTls: this.getIgnoreTls(),
|
||||
upsideDown: this.isUpsideDown(),
|
||||
maxredirects: this.maxredirects,
|
||||
|
@ -52,6 +55,7 @@ class Monitor extends BeanModel {
|
|||
dns_last_result: this.dns_last_result,
|
||||
notificationIDList,
|
||||
checks: this.checks,
|
||||
tags: tags,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -288,12 +292,17 @@ class Monitor extends BeanModel {
|
|||
bean.important = false;
|
||||
}
|
||||
|
||||
let beatInterval = this.interval;
|
||||
|
||||
if (bean.status === UP) {
|
||||
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
|
||||
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`)
|
||||
} else if (bean.status === PENDING) {
|
||||
console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Type: ${this.type}`)
|
||||
if (this.retryInterval !== this.interval) {
|
||||
beatInterval = this.retryInterval;
|
||||
}
|
||||
console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`)
|
||||
} else {
|
||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`)
|
||||
}
|
||||
|
||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||
|
@ -305,7 +314,7 @@ class Monitor extends BeanModel {
|
|||
previousBeat = bean;
|
||||
|
||||
if (! this.isStop) {
|
||||
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
|
||||
this.heartbeatInterval = setTimeout(beat, beatInterval * 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
13
server/model/tag.js
Normal file
13
server/model/tag.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
class Tag extends BeanModel {
|
||||
toJSON() {
|
||||
return {
|
||||
id: this._id,
|
||||
name: this._name,
|
||||
color: this._color,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Tag;
|
|
@ -62,6 +62,11 @@ class Discord extends NotificationProvider {
|
|||
],
|
||||
}],
|
||||
}
|
||||
|
||||
if (notification.discordPrefixMessage) {
|
||||
discorddowndata.content = notification.discordPrefixMessage;
|
||||
}
|
||||
|
||||
await axios.post(notification.discordWebhookUrl, discorddowndata)
|
||||
return okMsg;
|
||||
|
||||
|
@ -92,6 +97,11 @@ class Discord extends NotificationProvider {
|
|||
],
|
||||
}],
|
||||
}
|
||||
|
||||
if (notification.discordPrefixMessage) {
|
||||
discordupdata.content = notification.discordPrefixMessage;
|
||||
}
|
||||
|
||||
await axios.post(notification.discordWebhookUrl, discordupdata)
|
||||
return okMsg;
|
||||
}
|
||||
|
|
124
server/notification-providers/teams.js
Normal file
124
server/notification-providers/teams.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class Teams extends NotificationProvider {
|
||||
name = "teams";
|
||||
|
||||
_statusMessageFactory = (status, monitorName) => {
|
||||
if (status === DOWN) {
|
||||
return `🔴 Application [${monitorName}] went down`;
|
||||
} else if (status === UP) {
|
||||
return `✅ Application [${monitorName}] is back online`;
|
||||
}
|
||||
return "Notification";
|
||||
};
|
||||
|
||||
_getThemeColor = (status) => {
|
||||
if (status === DOWN) {
|
||||
return "ff0000";
|
||||
}
|
||||
if (status === UP) {
|
||||
return "00e804";
|
||||
}
|
||||
return "008cff";
|
||||
};
|
||||
|
||||
_notificationPayloadFactory = ({
|
||||
status,
|
||||
monitorMessage,
|
||||
monitorName,
|
||||
monitorUrl,
|
||||
}) => {
|
||||
const notificationMessage = this._statusMessageFactory(
|
||||
status,
|
||||
monitorName
|
||||
);
|
||||
|
||||
const facts = [];
|
||||
|
||||
if (monitorName) {
|
||||
facts.push({
|
||||
name: "Monitor",
|
||||
value: monitorName,
|
||||
});
|
||||
}
|
||||
|
||||
if (monitorUrl) {
|
||||
facts.push({
|
||||
name: "URL",
|
||||
value: monitorUrl,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
"@context": "https://schema.org/extensions",
|
||||
"@type": "MessageCard",
|
||||
themeColor: this._getThemeColor(status),
|
||||
summary: notificationMessage,
|
||||
sections: [
|
||||
{
|
||||
activityImage:
|
||||
"https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png",
|
||||
activityTitle: "**Uptime Kuma**",
|
||||
},
|
||||
{
|
||||
activityTitle: notificationMessage,
|
||||
},
|
||||
{
|
||||
activityTitle: "**Description**",
|
||||
text: monitorMessage,
|
||||
facts,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
_sendNotification = async (webhookUrl, payload) => {
|
||||
await axios.post(webhookUrl, payload);
|
||||
};
|
||||
|
||||
_handleGeneralNotification = (webhookUrl, msg) => {
|
||||
const payload = this._notificationPayloadFactory({
|
||||
monitorMessage: msg
|
||||
});
|
||||
|
||||
return this._sendNotification(webhookUrl, payload);
|
||||
};
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully. ";
|
||||
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
await this._handleGeneralNotification(notification.webhookUrl, msg);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let url;
|
||||
|
||||
if (monitorJSON["type"] === "port") {
|
||||
url = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
url += ":" + monitorJSON["port"];
|
||||
}
|
||||
} else {
|
||||
url = monitorJSON["url"];
|
||||
}
|
||||
|
||||
const payload = this._notificationPayloadFactory({
|
||||
monitorMessage: heartbeatJSON.msg,
|
||||
monitorName: monitorJSON.name,
|
||||
monitorUrl: url,
|
||||
status: heartbeatJSON.status,
|
||||
});
|
||||
|
||||
await this._sendNotification(notification.webhookUrl, payload);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Teams;
|
|
@ -13,6 +13,7 @@ const RocketChat = require("./notification-providers/rocket-chat");
|
|||
const Signal = require("./notification-providers/signal");
|
||||
const Slack = require("./notification-providers/slack");
|
||||
const SMTP = require("./notification-providers/smtp");
|
||||
const Teams = require("./notification-providers/teams");
|
||||
const Telegram = require("./notification-providers/telegram");
|
||||
const Webhook = require("./notification-providers/webhook");
|
||||
|
||||
|
@ -28,6 +29,7 @@ class Notification {
|
|||
const list = [
|
||||
new Apprise(),
|
||||
new Discord(),
|
||||
new Teams(),
|
||||
new Gotify(),
|
||||
new Line(),
|
||||
new LunaSea(),
|
||||
|
|
282
server/server.js
282
server/server.js
|
@ -1,4 +1,9 @@
|
|||
console.log("Welcome to Uptime Kuma");
|
||||
|
||||
if (! process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = "production";
|
||||
}
|
||||
|
||||
console.log("Node Env: " + process.env.NODE_ENV);
|
||||
|
||||
const { sleep, debug, getRandomInt } = require("../src/util");
|
||||
|
@ -151,6 +156,10 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||
|
||||
app.use("/", express.static("dist"));
|
||||
|
||||
app.get("/.well-known/change-password", async (_, response) => {
|
||||
response.redirect("https://github.com/louislam/uptime-kuma/wiki/Reset-Password-via-CLI");
|
||||
});
|
||||
|
||||
// Universal Route Handler, must be at the end
|
||||
app.get("*", async (_request, response) => {
|
||||
response.send(indexHTML);
|
||||
|
@ -478,6 +487,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||
bean.type = monitor.type
|
||||
bean.url = monitor.url
|
||||
bean.interval = monitor.interval
|
||||
bean.retryInterval = monitor.retryInterval;
|
||||
bean.hostname = monitor.hostname;
|
||||
bean.maxretries = monitor.maxretries;
|
||||
bean.port = monitor.port;
|
||||
|
@ -513,6 +523,22 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("getMonitorList", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
await sendMonitorList(socket);
|
||||
callback({
|
||||
ok: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getMonitor", async (monitorID, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
@ -607,6 +633,160 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("getTags", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
const list = await R.findAll("tag")
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
tags: list.map(bean => bean.toJSON()),
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("addTag", async (tag, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
let bean = R.dispense("tag")
|
||||
bean.name = tag.name
|
||||
bean.color = tag.color
|
||||
await R.store(bean)
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
tag: await bean.toJSON(),
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("editTag", async (tag, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
let bean = await R.findOne("monitor", " id = ? ", [ tag.id ])
|
||||
bean.name = tag.name
|
||||
bean.color = tag.color
|
||||
await R.store(bean)
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
tag: await bean.toJSON(),
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("deleteTag", async (tagID, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
await R.exec("DELETE FROM tag WHERE id = ? ", [ tagID ])
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("addMonitorTag", async (tagID, monitorID, value, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
await R.exec("INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)", [
|
||||
tagID,
|
||||
monitorID,
|
||||
value,
|
||||
])
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("editMonitorTag", async (tagID, monitorID, value, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
await R.exec("UPDATE monitor_tag SET value = ? WHERE tag_id = ? AND monitor_id = ?", [
|
||||
value,
|
||||
tagID,
|
||||
monitorID,
|
||||
])
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Edited Successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("deleteMonitorTag", async (tagID, monitorID, value, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
await R.exec("DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ? AND value = ?", [
|
||||
tagID,
|
||||
monitorID,
|
||||
value,
|
||||
])
|
||||
|
||||
// Cleanup unused Tags
|
||||
await R.exec("delete from tag where ( select count(*) from monitor_tag mt where tag.id = mt.tag_id ) = 0");
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("changePassword", async (password, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
@ -747,7 +927,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("uploadBackup", async (uploadedJSON, callback) => {
|
||||
socket.on("uploadBackup", async (uploadedJSON, importHandle, callback) => {
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
|
@ -755,53 +935,79 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||
|
||||
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`)
|
||||
|
||||
let notificationList = backupData.notificationList;
|
||||
let monitorList = backupData.monitorList;
|
||||
let notificationListData = backupData.notificationList;
|
||||
let monitorListData = backupData.monitorList;
|
||||
|
||||
if (notificationList.length >= 1) {
|
||||
for (let i = 0; i < notificationList.length; i++) {
|
||||
let notification = JSON.parse(notificationList[i].config);
|
||||
await Notification.save(notification, null, socket.userID)
|
||||
if (importHandle == "overwrite") {
|
||||
for (let id in monitorList) {
|
||||
let monitor = monitorList[id]
|
||||
await monitor.stop()
|
||||
}
|
||||
await R.exec("DELETE FROM heartbeat");
|
||||
await R.exec("DELETE FROM monitor_notification");
|
||||
await R.exec("DELETE FROM monitor_tls_info");
|
||||
await R.exec("DELETE FROM notification");
|
||||
await R.exec("DELETE FROM monitor");
|
||||
}
|
||||
|
||||
if (notificationListData.length >= 1) {
|
||||
let notificationNameList = await R.getAll("SELECT name FROM notification");
|
||||
let notificationNameListString = JSON.stringify(notificationNameList);
|
||||
|
||||
for (let i = 0; i < notificationListData.length; i++) {
|
||||
if ((importHandle == "skip" && notificationNameListString.includes(notificationListData[i].name) == false) || importHandle == "keep" || importHandle == "overwrite") {
|
||||
|
||||
let notification = JSON.parse(notificationListData[i].config);
|
||||
await Notification.save(notification, null, socket.userID)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monitorList.length >= 1) {
|
||||
for (let i = 0; i < monitorList.length; i++) {
|
||||
let monitor = {
|
||||
name: monitorList[i].name,
|
||||
type: monitorList[i].type,
|
||||
url: monitorList[i].url,
|
||||
interval: monitorList[i].interval,
|
||||
hostname: monitorList[i].hostname,
|
||||
maxretries: monitorList[i].maxretries,
|
||||
port: monitorList[i].port,
|
||||
ignoreTls: monitorList[i].ignoreTls,
|
||||
upsideDown: monitorList[i].upsideDown,
|
||||
maxredirects: monitorList[i].maxredirects,
|
||||
checks: monitorList[i].checks,
|
||||
dns_resolve_type: monitorList[i].dns_resolve_type,
|
||||
dns_resolve_server: monitorList[i].dns_resolve_server,
|
||||
notificationIDList: {},
|
||||
}
|
||||
if (monitorListData.length >= 1) {
|
||||
let monitorNameList = await R.getAll("SELECT name FROM monitor");
|
||||
let monitorNameListString = JSON.stringify(monitorNameList);
|
||||
|
||||
let bean = R.dispense("monitor")
|
||||
for (let i = 0; i < monitorListData.length; i++) {
|
||||
if ((importHandle == "skip" && monitorNameListString.includes(monitorListData[i].name) == false) || importHandle == "keep" || importHandle == "overwrite") {
|
||||
|
||||
let notificationIDList = monitor.notificationIDList;
|
||||
delete monitor.notificationIDList;
|
||||
let monitor = {
|
||||
name: monitorList[i].name,
|
||||
type: monitorList[i].type,
|
||||
url: monitorList[i].url,
|
||||
interval: monitorList[i].interval,
|
||||
hostname: monitorList[i].hostname,
|
||||
maxretries: monitorList[i].maxretries,
|
||||
port: monitorList[i].port,
|
||||
ignoreTls: monitorList[i].ignoreTls,
|
||||
upsideDown: monitorList[i].upsideDown,
|
||||
maxredirects: monitorList[i].maxredirects,
|
||||
checks: monitorList[i].checks,
|
||||
dns_resolve_type: monitorList[i].dns_resolve_type,
|
||||
dns_resolve_server: monitorList[i].dns_resolve_server,
|
||||
notificationIDList: {},
|
||||
}
|
||||
|
||||
monitor.checks_json = JSON.stringify(monitor.checks);
|
||||
delete monitor.accepted_statuscodes;
|
||||
let bean = R.dispense("monitor")
|
||||
|
||||
bean.import(monitor)
|
||||
bean.user_id = socket.userID
|
||||
await R.store(bean)
|
||||
let notificationIDList = monitor.notificationIDList;
|
||||
delete monitor.notificationIDList;
|
||||
|
||||
monitor.checks_json = JSON.stringify(monitor.checks);
|
||||
delete monitor.accepted_statuscodes;
|
||||
|
||||
await updateMonitorNotification(bean.id, notificationIDList)
|
||||
bean.import(monitor)
|
||||
bean.user_id = socket.userID
|
||||
await R.store(bean)
|
||||
|
||||
await updateMonitorNotification(bean.id, notificationIDList)
|
||||
|
||||
if (monitorListData[i].active == 1) {
|
||||
await startMonitor(socket.userID, bean.id);
|
||||
} else {
|
||||
await pauseMonitor(socket.userID, bean.id);
|
||||
}
|
||||
|
||||
if (monitorList[i].active == 1) {
|
||||
await startMonitor(socket.userID, bean.id);
|
||||
} else {
|
||||
await pauseMonitor(socket.userID, bean.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,44 +1,69 @@
|
|||
<template>
|
||||
<div class="shadow-box list mb-3" :class="{ scrollbar: scrollbar }">
|
||||
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||
<div class="shadow-box mb-3">
|
||||
<div class="list-header">
|
||||
<div class="placeholder"></div>
|
||||
<div class="search-wrapper">
|
||||
<a v-if="searchText == ''" class="search-icon">
|
||||
<font-awesome-icon icon="search" />
|
||||
</a>
|
||||
<a v-if="searchText != ''" class="search-icon" @click="clearSearchText">
|
||||
<font-awesome-icon icon="times" />
|
||||
</a>
|
||||
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="list" :class="{ scrollbar: scrollbar }">
|
||||
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||
</div>
|
||||
|
||||
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }">
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-8 small-padding" :class="{ 'monitorItem': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info">
|
||||
<Uptime :monitor="item" type="24" :pill="true" />
|
||||
{{ item.name }}
|
||||
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }">
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-8 small-padding" :class="{ 'monitorItem': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info">
|
||||
<Uptime :monitor="item" type="24" :pill="true" />
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="tags">
|
||||
<Tag v-for="tag in item.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-6 col-md-4">
|
||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-6 col-md-4">
|
||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||
<div class="col-12">
|
||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||
<div class="col-12">
|
||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
||||
import Uptime from "../components/Uptime.vue";
|
||||
import Tag from "../components/Tag.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Uptime,
|
||||
HeartbeatBar,
|
||||
Tag,
|
||||
},
|
||||
props: {
|
||||
scrollbar: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchText: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedMonitorList() {
|
||||
let result = Object.values(this.$root.monitorList);
|
||||
|
@ -68,6 +93,17 @@ export default {
|
|||
return m1.name.localeCompare(m2.name);
|
||||
})
|
||||
|
||||
// Simple filter by search text
|
||||
// finds monitor name, tag name or tag value
|
||||
if (this.searchText != "") {
|
||||
const loweredSearchText = this.searchText.toLowerCase();
|
||||
result = result.filter(monitor => {
|
||||
return monitor.name.toLowerCase().includes(loweredSearchText)
|
||||
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
||||
|| tag.value?.toLowerCase().includes(loweredSearchText))
|
||||
})
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
@ -75,6 +111,9 @@ export default {
|
|||
monitorURL(id) {
|
||||
return "/dashboard/" + id;
|
||||
},
|
||||
clearSearchText() {
|
||||
this.searchText = "";
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -87,6 +126,43 @@ export default {
|
|||
padding-right: 5px !important;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
border-radius: 10px 10px 0 0;
|
||||
margin: -10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.dark & {
|
||||
background-color: #161b22;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 770px) {
|
||||
.list-header {
|
||||
margin: -20px;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
padding: 10px;
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
max-width: 15em;
|
||||
}
|
||||
|
||||
.list {
|
||||
&.scrollbar {
|
||||
min-height: calc(100vh - 240px);
|
||||
|
@ -140,4 +216,11 @@ export default {
|
|||
.monitorItem {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tags {
|
||||
padding-left: 62px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<option value="webhook">Webhook</option>
|
||||
<option value="smtp">{{ $t("Email") }} (SMTP)</option>
|
||||
<option value="discord">Discord</option>
|
||||
<option value="teams">Microsoft Teams</option>
|
||||
<option value="signal">Signal</option>
|
||||
<option value="gotify">Gotify</option>
|
||||
<option value="slack">Slack</option>
|
||||
|
@ -80,6 +81,11 @@
|
|||
<label for="discord-username" class="form-label">Bot Display Name</label>
|
||||
<input id="discord-username" v-model="notification.discordUsername" type="text" class="form-control" autocomplete="false" :placeholder="$root.appName">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="discord-prefix-message" class="form-label">Prefix Custom Message</label>
|
||||
<input id="discord-prefix-message" v-model="notification.discordPrefixMessage" type="text" class="form-control" autocomplete="false" placeholder="Hello @everyone is...">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'signal'">
|
||||
|
@ -395,6 +401,8 @@
|
|||
|
||||
<!-- DEPRECATED! Please create vue component in "./src/components/notifications/{notification name}.vue" -->
|
||||
|
||||
<Teams v-if="notification.type === 'teams'" />
|
||||
|
||||
<div class="mb-3 mt-4">
|
||||
<hr class="dropdown-divider mb-4">
|
||||
|
||||
|
@ -444,6 +452,7 @@ import { ucfirst } from "../util.ts"
|
|||
import Confirm from "./Confirm.vue";
|
||||
import HiddenInput from "./HiddenInput.vue";
|
||||
import Telegram from "./notifications/Telegram.vue";
|
||||
import Teams from "./notifications/Teams.vue";
|
||||
import SMTP from "./notifications/SMTP.vue";
|
||||
|
||||
export default {
|
||||
|
@ -451,6 +460,7 @@ export default {
|
|||
Confirm,
|
||||
HiddenInput,
|
||||
Telegram,
|
||||
Teams,
|
||||
SMTP,
|
||||
},
|
||||
props: {},
|
||||
|
|
73
src/components/Tag.vue
Normal file
73
src/components/Tag.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div class="tag-wrapper rounded d-inline-flex"
|
||||
:class="{ 'px-3': size == 'normal',
|
||||
'py-1': size == 'normal',
|
||||
'm-2': size == 'normal',
|
||||
'px-2': size == 'sm',
|
||||
'py-0': size == 'sm',
|
||||
'm-1': size == 'sm',
|
||||
}"
|
||||
:style="{ backgroundColor: item.color, fontSize: size == 'sm' ? '0.7em' : '1em' }"
|
||||
>
|
||||
<span class="tag-text">{{ displayText }}</span>
|
||||
<span v-if="remove != null" class="ps-1 btn-remove" @click="remove(item)">
|
||||
<font-awesome-icon icon="times" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
remove: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "normal",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayText() {
|
||||
if (this.item.value == "") {
|
||||
return this.item.name;
|
||||
} else {
|
||||
return `${this.item.name}: ${this.item.value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag-wrapper {
|
||||
color: white;
|
||||
opacity: 0.85;
|
||||
|
||||
.dark & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
padding-bottom: 1px !important;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-remove {
|
||||
font-size: 0.9em;
|
||||
line-height: 24px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.btn-remove:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
405
src/components/TagsManager.vue
Normal file
405
src/components/TagsManager.vue
Normal file
|
@ -0,0 +1,405 @@
|
|||
<template>
|
||||
<div>
|
||||
<h4 class="mb-3">{{ $t("Tags") }}</h4>
|
||||
<div class="mb-3 p-1">
|
||||
<tag
|
||||
v-for="item in selectedTags"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:remove="deleteTag"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-add"
|
||||
:disabled="processing"
|
||||
@click.stop="showAddDialog"
|
||||
>
|
||||
<font-awesome-icon class="me-1" icon="plus" /> {{ $t("Add") }}
|
||||
</button>
|
||||
</div>
|
||||
<div ref="modal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<vue-multiselect
|
||||
v-model="newDraftTag.select"
|
||||
class="mb-2"
|
||||
:options="tagOptions"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('Add New below or Select...')"
|
||||
track-by="id"
|
||||
label="name"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="mx-2 py-1 px-3 rounded d-inline-flex"
|
||||
style="margin-top: -5px; margin-bottom: -5px; height: 24px;"
|
||||
:style="{ color: textColor(option), backgroundColor: option.color + ' !important' }"
|
||||
>
|
||||
<span>
|
||||
{{ option.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #singleLabel="{ option }">
|
||||
<div class="py-1 px-3 rounded d-inline-flex"
|
||||
style="height: 24px;"
|
||||
:style="{ color: textColor(option), backgroundColor: option.color + ' !important' }"
|
||||
>
|
||||
<span>{{ option.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</vue-multiselect>
|
||||
<div v-if="newDraftTag.select?.name == null" class="d-flex mb-2">
|
||||
<div class="w-50 pe-2">
|
||||
<input v-model="newDraftTag.name" class="form-control"
|
||||
:class="{'is-invalid': validateDraftTag.nameInvalid}"
|
||||
:placeholder="$t('Name')"
|
||||
@keydown.enter.prevent="onEnter"
|
||||
/>
|
||||
<div class="invalid-feedback">
|
||||
{{ $t("Tag with this name already exist.") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-50 ps-2">
|
||||
<vue-multiselect
|
||||
v-model="newDraftTag.color"
|
||||
:options="colorOptions"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('color')"
|
||||
track-by="color"
|
||||
label="name"
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="mx-2 py-1 px-3 rounded d-inline-flex"
|
||||
style="height: 24px; color: white;"
|
||||
:style="{ backgroundColor: option.color + ' !important' }"
|
||||
>
|
||||
<span>{{ option.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #singleLabel="{ option }">
|
||||
<div class="py-1 px-3 rounded d-inline-flex"
|
||||
style="height: 24px; color: white;"
|
||||
:style="{ backgroundColor: option.color + ' !important' }"
|
||||
>
|
||||
<span>{{ option.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</vue-multiselect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input v-model="newDraftTag.value" class="form-control"
|
||||
:class="{'is-invalid': validateDraftTag.valueInvalid}"
|
||||
:placeholder="$t('value (optional)')"
|
||||
@keydown.enter.prevent="onEnter"
|
||||
/>
|
||||
<div class="invalid-feedback">
|
||||
{{ $t("Tag with this value already exist.") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary float-end"
|
||||
:disabled="processing || validateDraftTag.invalid"
|
||||
@click.stop="addDraftTag"
|
||||
>
|
||||
{{ $t("Add") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Modal } from "bootstrap";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import Tag from "../components/Tag.vue";
|
||||
import { useToast } from "vue-toastification"
|
||||
const toast = useToast()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Tag,
|
||||
VueMultiselect,
|
||||
},
|
||||
props: {
|
||||
preSelectedTags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: null,
|
||||
existingTags: [],
|
||||
processing: false,
|
||||
newTags: [],
|
||||
deleteTags: [],
|
||||
newDraftTag: {
|
||||
name: null,
|
||||
select: null,
|
||||
color: null,
|
||||
value: "",
|
||||
invalid: true,
|
||||
nameInvalid: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tagOptions() {
|
||||
const tagOptions = this.existingTags;
|
||||
for (const tag of this.newTags) {
|
||||
if (!tagOptions.find(t => t.name == tag.name && t.color == tag.color)) {
|
||||
tagOptions.push(tag);
|
||||
}
|
||||
}
|
||||
return tagOptions;
|
||||
},
|
||||
selectedTags() {
|
||||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id == tag.id));
|
||||
},
|
||||
colorOptions() {
|
||||
return [
|
||||
{ name: this.$t("Gray"),
|
||||
color: "#4B5563" },
|
||||
{ name: this.$t("Red"),
|
||||
color: "#DC2626" },
|
||||
{ name: this.$t("Orange"),
|
||||
color: "#D97706" },
|
||||
{ name: this.$t("Green"),
|
||||
color: "#059669" },
|
||||
{ name: this.$t("Blue"),
|
||||
color: "#2563EB" },
|
||||
{ name: this.$t("Indigo"),
|
||||
color: "#4F46E5" },
|
||||
{ name: this.$t("Purple"),
|
||||
color: "#7C3AED" },
|
||||
{ name: this.$t("Pink"),
|
||||
color: "#DB2777" },
|
||||
]
|
||||
},
|
||||
validateDraftTag() {
|
||||
let nameInvalid = false;
|
||||
let valueInvalid = false;
|
||||
let invalid = true;
|
||||
if (this.deleteTags.find(tag => tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value)) {
|
||||
// Undo removing a Tag
|
||||
nameInvalid = false;
|
||||
valueInvalid = false;
|
||||
invalid = false;
|
||||
} else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0) {
|
||||
// Try to create new tag with existing name
|
||||
nameInvalid = true;
|
||||
invalid = true;
|
||||
} else if (this.newTags.concat(this.preSelectedTags).filter(tag => (
|
||||
tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value
|
||||
) || (
|
||||
tag.name == this.newDraftTag.name && tag.value == this.newDraftTag.value
|
||||
)).length > 0) {
|
||||
// Try to add a tag with existing name and value
|
||||
valueInvalid = true;
|
||||
invalid = true;
|
||||
} else if (this.newDraftTag.select != null) {
|
||||
// Select an existing tag, no need to validate
|
||||
invalid = false;
|
||||
valueInvalid = false;
|
||||
} else if (this.newDraftTag.color == null || this.newDraftTag.name === "") {
|
||||
// Missing form inputs
|
||||
nameInvalid = false;
|
||||
invalid = true;
|
||||
} else {
|
||||
// Looks valid
|
||||
invalid = false;
|
||||
nameInvalid = false;
|
||||
valueInvalid = false;
|
||||
}
|
||||
return {
|
||||
invalid,
|
||||
nameInvalid,
|
||||
valueInvalid,
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
this.getExistingTags();
|
||||
},
|
||||
methods: {
|
||||
showAddDialog() {
|
||||
this.modal.show();
|
||||
},
|
||||
getExistingTags() {
|
||||
this.$root.getSocket().emit("getTags", (res) => {
|
||||
if (res.ok) {
|
||||
this.existingTags = res.tags;
|
||||
} else {
|
||||
toast.error(res.msg)
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteTag(item) {
|
||||
if (item.new) {
|
||||
// Undo Adding a new Tag
|
||||
this.newTags = this.newTags.filter(tag => !(tag.name == item.name && tag.value == item.value));
|
||||
} else {
|
||||
// Remove an Existing Tag
|
||||
this.deleteTags.push(item);
|
||||
}
|
||||
},
|
||||
textColor(option) {
|
||||
if (option.color) {
|
||||
return "white";
|
||||
} else {
|
||||
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
|
||||
}
|
||||
},
|
||||
addDraftTag() {
|
||||
console.log("Adding Draft Tag: ", this.newDraftTag);
|
||||
if (this.newDraftTag.select != null) {
|
||||
if (this.deleteTags.find(tag => tag.name == this.newDraftTag.select.name && tag.value == this.newDraftTag.value)) {
|
||||
// Undo removing a tag
|
||||
this.deleteTags = this.deleteTags.filter(tag => !(tag.name == this.newDraftTag.select.name && tag.value == this.newDraftTag.value));
|
||||
} else {
|
||||
// Add an existing Tag
|
||||
this.newTags.push({
|
||||
id: this.newDraftTag.select.id,
|
||||
color: this.newDraftTag.select.color,
|
||||
name: this.newDraftTag.select.name,
|
||||
value: this.newDraftTag.value,
|
||||
new: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Add new Tag
|
||||
this.newTags.push({
|
||||
color: this.newDraftTag.color.color,
|
||||
name: this.newDraftTag.name.trim(),
|
||||
value: this.newDraftTag.value,
|
||||
new: true,
|
||||
})
|
||||
}
|
||||
this.clearDraftTag();
|
||||
},
|
||||
clearDraftTag() {
|
||||
this.newDraftTag = {
|
||||
name: null,
|
||||
select: null,
|
||||
color: null,
|
||||
value: "",
|
||||
invalid: true,
|
||||
nameInvalid: false,
|
||||
};
|
||||
this.modal.hide();
|
||||
},
|
||||
addTagAsync(newTag) {
|
||||
return new Promise((resolve) => {
|
||||
this.$root.getSocket().emit("addTag", newTag, resolve);
|
||||
});
|
||||
},
|
||||
addMonitorTagAsync(tagId, monitorId, value) {
|
||||
return new Promise((resolve) => {
|
||||
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve);
|
||||
});
|
||||
},
|
||||
deleteMonitorTagAsync(tagId, monitorId, value) {
|
||||
return new Promise((resolve) => {
|
||||
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
|
||||
});
|
||||
},
|
||||
onEnter() {
|
||||
if (!this.validateDraftTag.invalid) {
|
||||
this.addDraftTag();
|
||||
}
|
||||
},
|
||||
async submit(monitorId) {
|
||||
console.log(`Submitting tag changes for monitor ${monitorId}...`);
|
||||
this.processing = true;
|
||||
|
||||
for (const newTag of this.newTags) {
|
||||
let tagId;
|
||||
if (newTag.id == null) {
|
||||
// Create a New Tag
|
||||
let newTagResult;
|
||||
await this.addTagAsync(newTag).then((res) => {
|
||||
if (!res.ok) {
|
||||
toast.error(res.msg);
|
||||
newTagResult = false;
|
||||
}
|
||||
newTagResult = res.tag;
|
||||
});
|
||||
if (!newTagResult) {
|
||||
// abort
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
tagId = newTagResult.id;
|
||||
// Assign the new ID to the tags of the same name & color
|
||||
this.newTags.map(tag => {
|
||||
if (tag.name == newTag.name && tag.color == newTag.color) {
|
||||
tag.id = newTagResult.id;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
tagId = newTag.id;
|
||||
}
|
||||
|
||||
let newMonitorTagResult;
|
||||
// Assign tag to monitor
|
||||
await this.addMonitorTagAsync(tagId, monitorId, newTag.value).then((res) => {
|
||||
if (!res.ok) {
|
||||
toast.error(res.msg);
|
||||
newMonitorTagResult = false;
|
||||
}
|
||||
newMonitorTagResult = true;
|
||||
});
|
||||
if (!newMonitorTagResult) {
|
||||
// abort
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const deleteTag of this.deleteTags) {
|
||||
let deleteMonitorTagResult;
|
||||
await this.deleteMonitorTagAsync(deleteTag.tag_id, deleteTag.monitor_id, deleteTag.value).then((res) => {
|
||||
if (!res.ok) {
|
||||
toast.error(res.msg);
|
||||
deleteMonitorTagResult = false;
|
||||
}
|
||||
deleteMonitorTagResult = true;
|
||||
});
|
||||
if (!deleteMonitorTagResult) {
|
||||
// abort
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.getExistingTags();
|
||||
this.newTags = [];
|
||||
this.deleteTags = [];
|
||||
this.processing = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.btn-add {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
</style>
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">{{ $t("Password") }}</label>
|
||||
<HiddenInput id="password" v-model="$parent.notification.smtpPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<HiddenInput id="password" v-model="$parent.notification.smtpPassword" :required="false" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
|
29
src/components/notifications/Teams.vue
Normal file
29
src/components/notifications/Teams.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="teams-webhookurl" class="form-label">Webhook URL</label>
|
||||
<input
|
||||
id="teams-webhookurl"
|
||||
v-model="$parent.notification.webhookUrl"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<div class="form-text">
|
||||
You can learn how to create a webhook url
|
||||
<a
|
||||
href="https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook"
|
||||
target="_blank"
|
||||
>here</a>.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: "teams",
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
48
src/i18n.js
Normal file
48
src/i18n.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { createI18n } from "vue-i18n";
|
||||
import daDK from "./languages/da-DK";
|
||||
import deDE from "./languages/de-DE";
|
||||
import en from "./languages/en";
|
||||
import esEs from "./languages/es-ES";
|
||||
import etEE from "./languages/et-EE";
|
||||
import frFR from "./languages/fr-FR";
|
||||
import itIT from "./languages/it-IT";
|
||||
import ja from "./languages/ja";
|
||||
import koKR from "./languages/ko-KR";
|
||||
import nlNL from "./languages/nl-NL";
|
||||
import pl from "./languages/pl";
|
||||
import ruRU from "./languages/ru-RU";
|
||||
import sr from "./languages/sr";
|
||||
import srLatn from "./languages/sr-latn";
|
||||
import trTR from "./languages/tr-TR";
|
||||
import svSE from "./languages/sv-SE";
|
||||
import zhCN from "./languages/zh-CN";
|
||||
import zhHK from "./languages/zh-HK";
|
||||
|
||||
const languageList = {
|
||||
en,
|
||||
"zh-HK": zhHK,
|
||||
"de-DE": deDE,
|
||||
"nl-NL": nlNL,
|
||||
"es-ES": esEs,
|
||||
"fr-FR": frFR,
|
||||
"it-IT": itIT,
|
||||
"ja": ja,
|
||||
"da-DK": daDK,
|
||||
"sr": sr,
|
||||
"sr-latn": srLatn,
|
||||
"sv-SE": svSE,
|
||||
"tr-TR": trTR,
|
||||
"ko-KR": koKR,
|
||||
"ru-RU": ruRU,
|
||||
"zh-CN": zhCN,
|
||||
"pl": pl,
|
||||
"et-EE": etEE,
|
||||
};
|
||||
|
||||
export const i18n = createI18n({
|
||||
locale: localStorage.locale || "en",
|
||||
fallbackLocale: "en",
|
||||
silentFallbackWarn: true,
|
||||
silentTranslationWarn: false,
|
||||
messages: languageList,
|
||||
});
|
39
src/icon.js
39
src/icon.js
|
@ -1,10 +1,39 @@
|
|||
import { library } from "@fortawesome/fontawesome-svg-core"
|
||||
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash, faTimes } from "@fortawesome/free-solid-svg-icons"
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faArrowAltCircleUp,
|
||||
faCog,
|
||||
faEdit,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faList,
|
||||
faPause,
|
||||
faPlay,
|
||||
faPlus,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faTimes,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-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
|
||||
// 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, faEye, faEyeSlash, faTimes);
|
||||
library.add(
|
||||
faArrowAltCircleUp,
|
||||
faCog,
|
||||
faEdit,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faList,
|
||||
faPause,
|
||||
faPlay,
|
||||
faPlus,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faTimes,
|
||||
faTrash,
|
||||
);
|
||||
|
||||
export { FontAwesomeIcon };
|
||||
|
||||
export { FontAwesomeIcon }
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
# How to translate
|
||||
|
||||
1. Fork this repo.
|
||||
2. Create a language file. (e.g. `zh-TW.js`) The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm
|
||||
3. `npm run update-language-files --base-lang=de-DE`
|
||||
6. Your language file should be filled in. You can translate now.
|
||||
7. Translate `src/pages/Settings.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
|
||||
8. Import your language file in `src/main.js` and add it to `languageList` constant.
|
||||
9. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
|
||||
|
||||
|
||||
2. Create a language file (e.g. `zh-TW.js`). The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm
|
||||
3. Run `npm run update-language-files`. You can also use this command to check if there are new strings to translate for your language.
|
||||
4. Your language file should be filled in. You can translate now.
|
||||
5. Translate `src/pages/Settings.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
|
||||
6. Import your language file in `src/i18n.js` and add it to `languageList` constant.
|
||||
7. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
|
||||
|
||||
One of good examples:
|
||||
https://github.com/louislam/uptime-kuma/pull/316/files
|
||||
|
||||
|
||||
If you do not have programming skills, let me know in [Issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ export default {
|
|||
hour: "Timer",
|
||||
"-hour": "-Timer",
|
||||
checkEverySecond: "Tjek hvert {0} sekund",
|
||||
"Avg.": "Gns.",
|
||||
Response: "Respons",
|
||||
Ping: "Ping",
|
||||
"Monitor Type": "Overvåger Type",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For hver ny overvåger aktiveres denne underretning som standard. Du kan stadig deaktivere underretningen separat for hver skærm.",
|
||||
"Default enabled": "Standard aktiveret",
|
||||
"Also apply to existing monitors": "Anvend også på eksisterende overvågere",
|
||||
"Import/Export Backup": " Importér/Eksportér sikkerhedskopi",
|
||||
Export: "Eksport",
|
||||
Import: "Import",
|
||||
backupDescription: "Du kan sikkerhedskopiere alle Overvågere og alle underretninger til en JSON-fil.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ export default {
|
|||
hour: "Stunde",
|
||||
"-hour": "-Stunden",
|
||||
checkEverySecond: "Überprüfe alle {0} Sekunden",
|
||||
"Avg.": "Durchschn.",
|
||||
Response: "Antwortzeit",
|
||||
Ping: "Ping",
|
||||
"Monitor Type": "Monitor Typ",
|
||||
|
@ -113,7 +112,6 @@ export default {
|
|||
"Create your admin account": "Erstelle dein Admin Konto",
|
||||
"Repeat Password": "Wiederhole das Passwort",
|
||||
"Resource Record Type": "Resource Record Type",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
respTime: "Antw. Zeit (ms)",
|
||||
|
@ -128,6 +126,13 @@ export default {
|
|||
backupDescription3: "Sensible Daten wie Benachrichtigungstoken sind in der Exportdatei enthalten, bitte bewahre sie sorgfältig auf.",
|
||||
alertNoFile: "Bitte wähle eine Datei zum importieren aus.",
|
||||
alertWrongFileType: "Bitte wähle eine JSON Datei aus.",
|
||||
"Clear all statistics": "Lösche alle Statistiken",
|
||||
importHandleDescription: "Wähle 'Vorhandene überspringen' aus, wenn jeder Monitor oder Benachrichtigung mit demselben Namen übersprungen werden soll. 'Überschreiben' löscht jeden vorhandenen Monitor sowie Benachrichtigungen.",
|
||||
"Skip existing": "Vorhandene überspringen",
|
||||
Overwrite: "Überschreiben",
|
||||
Options: "Optionen",
|
||||
confirmImportMsg: "Möchtest du das Backup wirklich importieren? Bitte stelle sicher, dass die richtige Import Option ausgewählt ist.",
|
||||
"Keep both": "Beide behalten",
|
||||
twoFAVerifyLabel: "Bitte trage deinen Token ein um zu verifizieren das 2FA funktioniert",
|
||||
"Verify Token": "Token verifizieren",
|
||||
"Setup 2FA": "2FA Einrichten",
|
||||
|
@ -142,5 +147,25 @@ export default {
|
|||
Inactive: "Inaktiv",
|
||||
Token: "Token",
|
||||
"Show URI": "URI Anzeigen",
|
||||
"Clear all statistics": "Lösche alle Statistiken",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Füge neuen hinzu oder wähle aus...",
|
||||
"Tag with this name already exist.": "Ein Tag mit dem Namen existiert bereits.",
|
||||
"Tag with this value already exist.": "Ein Tag mit dem Wert existiert bereits.",
|
||||
color: "Farbe",
|
||||
"value (optional)": "Wert (Optional)",
|
||||
Gray: "Grau",
|
||||
Red: "Rot",
|
||||
Orange: "Orange",
|
||||
Green: "Grün",
|
||||
Blue: "Blau",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Lila",
|
||||
Pink: "Pink",
|
||||
"Search...": "Suchen...",
|
||||
"Heartbeat Retry Interval": "Takt-Wiederholungsintervall",
|
||||
retryCheckEverySecond: "Versuche alle {0} Sekunden",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
languageName: "English",
|
||||
checkEverySecond: "Check every {0} seconds.",
|
||||
"Avg.": "Avg.",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
|
||||
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
|
||||
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||
|
@ -20,6 +20,8 @@ export default {
|
|||
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?",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
||||
|
@ -68,6 +70,7 @@ export default {
|
|||
Port: "Port",
|
||||
"Heartbeat Interval": "Heartbeat Interval",
|
||||
Retries: "Retries",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
Advanced: "Advanced",
|
||||
"Upside Down Mode": "Upside Down Mode",
|
||||
"Max. Redirects": "Max. Redirects",
|
||||
|
@ -115,7 +118,8 @@ export default {
|
|||
"Last Result": "Last Result",
|
||||
"Create your admin account": "Create your admin account",
|
||||
"Repeat Password": "Repeat Password",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
respTime: "Resp. Time (ms)",
|
||||
|
@ -132,6 +136,11 @@ export default {
|
|||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
||||
alertNoFile: "Please select a file to import.",
|
||||
alertWrongFileType: "Please select a JSON file.",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
"Verify Token": "Verify Token",
|
||||
"Setup 2FA": "Setup 2FA",
|
||||
"Enable 2FA": "Enable 2FA",
|
||||
|
@ -142,5 +151,21 @@ export default {
|
|||
Inactive: "Inactive",
|
||||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "Español",
|
||||
checkEverySecond: "Comprobar cada {0} segundos.",
|
||||
"Avg.": "Media.",
|
||||
retriesDescription: "Número máximo de intentos antes de que el servicio se marque como CAÍDO y una notificación sea enviada.",
|
||||
ignoreTLSError: "Ignorar error TLS/SSL para sitios web HTTPS",
|
||||
upsideDownModeDescription: "Invertir el estado. Si el servicio es alcanzable, está CAÍDO.",
|
||||
|
@ -32,7 +31,7 @@ export default {
|
|||
Up: "Funcional",
|
||||
Down: "Caído",
|
||||
Pending: "Pendiente",
|
||||
Unknown: "Desconociso",
|
||||
Unknown: "Desconocido",
|
||||
Pause: "Pausa",
|
||||
Name: "Nombre",
|
||||
Status: "Estado",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "eesti",
|
||||
checkEverySecond: "Kontrolli {0} sekundilise vahega.",
|
||||
"Avg.": "≈",
|
||||
retriesDescription: "Mitu korda tuleb kontrollida, mille järel märkida 'maas' ja saata välja teavitus.",
|
||||
ignoreTLSError: "Eira TLS/SSL viga HTTPS veebisaitidel.",
|
||||
upsideDownModeDescription: "Käitle teenuse saadavust rikkena, teenuse kättesaamatust töötavaks.",
|
||||
|
@ -113,7 +112,6 @@ export default {
|
|||
clearEventsMsg: "Kas soovid seire kõik sündmused kustutada?",
|
||||
clearHeartbeatsMsg: "Kas soovid seire kõik tuksed kustutada?",
|
||||
confirmClearStatisticsMsg: "Kas soovid KÕIK statistika kustutada?",
|
||||
"Import/Export Backup": "Impordi/Ekspordi varukoopia",
|
||||
Export: "Eksport",
|
||||
Import: "Import",
|
||||
"Default enabled": "Kasuta vaikimisi",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ export default {
|
|||
hour: "Heure",
|
||||
"-hour": "Heures",
|
||||
checkEverySecond: "Vérifier toutes les {0} secondes",
|
||||
"Avg.": "Moyen",
|
||||
Response: "Temps de réponse",
|
||||
Ping: "Ping",
|
||||
"Monitor Type": "Type de Sonde",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
languageName: "Italiano (Italian)",
|
||||
checkEverySecond: "controlla ogni {0} secondi",
|
||||
"Avg.": "Media",
|
||||
retryCheckEverySecond: "Riprova ogni {0} secondi.",
|
||||
retriesDescription: "Tentativi da fare prima che il servizio venga marcato come \"giù\" e che una notifica venga inviata.",
|
||||
ignoreTLSError: "Ignora gli errori TLS/SSL per i siti in HTTPS.",
|
||||
upsideDownModeDescription: "Capovolgi lo stato. Se il servizio è raggiungibile viene marcato come \"GIÙ\".",
|
||||
|
@ -16,9 +16,16 @@ export default {
|
|||
resoverserverDescription: "Cloudflare è il server predefinito, è possibile cambiare il server DNS.",
|
||||
rrtypeDescription: "Scegliere il tipo di RR che si vuole monitorare",
|
||||
pauseMonitorMsg: "Si è certi di voler mettere in pausa?",
|
||||
enableDefaultNotificationDescription: "Per ogni nuovo monitoraggio questa notifica sarà abilitata di default. È comunque possibile disabilitare la notifica separatamente per ogni monitoraggio.",
|
||||
clearEventsMsg: "Si è certi di voler eliminare tutti gli eventi per questo servizio?",
|
||||
clearHeartbeatsMsg: "Si è certi di voler eliminare tutti gli intervalli di controllo per questo servizio?",
|
||||
confirmClearStatisticsMsg: "Si è certi di voler eliminare TUTTE le statistiche?",
|
||||
importHandleDescription: "Selezionare 'Ignora gli esistenti' si vuole ignorare l'importazione dei monitoraggi o delle notifiche con lo stesso nome. 'Sovrascrivi' eliminerà ogni monitoraggio e notifica esistente.",
|
||||
confirmImportMsg: "Si è certi di voler importare il backup? Essere certi di aver selezionato l'opzione corretta di importazione.",
|
||||
twoFAVerifyLabel: "Scrivi il token per verificare che l'autenticazione a due fattori funzioni",
|
||||
tokenValidSettingsMsg: "Il token è valido! È ora possibile salvare le impostazioni.",
|
||||
confirmEnableTwoFAMsg: "Si è certi di voler abilitare l'autenticazione a due fattori?",
|
||||
confirmDisableTwoFAMsg: "Si è certi di voler disabilitare l'autenticazione a due fattori?",
|
||||
Settings: "Impostazioni",
|
||||
Dashboard: "Cruscotto",
|
||||
"New Update": "Nuovo Aggiornamento Disponibile",
|
||||
|
@ -63,6 +70,7 @@ export default {
|
|||
Port: "Porta",
|
||||
"Heartbeat Interval": "Intervallo di controllo",
|
||||
Retries: "Tentativi",
|
||||
"Heartbeat Retry Interval": "Intervallo tra un tentativo di controllo e l'altro",
|
||||
Advanced: "Avanzate",
|
||||
"Upside Down Mode": "Modalità capovolta",
|
||||
"Max. Redirects": "Redirezionamenti massimi",
|
||||
|
@ -110,38 +118,54 @@ export default {
|
|||
"Last Result": "Ultimo risultato",
|
||||
"Create your admin account": "Crea l'account amministratore",
|
||||
"Repeat Password": "Ripeti Password",
|
||||
"Import Backup": "Importa Backup",
|
||||
"Export Backup": "Esporta Backup",
|
||||
Export: "Esporta",
|
||||
Import: "Importa",
|
||||
respTime: "Tempo di Risposta (ms)",
|
||||
notAvailableShort: "N/D",
|
||||
"Default enabled": "Abilitato di default",
|
||||
"Apply on all existing monitors": "Applica su tutti i monitoraggi",
|
||||
Create: "Crea",
|
||||
"Clear Data": "Cancella dati",
|
||||
Events: "Eventi",
|
||||
Heartbeats: "Controlli",
|
||||
"Auto Get": "Auto Get",
|
||||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
backupDescription2: "PS: History and event data is not included.",
|
||||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
||||
alertNoFile: "Please select a file to import.",
|
||||
alertWrongFileType: "Please select a JSON file.",
|
||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
||||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
|
||||
"Apply on all existing monitors": "Apply on all existing monitors",
|
||||
"Verify Token": "Verify Token",
|
||||
"Setup 2FA": "Setup 2FA",
|
||||
"Enable 2FA": "Enable 2FA",
|
||||
"Disable 2FA": "Disable 2FA",
|
||||
"2FA Settings": "2FA Settings",
|
||||
"Two Factor Authentication": "Two Factor Authentication",
|
||||
Active: "Active",
|
||||
Inactive: "Inactive",
|
||||
backupDescription: "È possibile fare il backup di tutti i monitoraggi e di tutte le notifiche in un file JSON.",
|
||||
backupDescription2: "P.S.: lo storico e i dati relativi agli eventi non saranno inclusi.",
|
||||
backupDescription3: "Dati sensibili come i token di autenticazione saranno inclusi nel backup, tenere quindi in un luogo sicuro.",
|
||||
alertNoFile: "Selezionare il file da importare.",
|
||||
alertWrongFileType: "Selezionare un file JSON.",
|
||||
"Clear all statistics": "Pulisci tutte le statistiche",
|
||||
"Skip existing": "Ignora gli esistenti",
|
||||
Overwrite: "Sovrascrivi",
|
||||
Options: "Opzioni",
|
||||
"Keep both": "Mantieni entrambi",
|
||||
"Verify Token": "Verifica Token",
|
||||
"Setup 2FA": "Imposta l'autenticazione a due fattori",
|
||||
"Enable 2FA": "Abilita l'autenticazione a due fattori",
|
||||
"Disable 2FA": "Disabilita l'autenticazione a due fattori",
|
||||
"2FA Settings": "Impostazioni autenticazione a due fattori",
|
||||
"Two Factor Authentication": "Autenticazione a due fattori",
|
||||
Active: "Attivata",
|
||||
Inactive: "Disattivata",
|
||||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
"Show URI": "Mostra URI",
|
||||
Tags: "Etichette",
|
||||
"Add New below or Select...": "Aggiungine una oppure scegli...",
|
||||
"Tag with this name already exist.": "Un'etichetta con questo nome già esiste.",
|
||||
"Tag with this value already exist.": "Un'etichetta con questo valore già esiste.",
|
||||
color: "colori",
|
||||
"value (optional)": "valore (opzionale)",
|
||||
Gray: "Grigio",
|
||||
Red: "Rosso",
|
||||
Orange: "Arancione",
|
||||
Green: "Verde",
|
||||
Blue: "Blu",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Viola",
|
||||
Pink: "Rosa",
|
||||
"Search...": "Cerca...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "日本語",
|
||||
checkEverySecond: "{0}秒ごとにチェックします。",
|
||||
"Avg.": "平均",
|
||||
retriesDescription: "サービスがダウンとしてマークされ、通知が送信されるまでの最大リトライ数",
|
||||
ignoreTLSError: "HTTPS ウェブサイトの TLS/SSL エラーを無視する",
|
||||
upsideDownModeDescription: "ステータスの扱いを逆にします。サービスに到達可能な場合は、DOWNとなる。",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "한국어",
|
||||
checkEverySecond: "{0} 초마다 체크해요.",
|
||||
"Avg.": "평균",
|
||||
retriesDescription: "서비스가 중단된 후 알림을 보내기 전 최대 재시도 횟수",
|
||||
ignoreTLSError: "HTTPS 웹사이트에서 TLS/SSL 에러 무시하기",
|
||||
upsideDownModeDescription: "서버 상태를 반대로 표시해요. 서버가 작동하면 오프라인으로 표시할 거에요.",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "Nederlands",
|
||||
checkEverySecond: "Controleer elke {0} seconden.",
|
||||
"Avg.": "Gem.",
|
||||
retriesDescription: "Maximum aantal nieuwe pogingen voordat de service wordt gemarkeerd als niet beschikbaar en er een melding wordt verzonden",
|
||||
ignoreTLSError: "Negeer TLS/SSL-fout voor HTTPS-websites",
|
||||
upsideDownModeDescription: "Draai de status om. Als de service bereikbaar is, is deze OFFLINE.",
|
||||
|
@ -16,6 +15,14 @@ export default {
|
|||
resoverserverDescription: "Cloudflare is de standaardserver, u kunt de resolver server op elk moment wijzigen.",
|
||||
rrtypeDescription: "Selecteer het RR-type dat u wilt monitoren",
|
||||
pauseMonitorMsg: "Weet je zeker dat je wilt pauzeren?",
|
||||
enableDefaultNotificationDescription: "Voor elke nieuwe monitor wordt deze melding standaard ingeschakeld. U kunt de melding nog steeds afzonderlijk uitschakelen voor elke monitor.",
|
||||
clearEventsMsg: "Weet je zeker dat je alle evenementen voor deze monitor wilt verwijderen?",
|
||||
clearHeartbeatsMsg: "Weet je zeker dat je alle heartbeats voor deze monitor wilt verwijderen?",
|
||||
confirmClearStatisticsMsg: "Weet u zeker dat u alle statistieken wilt verwijderen?",
|
||||
twoFAVerifyLabel: "Voer uw 2FA controle token in voor verificatie",
|
||||
tokenValidSettingsMsg: "Token is geldig! U kunt nu de 2FA-instellingen opslaan.",
|
||||
confirmEnableTwoFAMsg: "Weet je zeker dat je 2FA wilt inschakelen?",
|
||||
confirmDisableTwoFAMsg: "Weet je zeker dat je 2FA wilt uitschakelen?",
|
||||
Settings: "Instellingen",
|
||||
Dashboard: "Dashboard",
|
||||
"New Update": "Nieuwe update",
|
||||
|
@ -107,41 +114,59 @@ export default {
|
|||
"Last Result": "Laatste resultaat",
|
||||
"Create your admin account": "Maak uw beheerdersaccount aan",
|
||||
"Repeat Password": "Herhaal wachtwoord",
|
||||
Export: "Exporteren",
|
||||
Import: "Importeren",
|
||||
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?",
|
||||
"Clear Data": "Clear Data",
|
||||
Events: "Events",
|
||||
"Default enabled": "Default enabled",
|
||||
"Apply on all existing monitors": "Pas toe op alle bestaande monitors",
|
||||
Create: "Aanmaken",
|
||||
"Clear Data": "Data wissen",
|
||||
Events: "Gebeurtenissen",
|
||||
Heartbeats: "Heartbeats",
|
||||
"Auto Get": "Auto Get",
|
||||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
backupDescription: "U kunt een back-up maken van alle monitoren en alle meldingen in een JSON-bestand.",
|
||||
backupDescription2: "PS: Geschiedenis- en gebeurtenisgegevens zijn niet inbegrepen.",
|
||||
backupDescription3: "Gevoelige gegevens zoals melding tokens zijn opgenomen in het exportbestand, houd het veilig opgeslagen.",
|
||||
alertNoFile: "Selecteer een bestand om te importeren.",
|
||||
alertWrongFileType: "Selecteer een JSON-bestand.",
|
||||
"Verify Token": "Controleer token",
|
||||
"Setup 2FA": "2FA instellingen",
|
||||
"Enable 2FA": "Schakel 2FA in",
|
||||
"Disable 2FA": "Schakel 2FA uit",
|
||||
"2FA Settings": "2FA-instellingen",
|
||||
"Two Factor Authentication": "Two Factor Authenticatie",
|
||||
Active: "Actief",
|
||||
Inactive: "Inactief",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
backupDescription2: "PS: History and event data is not included.",
|
||||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
||||
alertNoFile: "Please select a file to import.",
|
||||
alertWrongFileType: "Please select a JSON file.",
|
||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
||||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
|
||||
"Apply on all existing monitors": "Apply on all existing monitors",
|
||||
"Verify Token": "Verify Token",
|
||||
"Setup 2FA": "Setup 2FA",
|
||||
"Enable 2FA": "Enable 2FA",
|
||||
"Disable 2FA": "Disable 2FA",
|
||||
"2FA Settings": "2FA Settings",
|
||||
"Two Factor Authentication": "Two Factor Authentication",
|
||||
Active: "Active",
|
||||
Inactive: "Inactive",
|
||||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
"Show URI": "Toon URI",
|
||||
"Clear all statistics": "Wis alle statistieken",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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.",
|
||||
|
@ -110,38 +109,64 @@ export default {
|
|||
respTime: "Czas odp. (ms)",
|
||||
notAvailableShort: "N/A",
|
||||
Create: "Stwórz",
|
||||
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?",
|
||||
"Clear Data": "Clear Data",
|
||||
Events: "Events",
|
||||
Heartbeats: "Heartbeats",
|
||||
"Auto Get": "Auto Get",
|
||||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
backupDescription2: "PS: History and event data is not included.",
|
||||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
||||
alertNoFile: "Please select a file to import.",
|
||||
alertWrongFileType: "Please select a JSON file.",
|
||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
||||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
|
||||
"Apply on all existing monitors": "Apply on all existing monitors",
|
||||
"Verify Token": "Verify Token",
|
||||
"Setup 2FA": "Setup 2FA",
|
||||
"Enable 2FA": "Enable 2FA",
|
||||
"Disable 2FA": "Disable 2FA",
|
||||
"2FA Settings": "2FA Settings",
|
||||
"Two Factor Authentication": "Two Factor Authentication",
|
||||
Active: "Active",
|
||||
Inactive: "Inactive",
|
||||
clearEventsMsg: "Jesteś pewien, że chcesz usunąć wszystkie monitory dla tej strony?",
|
||||
clearHeartbeatsMsg: "Jesteś pewien, że chcesz usunąć wszystkie bicia serca dla tego monitora?",
|
||||
confirmClearStatisticsMsg: "Jesteś pewien, że chcesz usunąć WSZYSTKIE statystyki?",
|
||||
"Clear Data": "Usuń dane",
|
||||
Events: "Wydarzenia",
|
||||
Heartbeats: "Bicia serca",
|
||||
"Auto Get": "Pobierz automatycznie",
|
||||
enableDefaultNotificationDescription: "Dla każdego nowego monitora to powiadomienie będzie domyślnie włączone. Nadal możesz wyłączyć powiadomienia osobno dla każdego monitora.",
|
||||
"Default enabled": "Domyślnie włączone",
|
||||
"Also apply to existing monitors": "Również zastosuj do obecnych monitorów",
|
||||
Export: "Eksportuj",
|
||||
Import: "Importuj",
|
||||
backupDescription: "Możesz wykonać kopię zapasową wszystkich monitorów i wszystkich powiadomień do pliku JSON.",
|
||||
backupDescription2: "PS: Historia i dane zdarzeń nie są uwzględniane.",
|
||||
backupDescription3: "Poufne dane, takie jak tokeny powiadomień, są zawarte w pliku eksportu, prosimy o ostrożne przechowywanie.",
|
||||
alertNoFile: "Proszę wybrać plik do importu.",
|
||||
alertWrongFileType: "Proszę wybrać plik JSON.",
|
||||
twoFAVerifyLabel: "Proszę podaj swój token 2FA, aby sprawdzić czy 2FA działa",
|
||||
tokenValidSettingsMsg: "Token jest poprawny! Możesz teraz zapisać ustawienia 2FA.",
|
||||
confirmEnableTwoFAMsg: "Jesteś pewien że chcesz włączyć 2FA?",
|
||||
confirmDisableTwoFAMsg: "Jesteś pewien że chcesz wyłączyć 2FA?",
|
||||
"Apply on all existing monitors": "Zastosuj do wszystki obecnych monitorów",
|
||||
"Verify Token": "Weryfikuj token",
|
||||
"Setup 2FA": "Konfiguracja 2FA",
|
||||
"Enable 2FA": "Włącz 2FA",
|
||||
"Disable 2FA": "Wyłącz 2FA",
|
||||
"2FA Settings": "Ustawienia 2FA",
|
||||
"Two Factor Authentication": "Uwierzytelnienie dwuskładnikowe",
|
||||
Active: "Włączone",
|
||||
Inactive: "Wyłączone",
|
||||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
"Show URI": "Pokaż URI",
|
||||
"Clear all statistics": "Wyczyść wszystkie statystyki",
|
||||
retryCheckEverySecond: "Ponawiaj co {0} sekund.",
|
||||
importHandleDescription: "Wybierz 'Pomiń istniejące', jeśli chcesz pominąć każdy monitor lub powiadomienie o tej samej nazwie. 'Nadpisz' spowoduje usunięcie każdego istniejącego monitora i powiadomienia.",
|
||||
confirmImportMsg: "Czy na pewno chcesz zaimportować kopię zapasową? Upewnij się, że wybrałeś właściwą opcję importu.",
|
||||
"Heartbeat Retry Interval": "Częstotliwość ponawiania bicia serca",
|
||||
"Import Backup": "Importuj kopię zapasową",
|
||||
"Export Backup": "Eksportuj kopię zapasową",
|
||||
"Skip existing": "Pomiń istniejące",
|
||||
Overwrite: "Nadpisz",
|
||||
Options: "Opcje",
|
||||
"Keep both": "Zachowaj oba",
|
||||
Tags: "Tagi",
|
||||
"Add New below or Select...": "Dodaj nowy poniżej lub wybierz...",
|
||||
"Tag with this name already exist.": "Tag o tej nazwie już istnieje.",
|
||||
"Tag with this value already exist.": "Tag o tej wartości już istnieje.",
|
||||
color: "kolor",
|
||||
"value (optional)": "wartość (opcjonalnie)",
|
||||
Gray: "Szary",
|
||||
Red: "Czerwony",
|
||||
Orange: "Pomarańczowy",
|
||||
Green: "Zielony",
|
||||
Blue: "Niebieski",
|
||||
Indigo: "Indygo",
|
||||
Purple: "Fioletowy",
|
||||
Pink: "Różowy",
|
||||
"Search...": "Szukaj...",
|
||||
"Avg. Ping": "Średni ping",
|
||||
"Avg. Response": "Średnia odpowiedź",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "Русский",
|
||||
checkEverySecond: "Проверять каждые {0} секунд.",
|
||||
"Avg.": "Средн.",
|
||||
retriesDescription: "Максимальное количество попыток перед пометкой сервиса как недоступного и отправкой уведомления",
|
||||
ignoreTLSError: "Игнорировать ошибку TLS/SSL для HTTPS сайтов",
|
||||
upsideDownModeDescription: "Реверс статуса сервиса. Если сервис доступен, то он помечается как НЕДОСТУПНЫЙ.",
|
||||
|
@ -107,41 +106,67 @@ export default {
|
|||
"Last Result": "Последний результат",
|
||||
"Create your admin account": "Создайте аккаунт администратора",
|
||||
"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?",
|
||||
"Clear Data": "Clear Data",
|
||||
Events: "Events",
|
||||
Heartbeats: "Heartbeats",
|
||||
"Auto Get": "Auto Get",
|
||||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
backupDescription2: "PS: History and event data is not included.",
|
||||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
||||
alertNoFile: "Please select a file to import.",
|
||||
alertWrongFileType: "Please select a JSON file.",
|
||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
||||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
|
||||
"Apply on all existing monitors": "Apply on all existing monitors",
|
||||
"Verify Token": "Verify Token",
|
||||
"Setup 2FA": "Setup 2FA",
|
||||
"Enable 2FA": "Enable 2FA",
|
||||
"Disable 2FA": "Disable 2FA",
|
||||
"2FA Settings": "2FA Settings",
|
||||
"Two Factor Authentication": "Two Factor Authentication",
|
||||
Active: "Active",
|
||||
Inactive: "Inactive",
|
||||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
respTime: "Время ответа (мс)",
|
||||
notAvailableShort: "Н/Д",
|
||||
Create: "Создать",
|
||||
clearEventsMsg: "Вы действительно хотите удалить всю статистику событий данного монитора?",
|
||||
clearHeartbeatsMsg: "Вы действительно хотите удалить всю статистику опросов данного монитора?",
|
||||
confirmClearStatisticsMsg: "Вы действительно хотите удалить ВСЮ статистику?",
|
||||
"Clear Data": "Очистить статистику",
|
||||
Events: "События",
|
||||
Heartbeats: "Опросы",
|
||||
"Auto Get": "Авто-получение",
|
||||
enableDefaultNotificationDescription: "Для каждого нового монитора это уведомление будет включено по умолчанию. Вы всё ещё можете отключить уведомления в каждом мониторе отдельно.",
|
||||
"Default enabled": "Использовать по умолчанию",
|
||||
"Also apply to existing monitors": "Применить к существующим мониторам",
|
||||
Export: "Экспорт",
|
||||
Import: "Импорт",
|
||||
backupDescription: "Вы можете сохранить резервную копию всех мониторов и уведомлений в виде JSON-файла",
|
||||
backupDescription2: "P.S.: История и события сохранены не будут.",
|
||||
backupDescription3: "Важные данные, такие как токены уведомлений, добавляются при экспорте, поэтому храните файлы в безопасном месте.",
|
||||
alertNoFile: "Выберите файл для импорта.",
|
||||
alertWrongFileType: "Выберите JSON-файл.",
|
||||
twoFAVerifyLabel: "Пожалуйста, введите свой токен, чтобы проверить работу 2FA",
|
||||
tokenValidSettingsMsg: "Токен действителен! Теперь вы можете сохранить настройки 2FA.",
|
||||
confirmEnableTwoFAMsg: "Вы действительно хотите включить 2FA?",
|
||||
confirmDisableTwoFAMsg: "Вы действительно хотите выключить 2FA?",
|
||||
"Apply on all existing monitors": "Применить ко всем существующим мониторам",
|
||||
"Verify Token": "Проверить токен",
|
||||
"Setup 2FA": "Настройка 2FA",
|
||||
"Enable 2FA": "Включить 2FA",
|
||||
"Disable 2FA": "Выключить 2FA",
|
||||
"2FA Settings": "Настройки 2FA",
|
||||
"Two Factor Authentication": "Двухфакторная аутентификация",
|
||||
Active: "Активно",
|
||||
Inactive: "Неактивно",
|
||||
Token: "Токен",
|
||||
"Show URI": "Показать URI",
|
||||
"Clear all statistics": "Очистить всю статистику",
|
||||
retryCheckEverySecond: "Повторять каждые {0} секунд.",
|
||||
importHandleDescription: "Выберите 'Пропустить существующие' если вы хотите пропустить каждый монитор или уведомление с таким же именем. 'Перезаписать' удалит каждый существующий монитор или уведомление.",
|
||||
confirmImportMsg: "Вы действительно хотите восстановить резервную копию? Убедитесь, что вы выбрали подходящий вариант импорта.",
|
||||
"Heartbeat Retry Interval": "Интервал повтора опроса",
|
||||
"Import Backup": "Импорт резервной копии",
|
||||
"Export Backup": "Экспорт резервной копии",
|
||||
"Skip existing": "Пропустить существующие",
|
||||
Overwrite: "Перезаписать",
|
||||
Options: "Опции",
|
||||
"Keep both": "Оставить оба",
|
||||
Tags: "Теги",
|
||||
"Add New below or Select...": "Добавить новое ниже или выбрать...",
|
||||
"Tag with this name already exist.": "Такой тег уже существует.",
|
||||
"Tag with this value already exist.": "Тег с таким значением уже существует.",
|
||||
color: "цвет",
|
||||
"value (optional)": "значение (опционально)",
|
||||
Gray: "Серый",
|
||||
Red: "Красный",
|
||||
Orange: "Оранжевый",
|
||||
Green: "Зелёный",
|
||||
Blue: "Синий",
|
||||
Indigo: "Индиго",
|
||||
Purple: "Пурпурный",
|
||||
Pink: "Розовый",
|
||||
"Search...": "Поиск...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "Srpski",
|
||||
checkEverySecond: "Proveri svakih {0} sekundi.",
|
||||
"Avg.": "Prosečni",
|
||||
retriesDescription: "Maksimum pokušaja pre nego što se servis obeleži kao neaktivan i pošalje se obaveštenje.",
|
||||
ignoreTLSError: "Ignoriši TLS/SSL greške za HTTPS veb stranice.",
|
||||
upsideDownModeDescription: "Obrnite status. Ako je servis dostupan, onda je obeležen kao neaktivan.",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "Српски",
|
||||
checkEverySecond: "Провери сваких {0} секунди.",
|
||||
"Avg.": "Просечни",
|
||||
retriesDescription: "Максимум покушаја пре него што се сервис обележи као неактиван и пошаље се обавештење.",
|
||||
ignoreTLSError: "Игнориши TLS/SSL грешке за HTTPS веб странице.",
|
||||
upsideDownModeDescription: "Обрните статус. Ако је сервис доступан, онда је обележен као неактиван.",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "Svenska",
|
||||
checkEverySecond: "Uppdatera var {0} sekund.",
|
||||
"Avg.": "Genomsnittligt",
|
||||
retriesDescription: "Max antal försök innan tjänsten markeras som nere och en notis skickas",
|
||||
ignoreTLSError: "Ignorera TLS/SSL-fel för webbsidor med HTTPS",
|
||||
upsideDownModeDescription: "Vänd upp och ner på statusen. Om tjänsten är nåbar visas den som NERE.",
|
||||
|
@ -120,7 +119,6 @@ export default {
|
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
"Default enabled": "Default enabled",
|
||||
"Also apply to existing monitors": "Also apply to existing monitors",
|
||||
"Import/Export Backup": "Import/Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
|
@ -144,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
171
src/languages/tr-TR.js
Normal file
171
src/languages/tr-TR.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
export default {
|
||||
languageName: "Türkçe",
|
||||
checkEverySecond: "{0} Saniyede bir kontrol et.",
|
||||
retriesDescription: "Servisin kapalı olarak işaretlenmeden ve bir bildirim gönderilmeden önce maksimum yeniden deneme sayısı",
|
||||
ignoreTLSError: "HTTPS web siteleri için TLS/SSL hatasını yoksay",
|
||||
upsideDownModeDescription: "Servisin durumunu tersine çevirir. Servis çalışıyorsa kapalı olarak işaretler.",
|
||||
maxRedirectDescription: "İzlenecek maksimum yönlendirme sayısı. Yönlendirmeleri devre dışı bırakmak için 0'a ayarlayın.",
|
||||
acceptedStatusCodesDescription: "Servisin çalıştığını hangi durum kodları belirlesin?",
|
||||
passwordNotMatchMsg: "Şifre eşleşmiyor.",
|
||||
notificationDescription: "Servislerin bildirim gönderebilmesi için bir bildirim yöntemi belirleyin.",
|
||||
keywordDescription: "Anahtar kelimeyi düz html veya JSON yanıtında arayın ve büyük/küçük harfe duyarlıdır",
|
||||
pauseDashboardHome: "Durdur",
|
||||
deleteMonitorMsg: "Servisi silmek istediğinden emin misin?",
|
||||
deleteNotificationMsg: "Bu bildirimi tüm servisler için silmek istediğinden emin misin?",
|
||||
resoverserverDescription: "Cloudflare varsayılan sunucudur, çözümleyici sunucusunu istediğiniz zaman değiştirebilirsiniz.",
|
||||
rrtypeDescription: "İzlemek istediğiniz servisin RR-Tipini seçin",
|
||||
pauseMonitorMsg: "Durdurmak istediğinden emin misin?",
|
||||
clearEventsMsg: "Bu servisin bütün kayıtlarını silmek istediğinden emin misin?",
|
||||
clearHeartbeatsMsg: "Bu servis için tüm sağlık durumunu silmek istediğinden emin misin?",
|
||||
confirmClearStatisticsMsg: "Tüm istatistikleri silmek istediğinden emin misin?",
|
||||
Settings: "Ayarlar",
|
||||
Dashboard: "Panel",
|
||||
"New Update": "Yeni Güncelleme",
|
||||
Language: "Dil",
|
||||
Appearance: "Görünüm",
|
||||
Theme: "Tema",
|
||||
General: "Genel",
|
||||
Version: "Versiyon",
|
||||
"Check Update On GitHub": "GitHub'da Güncellemeyi Kontrol Edin",
|
||||
List: "Liste",
|
||||
Add: "Ekle",
|
||||
"Add New Monitor": "Yeni Servis Ekle",
|
||||
"Quick Stats": "Servis istatistikleri",
|
||||
Up: "Normal",
|
||||
Down: "Hatalı",
|
||||
Pending: "Bekliyor",
|
||||
Unknown: "Bilinmeyen",
|
||||
Pause: "Durdur",
|
||||
Name: "Servis ismi",
|
||||
Status: "Durum",
|
||||
DateTime: "Zaman",
|
||||
Message: "Mesaj",
|
||||
"No important events": "Önemli olay yok",
|
||||
Resume: "Devam et",
|
||||
Edit: "Düzenle",
|
||||
Delete: "Sil",
|
||||
Current: "Şu anda",
|
||||
Uptime: "Çalışma zamanı",
|
||||
"Cert Exp.": "Sertifika Süresi",
|
||||
days: "günler",
|
||||
day: "gün",
|
||||
"-day": "-gün",
|
||||
hour: "saat",
|
||||
"-hour": "-saat",
|
||||
Response: "Cevap Süresi",
|
||||
Ping: "Ping",
|
||||
"Monitor Type": "Servis Tipi",
|
||||
Keyword: "Anahtar Kelime",
|
||||
"Friendly Name": "Panelde görünecek isim",
|
||||
URL: "URL",
|
||||
Hostname: "IP Adresi",
|
||||
Port: "Port",
|
||||
"Heartbeat Interval": "Servis Test Aralığı",
|
||||
Retries: "Yeniden deneme",
|
||||
Advanced: "Gelişmiş",
|
||||
"Upside Down Mode": "Ters/Düz Modu",
|
||||
"Max. Redirects": "Maksimum Yönlendirme",
|
||||
"Accepted Status Codes": "Kabul Edilen Durum Kodları",
|
||||
Save: "Kaydet",
|
||||
Notifications: "Bildirimler",
|
||||
"Not available, please setup.": "Atanmış bildirim yöntemi yok. Ayarlardan belirleyebilirsiniz.",
|
||||
"Setup Notification": "Bildirim yöntemi kur",
|
||||
Light: "Açık",
|
||||
Dark: "Koyu",
|
||||
Auto: "Oto",
|
||||
"Theme - Heartbeat Bar": "Servis Bar Konumu",
|
||||
Normal: "Normal",
|
||||
Bottom: "Aşağıda",
|
||||
None: "Gösterme",
|
||||
Timezone: "Zaman Dilimi",
|
||||
"Search Engine Visibility": "Arama Motoru Görünürlüğü",
|
||||
"Allow indexing": "İndekslemeye izin ver",
|
||||
"Discourage search engines from indexing site": "İndekslemeyi reddet",
|
||||
"Change Password": "Şifre Değiştir",
|
||||
"Current Password": "Şuan ki Şifre",
|
||||
"New Password": "Yeni Şifre",
|
||||
"Repeat New Password": "Yeni Şifreyi Tekrar Girin",
|
||||
"Update Password": "Şifreyi Değiştir",
|
||||
"Disable Auth": "Şifreli girişi iptal et.",
|
||||
"Enable Auth": "Şifreli girişi aktif et.",
|
||||
Logout: "Çıkış yap",
|
||||
Leave: "Ayrıl",
|
||||
"I understand, please disable": "Evet farkındayım, iptal et",
|
||||
Confirm: "Onayla",
|
||||
Yes: "Evet",
|
||||
No: "Hayır",
|
||||
Username: "Kullanıcı Adı",
|
||||
Password: "Şifre",
|
||||
"Remember me": "Beni Hatırla",
|
||||
Login: "Giriş yap",
|
||||
"No Monitors, please": "Servis yok, lütfen",
|
||||
"add one": "bir servis ekleyin",
|
||||
"Notification Type": "Bildirim Yöntemi",
|
||||
Email: "E-mail",
|
||||
Test: "Test",
|
||||
"Certificate Info": "Sertifika Bilgisi",
|
||||
"Resolver Server": "Çözümleyici Sunucu",
|
||||
"Resource Record Type": "Kaynak Kayıt Türü",
|
||||
"Last Result": "En son sonuçlar",
|
||||
"Create your admin account": "Yönetici hesabınızı oluşturun",
|
||||
"Repeat Password": "Şifrenizi tekrar girin",
|
||||
respTime: "Cevap Süresi (ms)",
|
||||
notAvailableShort: "N/A",
|
||||
Create: "Yarat",
|
||||
"Clear Data": "Verileri Temizle",
|
||||
Events: "Olaylar",
|
||||
Heartbeats: "Sağlık Durumları",
|
||||
"Auto Get": "Otomatik Al",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
||||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
"Default enabled": "Default enabled",
|
||||
"Apply on all existing monitors": "Apply on all existing monitors",
|
||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
||||
backupDescription2: "PS: History and event data is not included.",
|
||||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
||||
alertNoFile: "Please select a file to import.",
|
||||
alertWrongFileType: "Please select a JSON file.",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
"Verify Token": "Verify Token",
|
||||
"Setup 2FA": "Setup 2FA",
|
||||
"Enable 2FA": "Enable 2FA",
|
||||
"Disable 2FA": "Disable 2FA",
|
||||
"2FA Settings": "2FA Settings",
|
||||
"Two Factor Authentication": "Two Factor Authentication",
|
||||
Active: "Active",
|
||||
Inactive: "Inactive",
|
||||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
languageName: "简体中文",
|
||||
checkEverySecond: "检测频率 {0} 秒",
|
||||
"Avg.": "平均",
|
||||
retriesDescription: "最大重试失败次数",
|
||||
ignoreTLSError: "忽略HTTPS站点的证书错误",
|
||||
upsideDownModeDescription: "反向状态监控(状态码范围外为有效状态,反之为无效)",
|
||||
|
@ -119,7 +118,7 @@ export default {
|
|||
"Auto Get": "自动获取",
|
||||
enableDefaultNotificationDescription: "新的监控项将默认启用,你也可以在每个监控项中分别设置",
|
||||
"Default enabled": "默认开启",
|
||||
"Import/Export Backup": "导入/导出备份",
|
||||
"Also apply to existing monitors": "应用到所有监控项",
|
||||
Export: "导出",
|
||||
Import: "导入",
|
||||
backupDescription: "你可以将所有的监控项和消息通知备份到一个 JSON 文件中",
|
||||
|
@ -143,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "Show URI",
|
||||
"Clear all statistics": "Clear all Statistics",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ export default {
|
|||
hour: "小時",
|
||||
"-hour": "小時",
|
||||
checkEverySecond: "每 {0} 秒檢查一次",
|
||||
"Avg.": "平均",
|
||||
Response: "反應時間",
|
||||
Ping: "反應時間",
|
||||
"Monitor Type": "監測器類型",
|
||||
|
@ -119,7 +118,7 @@ export default {
|
|||
"Auto Get": "自動獲取",
|
||||
enableDefaultNotificationDescription: "新增監測器時這個通知會預設啟用,當然每個監測器亦可分別控制開關。",
|
||||
"Default enabled": "預設通知",
|
||||
"Import/Export Backup": "匯入/匯出 備份",
|
||||
"Also apply to existing monitors": "同時取用至目前所有監測器",
|
||||
Export: "匯出",
|
||||
Import: "匯入",
|
||||
backupDescription: "您可以備份所有監測器及所有通知。",
|
||||
|
@ -143,4 +142,31 @@ export default {
|
|||
Token: "Token",
|
||||
"Show URI": "顯示 URI",
|
||||
"Clear all statistics": "清除所有歷史記錄",
|
||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Import Backup": "Import Backup",
|
||||
"Export Backup": "Export Backup",
|
||||
"Skip existing": "Skip existing",
|
||||
Overwrite: "Overwrite",
|
||||
Options: "Options",
|
||||
"Keep both": "Keep both",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Add New below or Select...",
|
||||
"Tag with this name already exist.": "Tag with this name already exist.",
|
||||
"Tag with this value already exist.": "Tag with this value already exist.",
|
||||
color: "color",
|
||||
"value (optional)": "value (optional)",
|
||||
Gray: "Gray",
|
||||
Red: "Red",
|
||||
Orange: "Orange",
|
||||
Green: "Green",
|
||||
Blue: "Blue",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
}
|
||||
|
|
125
src/main.js
125
src/main.js
|
@ -1,135 +1,18 @@
|
|||
import "bootstrap";
|
||||
import { createApp, h } from "vue";
|
||||
import { createI18n } from "vue-i18n"
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import Toast from "vue-toastification";
|
||||
import "vue-toastification/dist/index.css";
|
||||
import App from "./App.vue";
|
||||
import "./assets/app.scss";
|
||||
import { i18n } from "./i18n";
|
||||
import { FontAwesomeIcon } from "./icon.js";
|
||||
import EmptyLayout from "./layouts/EmptyLayout.vue";
|
||||
import Layout from "./layouts/Layout.vue";
|
||||
import datetime from "./mixins/datetime";
|
||||
import mobile from "./mixins/mobile";
|
||||
import socket from "./mixins/socket";
|
||||
import theme from "./mixins/theme";
|
||||
import mobile from "./mixins/mobile";
|
||||
import datetime from "./mixins/datetime";
|
||||
import Dashboard from "./pages/Dashboard.vue";
|
||||
import DashboardHome from "./pages/DashboardHome.vue";
|
||||
import Details from "./pages/Details.vue";
|
||||
import EditMonitor from "./pages/EditMonitor.vue";
|
||||
import Settings from "./pages/Settings.vue";
|
||||
import Setup from "./pages/Setup.vue";
|
||||
import List from "./pages/List.vue";
|
||||
|
||||
import { router } from "./router";
|
||||
import { appName } from "./util.ts";
|
||||
|
||||
import en from "./languages/en";
|
||||
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 frFR from "./languages/fr-FR";
|
||||
import itIT from "./languages/it-IT";
|
||||
import ja from "./languages/ja";
|
||||
import daDK from "./languages/da-DK";
|
||||
import sr from "./languages/sr";
|
||||
import srLatn from "./languages/sr-latn";
|
||||
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"
|
||||
import etEE from "./languages/et-EE"
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
name: "root",
|
||||
path: "",
|
||||
component: Dashboard,
|
||||
children: [
|
||||
{
|
||||
name: "DashboardHome",
|
||||
path: "/dashboard",
|
||||
component: DashboardHome,
|
||||
children: [
|
||||
{
|
||||
path: "/dashboard/:id",
|
||||
component: EmptyLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: Details,
|
||||
},
|
||||
{
|
||||
path: "/edit/:id",
|
||||
component: EditMonitor,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/add",
|
||||
component: EditMonitor,
|
||||
},
|
||||
{
|
||||
path: "/list",
|
||||
component: List,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
component: Settings,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
],
|
||||
|
||||
},
|
||||
{
|
||||
path: "/setup",
|
||||
component: Setup,
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
linkActiveClass: "active",
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
const languageList = {
|
||||
en,
|
||||
"zh-HK": zhHK,
|
||||
"de-DE": deDE,
|
||||
"nl-NL": nlNL,
|
||||
"es-ES": esEs,
|
||||
"fr-FR": frFR,
|
||||
"it-IT": itIT,
|
||||
"ja": ja,
|
||||
"da-DK": daDK,
|
||||
"sr": sr,
|
||||
"sr-latn": srLatn,
|
||||
"sv-SE": svSE,
|
||||
"ko-KR": koKR,
|
||||
"ru-RU": ruRU,
|
||||
"zh-CN": zhCN,
|
||||
"pl": pl,
|
||||
"et-EE": etEE,
|
||||
};
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: localStorage.locale || "en",
|
||||
fallbackLocale: "en",
|
||||
silentFallbackWarn: true,
|
||||
silentTranslationWarn: true,
|
||||
messages: languageList
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
mixins: [
|
||||
socket,
|
||||
|
|
|
@ -266,6 +266,10 @@ export default {
|
|||
socket.emit("twoFAStatus", callback)
|
||||
},
|
||||
|
||||
getMonitorList(callback) {
|
||||
socket.emit("getMonitorList", callback)
|
||||
},
|
||||
|
||||
add(monitor, callback) {
|
||||
socket.emit("add", monitor, callback)
|
||||
},
|
||||
|
@ -280,8 +284,8 @@ export default {
|
|||
this.importantHeartbeatList = {}
|
||||
},
|
||||
|
||||
uploadBackup(uploadedJSON, callback) {
|
||||
socket.emit("uploadBackup", uploadedJSON, callback)
|
||||
uploadBackup(uploadedJSON, importHandle, callback) {
|
||||
socket.emit("uploadBackup", uploadedJSON, importHandle, callback)
|
||||
},
|
||||
|
||||
clearEvents(monitorID, callback) {
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
<transition name="slide-fade" appear>
|
||||
<div v-if="monitor">
|
||||
<h1> {{ monitor.name }}</h1>
|
||||
<div class="tags">
|
||||
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
<p class="url">
|
||||
<a v-if="monitor.type === 'http'" :href="monitor.url" target="_blank">{{ monitor.url }}</a>
|
||||
<span v-if="monitor.type === 'port'">TCP Ping {{ monitor.hostname }}:{{ monitor.port }}</span>
|
||||
|
@ -42,7 +45,7 @@
|
|||
<div class="shadow-box big-padding text-center stats">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>{{ pingTitle }}</h4>
|
||||
<h4>{{ pingTitle() }}</h4>
|
||||
<p>({{ $t("Current") }})</p>
|
||||
<span class="num">
|
||||
<a href="#" @click.prevent="showPingChartBox = !showPingChartBox">
|
||||
|
@ -51,7 +54,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4>{{ $t("Avg.") }} {{ pingTitle }}</h4>
|
||||
<h4>{{ pingTitle(true) }}</h4>
|
||||
<p>(24{{ $t("-hour") }})</p>
|
||||
<span class="num"><CountUp :value="avgPing" /></span>
|
||||
</div>
|
||||
|
@ -209,6 +212,7 @@ import CountUp from "../components/CountUp.vue";
|
|||
import Uptime from "../components/Uptime.vue";
|
||||
import Pagination from "v-pagination-3";
|
||||
const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
|
||||
import Tag from "../components/Tag.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -220,6 +224,7 @@ export default {
|
|||
Status,
|
||||
Pagination,
|
||||
PingChart,
|
||||
Tag,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -231,14 +236,6 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
pingTitle() {
|
||||
if (this.monitor.type === "http") {
|
||||
return this.$t("Response");
|
||||
}
|
||||
return this.$t("Ping");
|
||||
},
|
||||
|
||||
monitor() {
|
||||
let id = this.$route.params.id
|
||||
return this.$root.monitorList[id];
|
||||
|
@ -369,6 +366,19 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
|
||||
pingTitle(average = false) {
|
||||
let translationPrefix = ""
|
||||
if (average) {
|
||||
translationPrefix = "Avg. "
|
||||
}
|
||||
|
||||
if (this.monitor.type === "http") {
|
||||
return this.$t(translationPrefix + "Response");
|
||||
}
|
||||
|
||||
return this.$t(translationPrefix + "Ping");
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -499,4 +509,12 @@ table {
|
|||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tags > div:first-child {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<!-- TCP Port / Ping / DNS only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required>
|
||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="ipRegexPattern || hostnameRegexPattern" required>
|
||||
</div>
|
||||
|
||||
<!-- For TCP Port Type -->
|
||||
|
@ -95,6 +95,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="retry-interval" class="form-label">
|
||||
{{ $t("Heartbeat Retry Interval") }}
|
||||
<span>({{ $t("retryCheckEverySecond", [ monitor.retryInterval ]) }})</span>
|
||||
</label>
|
||||
<input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required min="20" step="1">
|
||||
</div>
|
||||
|
||||
<h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||
|
||||
<div v-if="monitor.type === 'http'" class="my-3 form-check">
|
||||
|
@ -134,6 +142,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<div class="my-3">
|
||||
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 mb-1">
|
||||
<button class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
||||
</div>
|
||||
|
@ -173,6 +185,7 @@
|
|||
|
||||
<script>
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import MonitorCheckEditor from "../components/MonitorCheckEditor.vue";
|
||||
import { useToast } from "vue-toastification"
|
||||
import VueMultiselect from "vue-multiselect"
|
||||
|
@ -182,6 +195,7 @@ const toast = useToast()
|
|||
export default {
|
||||
components: {
|
||||
NotificationDialog,
|
||||
TagsManager,
|
||||
MonitorCheckEditor,
|
||||
VueMultiselect,
|
||||
},
|
||||
|
@ -199,6 +213,9 @@ export default {
|
|||
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
||||
// eslint-disable-next-line
|
||||
ipRegexPattern: "((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))",
|
||||
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
||||
// eslint-disable-next-line
|
||||
hostnameRegexPattern: "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -227,6 +244,12 @@ export default {
|
|||
"$route.fullPath"() {
|
||||
this.init();
|
||||
},
|
||||
"monitor.interval"(value, oldValue) {
|
||||
// Link interval and retryInerval if they are the same value.
|
||||
if (this.monitor.retryInterval === oldValue) {
|
||||
this.monitor.retryInterval = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
|
@ -268,6 +291,7 @@ export default {
|
|||
name: "",
|
||||
url: "https://",
|
||||
interval: 60,
|
||||
retryInterval: this.interval,
|
||||
maxretries: 0,
|
||||
notificationIDList: {},
|
||||
ignoreTls: false,
|
||||
|
@ -305,25 +329,32 @@ export default {
|
|||
this.monitor.checks = this.monitor.checks.splice(index, 1);
|
||||
},
|
||||
|
||||
submit() {
|
||||
async submit() {
|
||||
this.processing = true;
|
||||
|
||||
if (this.isAdd) {
|
||||
this.$root.add(this.monitor, (res) => {
|
||||
this.processing = false;
|
||||
this.$root.add(this.monitor, async (res) => {
|
||||
|
||||
if (res.ok) {
|
||||
await this.$refs.tagsManager.submit(res.monitorID);
|
||||
|
||||
toast.success(res.msg);
|
||||
this.processing = false;
|
||||
this.$root.getMonitorList();
|
||||
this.$router.push("/dashboard/" + res.monitorID)
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
await this.$refs.tagsManager.submit(this.monitor.id);
|
||||
|
||||
this.$root.getSocket().emit("editMonitor", this.monitor, (res) => {
|
||||
this.processing = false;
|
||||
this.$root.toastRes(res)
|
||||
this.$root.toastRes(res);
|
||||
this.init();
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -345,6 +376,8 @@ export default {
|
|||
.multiselect__tags {
|
||||
border-radius: 1.5rem;
|
||||
border: 1px solid #ced4da;
|
||||
min-height: 38px;
|
||||
padding: 6px 40px 0 8px;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__tags {
|
||||
|
@ -361,9 +394,25 @@ export default {
|
|||
|
||||
.multiselect__tag {
|
||||
border-radius: 50rem;
|
||||
margin-bottom: 0;
|
||||
padding: 6px 26px 6px 10px;
|
||||
background: $primary !important;
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
font-size: 1rem;
|
||||
padding-left: 6px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
opacity: 0.67;
|
||||
}
|
||||
|
||||
.multiselect__input, .multiselect__single {
|
||||
line-height: 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.multiselect__tag {
|
||||
color: $dark-font-color2;
|
||||
|
|
|
@ -120,35 +120,61 @@
|
|||
</form>
|
||||
</template>
|
||||
|
||||
<h2 class="mt-5 mb-2">
|
||||
{{ $t("Two Factor Authentication") }}
|
||||
</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
|
||||
<h2 class="mb-2">
|
||||
{{ $t("Two Factor Authentication") }}
|
||||
</h2>
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.TwoFADialog.show()">{{ $t("2FA Settings") }}</button>
|
||||
</div>
|
||||
|
||||
<h2 class="mt-5 mb-2">{{ $t("Import/Export Backup") }}</h2>
|
||||
<h2 class="mt-5 mb-2">{{ $t("Export Backup") }}</h2>
|
||||
|
||||
<p>
|
||||
{{ $t("backupDescription") }} <br />
|
||||
({{ $t("backupDescription2") }}) <br />
|
||||
</p>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<button class="btn btn-outline-primary" @click="downloadBackup">{{ $t("Export") }}</button>
|
||||
<button type="button" class="btn btn-outline-primary" :disabled="processing" @click="importBackup">
|
||||
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
|
||||
{{ $t("Import") }}
|
||||
</button>
|
||||
<input id="importBackup" type="file" class="form-control" accept="application/json">
|
||||
</div>
|
||||
<div v-if="importAlert" class="alert alert-danger mt-3" style="padding: 6px 16px;">
|
||||
{{ importAlert }}
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-primary" @click="downloadBackup">{{ $t("Export") }}</button>
|
||||
</div>
|
||||
|
||||
<p><strong>{{ $t("backupDescription3") }}</strong></p>
|
||||
|
||||
<h2 class="mt-5 mb-2">{{ $t("Import Backup") }}</h2>
|
||||
|
||||
<label class="form-label">{{ $t("Options") }}:</label>
|
||||
<br>
|
||||
<div class="form-check form-check-inline">
|
||||
<input id="radioKeep" v-model="importHandle" class="form-check-input" type="radio" name="radioImportHandle" value="keep">
|
||||
<label class="form-check-label" for="radioKeep">{{ $t("Keep both") }}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input id="radioSkip" v-model="importHandle" class="form-check-input" type="radio" name="radioImportHandle" value="skip">
|
||||
<label class="form-check-label" for="radioSkip">{{ $t("Skip existing") }}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input id="radioOverwrite" v-model="importHandle" class="form-check-input" type="radio" name="radioImportHandle" value="overwrite">
|
||||
<label class="form-check-label" for="radioOverwrite">{{ $t("Overwrite") }}</label>
|
||||
</div>
|
||||
<div class="form-text mb-2">
|
||||
{{ $t("importHandleDescription") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<input id="importBackup" type="file" class="form-control" accept="application/json">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2 justify-content-end">
|
||||
<button type="button" class="btn btn-outline-primary" :disabled="processing" @click="confirmImport">
|
||||
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
|
||||
{{ $t("Import") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="importAlert" class="alert alert-danger mt-3" style="padding: 6px 16px;">
|
||||
{{ importAlert }}
|
||||
</div>
|
||||
|
||||
<h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
|
@ -233,6 +259,12 @@
|
|||
<p>Molim Vas koristite ovo sa pažnjom.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'tr-TR' ">
|
||||
<p><strong>Şifreli girişi devre dışı bırakmak istediğinizden</strong>emin misiniz?</p>
|
||||
<p>Bu, Uptime Kuma'nın önünde Cloudflare Access gibi <strong>üçüncü taraf yetkilendirmesi olan</strong> kişiler içindir.</p>
|
||||
<p>Lütfen dikkatli kullanın.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'ko-KR' ">
|
||||
<p>정말로 <strong>인증 기능을 끌까요</strong>?</p>
|
||||
<p>이 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong>을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.</p>
|
||||
|
@ -257,6 +289,12 @@
|
|||
<p>Utilizzare con attenzione.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'ru-RU' ">
|
||||
<p>Вы уверены, что хотите <strong>отключить авторизацию</strong>?</p>
|
||||
<p>Это подходит для <strong>тех, у кого стоит другая авторизация</strong> перед открытием Uptime Kuma, например Cloudflare Access.</p>
|
||||
<p>Пожалуйста, используйте с осторожностью.</p>
|
||||
</template>
|
||||
|
||||
<!-- English (en) -->
|
||||
<template v-else>
|
||||
<p>Are you sure want to <strong>disable auth</strong>?</p>
|
||||
|
@ -268,6 +306,9 @@
|
|||
<Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics">
|
||||
{{ $t("confirmClearStatisticsMsg") }}
|
||||
</Confirm>
|
||||
<Confirm ref="confirmImport" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="importBackup">
|
||||
{{ $t("confirmImportMsg") }}
|
||||
</Confirm>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
@ -308,6 +349,7 @@ export default {
|
|||
},
|
||||
loaded: false,
|
||||
importAlert: null,
|
||||
importHandle: "skip",
|
||||
processing: false,
|
||||
}
|
||||
},
|
||||
|
@ -374,6 +416,10 @@ export default {
|
|||
this.$refs.confirmClearStatistics.show();
|
||||
},
|
||||
|
||||
confirmImport() {
|
||||
this.$refs.confirmImport.show();
|
||||
},
|
||||
|
||||
disableAuth() {
|
||||
this.settings.disableAuth = true;
|
||||
this.saveSettings();
|
||||
|
@ -396,7 +442,7 @@ export default {
|
|||
}
|
||||
exportData = JSON.stringify(exportData, null, 4);
|
||||
let downloadItem = document.createElement("a");
|
||||
downloadItem.setAttribute("href", "data:application/json;charset=utf-8," + encodeURI(exportData));
|
||||
downloadItem.setAttribute("href", "data:application/json;charset=utf-8," + encodeURIComponent(exportData));
|
||||
downloadItem.setAttribute("download", fileName);
|
||||
downloadItem.click();
|
||||
},
|
||||
|
@ -419,7 +465,7 @@ export default {
|
|||
fileReader.readAsText(uploadItem.item(0));
|
||||
|
||||
fileReader.onload = item => {
|
||||
this.$root.uploadBackup(item.target.result, (res) => {
|
||||
this.$root.uploadBackup(item.target.result, this.importHandle, (res) => {
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok) {
|
||||
|
|
71
src/router.js
Normal file
71
src/router.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import EmptyLayout from "./layouts/EmptyLayout.vue";
|
||||
import Layout from "./layouts/Layout.vue";
|
||||
import Dashboard from "./pages/Dashboard.vue";
|
||||
import DashboardHome from "./pages/DashboardHome.vue";
|
||||
import Details from "./pages/Details.vue";
|
||||
import EditMonitor from "./pages/EditMonitor.vue";
|
||||
import List from "./pages/List.vue";
|
||||
import Settings from "./pages/Settings.vue";
|
||||
import Setup from "./pages/Setup.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
name: "root",
|
||||
path: "",
|
||||
component: Dashboard,
|
||||
children: [
|
||||
{
|
||||
name: "DashboardHome",
|
||||
path: "/dashboard",
|
||||
component: DashboardHome,
|
||||
children: [
|
||||
{
|
||||
path: "/dashboard/:id",
|
||||
component: EmptyLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: Details,
|
||||
},
|
||||
{
|
||||
path: "/edit/:id",
|
||||
component: EditMonitor,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/add",
|
||||
component: EditMonitor,
|
||||
},
|
||||
{
|
||||
path: "/list",
|
||||
component: List,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
component: Settings,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
],
|
||||
|
||||
},
|
||||
{
|
||||
path: "/setup",
|
||||
component: Setup,
|
||||
},
|
||||
]
|
||||
|
||||
export const router = createRouter({
|
||||
linkActiveClass: "active",
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
import dayjs from "dayjs";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezones from "timezones-list";
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
@ -16,376 +17,21 @@ function getTimezoneOffset(timeZone) {
|
|||
return -offset;
|
||||
}
|
||||
|
||||
// From: https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript
|
||||
// TODO: Move to separate file
|
||||
const aryIannaTimeZones = [
|
||||
"Europe/Andorra",
|
||||
"Asia/Dubai",
|
||||
"Asia/Kabul",
|
||||
"Europe/Tirane",
|
||||
"Asia/Yerevan",
|
||||
"Antarctica/Casey",
|
||||
"Antarctica/Davis",
|
||||
"Antarctica/Mawson",
|
||||
"Antarctica/Palmer",
|
||||
"Antarctica/Rothera",
|
||||
"Antarctica/Syowa",
|
||||
"Antarctica/Troll",
|
||||
"Antarctica/Vostok",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Cordoba",
|
||||
"America/Argentina/Salta",
|
||||
"America/Argentina/Jujuy",
|
||||
"America/Argentina/Tucuman",
|
||||
"America/Argentina/Catamarca",
|
||||
"America/Argentina/La_Rioja",
|
||||
"America/Argentina/San_Juan",
|
||||
"America/Argentina/Mendoza",
|
||||
"America/Argentina/San_Luis",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Ushuaia",
|
||||
"Pacific/Pago_Pago",
|
||||
"Europe/Vienna",
|
||||
"Australia/Lord_Howe",
|
||||
"Antarctica/Macquarie",
|
||||
"Australia/Hobart",
|
||||
"Australia/Currie",
|
||||
"Australia/Melbourne",
|
||||
"Australia/Sydney",
|
||||
"Australia/Broken_Hill",
|
||||
"Australia/Brisbane",
|
||||
"Australia/Lindeman",
|
||||
"Australia/Adelaide",
|
||||
"Australia/Darwin",
|
||||
"Australia/Perth",
|
||||
"Australia/Eucla",
|
||||
"Asia/Baku",
|
||||
"America/Barbados",
|
||||
"Asia/Dhaka",
|
||||
"Europe/Brussels",
|
||||
"Europe/Sofia",
|
||||
"Atlantic/Bermuda",
|
||||
"Asia/Brunei",
|
||||
"America/La_Paz",
|
||||
"America/Noronha",
|
||||
"America/Belem",
|
||||
"America/Fortaleza",
|
||||
"America/Recife",
|
||||
"America/Araguaina",
|
||||
"America/Maceio",
|
||||
"America/Bahia",
|
||||
"America/Sao_Paulo",
|
||||
"America/Campo_Grande",
|
||||
"America/Cuiaba",
|
||||
"America/Santarem",
|
||||
"America/Porto_Velho",
|
||||
"America/Boa_Vista",
|
||||
"America/Manaus",
|
||||
"America/Eirunepe",
|
||||
"America/Rio_Branco",
|
||||
"America/Nassau",
|
||||
"Asia/Thimphu",
|
||||
"Europe/Minsk",
|
||||
"America/Belize",
|
||||
"America/St_Johns",
|
||||
"America/Halifax",
|
||||
"America/Glace_Bay",
|
||||
"America/Moncton",
|
||||
"America/Goose_Bay",
|
||||
"America/Blanc-Sablon",
|
||||
"America/Toronto",
|
||||
"America/Nipigon",
|
||||
"America/Thunder_Bay",
|
||||
"America/Iqaluit",
|
||||
"America/Pangnirtung",
|
||||
"America/Atikokan",
|
||||
"America/Winnipeg",
|
||||
"America/Rainy_River",
|
||||
"America/Resolute",
|
||||
"America/Rankin_Inlet",
|
||||
"America/Regina",
|
||||
"America/Swift_Current",
|
||||
"America/Edmonton",
|
||||
"America/Cambridge_Bay",
|
||||
"America/Yellowknife",
|
||||
"America/Inuvik",
|
||||
"America/Creston",
|
||||
"America/Dawson_Creek",
|
||||
"America/Fort_Nelson",
|
||||
"America/Vancouver",
|
||||
"America/Whitehorse",
|
||||
"America/Dawson",
|
||||
"Indian/Cocos",
|
||||
"Europe/Zurich",
|
||||
"Africa/Abidjan",
|
||||
"Pacific/Rarotonga",
|
||||
"America/Santiago",
|
||||
"America/Punta_Arenas",
|
||||
"Pacific/Easter",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Urumqi",
|
||||
"America/Bogota",
|
||||
"America/Costa_Rica",
|
||||
"America/Havana",
|
||||
"Atlantic/Cape_Verde",
|
||||
"America/Curacao",
|
||||
"Indian/Christmas",
|
||||
"Asia/Nicosia",
|
||||
"Asia/Famagusta",
|
||||
"Europe/Prague",
|
||||
"Europe/Berlin",
|
||||
"Europe/Copenhagen",
|
||||
"America/Santo_Domingo",
|
||||
"Africa/Algiers",
|
||||
"America/Guayaquil",
|
||||
"Pacific/Galapagos",
|
||||
"Europe/Tallinn",
|
||||
"Africa/Cairo",
|
||||
"Africa/El_Aaiun",
|
||||
"Europe/Madrid",
|
||||
"Africa/Ceuta",
|
||||
"Atlantic/Canary",
|
||||
"Europe/Helsinki",
|
||||
"Pacific/Fiji",
|
||||
"Atlantic/Stanley",
|
||||
"Pacific/Chuuk",
|
||||
"Pacific/Pohnpei",
|
||||
"Pacific/Kosrae",
|
||||
"Atlantic/Faroe",
|
||||
"Europe/Paris",
|
||||
"Europe/London",
|
||||
"Asia/Tbilisi",
|
||||
"America/Cayenne",
|
||||
"Africa/Accra",
|
||||
"Europe/Gibraltar",
|
||||
"America/Godthab",
|
||||
"America/Danmarkshavn",
|
||||
"America/Scoresbysund",
|
||||
"America/Thule",
|
||||
"Europe/Athens",
|
||||
"Atlantic/South_Georgia",
|
||||
"America/Guatemala",
|
||||
"Pacific/Guam",
|
||||
"Africa/Bissau",
|
||||
"America/Guyana",
|
||||
"Asia/Hong_Kong",
|
||||
"America/Tegucigalpa",
|
||||
"America/Port-au-Prince",
|
||||
"Europe/Budapest",
|
||||
"Asia/Jakarta",
|
||||
"Asia/Pontianak",
|
||||
"Asia/Makassar",
|
||||
"Asia/Jayapura",
|
||||
"Europe/Dublin",
|
||||
"Asia/Jerusalem",
|
||||
"Asia/Kolkata",
|
||||
"Indian/Chagos",
|
||||
"Asia/Baghdad",
|
||||
"Asia/Tehran",
|
||||
"Atlantic/Reykjavik",
|
||||
"Europe/Rome",
|
||||
"America/Jamaica",
|
||||
"Asia/Amman",
|
||||
"Asia/Tokyo",
|
||||
"Africa/Nairobi",
|
||||
"Asia/Bishkek",
|
||||
"Pacific/Tarawa",
|
||||
"Pacific/Enderbury",
|
||||
"Pacific/Kiritimati",
|
||||
"Asia/Pyongyang",
|
||||
"Asia/Seoul",
|
||||
"Asia/Almaty",
|
||||
"Asia/Qyzylorda",
|
||||
"Asia/Aqtobe",
|
||||
"Asia/Aqtau",
|
||||
"Asia/Atyrau",
|
||||
"Asia/Oral",
|
||||
"Asia/Beirut",
|
||||
"Asia/Colombo",
|
||||
"Africa/Monrovia",
|
||||
"Europe/Vilnius",
|
||||
"Europe/Luxembourg",
|
||||
"Europe/Riga",
|
||||
"Africa/Tripoli",
|
||||
"Africa/Casablanca",
|
||||
"Europe/Monaco",
|
||||
"Europe/Chisinau",
|
||||
"Pacific/Majuro",
|
||||
"Pacific/Kwajalein",
|
||||
"Asia/Yangon",
|
||||
"Asia/Ulaanbaatar",
|
||||
"Asia/Hovd",
|
||||
"Asia/Choibalsan",
|
||||
"Asia/Macau",
|
||||
"America/Martinique",
|
||||
"Europe/Malta",
|
||||
"Indian/Mauritius",
|
||||
"Indian/Maldives",
|
||||
"America/Mexico_City",
|
||||
"America/Cancun",
|
||||
"America/Merida",
|
||||
"America/Monterrey",
|
||||
"America/Matamoros",
|
||||
"America/Mazatlan",
|
||||
"America/Chihuahua",
|
||||
"America/Ojinaga",
|
||||
"America/Hermosillo",
|
||||
"America/Tijuana",
|
||||
"America/Bahia_Banderas",
|
||||
"Asia/Kuala_Lumpur",
|
||||
"Asia/Kuching",
|
||||
"Africa/Maputo",
|
||||
"Africa/Windhoek",
|
||||
"Pacific/Noumea",
|
||||
"Pacific/Norfolk",
|
||||
"Africa/Lagos",
|
||||
"America/Managua",
|
||||
"Europe/Amsterdam",
|
||||
"Europe/Oslo",
|
||||
"Asia/Kathmandu",
|
||||
"Pacific/Nauru",
|
||||
"Pacific/Niue",
|
||||
"Pacific/Auckland",
|
||||
"Pacific/Chatham",
|
||||
"America/Panama",
|
||||
"America/Lima",
|
||||
"Pacific/Tahiti",
|
||||
"Pacific/Marquesas",
|
||||
"Pacific/Gambier",
|
||||
"Pacific/Port_Moresby",
|
||||
"Pacific/Bougainville",
|
||||
"Asia/Manila",
|
||||
"Asia/Karachi",
|
||||
"Europe/Warsaw",
|
||||
"America/Miquelon",
|
||||
"Pacific/Pitcairn",
|
||||
"America/Puerto_Rico",
|
||||
"Asia/Gaza",
|
||||
"Asia/Hebron",
|
||||
"Europe/Lisbon",
|
||||
"Atlantic/Madeira",
|
||||
"Atlantic/Azores",
|
||||
"Pacific/Palau",
|
||||
"America/Asuncion",
|
||||
"Asia/Qatar",
|
||||
"Indian/Reunion",
|
||||
"Europe/Bucharest",
|
||||
"Europe/Belgrade",
|
||||
"Europe/Kaliningrad",
|
||||
"Europe/Moscow",
|
||||
"Europe/Simferopol",
|
||||
"Europe/Kirov",
|
||||
"Europe/Astrakhan",
|
||||
"Europe/Volgograd",
|
||||
"Europe/Saratov",
|
||||
"Europe/Ulyanovsk",
|
||||
"Europe/Samara",
|
||||
"Asia/Yekaterinburg",
|
||||
"Asia/Omsk",
|
||||
"Asia/Novosibirsk",
|
||||
"Asia/Barnaul",
|
||||
"Asia/Tomsk",
|
||||
"Asia/Novokuznetsk",
|
||||
"Asia/Krasnoyarsk",
|
||||
"Asia/Irkutsk",
|
||||
"Asia/Chita",
|
||||
"Asia/Yakutsk",
|
||||
"Asia/Khandyga",
|
||||
"Asia/Vladivostok",
|
||||
"Asia/Ust-Nera",
|
||||
"Asia/Magadan",
|
||||
"Asia/Sakhalin",
|
||||
"Asia/Srednekolymsk",
|
||||
"Asia/Kamchatka",
|
||||
"Asia/Anadyr",
|
||||
"Asia/Riyadh",
|
||||
"Pacific/Guadalcanal",
|
||||
"Indian/Mahe",
|
||||
"Africa/Khartoum",
|
||||
"Europe/Stockholm",
|
||||
"Asia/Singapore",
|
||||
"America/Paramaribo",
|
||||
"Africa/Juba",
|
||||
"Africa/Sao_Tome",
|
||||
"America/El_Salvador",
|
||||
"Asia/Damascus",
|
||||
"America/Grand_Turk",
|
||||
"Africa/Ndjamena",
|
||||
"Indian/Kerguelen",
|
||||
"Asia/Bangkok",
|
||||
"Asia/Dushanbe",
|
||||
"Pacific/Fakaofo",
|
||||
"Asia/Dili",
|
||||
"Asia/Ashgabat",
|
||||
"Africa/Tunis",
|
||||
"Pacific/Tongatapu",
|
||||
"Europe/Istanbul",
|
||||
"America/Port_of_Spain",
|
||||
"Pacific/Funafuti",
|
||||
"Asia/Taipei",
|
||||
"Europe/Kiev",
|
||||
"Europe/Uzhgorod",
|
||||
"Europe/Zaporozhye",
|
||||
"Pacific/Wake",
|
||||
"America/New_York",
|
||||
"America/Detroit",
|
||||
"America/Kentucky/Louisville",
|
||||
"America/Kentucky/Monticello",
|
||||
"America/Indiana/Indianapolis",
|
||||
"America/Indiana/Vincennes",
|
||||
"America/Indiana/Winamac",
|
||||
"America/Indiana/Marengo",
|
||||
"America/Indiana/Petersburg",
|
||||
"America/Indiana/Vevay",
|
||||
"America/Chicago",
|
||||
"America/Indiana/Tell_City",
|
||||
"America/Indiana/Knox",
|
||||
"America/Menominee",
|
||||
"America/North_Dakota/Center",
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/North_Dakota/Beulah",
|
||||
"America/Denver",
|
||||
"America/Boise",
|
||||
"America/Phoenix",
|
||||
"America/Los_Angeles",
|
||||
"America/Anchorage",
|
||||
"America/Juneau",
|
||||
"America/Sitka",
|
||||
"America/Metlakatla",
|
||||
"America/Yakutat",
|
||||
"America/Nome",
|
||||
"America/Adak",
|
||||
"Pacific/Honolulu",
|
||||
"America/Montevideo",
|
||||
"Asia/Samarkand",
|
||||
"Asia/Tashkent",
|
||||
"America/Caracas",
|
||||
"Asia/Ho_Chi_Minh",
|
||||
"Pacific/Efate",
|
||||
"Pacific/Wallis",
|
||||
"Pacific/Apia",
|
||||
"Africa/Johannesburg",
|
||||
];
|
||||
|
||||
export function timezoneList() {
|
||||
|
||||
let result = [];
|
||||
|
||||
for (let timezone of aryIannaTimeZones) {
|
||||
|
||||
for (let timezone of timezones) {
|
||||
try {
|
||||
let display = dayjs().tz(timezone).format("Z");
|
||||
let display = dayjs().tz(timezone.tzCode).format("Z");
|
||||
|
||||
result.push({
|
||||
name: `(UTC${display}) ${timezone}`,
|
||||
value: timezone,
|
||||
time: getTimezoneOffset(timezone),
|
||||
name: `(UTC${display}) ${timezone.tzCode}`,
|
||||
value: timezone.tzCode,
|
||||
time: getTimezoneOffset(timezone.tzCode),
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
console.log("Skip this timezone")
|
||||
console.log("Skip Timezone: " + timezone.tzCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result.sort((a, b) => {
|
||||
|
|
10
test/ubuntu-nodejs16.dockerfile
Normal file
10
test/ubuntu-nodejs16.dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
|||
FROM ubuntu
|
||||
WORKDIR /app
|
||||
RUN apt update && apt --yes install git curl
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash -
|
||||
RUN apt --yes install nodejs
|
||||
RUN git clone https://github.com/louislam/uptime-kuma.git .
|
||||
RUN npm run setup
|
||||
|
||||
# Option 1. Try it
|
||||
RUN node server/server.js
|
Loading…
Add table
Reference in a new issue