mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-19 18:56:48 +02:00
Compare commits
117 commits
2.0.0-beta
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b1e8d9b4d2 | ||
![]() |
e0335ecfbd | ||
![]() |
4962817795 | ||
![]() |
443d5cf554 | ||
![]() |
609429bd7e | ||
![]() |
3b6a78bd80 | ||
![]() |
55817061c0 | ||
![]() |
18cfa901ad | ||
![]() |
f282422b22 | ||
![]() |
53e83e7722 | ||
![]() |
8909cd008c | ||
![]() |
c7bacbb7fe | ||
![]() |
4d51aaa6f8 | ||
![]() |
f27811c394 | ||
![]() |
c46772dafc | ||
![]() |
4bdb616641 | ||
![]() |
87308a7778 | ||
![]() |
8d710e217a | ||
![]() |
63c5ebe624 | ||
![]() |
93daab9790 | ||
![]() |
dcff640248 | ||
![]() |
71965e6b69 | ||
![]() |
ab418342a8 | ||
![]() |
c0598ac606 | ||
![]() |
1ca035da80 | ||
![]() |
735f4a8268 | ||
![]() |
839ead80cc | ||
![]() |
c69fcd4093 | ||
![]() |
49eb0ff87b | ||
![]() |
f3828262c9 | ||
![]() |
3760d8021b | ||
![]() |
c67f6efe29 | ||
![]() |
289e824a5d | ||
![]() |
0f1547febd | ||
![]() |
0b29fd49ad | ||
![]() |
413cb08a81 | ||
![]() |
60b3fb4c18 | ||
![]() |
cd6dc144a7 | ||
![]() |
2b3f49a266 | ||
![]() |
86b3ef9c86 | ||
![]() |
eb18677e4f | ||
![]() |
edf788f97f | ||
![]() |
76c382f229 | ||
![]() |
e1bb3324e4 | ||
![]() |
32d92eccfa | ||
![]() |
6f0571fc30 | ||
![]() |
36741e2023 | ||
![]() |
4bbe61fb46 | ||
![]() |
6a5011ad34 | ||
![]() |
f814d86dd2 | ||
![]() |
0306596311 | ||
![]() |
5d3d8b387d | ||
![]() |
474861e1c1 | ||
![]() |
fb980b1065 | ||
![]() |
b22969a3e6 | ||
![]() |
00025e2a2d | ||
![]() |
2ba33f068b | ||
![]() |
5255b6bbb9 | ||
![]() |
5c7b91a99d | ||
![]() |
d2a4972f6a | ||
![]() |
8f245b89cf | ||
![]() |
94275de493 | ||
![]() |
c60c0c4912 | ||
![]() |
cf82085917 | ||
![]() |
fbd9490fc7 | ||
![]() |
2cbf3483bd | ||
![]() |
8396330552 | ||
![]() |
e7d3fa6a78 | ||
![]() |
a3ca2d1838 | ||
![]() |
b3a11a4d9d | ||
![]() |
95023e64ab | ||
![]() |
64b2c6b183 | ||
![]() |
9bba309b88 | ||
![]() |
0e9ba79ef2 | ||
![]() |
e6aea02ddf | ||
![]() |
9f92760238 | ||
![]() |
dfc424494b | ||
![]() |
ae52d9f68f | ||
![]() |
edadbd5e9e | ||
![]() |
cf7a6b0831 | ||
![]() |
8ea23daf2e | ||
![]() |
a09cbd176f | ||
![]() |
2ac56d39bd | ||
![]() |
2660382a73 | ||
![]() |
6a7c5eb11d | ||
![]() |
6869036a30 | ||
![]() |
d0d5dbc8e7 | ||
![]() |
139a2b7865 | ||
![]() |
395e505c18 | ||
![]() |
16c048f3bb | ||
![]() |
acdd1e6a32 | ||
![]() |
8eea2bde65 | ||
![]() |
c82ee23d65 | ||
![]() |
87ab164a45 | ||
![]() |
edc62bd68e | ||
![]() |
5654130ab4 | ||
![]() |
4953bf5eac | ||
![]() |
52b0856a2a | ||
![]() |
27f808448f | ||
![]() |
33f253f825 | ||
![]() |
f6444aacd2 | ||
![]() |
3b58ac3fd3 | ||
![]() |
0876b1cbf5 | ||
![]() |
999132aca8 | ||
![]() |
8d8e3e5a8e | ||
![]() |
ceb9c7e742 | ||
![]() |
510056fbbc | ||
![]() |
13a85b8200 | ||
![]() |
f1baa02d7a | ||
![]() |
bdf37c5a48 | ||
![]() |
cfb53c4a3f | ||
![]() |
867fff4264 | ||
![]() |
43a26eba88 | ||
![]() |
a5f6a2b99a | ||
![]() |
4b0a3df226 | ||
![]() |
85709f9ad4 | ||
![]() |
6de4aec67b |
116 changed files with 5555 additions and 2326 deletions
75
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
75
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
|
@ -1,75 +0,0 @@
|
|||
name: "❓ Ask for help"
|
||||
description: "Submit any question related to Uptime Kuma"
|
||||
#title: "[Help] "
|
||||
labels: [help]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "⚠️ Please verify that this question has NOT been raised before."
|
||||
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=)"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: "🛡️ Security Policy"
|
||||
description: Please review the security policy before reporting security related issues/bugs.
|
||||
options:
|
||||
- label: I agree to have read this project [Security Policy](https://github.com/louislam/uptime-kuma/security/policy)
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "📝 Describe your problem"
|
||||
description: "Please walk us through it step by step. Include all important details and add screenshots where appropriate"
|
||||
placeholder: "Describe what are you asking for..."
|
||||
- type: textarea
|
||||
id: error-msg
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "📝 Error Message(s) or Log"
|
||||
- type: input
|
||||
id: uptime-kuma-version
|
||||
attributes:
|
||||
label: "🐻 Uptime-Kuma Version"
|
||||
description: "Which version of Uptime-Kuma are you running? Please do NOT provide the docker tag such as latest or 1"
|
||||
placeholder: "Ex. 1.10.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: "💻 Operating System and Arch"
|
||||
description: "Which OS is your server/device running on? (For Replit, please do not report this bug)"
|
||||
placeholder: "Ex. Ubuntu 20.04 x86"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-vendor
|
||||
attributes:
|
||||
label: "🌐 Browser"
|
||||
description: "Which browser are you running on? (For Replit, please do not report this bug)"
|
||||
placeholder: "Ex. Google Chrome 95.0.4638.69"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: deployment-info
|
||||
attributes:
|
||||
label: "🖥️ Deployment Environment"
|
||||
description: |
|
||||
examples:
|
||||
- **Runtime**: Docker 20.10.9 / nodejs 14.18.0 / K8S via ... v1.3.3 / ..
|
||||
- **Database**: sqlite/embedded mariadb/external mariadb
|
||||
- **Filesystem used to store the database on**: Windows/ZFS/btrfs/NFSv3 on a SSD/HDD/eMMC
|
||||
- **number of monitors**: 42
|
||||
value: |
|
||||
- Runtime:
|
||||
- Database:
|
||||
- Filesystem used to store the database on:
|
||||
- number of monitors:
|
||||
validations:
|
||||
required: true
|
120
.github/ISSUE_TEMPLATE/ask_for_help.yml
vendored
Normal file
120
.github/ISSUE_TEMPLATE/ask_for_help.yml
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
name: ❓ Ask for help
|
||||
description: |
|
||||
Submit any question related to Uptime Kuma
|
||||
#title: "[Help]"
|
||||
labels: ["help", "P3-low"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
🚫 **We kindly ask you to refrain from pinging maintainers unless absolutely necessary. Pings are reserved for critical/urgent issues that require immediate attention.**
|
||||
|
||||
**Why**: Reserving pings for urgent matters ensures maintainers can prioritize critical tasks effectively
|
||||
|
||||
- type: checkboxes
|
||||
id: no-duplicate-question
|
||||
attributes:
|
||||
label: ⚠️ Please verify that your question has not already been reported
|
||||
description: |
|
||||
To avoid duplicate reports, please search for any existing issues before submitting a new one. You can find the list of existing issues **[HERE](https://github.com/louislam/uptime-kuma/issues?q=is%3Aissue%20sort%3Acreated-desc%20)**.
|
||||
options:
|
||||
- label: |
|
||||
I have searched the [existing issues](https://github.com/louislam/uptime-kuma/issues?q=is%3Aissue%20sort%3Acreated-desc%20) and found no similar reports.
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: security-policy
|
||||
attributes:
|
||||
label: 🛡️ Security Policy
|
||||
description: |
|
||||
Please review and acknowledge the Security Policy before reporting any security-related issues or bugs. You can find the full Security Policy **[HERE](https://github.com/louislam/uptime-kuma/security/policy)**.
|
||||
options:
|
||||
- label: |
|
||||
I have read and agree to Uptime Kuma's [Security Policy](https://github.com/louislam/uptime-kuma/security/policy).
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 📝 Describe your problem
|
||||
description: |
|
||||
Please walk us through it step by step. Include all important details and add screenshots where appropriate
|
||||
placeholder: |
|
||||
Describe what are you asking for ...
|
||||
|
||||
- type: textarea
|
||||
id: error-msg
|
||||
attributes:
|
||||
label: 📝 Error Message(s) or Log
|
||||
description: |
|
||||
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: bash session
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: uptime-kuma-version
|
||||
attributes:
|
||||
label: 🐻 Uptime-Kuma Version
|
||||
description: |
|
||||
What version of Uptime-Kuma are you running? Please do not provide Docker tags like `latest` or `1`.
|
||||
placeholder: |
|
||||
e.g., 1.23.16 or 2.0.0-beta.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: 💻 Operating System and Arch
|
||||
description: |
|
||||
Which OS is your server/device running on? (For Replit, please do not report this bug)
|
||||
placeholder: |
|
||||
e.g., Ubuntu Server 24.04.2 LTS (GNU/Linux 6.8.0-55-generic x86_64)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: browser-vendor
|
||||
attributes:
|
||||
label: 🌐 Browser
|
||||
description: |
|
||||
Which browser are you running on? (For Replit, please do not report this bug)
|
||||
placeholder: |
|
||||
e.g., Google Chrome 134.0.6998.183 (Official Build) (64-bit)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: deployment-info
|
||||
attributes:
|
||||
label: 🖥️ Deployment Environment
|
||||
description: |
|
||||
Provide details about the deployment environment, including runtime components, databases, and storage configurations. This will
|
||||
help assess the infrastructure and identify any potential compatibility requirements.
|
||||
|
||||
**Remove any fields that do not apply to your setup.**
|
||||
value: |
|
||||
- **Runtime Environment**:
|
||||
- Docker: Version `X.X.X` (Build `Y.Y.Y`)
|
||||
- Docker Compose: Version `X.X.X`
|
||||
- Portainer (BE/CE): Version `X.X.X` (LTS: Yes/No)
|
||||
- MariaDB: Version `X.X.X` (LTS: Yes/No)
|
||||
- Node.js: Version `X.X.X` (LTS: Yes/No)
|
||||
- Kubernetes (K3S/K8S): Version `X.X.X` (LTS: Yes/No, via `[method/tool]`)
|
||||
- **Database**:
|
||||
- SQLite: Embedded
|
||||
- MariaDB: Embedded/External
|
||||
- **Database Storage**:
|
||||
- **Filesystem**:
|
||||
- Linux: ext4/XFS/Btrfs/ZFS/F2FS
|
||||
- macOS: APFS/ HFS+
|
||||
- Windows: NTFS/ReFS
|
||||
- **Storage Medium**: HDD/eMMC/SSD/NVMe
|
||||
- **Uptime Kuma Setup**:
|
||||
- Number of monitors: `X`
|
||||
validations:
|
||||
required: true
|
100
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
100
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
|
@ -1,100 +0,0 @@
|
|||
name: "🐛 Bug Report"
|
||||
description: "Submit a bug report to help us improve"
|
||||
#title: "[Bug] "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: textarea
|
||||
id: related-issues
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "📑 I have found these related issues/pull requests"
|
||||
description: "Search related issues by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=) and explain what the difference between them or explain that you are unable to find any related issues"
|
||||
placeholder: "Related to #1 by also touching the ... system. They should not be merged because ..."
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: "🛡️ Security Policy"
|
||||
description: Please review the security policy before reporting security related issues/bugs.
|
||||
options:
|
||||
- label: I agree to have read this project [Security Policy](https://github.com/louislam/uptime-kuma/security/policy)
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "Description"
|
||||
description: "You could also upload screenshots"
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👟 Reproduction steps"
|
||||
description: "How do you trigger this bug? Please walk us through it step by step. Include all important details and add screenshots where appropriate"
|
||||
placeholder: "..."
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👀 Expected behavior"
|
||||
description: "What did you think would happen?"
|
||||
placeholder: "..."
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "😓 Actual Behavior"
|
||||
description: "What actually happen?"
|
||||
placeholder: "..."
|
||||
- type: input
|
||||
id: uptime-kuma-version
|
||||
attributes:
|
||||
label: "🐻 Uptime-Kuma Version"
|
||||
description: "Which version of Uptime-Kuma are you running? Please do NOT provide the docker tag such as latest or 1"
|
||||
placeholder: "Ex. 1.10.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: "💻 Operating System and Arch"
|
||||
description: "Which OS is your server/device running on? (For Replit, please do not report this bug)"
|
||||
placeholder: "Ex. Ubuntu 20.04 x64 "
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-vendor
|
||||
attributes:
|
||||
label: "🌐 Browser"
|
||||
description: "Which browser are you running on?"
|
||||
placeholder: "Ex. Google Chrome 95.0.4638.69"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: deployment-info
|
||||
attributes:
|
||||
label: "🖥️ Deployment Environment"
|
||||
description: |
|
||||
examples:
|
||||
- **Runtime**: Docker 20.10.9 / nodejs 18.17.1 / K8S via ... v1.3.3 / ..
|
||||
- **Database**: sqlite/embedded mariadb/external mariadb
|
||||
- **Filesystem used to store the database on**: Windows/ZFS/btrfs/NFSv3 on a SSD/HDD/eMMC
|
||||
- **number of monitors**: 42
|
||||
value: |
|
||||
- Runtime:
|
||||
- Database:
|
||||
- Filesystem used to store the database on:
|
||||
- number of monitors:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "📝 Relevant log output"
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
154
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
154
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
---
|
||||
name: 🐛 Bug Report
|
||||
description: |
|
||||
Submit a bug report to help us improve
|
||||
#title: "[Bug]"
|
||||
labels: ["bug", "P2-medium"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
🚫 **We kindly ask you to refrain from pinging maintainers unless absolutely necessary. Pings are reserved for critical/urgent issues that require immediate attention.**
|
||||
|
||||
**Why**: Reserving pings for urgent matters ensures maintainers can prioritize critical tasks effectively
|
||||
|
||||
- type: textarea
|
||||
id: related-issues
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 📑 I have found these related issues/pull requests
|
||||
description: |
|
||||
Please search for related **[ISSUES](https://github.com/louislam/uptime-kuma/issues?q=is%3Aissue%20sort%3Acreated-desc)**
|
||||
and **[PULL REQUESTS](https://github.com/louislam/uptime-kuma/pulls?q=is%3Apr+sort%3Acreated-desc+)**.
|
||||
Explain the differences between them or clarify if you were unable to find any related issues/pull requests.
|
||||
placeholder: |
|
||||
Example: This relates to issue #1, which also affects the ... system. It should not be merged because ...
|
||||
|
||||
- type: checkboxes
|
||||
id: security-policy
|
||||
attributes:
|
||||
label: 🛡️ Security Policy
|
||||
description: |
|
||||
Please review and acknowledge the Security Policy before reporting any security-related issues or bugs. You can find the full Security Policy **[HERE](https://github.com/louislam/uptime-kuma/security/policy)**.
|
||||
options:
|
||||
- label: |
|
||||
I have read and agree to Uptime Kuma's [Security Policy](https://github.com/louislam/uptime-kuma/security/policy).
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: 📝 Description
|
||||
description: |
|
||||
You could also upload screenshots
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 👟 Reproduction steps
|
||||
description: |
|
||||
How do you trigger this bug? Please walk us through it step by step. Include all important details and add screenshots where appropriate
|
||||
placeholder: |
|
||||
...
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 👀 Expected behavior
|
||||
description: |
|
||||
What did you think would happen?
|
||||
placeholder: |
|
||||
...
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 😓 Actual Behavior
|
||||
description: |
|
||||
What actually happen?
|
||||
placeholder: |
|
||||
...
|
||||
|
||||
- type: input
|
||||
id: uptime-kuma-version
|
||||
attributes:
|
||||
label: 🐻 Uptime-Kuma Version
|
||||
description: |
|
||||
What version of Uptime-Kuma are you running? Please do not provide Docker tags like `latest` or `1`.
|
||||
placeholder: |
|
||||
e.g., 1.23.16 or 2.0.0-beta.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: 💻 Operating System and Arch
|
||||
description: |
|
||||
Which OS is your server/device running on? (For Replit, please do not
|
||||
report this bug)
|
||||
placeholder: |
|
||||
e.g., Ubuntu Server 24.04.2 LTS (GNU/Linux 6.8.0-55-generic x86_64)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: browser-vendor
|
||||
attributes:
|
||||
label: 🌐 Browser
|
||||
description: |
|
||||
Which browser are you running on?
|
||||
placeholder: |
|
||||
e.g., Google Chrome 134.0.6998.183 (Official Build) (64-bit)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: deployment-info
|
||||
attributes:
|
||||
label: 🖥️ Deployment Environment
|
||||
description: |
|
||||
Provide details about the deployment environment, including runtime components, databases, and storage configurations. This will
|
||||
help assess the infrastructure and identify any potential compatibility requirements.
|
||||
|
||||
**Remove any fields that do not apply to your setup.**
|
||||
value: |
|
||||
- **Runtime Environment**:
|
||||
- Docker: Version `X.X.X` (Build `Y.Y.Y`)
|
||||
- Docker Compose: Version `X.X.X`
|
||||
- Portainer (BE/CE): Version `X.X.X` (LTS: Yes/No)
|
||||
- MariaDB: Version `X.X.X` (LTS: Yes/No)
|
||||
- Node.js: Version `X.X.X` (LTS: Yes/No)
|
||||
- Kubernetes (K3S/K8S): Version `X.X.X` (LTS: Yes/No, via `[method/tool]`)
|
||||
- **Database**:
|
||||
- SQLite: Embedded
|
||||
- MariaDB: Embedded/External
|
||||
- **Database Storage**:
|
||||
- **Filesystem**:
|
||||
- Linux: ext4/XFS/Btrfs/ZFS/F2FS
|
||||
- macOS: APFS/ HFS+
|
||||
- Windows: NTFS/ReFS
|
||||
- **Storage Medium**: HDD/eMMC/SSD/NVMe
|
||||
- **Uptime Kuma Setup**:
|
||||
- Number of monitors: `X`
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 📝 Relevant log output
|
||||
description: |
|
||||
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: bash session
|
||||
validations:
|
||||
required: false
|
66
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
66
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
|
@ -1,66 +0,0 @@
|
|||
name: 🚀 Feature Request
|
||||
description: "Submit a proposal for a new feature"
|
||||
#title: "[Feature] "
|
||||
labels: [feature-request]
|
||||
body:
|
||||
- type: textarea
|
||||
id: related-issues
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "📑 I have found these related issues/pull requests"
|
||||
description: "Search related issues by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=) and explain what the difference between them or explain that you are unable to find any related issues"
|
||||
placeholder: "Related to #1 by also touching the ... system. They should not be merged because ..."
|
||||
- type: dropdown
|
||||
id: feature-area
|
||||
attributes:
|
||||
label: "🏷️ Feature Request Type"
|
||||
description: "What kind of feature request is this?"
|
||||
multiple: true
|
||||
options:
|
||||
- API / automation options
|
||||
- New notification-provider
|
||||
- Change to existing notification-provider
|
||||
- New monitor
|
||||
- Change to existing monitor
|
||||
- Dashboard
|
||||
- Status-page
|
||||
- Maintenance
|
||||
- Deployment
|
||||
- Certificate expiry
|
||||
- Settings
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🔖 Feature description"
|
||||
description: "A clear and concise description of what the feature request is."
|
||||
placeholder: "You should add ..."
|
||||
- type: textarea
|
||||
id: solution
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "✔️ Solution"
|
||||
description: "A clear and concise description of what you want to happen."
|
||||
placeholder: "In my use-case, ..."
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "❓ Alternatives"
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
placeholder: "I have considered ..."
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "📝 Additional Context"
|
||||
description: "Add any other context or screenshots about the feature request here."
|
||||
placeholder: "..."
|
106
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
106
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
name: 🚀 Feature Request
|
||||
description: |
|
||||
Submit a proposal for a new feature
|
||||
# title: "[Feature]"
|
||||
labels: ["feature-request", "P3-low"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## ❗Important Announcement
|
||||
|
||||
### 🚧 Temporary Delay in Feature Requests and Pull Request Reviews
|
||||
|
||||
**At this time, we may be slower to respond to new feature requests and review pull requests. Existing requests and PRs will remain in the backlog but may not be prioritized immediately.**
|
||||
|
||||
- **Reason**: Our current focus is on addressing bugs, improving system performance, and implementing essential updates. This will help stabilize the project and ensure smoother management.
|
||||
- **Impact**: While no new feature requests or pull requests are being outright rejected, there may be significant delays in reviews. We encourage the community to help by reviewing PRs or assisting other users in the meantime.
|
||||
- **What You Can Do**: If you're interested in contributing, reviewing open PRs by following our [Review Guidelines](https://github.com/louislam/uptime-kuma/blob/master/.github/REVIEW_GUIDELINES.md) or offering help to other users is greatly appreciated. All feature requests and PRs will be revisited once the suspension period is lifted.
|
||||
|
||||
We appreciate your patience and understanding as we continue to improve Uptime Kuma.
|
||||
|
||||
### 🚫 Please Avoid Unnecessary Pinging of Maintainers
|
||||
|
||||
**We kindly ask you to refrain from pinging maintainers unless absolutely necessary. Pings are reserved for critical/urgent pull requests that require immediate attention.**
|
||||
|
||||
**Why**: Reserving pings for urgent matters ensures maintainers can prioritize critical tasks effectively.
|
||||
- type: textarea
|
||||
id: related-issues
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 📑 I have found these related issues/pull requests
|
||||
description: |
|
||||
Please search for related **[ISSUES](https://github.com/louislam/uptime-kuma/issues?q=is%3Aissue%20sort%3Acreated-desc)**
|
||||
and **[PULL REQUESTS](https://github.com/louislam/uptime-kuma/pulls?q=is%3Apr+sort%3Acreated-desc+)**.
|
||||
Explain the differences between them or clarify if you were unable to find any related issues/pull requests.
|
||||
placeholder: |
|
||||
Example: This relates to issue #1, which also affects the ... system. It should not be merged because ...
|
||||
|
||||
- type: dropdown
|
||||
id: feature-area
|
||||
attributes:
|
||||
label: 🏷️ Feature Request Type
|
||||
description: |
|
||||
What kind of feature request is this?
|
||||
multiple: true
|
||||
options:
|
||||
- API / automation options
|
||||
- New notification-provider
|
||||
- Change to existing notification-provider
|
||||
- New monitor
|
||||
- Change to existing monitor
|
||||
- Dashboard
|
||||
- Status-page
|
||||
- Maintenance
|
||||
- Deployment
|
||||
- Certificate expiry
|
||||
- Settings
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: 🔖 Feature description
|
||||
description: |
|
||||
A clear and concise description of what the feature request is.
|
||||
placeholder: |
|
||||
You should add ...
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: ✔️ Solution
|
||||
description: |
|
||||
A clear and concise description of what you want to happen.
|
||||
placeholder: |
|
||||
In my use-case, ...
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: ❓ Alternatives
|
||||
description: |
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
placeholder: |
|
||||
I have considered ...
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: 📝 Additional Context
|
||||
description: |
|
||||
Add any other context or screenshots about the feature request here.
|
||||
placeholder: |
|
||||
...
|
16
.github/ISSUE_TEMPLATE/security_issue.yml
vendored
16
.github/ISSUE_TEMPLATE/security_issue.yml
vendored
|
@ -1,19 +1,19 @@
|
|||
---
|
||||
name: "🛡️ Security Issue"
|
||||
name: 🛡️ Security Issue
|
||||
description: |
|
||||
Notify Louis Lam about a security concern. Please do NOT include any sensitive details in this issue.
|
||||
# title: "Security Issue"
|
||||
labels: [security]
|
||||
labels: ["security", "P1-high"]
|
||||
assignees: [louislam]
|
||||
body:
|
||||
- type: "markdown"
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## **⚠️ Report a Security Vulnerability**
|
||||
## ❗ IMPORTANT: DO NOT SHARE VULNERABILITY DETAILS HERE
|
||||
|
||||
### **IMPORTANT: DO NOT SHARE VULNERABILITY DETAILS HERE**
|
||||
### ⚠️ Report a Security Vulnerability
|
||||
|
||||
If you have discovered a security vulnerability, please report it securely using the GitHub Security Advisory.
|
||||
**If you have discovered a security vulnerability, please report it securely using the GitHub Security Advisory.**
|
||||
|
||||
**Note**: This issue is only for notifying the maintainers of the repository, as the GitHub Security Advisory does not automatically send notifications.
|
||||
|
||||
|
@ -34,12 +34,12 @@ body:
|
|||
|
||||
Once you've created your advisory, please share the URL below. This will notify Louis Lam and enable them to take the appropriate action.
|
||||
|
||||
- type: "textarea"
|
||||
- type: textarea
|
||||
id: github-advisory-url
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "GitHub Advisory URL for @louislam"
|
||||
label: GitHub Advisory URL for @louislam
|
||||
placeholder: |
|
||||
Please paste the GitHub Advisory URL here. Only the URL is required.
|
||||
Example: https://github.com/louislam/uptime-kuma/security/advisories/GHSA-8h5r-7t6l-q3kz
|
||||
|
|
135
.github/PULL_REQUEST_TEMPLATE.md
vendored
135
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,33 +1,122 @@
|
|||
⚠️⚠️⚠️ Since we do not accept all types of pull requests and do not want to waste your time. Please be sure that you have read pull request rules:
|
||||
https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma
|
||||
**⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we don’t waste your time. Before submitting, make sure you have read our pull request guidelines: [Pull Request Rules](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma)**
|
||||
|
||||
Tick the checkbox if you understand [x]:
|
||||
- [ ] I have read and understand the pull request rules.
|
||||
## ❗ Important Announcement
|
||||
|
||||
# Description
|
||||
<details><summary>Click here for more details:</summary>
|
||||
</p>
|
||||
|
||||
Fixes #(issue)
|
||||
### 🚧 Temporary Delay in Feature Requests and Pull Request Reviews
|
||||
|
||||
## Type of change
|
||||
**At this time, we may be slower to respond to new feature requests and review pull requests. Existing requests and PRs will remain in the backlog but may not be prioritized immediately.**
|
||||
|
||||
Please delete any options that are not relevant.
|
||||
- **Reason**: Our current focus is on addressing bugs, improving system performance, and implementing essential updates. This will help stabilize the project and ensure smoother management.
|
||||
- **Impact**: While no new feature requests or pull requests are being outright rejected, there may be significant delays in reviews. We encourage the community to help by reviewing PRs or assisting other users in the meantime.
|
||||
- **What You Can Do**: If you're interested in contributing, reviewing open PRs by following our [Review Guidelines](https://github.com/louislam/uptime-kuma/blob/master/.github/REVIEW_GUIDELINES.md) or offering support to other users is greatly appreciated. All feature requests and PRs will be revisited once the suspension period is lifted.
|
||||
|
||||
- Bug fix (non-breaking change which fixes an issue)
|
||||
- User interface (UI)
|
||||
- New feature (non-breaking change which adds functionality)
|
||||
- Breaking change (a fix or feature that would cause existing functionality to not work as expected)
|
||||
- Other
|
||||
- This change requires a documentation update
|
||||
We appreciate your patience and understanding as we continue to improve Uptime Kuma.
|
||||
|
||||
## Checklist
|
||||
### 🚫 Please Avoid Unnecessary Pinging of Maintainers
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I ran ESLint and other linters for modified files
|
||||
- [ ] I have performed a self-review of my own code and tested it
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas (including JSDoc for methods)
|
||||
- [ ] My changes generates no new warnings
|
||||
- [ ] My code needed automated testing. I have added them (this is optional task)
|
||||
**We kindly ask you to refrain from pinging maintainers unless absolutely necessary. Pings are reserved for critical/urgent pull requests that require immediate attention.**
|
||||
|
||||
## Screenshots (if any)
|
||||
**Why**: Reserving pings for urgent matters ensures maintainers can prioritize critical tasks effectively.
|
||||
|
||||
Please do not use any external image service. Instead, just paste in or drag and drop the image here, and it will be uploaded automatically.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
Provide a clear summary of the purpose and scope of this pull request:
|
||||
|
||||
- **What problem does this pull request address?**
|
||||
|
||||
- Please provide a detailed explanation here.
|
||||
|
||||
- **What features or functionality does this pull request introduce or enhance?**
|
||||
|
||||
- Please provide a detailed explanation here.
|
||||
|
||||
## 🔄 Changes
|
||||
|
||||
### 🛠️ Type of change
|
||||
|
||||
<!-- Please select all options that apply -->
|
||||
|
||||
- [ ] 🐛 Bugfix (a non-breaking change that resolves an issue)
|
||||
- [ ] ✨ New feature (a non-breaking change that adds new functionality)
|
||||
- [ ] ⚠️ Breaking change (a fix or feature that alters existing functionality in a way that could cause issues)
|
||||
- [ ] 🎨 User Interface (UI) updates
|
||||
- [ ] 📄 New Documentation (addition of new documentation)
|
||||
- [ ] 📄 Documentation Update (modification of existing documentation)
|
||||
- [ ] 📄 Documentation Update Required (the change requires updates to related documentation)
|
||||
- [ ] 🔧 Other (please specify):
|
||||
- Provide additional details here.
|
||||
|
||||
## 🔗 Related Issues
|
||||
|
||||
<!--
|
||||
Please link any GitHub issues or tasks that this pull request addresses. Use the appropriate issue numbers or links.
|
||||
|
||||
**Note**: Include only issues directly related to this PR. Remove any irrelevant reference.
|
||||
-->
|
||||
|
||||
- Relates to #issue-number
|
||||
- Resolves #issue-number
|
||||
- Fixes #issue-number
|
||||
|
||||
## 📄 Checklist *
|
||||
|
||||
<!-- Please select all options that apply -->
|
||||
|
||||
- [ ] 🔍 My code adheres to the style guidelines of this project.
|
||||
- [ ] ✅ I ran ESLint and other code linters for modified files.
|
||||
- [ ] 🛠️ I have reviewed and tested my code.
|
||||
- [ ] 📝 I have commented my code, especially in hard-to-understand areas (e.g., using JSDoc for methods).
|
||||
- [ ] ⚠️ My changes generate no new warnings.
|
||||
- [ ] 🤖 My code needed automated testing. I have added them (this is an optional task).
|
||||
- [ ] 📄 Documentation updates are included (if applicable).
|
||||
- [ ] 🔒 I have considered potential security impacts and mitigated risks.
|
||||
- [ ] 🧰 Dependency updates are listed and explained.
|
||||
- [ ] 📚 I have read and understood the [Pull Request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#recommended-pull-request-guideline).
|
||||
|
||||
## 📷 Screenshots or Visual Changes
|
||||
|
||||
<!--
|
||||
Please upload the image directly here by pasting it or dragging and dropping. Avoid using external image services as the image will be uploaded automatically.
|
||||
|
||||
If this pull request introduces visual changes, please provide the following details.
|
||||
If not, remove this section.
|
||||
-->
|
||||
|
||||
- **UI Modifications**: Highlight any changes made to the user interface.
|
||||
- **Before & After**: Include screenshots or comparisons (if applicable).
|
||||
|
||||
| Event | Before | After |
|
||||
| ------------------ | --------------------- | -------------------- |
|
||||
| `UP` |  |  |
|
||||
| `DOWN` |  |  |
|
||||
| Certificate-expiry |  |  |
|
||||
| Testing |  |  |
|
||||
|
||||
## ℹ️ Additional Context
|
||||
|
||||
Provide any relevant details to assist reviewers in understanding the changes.
|
||||
|
||||
<details><summary>Click here for more details:</summary>
|
||||
</p>
|
||||
|
||||
**Key Considerations**:
|
||||
|
||||
- **Design decisions** – Key choices or trade-offs made during development.
|
||||
- **Alternative solutions** – Approaches considered but not implemented, along with reasons.
|
||||
- **Relevant links** – Specifications, discussions, or resources that provide context.
|
||||
- **Dependencies** – Related pull requests or issues that must be resolved before merging.
|
||||
- **Additional context** – Any other details that may help reviewers understand the changes.
|
||||
|
||||
Provide details here
|
||||
|
||||
## 💬 Requested Feedback
|
||||
|
||||
<!-- If a part of our docs is unclear, you are unsure how to do something/.. this is where we would appreciate your feedback -->
|
||||
|
||||
- `Mention documents needing feedback here`
|
||||
|
|
224
.github/REVIEW_GUIDELINES.md
vendored
Normal file
224
.github/REVIEW_GUIDELINES.md
vendored
Normal file
|
@ -0,0 +1,224 @@
|
|||
# Uptime Kuma Review Guidelines
|
||||
|
||||
> [!NOTE]
|
||||
> These review guidelines are a work in progress, and are frequently
|
||||
> updated and improved, so please check back frequently for the latest version.
|
||||
|
||||
## Preparing for a PR Review
|
||||
|
||||
### Read the PR description carefully
|
||||
|
||||
Make sure you understand what the PR is trying to solve or implement. This could
|
||||
be a bug fix, a new feature, or a refactor.
|
||||
|
||||
### Check the linked issues
|
||||
|
||||
If the PR has a linked issue, read it to better understand the context and the
|
||||
reason for the change.
|
||||
|
||||
### Check the test coverage
|
||||
|
||||
Make sure relevant tests have been added or modified. If the PR adds new
|
||||
functionality, there should be tests covering the change.
|
||||
|
||||
## General Review
|
||||
|
||||
### Code formatting and style
|
||||
|
||||
Check if the code adheres to the style guidelines of the project. Make sure
|
||||
there are no unused imports, variables, `console.log` for debugging in the PR.
|
||||
|
||||
- [Project Style](../CONTRIBUTING.md#project-styles)
|
||||
- [Coding Style](../CONTRIBUTING.md#coding-styles)
|
||||
|
||||
### Readability and maintainability
|
||||
|
||||
Is the code easy to understand for other developers? Make sure complex parts are
|
||||
explained with comments about **_why_** something is done, and use clear names
|
||||
to show **_how_**. Are variables and functions well-named, and is there a
|
||||
consistent naming style? Also, check if the code is maintainable:
|
||||
|
||||
- Is it unnecessarily complex? Could it be simplified?
|
||||
- Does it follow the **[Single Responsibility Principle (SRP)]**?
|
||||
|
||||
[Single Responsibility Principle (SRP)]: https://www.geeksforgeeks.org/single-responsibility-in-solid-design-principle/
|
||||
|
||||
### Documentation
|
||||
|
||||
Is the PR well documented? Check if the descriptions of functions, parameters,
|
||||
and return values are present. Are there any changes needed to the README or
|
||||
other documentation, for example, if new features or configurations are
|
||||
introduced?
|
||||
|
||||
## Functional Review
|
||||
|
||||
### Testing
|
||||
|
||||
Ensure that the new code is properly tested. This includes unit tests,
|
||||
integration tests, and if necessary, end-to-end tests.
|
||||
|
||||
### Test results
|
||||
|
||||
Did all tests pass in the CI pipeline (e.g., GitHub Actions, Travis, CircleCI)?
|
||||
|
||||
### Testing in different environments
|
||||
|
||||
If the changes depend on certain environments or configurations, verify that the
|
||||
code has been tested in various environments (e.g., local development, staging,
|
||||
production).
|
||||
|
||||
- [How to test Pull Requests](https://github.com/louislam/uptime-kuma/wiki/Test-Pull-Requests)
|
||||
|
||||
### Edge cases and regressions
|
||||
|
||||
- Are there test cases for possible edge cases?
|
||||
- Could this change introduce regressions in other parts of the system?
|
||||
|
||||
## Security
|
||||
|
||||
### Security issues
|
||||
|
||||
Check for potential security problems, such as SQL injection, XSS attacks, or
|
||||
unsafe API calls. Are there passwords, tokens, or other sensitive data left in
|
||||
the code by mistake?
|
||||
|
||||
### Authentication and authorization
|
||||
|
||||
Is access to sensitive data or functionality properly secured? Check that the
|
||||
correct authorization and authentication mechanisms are in place.
|
||||
|
||||
### Security Best Practices
|
||||
|
||||
- Ensure that the code is free from common vulnerabilities like **SQL
|
||||
injection**, **XSS attacks**, and **insecure API calls**.
|
||||
- Check for proper encryption of sensitive data, and ensure that **passwords**
|
||||
or **API tokens** are not hardcoded in the code.
|
||||
|
||||
## Performance
|
||||
|
||||
### Performance impact
|
||||
|
||||
Check if the changes negatively impact performance. This can include factors
|
||||
like load times, memory usage, or other performance aspects.
|
||||
|
||||
### Use of external libraries
|
||||
|
||||
- Have the right libraries been chosen?
|
||||
- Are there unnecessary dependencies that might reduce performance or increase
|
||||
code complexity?
|
||||
- Are these dependencies actively maintained and free of known vulnerabilities?
|
||||
|
||||
### Performance Best Practices
|
||||
|
||||
- **Measure performance** using tools like Lighthouse or profiling libraries.
|
||||
- **Avoid unnecessary dependencies** that may bloat the codebase.
|
||||
- Ensure that the **code does not degrade the user experience** (e.g., by
|
||||
increasing load times or memory consumption).
|
||||
|
||||
## Compliance and Integration
|
||||
|
||||
### Alignment with the project
|
||||
|
||||
Are the changes consistent with the project goals and requirements? Ensure the
|
||||
PR aligns with the architecture and design principles of the project.
|
||||
|
||||
### Integration
|
||||
|
||||
If the PR depends on other PRs or changes, verify that they integrate well with
|
||||
the rest of the project. Ensure the code does not cause conflicts with other
|
||||
active PRs.
|
||||
|
||||
### Backward compatibility
|
||||
|
||||
Does the change break compatibility with older versions of the software or
|
||||
dependencies? If so, is there a migration plan in place?
|
||||
|
||||
## Logging and Error Handling
|
||||
|
||||
### Proper error handling
|
||||
|
||||
- Are errors properly caught and handled instead of being silently ignored?
|
||||
- Are exceptions used appropriately?
|
||||
|
||||
### Logging
|
||||
|
||||
- Is sufficient logging included for debugging and monitoring?
|
||||
- Is there excessive logging that could affect performance?
|
||||
|
||||
## Accessibility (for UI-related changes)
|
||||
|
||||
If the PR affects the user interface, ensure that it meets accessibility
|
||||
standards:
|
||||
|
||||
- Can users navigate using only the keyboard?
|
||||
- Are screen readers supported?
|
||||
- Is there proper color contrast for readability?
|
||||
- Are there **WCAG** (Web Content Accessibility Guidelines) compliance issues?
|
||||
- Use tools like **Axe** or **Lighthouse** to evaluate accessibility.
|
||||
|
||||
## Providing Feedback
|
||||
|
||||
### Constructive feedback
|
||||
|
||||
Provide clear, constructive feedback on what is good and what can be improved.
|
||||
If improvements are needed, be specific about what should change.
|
||||
|
||||
### Clarity and collaboration
|
||||
|
||||
Ensure your feedback is friendly and open, so the team member who submitted the
|
||||
PR feels supported and motivated to make improvements.
|
||||
|
||||
<details><summary><b>For Maintainers only</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
## Go/No-Go Decision
|
||||
|
||||
### Go
|
||||
|
||||
If the code has no issues and meets the project requirements, approve it (and
|
||||
possibly merge it).
|
||||
|
||||
### No-Go
|
||||
|
||||
If there are significant issues, such as missing tests, security
|
||||
vulnerabilities, or performance problems, request the necessary changes before
|
||||
the PR can be approved. Some examples of **significant issues** include:
|
||||
|
||||
- Missing tests for new functionality.
|
||||
- Identified **security vulnerabilities**.
|
||||
- Code changes that break **backward compatibility** without a proper migration
|
||||
plan.
|
||||
- Code that causes **major performance regressions** (e.g., high CPU/memory
|
||||
usage).
|
||||
|
||||
## After the Review
|
||||
|
||||
### Reordering and merging
|
||||
|
||||
Once the necessary changes have been made and the PR is approved, the code can
|
||||
be merged into the main branch (e.g., `main` or `master`).
|
||||
|
||||
### Testing after merging
|
||||
|
||||
Ensure that the build passes after merging the PR, and re-test the functionality
|
||||
in the production environment if necessary.
|
||||
|
||||
## Follow-up
|
||||
|
||||
### Communication with team members
|
||||
|
||||
If the PR has long-term technical or functional implications, communicate the
|
||||
changes to the team.
|
||||
|
||||
### Monitoring
|
||||
|
||||
Continue monitoring the production environment for any unexpected issues that
|
||||
may arise after the code has been merged.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
This process ensures that PRs are systematically and thoroughly reviewed,
|
||||
improving overall code quality.
|
|
@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
|
|||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
@ -52,7 +52,7 @@ decisions when appropriate.
|
|||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
|
@ -60,8 +60,8 @@ representative at an online or offline event.
|
|||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
uptime@kuma.pet.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
<uptime@kuma.pet>. All complaints will be reviewed and investigated promptly and
|
||||
fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
|
|||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
|
@ -109,20 +109,24 @@ Violating these terms may lead to a permanent ban.
|
|||
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
|
||||
the community.
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
|
704
CONTRIBUTING.md
704
CONTRIBUTING.md
|
@ -1,16 +1,18 @@
|
|||
# Project Info
|
||||
|
||||
First of all, I want to thank everyone who has submitted issues or shared pull requests for Uptime Kuma.
|
||||
I never thought the GitHub community would be so nice!
|
||||
Because of this, I also never thought that other people would actually read and edit my code.
|
||||
Parts of the code are not very well-structured or commented, sorry about that.
|
||||
First of all, I want to thank everyone who has submitted issues or shared pull
|
||||
requests for Uptime Kuma. I never thought the GitHub community would be so nice!
|
||||
Because of this, I also never thought that other people would actually read and
|
||||
edit my code. Parts of the code are not very well-structured or commented, sorry
|
||||
about that.
|
||||
|
||||
The project was created with `vite.js` and is written in `vue3`.
|
||||
Our backend lives in the `server`-directory and mostly communicates via websockets.
|
||||
Both frontend and backend share the same `package.json`.
|
||||
The project was created with `vite.js` and is written in `vue3`. Our backend
|
||||
lives in the `server`-directory and mostly communicates via websockets. Both
|
||||
frontend and backend share the same `package.json`.
|
||||
|
||||
For production, the frontend is built into the `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
|
||||
For development, we run vite in development mode on another port.
|
||||
For production, the frontend is built into the `dist`-directory and the server
|
||||
(`express.js`) exposes the `dist` directory as the root of the endpoint. For
|
||||
development, we run vite in development mode on another port.
|
||||
|
||||
## Directories
|
||||
|
||||
|
@ -25,193 +27,437 @@ For development, we run vite in development mode on another port.
|
|||
- `src` (Frontend source code)
|
||||
- `test` (unit test)
|
||||
|
||||
## Can I create a pull request for Uptime Kuma?
|
||||
## Can I Create a Pull Request for Uptime Kuma?
|
||||
|
||||
Yes or no, it depends on what you will try to do.
|
||||
Both yours and our maintainers' time is precious, and we don't want to waste either.
|
||||
Whether or not you can create a pull request depends on the nature of your
|
||||
contribution. We value both your time and our maintainers' time, so we want to
|
||||
make sure it's spent efficiently.
|
||||
|
||||
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
|
||||
If you're unsure about any process or step, you're probably not the only one
|
||||
with that question—please feel free to ask. We're happy to help!
|
||||
|
||||
Different guidelines exist for different types of pull requests (PRs):
|
||||
- <details><summary><b>security fixes</b></summary>
|
||||
Different types of pull requests (PRs) may have different guidelines, so be sure
|
||||
to review the appropriate one for your contribution.
|
||||
|
||||
- <details><summary><b>Security Fixes</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
|
||||
Submitting security fixes is something that may put the community at risk.
|
||||
Please read through our [security policy](SECURITY.md) and submit vulnerabilities via an [advisory](https://github.com/louislam/uptime-kuma/security/advisories/new) + [issue](https://github.com/louislam/uptime-kuma/issues/new?assignees=&labels=help&template=security.md) instead.
|
||||
We encourage you to submit how to fix a vulnerability if you know how to, this is not required.
|
||||
Following the security policy allows us to properly test, fix bugs.
|
||||
This review allows us to notice, if there are any changes necessary to unrelated parts like the documentation.
|
||||
Please read through our [security policy](SECURITY.md) and submit
|
||||
vulnerabilities via an [advisory] + [issue] instead. We encourage you to
|
||||
submit how to fix a vulnerability if you know how to, this is not required.
|
||||
Following the security policy allows us to properly test, fix bugs. This
|
||||
review allows us to notice, if there are any changes necessary to unrelated
|
||||
parts like the documentation.
|
||||
[**PLEASE SEE OUR SECURITY POLICY.**](SECURITY.md)
|
||||
|
||||
|
||||
[advisory]: https://github.com/louislam/uptime-kuma/security/advisories/new
|
||||
[issue]:
|
||||
https://github.com/louislam/uptime-kuma/issues/new?template=security_issue.yml
|
||||
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary><b>small, non-breaking bug fixes</b></summary>
|
||||
|
||||
- <details><summary><b>Small, Non-Breaking Bug Fixes</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
|
||||
If you come across a bug and think you can solve, we appreciate your work.
|
||||
Please make sure that you follow these rules:
|
||||
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
|
||||
- test that your code does what you claim it does.
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary><b>translations / internationalisation (i18n)</b></summary>
|
||||
<p>
|
||||
|
||||
We use weblate to localise this project into many languages.
|
||||
If you are unhappy with a translation this is the best start.
|
||||
On how to translate using weblate, please see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
|
||||
|
||||
There are two cases in which a change cannot be done in weblate and requires a PR:
|
||||
- A text may not be currently localisable. In this case, **adding a new language key** via `$t("languageKey")` might be nessesary
|
||||
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
|
||||
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary><b>new notification providers</b></summary>
|
||||
<p>
|
||||
|
||||
To set up a new notification provider these files need to be modified/created:
|
||||
- `server/notification-providers/PROVIDER_NAME.js` is where the heart of the notification provider lives.
|
||||
- Both `monitorJSON` and `heartbeatJSON` can be `null` for some events.
|
||||
If both are `null`, this is a general testing message, but if just `heartbeatJSON` is `null` this is a certificate expiry.
|
||||
- Please wrap the axios call into a
|
||||
```js
|
||||
try {
|
||||
let result = await axios.post(...);
|
||||
if (result.status === ...) ...
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
```
|
||||
- `server/notification.js` is where the backend of the notification provider needs to be registered.
|
||||
*If you have an idea how we can skip this step, we would love to hear about it ^^*
|
||||
- `src/components/NotificationDialog.vue` you need to decide if the provider is a regional or a global one and add it with a name to the respective list
|
||||
- `src/components/notifications/PROVIDER_NAME.vue` is where the frontend of each provider lives.
|
||||
Please make sure that you have:
|
||||
- used `HiddenInput` for secret credentials
|
||||
- included all the necessary helptexts/placeholder/.. to make sure the notification provider is simple to setup for new users.
|
||||
- include all translations (`{{ $t("Translation key") }}`, [`i18n-t keypath="Translation key">`](https://vue-i18n.intlify.dev/guide/advanced/component.html)) in `src/lang/en.json` to enable our translators to translate this
|
||||
- `src/components/notifications/index.js` is where the frontend of the provider needs to be registered.
|
||||
*If you have an idea how we can skip this step, we would love to hear about it ^^*
|
||||
|
||||
Offering notifications is close to the core of what we are as an uptime monitor.
|
||||
Therefore, making sure that they work is also really important.
|
||||
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
|
||||
|
||||
To make sure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
|
||||
- `UP`/`DOWN`
|
||||
- Certificate Expiry via https://expired.badssl.com/
|
||||
- Testing (the test button on the notification provider setup page)
|
||||
|
||||
Using the following way to format this is encouraged:
|
||||
```md
|
||||
| Event | Before | After |
|
||||
------------------
|
||||
| `UP` | paste-image-here | paste-image-here |
|
||||
| `DOWN` | paste-image-here | paste-image-here |
|
||||
| Certificate-expiry | paste-image-here | paste-image-here |
|
||||
| Testing | paste-image-here | paste-image-here |
|
||||
- keep the PR as small as possible, fix only one thing at a time => keeping it
|
||||
reviewable
|
||||
- test that your code does what you claim it does.
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge
|
||||
uncontroversial PRs in this area.</sub>
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
- <details><summary><b>Translations / Internationalisation (i18n)</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
We use weblate to localise this project into many languages. If you are
|
||||
unhappy with a translation this is the best start. On how to translate using
|
||||
weblate, please see
|
||||
[these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
|
||||
|
||||
There are two cases in which a change cannot be done in weblate and requires a
|
||||
PR:
|
||||
|
||||
- A text may not be currently localisable. In this case, **adding a new
|
||||
language key** via `$t("languageKey")` might be nessesary
|
||||
- language keys need to be **added to `en.json`** to be visible in weblate. If
|
||||
this has not happened, a PR is appreciated.
|
||||
- **Adding a new language** requires a new file see
|
||||
[these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge
|
||||
uncontroversial PRs in this area.</sub>
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
- <details><summary><b>New Notification Providers</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
To set up a new notification provider these files need to be modified/created:
|
||||
|
||||
- `server/notification-providers/PROVIDER_NAME.js` is where the heart of the
|
||||
notification provider lives.
|
||||
|
||||
- Both `monitorJSON` and `heartbeatJSON` can be `null` for some events. If
|
||||
|
||||
both are `null`, this is a general testing message, but if just
|
||||
`heartbeatJSON` is `null` this is a certificate expiry.
|
||||
|
||||
- Please wrap the axios call into a
|
||||
|
||||
```js
|
||||
try {
|
||||
let result = await axios.post(...);
|
||||
if (result.status === ...) ...
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
```
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||
- `server/notification.js` is where the backend of the notification provider
|
||||
needs to be registered. _If you have an idea how we can skip this step, we
|
||||
would love to hear about it ^^_
|
||||
|
||||
- `src/components/NotificationDialog.vue` you need to decide if the provider
|
||||
is a regional or a global one and add it with a name to the respective list
|
||||
|
||||
- `src/components/notifications/PROVIDER_NAME.vue` is where the frontend of
|
||||
each provider lives. Please make sure that you have:
|
||||
|
||||
- used `HiddenInput` for secret credentials
|
||||
- included all the necessary helptexts/placeholder/.. to make sure the
|
||||
|
||||
notification provider is simple to setup for new users. - include all
|
||||
translations (`{{ $t("Translation key") }}`,
|
||||
[`i18n-t keypath="Translation key">`](https://vue-i18n.intlify.dev/guide/advanced/component.html))
|
||||
in `src/lang/en.json` to enable our translators to translate this
|
||||
|
||||
- `src/components/notifications/index.js` is where the frontend of the
|
||||
provider needs to be registered. _If you have an idea how we can skip this
|
||||
step, we would love to hear about it ^^_
|
||||
|
||||
Offering notifications is close to the core of what we are as an uptime
|
||||
monitor. Therefore, making sure that they work is also really important.
|
||||
Because testing notification providers is quite time intensive, we mostly
|
||||
offload this onto the person contributing a notification provider.
|
||||
|
||||
To make sure you have tested the notification provider, please include
|
||||
screenshots of the following events in the pull-request description:
|
||||
|
||||
- `UP`/`DOWN`
|
||||
- Certificate Expiry via <https://expired.badssl.com/>
|
||||
- Testing (the test button on the notification provider setup page)
|
||||
|
||||
<br/>
|
||||
|
||||
Using the following way to format this is encouraged:
|
||||
|
||||
```md
|
||||
| Event | Before | After |
|
||||
| ------------------ | --------------------- | -------------------- |
|
||||
| `UP` |  |  |
|
||||
| `DOWN` |  |  |
|
||||
| Certificate-expiry |  |  |
|
||||
| Testing |  |  |
|
||||
```
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge
|
||||
uncontroversial PRs in this area.</sub>
|
||||
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary><b>new monitoring types</b></summary>
|
||||
|
||||
- <details><summary><b>New Monitoring Types</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
To set up a new notification provider these files need to be modified/created:
|
||||
- `server/monitor-types/MONITORING_TYPE.js` is the core of each monitor.
|
||||
the `async check(...)`-function should:
|
||||
- throw an error for each fault that is detected with an actionable error message
|
||||
- in the happy-path, you should set `heartbeat.msg` to a successful message and set `heartbeat.status = UP`
|
||||
- `server/uptime-kuma-server.js` is where the monitoring backend needs to be registered.
|
||||
*If you have an idea how we can skip this step, we would love to hear about it ^^*
|
||||
|
||||
- `server/monitor-types/MONITORING_TYPE.js` is the core of each monitor. the
|
||||
`async check(...)`-function should:
|
||||
|
||||
- throw an error for each fault that is detected with an actionable error
|
||||
|
||||
message - in the happy-path, you should set `heartbeat.msg` to a successful
|
||||
message and set `heartbeat.status = UP`
|
||||
|
||||
- `server/uptime-kuma-server.js` is where the monitoring backend needs to be
|
||||
registered. _If you have an idea how we can skip this step, we would love to
|
||||
hear about it ^^_
|
||||
|
||||
- `src/pages/EditMonitor.vue` is the shared frontend users interact with.
|
||||
Please make sure that you have:
|
||||
- used `HiddenInput` for secret credentials
|
||||
- included all the necessary helptexts/placeholder/.. to make sure the notification provider is simple to setup for new users.
|
||||
- include all translations (`{{ $t("Translation key") }}`, [`i18n-t keypath="Translation key">`](https://vue-i18n.intlify.dev/guide/advanced/component.html)) in `src/lang/en.json` to enable our translators to translate this
|
||||
-
|
||||
Please make sure that you have: - used `HiddenInput` for secret
|
||||
credentials - included all the necessary helptexts/placeholder/.. to make
|
||||
sure the notification provider is simple to setup for new users. - include
|
||||
all translations (`{{ $t("Translation key") }}`,
|
||||
[`i18n-t keypath="Translation key">`](https://vue-i18n.intlify.dev/guide/advanced/component.html))
|
||||
in `src/lang/en.json` to enable our translators to translate this
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge
|
||||
uncontroversial PRs in this area.</sub>
|
||||
|
||||
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
|
||||
<p>
|
||||
|
||||
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
|
||||
This is especially important for a large pull request or when you don't know if it will be merged or not.
|
||||
|
||||
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
The following rules are essential for making your PR mergable:
|
||||
- Merging multiple issues by a huge PR is more difficult to review and causes conflicts with other PRs. Please
|
||||
- <details><summary><b>New Features / Major Changes / Breaking Bugfixes</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
be sure to **create an empty draft pull request or open an issue, so we can
|
||||
have a discussion first**. This is especially important for a large pull
|
||||
request or when you don't know if it will be merged or not.
|
||||
|
||||
<sub>Because of the large impact of this work, only senior maintainers may
|
||||
merge PRs in this area. </sub>
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
- <details><summary><b>Pull Request Guidelines</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
## Steps to Submit a Pull Request
|
||||
|
||||
1. **Fork** the [Uptime-Kuma repository].
|
||||
|
||||
[Uptime-Kuma repository]: https://github.com/louislam/uptime-kuma/
|
||||
|
||||
2. **Clone** your forked repository to your local machine.
|
||||
3. **Create a new branch** for your changes (e.g.,
|
||||
`feature/add-new-notification-provider-signal`).
|
||||
4. **Initiate a discussion before making major changes** by creating an empty
|
||||
commit:
|
||||
|
||||
```sh
|
||||
git commit -m "<YOUR TASK NAME>" --allow-empty
|
||||
```
|
||||
|
||||
5. **Push** your branch to your forked repository.
|
||||
6. **Open a pull request** using this link: [Compare & Pull Request].
|
||||
|
||||
[Compare & Pull Request]: https://github.com/louislam/uptime-kuma/compare/
|
||||
|
||||
7. **Select the correct source and target branches**.
|
||||
8. **Link to related issues** for context.
|
||||
9. **Provide a clear and concise description** explaining the changes and
|
||||
their purpose.
|
||||
|
||||
- **Type of changes**
|
||||
|
||||
- Bugfix (a non-breaking change that resolves an issue)
|
||||
- New feature (a non-breaking change that adds new functionality)
|
||||
- Breaking change (a fix or feature that alters existing functionality in a
|
||||
way that could cause issues)
|
||||
- User Interface (UI) updates
|
||||
- New Documentation (addition of new documentation)
|
||||
- Documentation Update (modification of existing documentation)
|
||||
- Documentation Update Required (the change requires updates to related
|
||||
documentation)
|
||||
- Other (please specify):
|
||||
- Provide additional details here.
|
||||
|
||||
- **Checklist**
|
||||
|
||||
- My code adheres to the style guidelines of this project.
|
||||
- I ran ESLint and other code linters for modified files.
|
||||
- I have reviewed and tested my code.
|
||||
- I have commented my code, especially in hard-to-understand areas (e.g.,
|
||||
using JSDoc for methods).
|
||||
- My changes generate no new warnings.
|
||||
- My code needed automated testing. I have added them (this is an optional
|
||||
task).
|
||||
- Documentation updates are included (if applicable).
|
||||
- I have considered potential security impacts and mitigated risks.
|
||||
- Dependency updates are listed and explained.
|
||||
- I have read and understood the
|
||||
[Pull Request guidelines](#recommended-pull-request-guideline).
|
||||
|
||||
10. **When publishing your PR, set it as a** `Draft pull request` **to allow
|
||||
for review and prevent automatic merging.**
|
||||
11. **Maintainers will assign relevant labels** (e.g., `A:maintenance`,
|
||||
`A:notifications`).
|
||||
12. **Complete the PR checklist**, ensuring that:
|
||||
|
||||
- Documentation is updated if necessary.
|
||||
- Tests are written or updated.
|
||||
- CI/CD checks pass successfully.
|
||||
|
||||
13. **Request feedback** from team members to refine your changes before the
|
||||
final review.
|
||||
|
||||
## When Can You Change the PR Status to "Ready for Review"?
|
||||
|
||||
A PR should remain in **draft status** until all tasks are completed. Only
|
||||
change the status to **Ready for Review** when:
|
||||
|
||||
- You have implemented all planned changes.
|
||||
- You have addressed all feedback.
|
||||
- Your code is fully tested and ready for integration.
|
||||
- You have updated or created the necessary tests.
|
||||
- You have verified that CI/CD checks pass successfully.
|
||||
|
||||
<br />
|
||||
|
||||
A **work-in-progress (WIP) PR** must stay in **draft status** until everything
|
||||
is finalized.
|
||||
|
||||
<sub>Since maintainer time is valuable, junior maintainers may merge
|
||||
uncontroversial PRs.</sub>
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## The following rules are essential for making your PR mergable
|
||||
|
||||
- Merging multiple issues by a huge PR is more difficult to review and causes
|
||||
conflicts with other PRs. Please
|
||||
|
||||
- (if possible) **create one PR for one issue** or
|
||||
- (if not possible) **explain which issues a PR addresses and why this PR should not be broken apart**
|
||||
- Make sure your **PR passes our continuous integration**.
|
||||
PRs will not be merged unless all CI-Checks are green.
|
||||
- **Breaking changes** (unless for a good reason and discussed beforehand) will not get merged / not get merged quickly.
|
||||
Such changes require a major version release.
|
||||
- **Test your code** before submitting a PR.
|
||||
Buggy PRs will not be merged.
|
||||
- (if not possible) **explain which issues a PR addresses and why this PR
|
||||
should not be broken apart**
|
||||
|
||||
- Make sure your **PR passes our continuous integration**. PRs will not be
|
||||
merged unless all CI-Checks are green.
|
||||
- **Breaking changes** (unless for a good reason and discussed beforehand) will
|
||||
not get merged / not get merged quickly. Such changes require a major version
|
||||
release.
|
||||
- **Test your code** before submitting a PR. Buggy PRs will not be merged.
|
||||
- Make sure the **UI/UX is close to Uptime Kuma**.
|
||||
- **Think about the maintainability**:
|
||||
Don't add functionality that is completely **out of scope**.
|
||||
Keep in mind that we need to be able to maintain the functionality.
|
||||
- **Think about the maintainability**: Don't add functionality that is
|
||||
completely **out of scope**. Keep in mind that we need to be able to maintain
|
||||
the functionality.
|
||||
- Don't modify or delete existing logic without a valid reason.
|
||||
- Don't convert existing code into other programming languages for no reason.
|
||||
|
||||
I ([@louislam](https://github.com/louislam)) have the final say.
|
||||
If your pull request does not meet my expectations, I will reject it, no matter how much time you spent on it.
|
||||
Therefore, it is essential to have a discussion beforehand.
|
||||
I ([@louislam](https://github.com/louislam)) have the final say. If your pull
|
||||
request does not meet my expectations, I will reject it, no matter how much time
|
||||
you spent on it. Therefore, it is essential to have a discussion beforehand.
|
||||
|
||||
I will assign your pull request to a [milestone](https://github.com/louislam/uptime-kuma/milestones), if I plan to review and merge it.
|
||||
I will assign your pull request to a [milestone], if I plan to review and merge
|
||||
it.
|
||||
|
||||
Please don't rush or ask for an ETA.
|
||||
We have to understand the pull request, make sure it has no breaking changes and stick to the vision of this project, especially for large pull requests.
|
||||
[milestone]: https://github.com/louislam/uptime-kuma/milestones
|
||||
|
||||
Please don't rush or ask for an ETA. We have to understand the pull request,
|
||||
make sure it has no breaking changes and stick to the vision of this project,
|
||||
especially for large pull requests.
|
||||
|
||||
## I'd like to work on an issue. How do I do that?
|
||||
## I'd Like to Work on an Issue. How Do I Do That?
|
||||
|
||||
We have found that assigning people to issues is management-overhead that we don't need.
|
||||
A short comment that you want to try your hand at this issue is appreciated to save other devs time.
|
||||
If you come across any problem during development, feel free to leave a comment with what you are stuck on.
|
||||
We have found that assigning people to issues is unnecessary management
|
||||
overhead. Instead, a short comment stating that you want to work on an issue is
|
||||
appreciated, as it saves time for other developers. If you encounter any
|
||||
problems during development, feel free to leave a comment describing what you
|
||||
are stuck on.
|
||||
|
||||
### Recommended Pull Request Guideline
|
||||
|
||||
Before diving deep into coding, having a discussion first by creating an empty pull request for discussion is preferred.
|
||||
The rationale behind this is that we can align the direction and scope of the feature to eliminate any conflicts with existing and planned work, and can help by pointing out any potential pitfalls.
|
||||
Before jumping into coding, it's recommended to initiate a discussion by
|
||||
creating an empty pull request. This approach allows us to align on the
|
||||
direction and scope of the feature, ensuring it doesn't conflict with existing
|
||||
or planned work. It also provides an opportunity to identify potential pitfalls
|
||||
early on, helping to avoid issues down the line.
|
||||
|
||||
1. Fork the project
|
||||
2. Clone your fork repo to local
|
||||
3. Create a new branch
|
||||
4. Create an empty commit: `git commit -m "<YOUR TASK NAME>" --allow-empty`
|
||||
5. Push to your fork repo
|
||||
6. Prepare a pull request: https://github.com/louislam/uptime-kuma/compare
|
||||
7. Write a proper description. You can mention @louislam in it, so @louislam will get the notification.
|
||||
8. Create your pull request as a Draft
|
||||
9. Wait for the discussion
|
||||
1. **Fork** the [Uptime-Kuma repository].
|
||||
2. **Clone** your forked repository to your local machine.
|
||||
3. **Create a new branch** for your changes (e.g.,
|
||||
`feature/add-new-notification-provider-signal`).
|
||||
4. **Initiate a discussion before making major changes** by creating an empty
|
||||
commit:
|
||||
|
||||
```sh
|
||||
git commit -m "<YOUR TASK NAME>" --allow-empty
|
||||
```
|
||||
|
||||
5. **Push** your branch to your forked repository.
|
||||
6. **Open a pull request** using this link: [Compare & Pull Request].
|
||||
7. **Select the correct source and target branches**.
|
||||
8. **Link to related issues** for context.
|
||||
9. **Provide a clear and concise description** explaining the changes and their
|
||||
purpose.
|
||||
|
||||
- **Type of changes**
|
||||
|
||||
- Bugfix (a non-breaking change that resolves an issue)
|
||||
- New feature (a non-breaking change that adds new functionality)
|
||||
- Breaking change (a fix or feature that alters existing functionality in a
|
||||
way that could cause issues)
|
||||
- User Interface (UI) updates
|
||||
- New Documentation (addition of new documentation)
|
||||
- Documentation Update (modification of existing documentation)
|
||||
- Documentation Update Required (the change requires updates to related
|
||||
documentation)
|
||||
- Other (please specify):
|
||||
- Provide additional details here.
|
||||
|
||||
- **Checklist**
|
||||
|
||||
- My code adheres to the style guidelines of this project.
|
||||
- I ran ESLint and other code linters for modified files.
|
||||
- I have reviewed and tested my code.
|
||||
- I have commented my code, especially in hard-to-understand areas (e.g.,
|
||||
using JSDoc for methods).
|
||||
- My changes generate no new warnings.
|
||||
- My code needed automated testing. I have added them (this is an optional
|
||||
task).
|
||||
- Documentation updates are included (if applicable).
|
||||
- I have considered potential security impacts and mitigated risks.
|
||||
- Dependency updates are listed and explained.
|
||||
- I have read and understood the
|
||||
[Pull Request guidelines](#recommended-pull-request-guideline).
|
||||
|
||||
10. **When publishing your PR, set it as a** `Draft pull request` **to allow for
|
||||
review and prevent automatic merging.**
|
||||
11. **Maintainers will assign relevant labels** (e.g., `A:maintenance`,
|
||||
`A:notifications`).
|
||||
12. **Complete the PR checklist**, ensuring that:
|
||||
|
||||
- Documentation is updated if necessary.
|
||||
- Tests are written or updated.
|
||||
- CI/CD checks pass successfully.
|
||||
|
||||
13. **Request feedback** from team members to refine your changes before the
|
||||
final review.
|
||||
|
||||
### When Can You Change the PR Status to "Ready for Review"?
|
||||
|
||||
A PR should remain in **draft status** until all tasks are completed. Only
|
||||
change the status to **Ready for Review** when:
|
||||
|
||||
- You have implemented all planned changes.
|
||||
- You have addressed all feedback.
|
||||
- Your code is fully tested and ready for integration.
|
||||
- You have updated or created the necessary tests.
|
||||
- You have verified that CI/CD checks pass successfully.
|
||||
|
||||
A **work-in-progress (WIP) PR** must stay in **draft status** until everything
|
||||
is finalized.
|
||||
|
||||
## Project Styles
|
||||
|
||||
I personally do not like something that requires a lot of configuration before you can finally start the app.
|
||||
The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
|
||||
I personally do not like something that requires a lot of configuration before
|
||||
you can finally start the app. The goal is to make the Uptime Kuma installation
|
||||
as easy as installing a mobile app.
|
||||
|
||||
- Easy to install for non-Docker users
|
||||
|
||||
- no native build dependency is needed (for `x86_64`/`armv7`/`arm64`)
|
||||
- no extra configuration and
|
||||
- no extra effort required to get it running
|
||||
|
||||
- Single container for Docker users
|
||||
|
||||
- no complex docker-compose file
|
||||
- mapping the volume and exposing the port should be the only requirements
|
||||
- Settings should be configurable in the frontend. Environment variables are discouraged, unless it is related to startup such as `DATA_DIR`
|
||||
|
||||
- Settings should be configurable in the frontend. Environment variables are
|
||||
discouraged, unless it is related to startup such as `DATA_DIR`
|
||||
- Easy to use
|
||||
- The web UI styling should be consistent and nice
|
||||
|
||||
|
@ -233,13 +479,18 @@ The goal is to make the Uptime Kuma installation as easy as installing a mobile
|
|||
- [`Node.js`](https://nodejs.org/) >= 18
|
||||
- [`npm`](https://www.npmjs.com/) >= 9.3
|
||||
- [`git`](https://git-scm.com/)
|
||||
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
|
||||
- A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/))
|
||||
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using
|
||||
[`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
|
||||
- A SQLite GUI tool (f.ex.
|
||||
[`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or
|
||||
[`DBeaver Community`](https://dbeaver.io/download/))
|
||||
|
||||
## Git Branches
|
||||
|
||||
- `master`: 2.X.X development. If you want to add a new feature, your pull request should base on this.
|
||||
- `1.23.X`: 1.23.X development. If you want to fix a bug for v1 and v2, your pull request should base on this.
|
||||
- `master`: 2.X.X development. If you want to add a new feature, your pull
|
||||
request should base on this.
|
||||
- `1.23.X`: 1.23.X development. If you want to fix a bug for v1 and v2, your
|
||||
pull request should base on this.
|
||||
- All other branches are unused, outdated or for dev.
|
||||
|
||||
## Install Dependencies for Development
|
||||
|
@ -260,7 +511,8 @@ Port `3000` and port `3001` will be used.
|
|||
npm run dev
|
||||
```
|
||||
|
||||
But sometimes you may want to restart the server without restarting the frontend. In that case, you can run these commands in two terminals:
|
||||
But sometimes you may want to restart the server without restarting the
|
||||
frontend. In that case, you can run these commands in two terminals:
|
||||
|
||||
```bash
|
||||
npm run start-frontend-dev
|
||||
|
@ -271,9 +523,9 @@ npm run start-server-dev
|
|||
|
||||
It binds to `0.0.0.0:3001` by default.
|
||||
|
||||
The backend is an `express.js` server with `socket.io` integrated.
|
||||
It uses `socket.io` to communicate with clients, and most server logic is encapsulated in the `socket.io` handlers.
|
||||
`express.js` is also used to serve:
|
||||
The backend is an `express.js` server with `socket.io` integrated. It uses
|
||||
`socket.io` to communicate with clients, and most server logic is encapsulated
|
||||
in the `socket.io` handlers. `express.js` is also used to serve:
|
||||
|
||||
- as an entry point for redirecting to a status page or the dashboard
|
||||
- the frontend built files (`index.html`, `*.js`, `*.css`, etc.)
|
||||
|
@ -289,11 +541,13 @@ It uses `socket.io` to communicate with clients, and most server logic is encaps
|
|||
- `routers/` (Express Routers)
|
||||
- `socket-handler/` (Socket.io Handlers)
|
||||
- `server.js` (Server entry point)
|
||||
- `uptime-kuma-server.js` (UptimeKumaServer class, main logic should be here, but some still in `server.js`)
|
||||
- `uptime-kuma-server.js` (UptimeKumaServer class, main logic should be here,
|
||||
but some still in `server.js`)
|
||||
|
||||
## Frontend Dev Server
|
||||
|
||||
It binds to `0.0.0.0:3000` by default. The frontend dev server is used for development only.
|
||||
It binds to `0.0.0.0:3000` by default. The frontend dev server is used for
|
||||
development only.
|
||||
|
||||
For production, it is not used. It will be compiled to `dist` directory instead.
|
||||
|
||||
|
@ -307,17 +561,19 @@ npm run build
|
|||
|
||||
### Frontend Details
|
||||
|
||||
Uptime Kuma Frontend is a single page application (SPA). Most paths are handled by Vue Router.
|
||||
Uptime Kuma Frontend is a single page application (SPA). Most paths are handled
|
||||
by Vue Router.
|
||||
|
||||
The router is in `src/router.js`
|
||||
|
||||
As you can see, most data in the frontend is stored at the root level, even though you changed the current router to any other pages.
|
||||
As you can see, most data in the frontend is stored at the root level, even
|
||||
though you changed the current router to any other pages.
|
||||
|
||||
The data and socket logic are in `src/mixins/socket.js`.
|
||||
|
||||
## Database Migration
|
||||
|
||||
See: https://github.com/louislam/uptime-kuma/tree/master/db/knex_migrations
|
||||
See: <https://github.com/louislam/uptime-kuma/tree/master/db/knex_migrations>
|
||||
|
||||
## Unit Test
|
||||
|
||||
|
@ -328,11 +584,12 @@ npm test
|
|||
|
||||
## Dependencies
|
||||
|
||||
Both frontend and backend share the same `package.json`.
|
||||
However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into `dist` files. So:
|
||||
Both frontend and backend share the same `package.json`. However, the frontend
|
||||
dependencies are eventually not used in the production environment, because it
|
||||
is usually also baked into `dist` files. So:
|
||||
|
||||
- Frontend dependencies = "devDependencies"
|
||||
- Examples: `vue`, `chart.js`
|
||||
- Examples: - `vue`, `chart.js`
|
||||
- Backend dependencies = "dependencies"
|
||||
- Examples: `socket.io`, `sqlite3`
|
||||
- Development dependencies = "devDependencies"
|
||||
|
@ -340,31 +597,41 @@ However, the frontend dependencies are eventually not used in the production env
|
|||
|
||||
### Update Dependencies
|
||||
|
||||
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update the patch release version only.
|
||||
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely,
|
||||
from now on, it should update the patch release version only.
|
||||
|
||||
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
|
||||
|
||||
If for security / bug / other reasons, a library must be updated, breaking changes need to be checked by the person proposing the change.
|
||||
If for security / bug / other reasons, a library must be updated, breaking
|
||||
changes need to be checked by the person proposing the change.
|
||||
|
||||
## Translations
|
||||
|
||||
Please add **all** the strings which are translatable to `src/lang/en.json` (if translation keys are omitted, they can not be translated.)
|
||||
Please add **all** the strings which are translatable to `src/lang/en.json` (if
|
||||
translation keys are omitted, they can not be translated.)
|
||||
|
||||
**Don't include any other languages in your initial pull request** (even if this is your mother tongue), to avoid merge-conflicts between weblate and `master`.
|
||||
The translations can then (after merging a PR into `master`) be translated by awesome people donating their language skills.
|
||||
**Don't include any other languages in your initial pull request** (even if this
|
||||
is your mother tongue), to avoid merge-conflicts between weblate and `master`.
|
||||
The translations can then (after merging a PR into `master`) be translated by
|
||||
awesome people donating their language skills.
|
||||
|
||||
If you want to help by translating Uptime Kuma into your language, please visit the [instructions on how to translate using weblate](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
|
||||
If you want to help by translating Uptime Kuma into your language, please visit
|
||||
the [instructions on how to translate using weblate].
|
||||
|
||||
[instructions on how to translate using weblate]:
|
||||
https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md
|
||||
|
||||
## Spelling & Grammar
|
||||
|
||||
Feel free to correct the grammar in the documentation or code.
|
||||
My mother language is not English and my grammar is not that great.
|
||||
Feel free to correct the grammar in the documentation or code. My mother
|
||||
language is not English and my grammar is not that great.
|
||||
|
||||
## Wiki
|
||||
|
||||
Since there is no way to make a pull request to the wiki, I have set up another repo to do that.
|
||||
Since there is no way to make a pull request to the wiki, I have set up another
|
||||
repo to do that.
|
||||
|
||||
https://github.com/louislam/uptime-kuma-wiki
|
||||
<https://github.com/louislam/uptime-kuma-wiki>
|
||||
|
||||
## Docker
|
||||
|
||||
|
@ -405,7 +672,7 @@ https://github.com/louislam/uptime-kuma-wiki
|
|||
## Maintainer
|
||||
|
||||
Check the latest issues and pull requests:
|
||||
https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
|
||||
<https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc>
|
||||
|
||||
### What is a maintainer and what are their roles?
|
||||
|
||||
|
@ -413,7 +680,7 @@ This project has multiple maintainers who specialise in different areas.
|
|||
Currently, there are 3 maintainers:
|
||||
|
||||
| Person | Role | Main Area |
|
||||
|-------------------|-------------------|------------------|
|
||||
| ----------------- | ----------------- | ---------------- |
|
||||
| `@louislam` | senior maintainer | major features |
|
||||
| `@chakflying` | junior maintainer | fixing bugs |
|
||||
| `@commanderstorm` | junior maintainer | issue-management |
|
||||
|
@ -421,39 +688,49 @@ Currently, there are 3 maintainers:
|
|||
### Procedures
|
||||
|
||||
We have a few procedures we follow. These are documented here:
|
||||
- <details><summary>Set up a Docker Builder</summary>
|
||||
|
||||
- <details><summary><b>Set up a Docker Builder</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
- amd64, armv7 using local.
|
||||
- arm64 using remote arm64 cpu, as the emulator is too slow and can no longer pass the `npm ci` command.
|
||||
1. Add the public key to the remote server.
|
||||
2. Add the remote context. The remote machine must be arm64 and installed Docker CE.
|
||||
```
|
||||
docker context create oracle-arm64-jp --docker "host=ssh://root@100.107.174.88"
|
||||
```
|
||||
3. Create a new builder.
|
||||
```
|
||||
docker buildx create --name kuma-builder --platform linux/amd64,linux/arm/v7
|
||||
docker buildx use kuma-builder
|
||||
docker buildx inspect --bootstrap
|
||||
```
|
||||
4. Append the remote context to the builder.
|
||||
```
|
||||
docker buildx create --append --name kuma-builder --platform linux/arm64 oracle-arm64-jp
|
||||
```
|
||||
5. Verify the builder and check if the builder is using `kuma-builder`.
|
||||
```
|
||||
docker buildx inspect kuma-builder
|
||||
docker buildx ls
|
||||
```
|
||||
- arm64 using remote arm64 cpu, as the emulator is too slow and can no longer
|
||||
pass the `npm ci` command.
|
||||
|
||||
1. Add the public key to the remote server.
|
||||
2. Add the remote context. The remote machine must be arm64 and installed
|
||||
Docker CE.
|
||||
|
||||
```bash
|
||||
docker context create oracle-arm64-jp --docker "host=ssh://root@100.107.174.88"
|
||||
```
|
||||
|
||||
3. Create a new builder.
|
||||
|
||||
```bash
|
||||
docker buildx create --name kuma-builder --platform linux/amd64,linux/arm/v7
|
||||
docker buildx use kuma-builder
|
||||
docker buildx inspect --bootstrap
|
||||
```
|
||||
|
||||
4. Append the remote context to the builder.
|
||||
|
||||
```bash
|
||||
docker buildx create --append --name kuma-builder --platform linux/arm64 oracle-arm64-jp
|
||||
```
|
||||
|
||||
5. Verify the builder and check if the builder is using `kuma-builder`.
|
||||
`docker buildx inspect kuma-builder docker buildx ls`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary>Release</summary>
|
||||
|
||||
- <details><summary><b>Release</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
1. Draft a release note
|
||||
2. Make sure the repo is cleared
|
||||
3. If the healthcheck is updated, remember to re-compile it: `npm run build-docker-builder-go`
|
||||
3. If the healthcheck is updated, remember to re-compile it:
|
||||
`npm run build-docker-builder-go`
|
||||
4. `npm run release-final` with env vars: `VERSION` and `GITHUB_TOKEN`
|
||||
5. Wait until the `Press any key to continue`
|
||||
6. `git push`
|
||||
|
@ -463,13 +740,16 @@ We have a few procedures we follow. These are documented here:
|
|||
|
||||
These Items need to be checked:
|
||||
|
||||
- [ ] Check all tags is fine on https://hub.docker.com/r/louislam/uptime-kuma/tags
|
||||
- [ ] Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7)
|
||||
- [ ] Check all tags is fine on
|
||||
<https://hub.docker.com/r/louislam/uptime-kuma/tags>
|
||||
- [ ] Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 /
|
||||
armv7)
|
||||
- [ ] Try clean installation with Node.js
|
||||
|
||||
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary>Release Beta</summary>
|
||||
|
||||
- <details><summary><b>Release Beta</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
1. Draft a release note, check `This is a pre-release`
|
||||
|
@ -478,35 +758,37 @@ We have a few procedures we follow. These are documented here:
|
|||
4. Wait until the `Press any key to continue`
|
||||
5. Publish the release note as `1.X.X-beta.X`
|
||||
6. Press any key to continue
|
||||
|
||||
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary>Release Wiki</summary>
|
||||
|
||||
- <details><summary><b>Release Wiki</b> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
**Setup Repo**
|
||||
|
||||
|
||||
```bash
|
||||
git clone https://github.com/louislam/uptime-kuma-wiki.git
|
||||
cd uptime-kuma-wiki
|
||||
git remote add production https://github.com/louislam/uptime-kuma.wiki.git
|
||||
```
|
||||
|
||||
|
||||
**Push to Production Wiki**
|
||||
|
||||
|
||||
```bash
|
||||
git pull
|
||||
git push production master
|
||||
```
|
||||
|
||||
|
||||
</p>
|
||||
</details>
|
||||
- <details><summary>Change the base of a pull request such as <code>master</code> to <code>1.23.X</code></summary>
|
||||
|
||||
- <details><summary>Change the base of a pull request such as <code>master</code> to <code>1.23.X</code> (click to expand)</summary>
|
||||
<p>
|
||||
|
||||
|
||||
```bash
|
||||
git rebase --onto <new parent> <old parent>
|
||||
```
|
||||
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
|
36
SECURITY.md
36
SECURITY.md
|
@ -2,29 +2,37 @@
|
|||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
1. Please report security issues to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
||||
2. Please also create an empty security issue to alert me, as GitHub Advisories do not send a notification, I probably will miss it without this. https://github.com/louislam/uptime-kuma/issues/new?assignees=&labels=help&template=security.md
|
||||
1. Please report security issues to
|
||||
<https://github.com/louislam/uptime-kuma/security/advisories/new>.
|
||||
2. Please also create an empty security issue to alert me, as GitHub Advisories
|
||||
do not send a notification, I probably will miss it without this.
|
||||
<https://github.com/louislam/uptime-kuma/issues/new?assignees=&labels=help&template=security.md>
|
||||
|
||||
Do not use the public issue tracker or discuss it in public as it will cause more damage.
|
||||
Do not use the public issue tracker or discuss it in public as it will cause
|
||||
more damage.
|
||||
|
||||
## Do you accept other 3rd-party bug bounty platforms?
|
||||
|
||||
At this moment, I DO NOT accept other bug bounty platforms, because I am not familiar with these platforms and someone has tried to send a phishing link to me by doing this already. To minimize my own risk, please report through GitHub Advisories only. I will ignore all 3rd-party bug bounty platforms emails.
|
||||
At this moment, I DO NOT accept other bug bounty platforms, because I am not
|
||||
familiar with these platforms and someone has tried to send a phishing link to
|
||||
me by doing this already. To minimize my own risk, please report through GitHub
|
||||
Advisories only. I will ignore all 3rd-party bug bounty platforms emails.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
### Uptime Kuma Versions
|
||||
|
||||
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` versions are upgradable to the latest version.
|
||||
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X`
|
||||
versions are upgradable to the latest version.
|
||||
|
||||
### Upgradable Docker Tags
|
||||
|
||||
| Tag | Supported |
|
||||
|-|-|
|
||||
| 1 | :white_check_mark: |
|
||||
| 1-debian | :white_check_mark: |
|
||||
| latest | :white_check_mark: |
|
||||
| debian | :white_check_mark: |
|
||||
| 1-alpine | ⚠️ Deprecated |
|
||||
| alpine | ⚠️ Deprecated |
|
||||
| All other tags | ❌ |
|
||||
| Tag | Supported |
|
||||
| -------------- | ------------------ |
|
||||
| 1 | :white_check_mark: |
|
||||
| 1-debian | :white_check_mark: |
|
||||
| latest | :white_check_mark: |
|
||||
| debian | :white_check_mark: |
|
||||
| 1-alpine | ⚠️ Deprecated |
|
||||
| alpine | ⚠️ Deprecated |
|
||||
| All other tags | ❌ |
|
||||
|
|
12
db/knex_migrations/2025-01-01-0000-add-smtp.js
Normal file
12
db/knex_migrations/2025-01-01-0000-add-smtp.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.string("smtp_security").defaultTo(null);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor", function (table) {
|
||||
table.dropColumn("smtp_security");
|
||||
});
|
||||
};
|
24
db/knex_migrations/2025-03-04-0000-ping-advanced-options.js
Normal file
24
db/knex_migrations/2025-03-04-0000-ping-advanced-options.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* SQL:
|
||||
ALTER TABLE monitor ADD ping_count INTEGER default 1 not null;
|
||||
ALTER TABLE monitor ADD ping_numeric BOOLEAN default true not null;
|
||||
ALTER TABLE monitor ADD ping_per_request_timeout INTEGER default 2 not null;
|
||||
*/
|
||||
exports.up = function (knex) {
|
||||
// Add new columns to table monitor
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.integer("ping_count").defaultTo(1).notNullable();
|
||||
table.boolean("ping_numeric").defaultTo(true).notNullable();
|
||||
table.integer("ping_per_request_timeout").defaultTo(2).notNullable();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.dropColumn("ping_count");
|
||||
table.dropColumn("ping_numeric");
|
||||
table.dropColumn("ping_per_request_timeout");
|
||||
});
|
||||
};
|
13
db/knex_migrations/2025-05-09-0000-add-custom-url.js
Normal file
13
db/knex_migrations/2025-05-09-0000-add-custom-url.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Add column custom_url to monitor_group table
|
||||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor_group", function (table) {
|
||||
table.text("custom_url", "text");
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor_group", function (table) {
|
||||
table.dropColumn("custom_url");
|
||||
});
|
||||
};
|
13
db/knex_migrations/2025-06-03-0000-add-ip-family.js
Normal file
13
db/knex_migrations/2025-06-03-0000-add-ip-family.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.boolean("ip_family").defaultTo(null);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.dropColumn("ip_family");
|
||||
});
|
||||
};
|
12
db/knex_migrations/2025-06-11-0000-add-manual-monitor.js
Normal file
12
db/knex_migrations/2025-06-11-0000-add-manual-monitor.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.string("manual_status").defaultTo(null);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor", function (table) {
|
||||
table.dropColumn("manual_status");
|
||||
});
|
||||
};
|
13
db/knex_migrations/2025-06-15-0001-manual-monitor-fix.js
Normal file
13
db/knex_migrations/2025-06-15-0001-manual-monitor-fix.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Fix: Change manual_status column type to smallint
|
||||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.smallint("manual_status").alter();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor", function (table) {
|
||||
table.string("manual_status").alter();
|
||||
});
|
||||
};
|
|
@ -3,6 +3,7 @@ console.log("== Uptime Kuma Reset Password Tool ==");
|
|||
const Database = require("../server/database");
|
||||
const { R } = require("redbean-node");
|
||||
const readline = require("readline");
|
||||
const { passwordStrength } = require("check-password-strength");
|
||||
const { initJWTSecret } = require("../server/util-server");
|
||||
const User = require("../server/model/user");
|
||||
const { io } = require("socket.io-client");
|
||||
|
@ -42,8 +43,15 @@ const main = async () => {
|
|||
console.log("Using password from argument");
|
||||
console.warn("\x1b[31m%s\x1b[0m", "Warning: the password might be stored, in plain text, in your shell's history");
|
||||
password = confirmPassword = args["new-password"] + "";
|
||||
if (passwordStrength(password).value === "Too weak") {
|
||||
throw new Error("Password is too weak, please use a stronger password.");
|
||||
}
|
||||
} else {
|
||||
password = await question("New Password: ");
|
||||
if (passwordStrength(password).value === "Too weak") {
|
||||
console.log("Password is too weak, please try again.");
|
||||
continue;
|
||||
}
|
||||
confirmPassword = await question("Confirm New Password: ");
|
||||
}
|
||||
|
||||
|
|
2126
package-lock.json
generated
2126
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "2.0.0-beta.1",
|
||||
"version": "2.0.0-beta.3",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -32,7 +32,7 @@
|
|||
"test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
|
||||
"playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
|
||||
"playwright-show-report": "playwright show-report ./private/playwright-report",
|
||||
"tsc": "tsc",
|
||||
"tsc": "tsc --project ./tsconfig-backend.json",
|
||||
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
||||
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2 --target base2 . --push",
|
||||
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2-slim --target base2-slim . --push",
|
||||
|
@ -72,7 +72,7 @@
|
|||
"@louislam/sqlite3": "15.1.6",
|
||||
"@vvo/tzdb": "^6.125.0",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.29.0",
|
||||
"axios": "~0.30.0",
|
||||
"badge-maker": "~3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"chardet": "~1.4.0",
|
||||
|
@ -82,6 +82,7 @@
|
|||
"command-exists": "~1.2.9",
|
||||
"compare-versions": "~3.6.0",
|
||||
"compression": "~1.7.4",
|
||||
"country-flag-emoji-polyfill": "^0.1.8",
|
||||
"croner": "~8.1.0",
|
||||
"dayjs": "~1.11.5",
|
||||
"dev-null": "^0.1.1",
|
||||
|
|
|
@ -26,7 +26,7 @@ exports.login = async function (username, password) {
|
|||
// Upgrade the hash to bcrypt
|
||||
if (passwordHash.needRehash(user.password)) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(password),
|
||||
await passwordHash.generate(password),
|
||||
user.id,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const fs = require("fs");
|
||||
const fsAsync = fs.promises;
|
||||
const { R } = require("redbean-node");
|
||||
const { setSetting, setting } = require("./util-server");
|
||||
const { log, sleep } = require("../src/util");
|
||||
|
@ -707,12 +708,12 @@ class Database {
|
|||
|
||||
/**
|
||||
* Get the size of the database (SQLite only)
|
||||
* @returns {number} Size of database
|
||||
* @returns {Promise<number>} Size of database
|
||||
*/
|
||||
static getSize() {
|
||||
static async getSize() {
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
log.debug("db", "Database.getSize()");
|
||||
let stats = fs.statSync(Database.sqlitePath);
|
||||
let stats = await fsAsync.stat(Database.sqlitePath);
|
||||
log.debug("db", stats);
|
||||
return stats.size;
|
||||
}
|
||||
|
@ -736,7 +737,7 @@ class Database {
|
|||
if (Database.dbConfig.type === "sqlite") {
|
||||
return "DATETIME('now', ? || ' hours')";
|
||||
} else {
|
||||
return "DATE_ADD(NOW(), INTERVAL ? HOUR)";
|
||||
return "DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? HOUR)";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class Group extends BeanModel {
|
|||
*/
|
||||
async getMonitorList() {
|
||||
return R.convertToBeans("monitor", await R.getAll(`
|
||||
SELECT monitor.*, monitor_group.send_url FROM monitor, monitor_group
|
||||
SELECT monitor.*, monitor_group.send_url, monitor_group.custom_url FROM monitor, monitor_group
|
||||
WHERE monitor.id = monitor_group.monitor_id
|
||||
AND group_id = ?
|
||||
ORDER BY monitor_group.weight
|
||||
|
|
|
@ -158,12 +158,22 @@ class Maintenance extends BeanModel {
|
|||
bean.active = obj.active;
|
||||
|
||||
if (obj.dateRange[0]) {
|
||||
const parsedDate = new Date(obj.dateRange[0]);
|
||||
if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
|
||||
throw new Error("Invalid start date");
|
||||
}
|
||||
|
||||
bean.start_date = obj.dateRange[0];
|
||||
} else {
|
||||
bean.start_date = null;
|
||||
}
|
||||
|
||||
if (obj.dateRange[1]) {
|
||||
const parsedDate = new Date(obj.dateRange[1]);
|
||||
if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
|
||||
throw new Error("Invalid end date");
|
||||
}
|
||||
|
||||
bean.end_date = obj.dateRange[1];
|
||||
} else {
|
||||
bean.end_date = null;
|
||||
|
@ -233,7 +243,7 @@ class Maintenance extends BeanModel {
|
|||
try {
|
||||
this.beanMeta.status = "scheduled";
|
||||
|
||||
let startEvent = (customDuration = 0) => {
|
||||
let startEvent = async (customDuration = 0) => {
|
||||
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||
|
||||
this.beanMeta.status = "under-maintenance";
|
||||
|
|
|
@ -2,7 +2,11 @@ const dayjs = require("dayjs");
|
|||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
||||
SQL_DATETIME_FORMAT, evaluateJsonQuery,
|
||||
PING_PACKET_SIZE_MIN, PING_PACKET_SIZE_MAX, PING_PACKET_SIZE_DEFAULT,
|
||||
PING_GLOBAL_TIMEOUT_MIN, PING_GLOBAL_TIMEOUT_MAX, PING_GLOBAL_TIMEOUT_DEFAULT,
|
||||
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
|
||||
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||
} = require("../../src/util");
|
||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
|
@ -53,7 +57,7 @@ class Monitor extends BeanModel {
|
|||
};
|
||||
|
||||
if (this.sendUrl) {
|
||||
obj.url = this.url;
|
||||
obj.url = this.customUrl ?? this.url;
|
||||
}
|
||||
|
||||
if (showTags) {
|
||||
|
@ -153,8 +157,15 @@ class Monitor extends BeanModel {
|
|||
snmpOid: this.snmpOid,
|
||||
jsonPathOperator: this.jsonPathOperator,
|
||||
snmpVersion: this.snmpVersion,
|
||||
smtpSecurity: this.smtpSecurity,
|
||||
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
|
||||
conditions: JSON.parse(this.conditions),
|
||||
ipFamily: this.ipFamily,
|
||||
|
||||
// ping advanced options
|
||||
ping_numeric: this.isPingNumeric(),
|
||||
ping_count: this.ping_count,
|
||||
ping_per_request_timeout: this.ping_per_request_timeout,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
|
@ -247,6 +258,14 @@ class Monitor extends BeanModel {
|
|||
return Boolean(this.expiryNotification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ping should use numeric output only
|
||||
* @returns {boolean} True if IP addresses will be output instead of symbolic hostnames
|
||||
*/
|
||||
isPingNumeric() {
|
||||
return Boolean(this.ping_numeric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean} Should TLS errors be ignored?
|
||||
|
@ -380,39 +399,6 @@ class Monitor extends BeanModel {
|
|||
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||
bean.msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else if (this.type === "group") {
|
||||
const children = await Monitor.getChildren(this.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
bean.status = UP;
|
||||
bean.msg = "All children up and running";
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
continue;
|
||||
}
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
// lastBeat.status could be null
|
||||
if (!lastBeat) {
|
||||
bean.status = PENDING;
|
||||
} else if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
bean.status = lastBeat.status;
|
||||
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||
bean.status = lastBeat.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (bean.status !== UP) {
|
||||
bean.msg = "Child inaccessible";
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
bean.status = PENDING;
|
||||
bean.msg = "Group empty";
|
||||
}
|
||||
|
||||
} else if (this.type === "http" || this.type === "keyword" || this.type === "json-query") {
|
||||
// Do not do any queries/high loading things before the "bean.ping"
|
||||
let startTime = dayjs().valueOf();
|
||||
|
@ -441,10 +427,26 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
let agentFamily = undefined;
|
||||
if (this.ipFamily === "ipv4") {
|
||||
agentFamily = 4;
|
||||
}
|
||||
if (this.ipFamily === "ipv6") {
|
||||
agentFamily = 6;
|
||||
}
|
||||
|
||||
const httpsAgentOptions = {
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||
autoSelectFamily: true,
|
||||
...(agentFamily ? { family: agentFamily } : {})
|
||||
};
|
||||
|
||||
const httpAgentOptions = {
|
||||
maxCachedSessions: 0,
|
||||
autoSelectFamily: true,
|
||||
...(agentFamily ? { family: agentFamily } : {})
|
||||
};
|
||||
|
||||
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
||||
|
@ -506,6 +508,7 @@ class Monitor extends BeanModel {
|
|||
if (proxy && proxy.active) {
|
||||
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, {
|
||||
httpsAgentOptions: httpsAgentOptions,
|
||||
httpAgentOptions: httpAgentOptions,
|
||||
});
|
||||
|
||||
options.proxy = false;
|
||||
|
@ -514,6 +517,10 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
if (!options.httpAgent) {
|
||||
options.httpAgent = new http.Agent(httpAgentOptions);
|
||||
}
|
||||
|
||||
if (!options.httpsAgent) {
|
||||
let jar = new CookieJar();
|
||||
let httpsCookieAgentOptions = {
|
||||
|
@ -617,7 +624,7 @@ class Monitor extends BeanModel {
|
|||
bean.status = UP;
|
||||
|
||||
} else if (this.type === "ping") {
|
||||
bean.ping = await ping(this.hostname, this.packetSize);
|
||||
bean.ping = await ping(this.hostname, this.ping_count, "", this.ping_numeric, this.packetSize, this.timeout, this.ping_per_request_timeout);
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
} else if (this.type === "push") { // Type: Push
|
||||
|
@ -689,7 +696,7 @@ class Monitor extends BeanModel {
|
|||
bean.msg = res.data.response.servers[0].name;
|
||||
|
||||
try {
|
||||
bean.ping = await ping(this.hostname, this.packetSize);
|
||||
bean.ping = await ping(this.hostname, PING_COUNT_DEFAULT, "", true, this.packetSize, PING_GLOBAL_TIMEOUT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT);
|
||||
} catch (_) { }
|
||||
} else {
|
||||
throw new Error("Server not found on Steam");
|
||||
|
@ -1327,7 +1334,8 @@ class Monitor extends BeanModel {
|
|||
try {
|
||||
const heartbeatJSON = bean.toJSON();
|
||||
const monitorData = [{ id: monitor.id,
|
||||
active: monitor.active
|
||||
active: monitor.active,
|
||||
name: monitor.name
|
||||
}];
|
||||
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||
|
@ -1500,6 +1508,31 @@ class Monitor extends BeanModel {
|
|||
if (this.interval < MIN_INTERVAL_SECOND) {
|
||||
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
||||
}
|
||||
|
||||
if (this.type === "ping") {
|
||||
// ping parameters validation
|
||||
if (this.packetSize && (this.packetSize < PING_PACKET_SIZE_MIN || this.packetSize > PING_PACKET_SIZE_MAX)) {
|
||||
throw new Error(`Packet size must be between ${PING_PACKET_SIZE_MIN} and ${PING_PACKET_SIZE_MAX} (default: ${PING_PACKET_SIZE_DEFAULT})`);
|
||||
}
|
||||
|
||||
if (this.ping_per_request_timeout && (this.ping_per_request_timeout < PING_PER_REQUEST_TIMEOUT_MIN || this.ping_per_request_timeout > PING_PER_REQUEST_TIMEOUT_MAX)) {
|
||||
throw new Error(`Per-ping timeout must be between ${PING_PER_REQUEST_TIMEOUT_MIN} and ${PING_PER_REQUEST_TIMEOUT_MAX} seconds (default: ${PING_PER_REQUEST_TIMEOUT_DEFAULT})`);
|
||||
}
|
||||
|
||||
if (this.ping_count && (this.ping_count < PING_COUNT_MIN || this.ping_count > PING_COUNT_MAX)) {
|
||||
throw new Error(`Echo requests count must be between ${PING_COUNT_MIN} and ${PING_COUNT_MAX} (default: ${PING_COUNT_DEFAULT})`);
|
||||
}
|
||||
|
||||
if (this.timeout) {
|
||||
const pingGlobalTimeout = Math.round(Number(this.timeout));
|
||||
|
||||
if (pingGlobalTimeout < this.ping_per_request_timeout || pingGlobalTimeout < PING_GLOBAL_TIMEOUT_MIN || pingGlobalTimeout > PING_GLOBAL_TIMEOUT_MAX) {
|
||||
throw new Error(`Timeout must be between ${PING_GLOBAL_TIMEOUT_MIN} and ${PING_GLOBAL_TIMEOUT_MAX} seconds (default: ${PING_GLOBAL_TIMEOUT_DEFAULT})`);
|
||||
}
|
||||
|
||||
this.timeout = pingGlobalTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1625,7 +1658,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Gets all Children of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>} Children
|
||||
* @returns {Promise<LooseObject<any>[]>} Children
|
||||
*/
|
||||
static async getChildren(monitorID) {
|
||||
return await R.getAll(`
|
||||
|
|
|
@ -120,8 +120,8 @@ class StatusPage extends BeanModel {
|
|||
|
||||
const head = $("head");
|
||||
|
||||
if (statusPage.googleAnalyticsTagId) {
|
||||
let escapedGoogleAnalyticsScript = googleAnalytics.getGoogleAnalyticsScript(statusPage.googleAnalyticsTagId);
|
||||
if (statusPage.google_analytics_tag_id) {
|
||||
let escapedGoogleAnalyticsScript = googleAnalytics.getGoogleAnalyticsScript(statusPage.google_analytics_tag_id);
|
||||
head.append($(escapedGoogleAnalyticsScript));
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class User extends BeanModel {
|
|||
*/
|
||||
static async resetPassword(userID, newPassword) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(newPassword),
|
||||
await passwordHash.generate(newPassword),
|
||||
userID
|
||||
]);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class User extends BeanModel {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resetPassword(newPassword) {
|
||||
const hashedPassword = passwordHash.generate(newPassword);
|
||||
const hashedPassword = await passwordHash.generate(newPassword);
|
||||
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
hashedPassword,
|
||||
|
|
|
@ -89,6 +89,9 @@ function NtlmClient(credentials, AxiosConfig) {
|
|||
switch (_b.label) {
|
||||
case 0:
|
||||
error = err.response;
|
||||
// The header may look like this: `Negotiate, NTLM, Basic realm="itsahiddenrealm.example.net"`Add commentMore actions
|
||||
// so extract the 'NTLM' part first
|
||||
const ntlmheader = error.headers['www-authenticate'].split(',').find(_ => _.match(/ *NTLM/))?.trim() || '';
|
||||
if (!(error && error.status === 401
|
||||
&& error.headers['www-authenticate']
|
||||
&& error.headers['www-authenticate'].includes('NTLM'))) return [3 /*break*/, 3];
|
||||
|
@ -96,12 +99,12 @@ function NtlmClient(credentials, AxiosConfig) {
|
|||
// include the Negotiate option when responding with the T2 message
|
||||
// There is nore we could do to ensure we are processing correctly,
|
||||
// but this is the easiest option for now
|
||||
if (error.headers['www-authenticate'].length < 50) {
|
||||
if (ntlmheader.length < 50) {
|
||||
t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain);
|
||||
error.config.headers["Authorization"] = t1Msg;
|
||||
}
|
||||
else {
|
||||
t2Msg = ntlm.decodeType2Message((error.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
||||
t2Msg = ntlm.decodeType2Message((ntlmheader.match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
||||
t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain);
|
||||
error.config.headers["X-retry"] = "false";
|
||||
error.config.headers["Authorization"] = t3Msg;
|
||||
|
|
|
@ -34,12 +34,16 @@ class DnsMonitorType extends MonitorType {
|
|||
switch (monitor.dns_resolve_type) {
|
||||
case "A":
|
||||
case "AAAA":
|
||||
case "TXT":
|
||||
case "PTR":
|
||||
dnsMessage = `Records: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "TXT":
|
||||
dnsMessage = `Records: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.flat().some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "CNAME":
|
||||
dnsMessage = dnsRes[0];
|
||||
conditionsResult = handleConditions({ record: dnsRes[0] });
|
||||
|
|
49
server/monitor-types/group.js
Normal file
49
server/monitor-types/group.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
const { UP, PENDING, DOWN } = require("../../src/util");
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const Monitor = require("../model/monitor");
|
||||
|
||||
class GroupMonitorType extends MonitorType {
|
||||
name = "group";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
const children = await Monitor.getChildren(monitor.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = "All children up and running";
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
continue;
|
||||
}
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
// lastBeat.status could be null
|
||||
if (!lastBeat) {
|
||||
heartbeat.status = PENDING;
|
||||
} else if (heartbeat.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
heartbeat.status = lastBeat.status;
|
||||
} else if (heartbeat.status === PENDING && lastBeat.status === DOWN) {
|
||||
heartbeat.status = lastBeat.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (heartbeat.status !== UP) {
|
||||
heartbeat.msg = "Child inaccessible";
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
heartbeat.status = PENDING;
|
||||
heartbeat.msg = "Group empty";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GroupMonitorType,
|
||||
};
|
||||
|
36
server/monitor-types/manual.js
Normal file
36
server/monitor-types/manual.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP, DOWN, PENDING } = require("../../src/util");
|
||||
|
||||
class ManualMonitorType extends MonitorType {
|
||||
name = "Manual";
|
||||
type = "manual";
|
||||
description = "A monitor that allows manual control of the status";
|
||||
supportsConditions = false;
|
||||
conditionVariables = [];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat) {
|
||||
if (monitor.manual_status !== null) {
|
||||
heartbeat.status = monitor.manual_status;
|
||||
switch (monitor.manual_status) {
|
||||
case UP:
|
||||
heartbeat.msg = "Up";
|
||||
break;
|
||||
case DOWN:
|
||||
heartbeat.msg = "Down";
|
||||
break;
|
||||
default:
|
||||
heartbeat.msg = "Pending";
|
||||
}
|
||||
} else {
|
||||
heartbeat.status = PENDING;
|
||||
heartbeat.msg = "Manual monitoring - No status set";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ManualMonitorType
|
||||
};
|
35
server/monitor-types/smtp.js
Normal file
35
server/monitor-types/smtp.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP } = require("../../src/util");
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
class SMTPMonitorType extends MonitorType {
|
||||
name = "smtp";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
let options = {
|
||||
port: monitor.port || 25,
|
||||
host: monitor.hostname,
|
||||
secure: monitor.smtpSecurity === "secure", // use SMTPS (not STARTTLS)
|
||||
ignoreTLS: monitor.smtpSecurity === "nostarttls", // don't use STARTTLS even if it's available
|
||||
requireTLS: monitor.smtpSecurity === "starttls", // use STARTTLS or fail
|
||||
};
|
||||
let transporter = nodemailer.createTransport(options);
|
||||
try {
|
||||
await transporter.verify();
|
||||
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = "SMTP connection verifies successfully";
|
||||
} catch (e) {
|
||||
throw new Error(`SMTP connection doesn't verify: ${e}`);
|
||||
} finally {
|
||||
transporter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SMTPMonitorType,
|
||||
};
|
|
@ -18,17 +18,28 @@ class Discord extends NotificationProvider {
|
|||
webhookUrl.searchParams.append("thread_id", notification.threadId);
|
||||
}
|
||||
|
||||
// Check if the webhook has an avatar
|
||||
let webhookHasAvatar = true;
|
||||
try {
|
||||
const webhookInfo = await axios.get(webhookUrl.toString());
|
||||
webhookHasAvatar = !!webhookInfo.data.avatar;
|
||||
} catch (e) {
|
||||
// If we can't verify, we assume he has an avatar to avoid forcing the default avatar
|
||||
webhookHasAvatar = true;
|
||||
}
|
||||
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let discordtestdata = {
|
||||
username: discordDisplayName,
|
||||
content: msg,
|
||||
};
|
||||
|
||||
if (!webhookHasAvatar) {
|
||||
discordtestdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
if (notification.discordChannelType === "createNewForumPost") {
|
||||
discordtestdata.thread_name = notification.postName;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), discordtestdata);
|
||||
return okMsg;
|
||||
}
|
||||
|
@ -46,10 +57,10 @@ class Discord extends NotificationProvider {
|
|||
name: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
...(!notification.disableUrl ? [{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: this.extractAddress(monitorJSON),
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
|
@ -61,6 +72,9 @@ class Discord extends NotificationProvider {
|
|||
],
|
||||
}],
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
discorddowndata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
if (notification.discordChannelType === "createNewForumPost") {
|
||||
discorddowndata.thread_name = notification.postName;
|
||||
}
|
||||
|
@ -83,10 +97,10 @@ class Discord extends NotificationProvider {
|
|||
name: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
...(!notification.disableUrl ? [{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: this.extractAddress(monitorJSON),
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
|
@ -98,6 +112,9 @@ class Discord extends NotificationProvider {
|
|||
],
|
||||
}],
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
discordupdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
|
||||
if (notification.discordChannelType === "createNewForumPost") {
|
||||
discordupdata.thread_name = notification.postName;
|
||||
|
|
|
@ -73,13 +73,13 @@ class FlashDuty extends NotificationProvider {
|
|||
}
|
||||
const options = {
|
||||
method: "POST",
|
||||
url: "https://api.flashcat.cloud/event/push/alert/standard?integration_key=" + notification.flashdutyIntegrationKey,
|
||||
url: notification.flashdutyIntegrationKey.startsWith("http") ? notification.flashdutyIntegrationKey : "https://api.flashcat.cloud/event/push/alert/standard?integration_key=" + notification.flashdutyIntegrationKey,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: {
|
||||
description: `[${title}] [${monitorInfo.name}] ${body}`,
|
||||
title,
|
||||
event_status: eventStatus || "Info",
|
||||
alert_key: String(monitorInfo.id) || Math.random().toString(36).substring(7),
|
||||
alert_key: monitorInfo.id ? String(monitorInfo.id) : Math.random().toString(36).substring(7),
|
||||
labels,
|
||||
}
|
||||
};
|
||||
|
|
53
server/notification-providers/notifery.js
Normal file
53
server/notification-providers/notifery.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Notifery extends NotificationProvider {
|
||||
name = "notifery";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://api.notifery.com/event";
|
||||
|
||||
let data = {
|
||||
title: notification.notiferyTitle || "Uptime Kuma Alert",
|
||||
message: msg,
|
||||
};
|
||||
|
||||
if (notification.notiferyGroup) {
|
||||
data.group = notification.notiferyGroup;
|
||||
}
|
||||
|
||||
// Link to the monitor
|
||||
const baseURL = await setting("primaryBaseURL");
|
||||
if (baseURL && monitorJSON) {
|
||||
data.message += `\n\nMonitor: ${baseURL}${getMonitorRelativeURL(monitorJSON.id)}`;
|
||||
}
|
||||
|
||||
if (heartbeatJSON) {
|
||||
data.code = heartbeatJSON.status === UP ? 0 : 1;
|
||||
|
||||
if (heartbeatJSON.ping) {
|
||||
data.duration = heartbeatJSON.ping;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": notification.notiferyApiKey,
|
||||
};
|
||||
|
||||
await axios.post(url, data, { headers });
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Notifery;
|
|
@ -41,8 +41,8 @@ class Ntfy extends NotificationProvider {
|
|||
if (heartbeatJSON.status === DOWN) {
|
||||
tags = [ "red_circle" ];
|
||||
status = "Down";
|
||||
// if priority is not 5, increase priority for down alerts
|
||||
priority = priority === 5 ? priority : priority + 1;
|
||||
// defaults to max(priority + 1, 5)
|
||||
priority = notification.ntfyPriorityDown || (priority === 5 ? priority : priority + 1);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
tags = [ "green_circle" ];
|
||||
status = "Up";
|
||||
|
|
73
server/notification-providers/onechat.js
Normal file
73
server/notification-providers/onechat.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class OneChat extends NotificationProvider {
|
||||
name = "OneChat";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://chat-api.one.th/message/api/v1/push_message";
|
||||
|
||||
try {
|
||||
const config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + notification.accessToken,
|
||||
},
|
||||
};
|
||||
if (heartbeatJSON == null) {
|
||||
const testMessage = {
|
||||
to: notification.recieverId,
|
||||
bot_id: notification.botId,
|
||||
type: "text",
|
||||
message: "Test Successful!",
|
||||
};
|
||||
await axios.post(url, testMessage, config);
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
const downMessage = {
|
||||
to: notification.recieverId,
|
||||
bot_id: notification.botId,
|
||||
type: "text",
|
||||
message:
|
||||
`UptimeKuma Alert:
|
||||
[🔴 Down]
|
||||
Name: ${monitorJSON["name"]}
|
||||
${heartbeatJSON["msg"]}
|
||||
Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
};
|
||||
await axios.post(url, downMessage, config);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
const upMessage = {
|
||||
to: notification.recieverId,
|
||||
bot_id: notification.botId,
|
||||
type: "text",
|
||||
message:
|
||||
`UptimeKuma Alert:
|
||||
[🟢 Up]
|
||||
Name: ${monitorJSON["name"]}
|
||||
${heartbeatJSON["msg"]}
|
||||
Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
};
|
||||
await axios.post(url, upMessage, config);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
// Handle errors and throw a descriptive message
|
||||
if (error.response) {
|
||||
const errorMessage =
|
||||
error.response.data?.message ||
|
||||
"Unknown API error occurred.";
|
||||
throw new Error(`OneChat API Error: ${errorMessage}`);
|
||||
} else {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OneChat;
|
48
server/notification-providers/pumble.js
Normal file
48
server/notification-providers/pumble.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { UP } = require("../../src/util");
|
||||
|
||||
class Pumble extends NotificationProvider {
|
||||
name = "pumble";
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
if (heartbeatJSON === null && monitorJSON === null) {
|
||||
let data = {
|
||||
"attachments": [
|
||||
{
|
||||
"title": "Uptime Kuma Alert",
|
||||
"text": msg,
|
||||
"color": "#5BDD8B"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await axios.post(notification.webhookURL, data);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let data = {
|
||||
"attachments": [
|
||||
{
|
||||
"title": `${monitorJSON["name"]} is ${heartbeatJSON["status"] === UP ? "up" : "down"}`,
|
||||
"text": heartbeatJSON["msg"],
|
||||
"color": (heartbeatJSON["status"] === UP ? "#5BDD8B" : "#DC3645"),
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await axios.post(notification.webhookURL, data);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pumble;
|
|
@ -1,5 +1,6 @@
|
|||
const { getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const { UP } = require("../../src/util");
|
||||
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
@ -43,15 +44,20 @@ class Pushover extends NotificationProvider {
|
|||
if (heartbeatJSON == null) {
|
||||
await axios.post(url, data);
|
||||
return okMsg;
|
||||
} else {
|
||||
data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>:${heartbeatJSON["localDateTime"]}`;
|
||||
await axios.post(url, data);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === UP && notification.pushoversounds_up) {
|
||||
// default = DOWN => DOWN-sound is also played for non-UP/DOWN notiifcations
|
||||
data.sound = notification.pushoversounds_up;
|
||||
}
|
||||
|
||||
data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>: ${heartbeatJSON["localDateTime"]}`;
|
||||
await axios.post(url, data);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,7 @@ class Slack extends NotificationProvider {
|
|||
|
||||
const title = "Uptime Kuma Alert";
|
||||
let data = {
|
||||
"text": msg,
|
||||
"channel": notification.slackchannel,
|
||||
"username": notification.slackusername,
|
||||
"icon_emoji": notification.slackiconemo,
|
||||
|
|
40
server/notification-providers/sms-planet.js
Normal file
40
server/notification-providers/sms-planet.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class SMSPlanet extends NotificationProvider {
|
||||
name = "SMSPlanet";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://api2.smsplanet.pl/sms";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + notification.smsplanetApiToken,
|
||||
"content-type": "multipart/form-data"
|
||||
}
|
||||
};
|
||||
|
||||
let data = {
|
||||
"from": notification.smsplanetSenderName,
|
||||
"to": notification.smsplanetPhoneNumbers,
|
||||
"msg": msg.replace(/🔴/, "❌")
|
||||
};
|
||||
|
||||
let response = await axios.post(url, data, config);
|
||||
if (!response.data?.messageId) {
|
||||
throw new Error(response.data?.errorMsg ?? "SMSPlanet server did not respond with the expected result");
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SMSPlanet;
|
|
@ -11,59 +11,127 @@ class SMSEagle extends NotificationProvider {
|
|||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
if (notification.smseagleApiType === "smseagle-apiv1") { // according to https://www.smseagle.eu/apiv1/
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
};
|
||||
|
||||
let sendMethod;
|
||||
let recipientType;
|
||||
let duration;
|
||||
let voiceId;
|
||||
|
||||
if (notification.smseagleRecipientType === "smseagle-contact") {
|
||||
recipientType = "contactname";
|
||||
sendMethod = "/send_tocontact";
|
||||
} else if (notification.smseagleRecipientType === "smseagle-group") {
|
||||
recipientType = "groupname";
|
||||
sendMethod = "/send_togroup";
|
||||
} else if (notification.smseagleRecipientType === "smseagle-to") {
|
||||
recipientType = "to";
|
||||
sendMethod = "/send_sms";
|
||||
if (notification.smseagleMsgType !== "smseagle-sms") {
|
||||
duration = notification.smseagleDuration ?? 10;
|
||||
|
||||
if (notification.smseagleMsgType === "smseagle-ring") {
|
||||
sendMethod = "/ring_call";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts") {
|
||||
sendMethod = "/tts_call";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts-advanced") {
|
||||
sendMethod = "/tts_adv_call";
|
||||
voiceId = notification.smseagleTtsModel ? notification.smseagleTtsModel : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let postData;
|
||||
let sendMethod;
|
||||
let recipientType;
|
||||
const url = new URL(notification.smseagleUrl + "/http_api" + sendMethod);
|
||||
|
||||
let encoding = (notification.smseagleEncoding) ? "1" : "0";
|
||||
let priority = (notification.smseaglePriority) ? notification.smseaglePriority : "0";
|
||||
|
||||
if (notification.smseagleRecipientType === "smseagle-contact") {
|
||||
recipientType = "contactname";
|
||||
sendMethod = "sms.send_tocontact";
|
||||
}
|
||||
if (notification.smseagleRecipientType === "smseagle-group") {
|
||||
recipientType = "groupname";
|
||||
sendMethod = "sms.send_togroup";
|
||||
}
|
||||
if (notification.smseagleRecipientType === "smseagle-to") {
|
||||
recipientType = "to";
|
||||
sendMethod = "sms.send_sms";
|
||||
}
|
||||
|
||||
let params = {
|
||||
access_token: notification.smseagleToken,
|
||||
[recipientType]: notification.smseagleRecipient,
|
||||
message: msg,
|
||||
responsetype: "extended",
|
||||
unicode: encoding,
|
||||
highpriority: priority
|
||||
};
|
||||
|
||||
postData = {
|
||||
method: sendMethod,
|
||||
params: params
|
||||
};
|
||||
|
||||
let resp = await axios.post(notification.smseagleUrl + "/jsonrpc/sms", postData, config);
|
||||
|
||||
if ((JSON.stringify(resp.data)).indexOf("message_id") === -1) {
|
||||
let error = "";
|
||||
if (resp.data.result && resp.data.result.error_text) {
|
||||
error = `SMSEagle API returned error: ${JSON.stringify(resp.data.result.error_text)}`;
|
||||
url.searchParams.append("access_token", notification.smseagleToken);
|
||||
url.searchParams.append(recipientType, notification.smseagleRecipient);
|
||||
if (!notification.smseagleRecipientType || notification.smseagleRecipientType === "smseagle-sms") {
|
||||
url.searchParams.append("unicode", (notification.smseagleEncoding) ? "1" : "0");
|
||||
url.searchParams.append("highpriority", notification.smseaglePriority ?? "0");
|
||||
} else {
|
||||
error = "SMSEagle API returned an unexpected response";
|
||||
url.searchParams.append("duration", duration);
|
||||
}
|
||||
if (notification.smseagleRecipientType !== "smseagle-ring") {
|
||||
url.searchParams.append("message", msg);
|
||||
}
|
||||
if (voiceId) {
|
||||
url.searchParams.append("voice_id", voiceId);
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
let resp = await axios.get(url.toString(), config);
|
||||
|
||||
if (resp.data.indexOf("OK") === -1) {
|
||||
let error = `SMSEagle API returned error: ${resp.data}`;
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} else if (notification.smseagleApiType === "smseagle-apiv2") { // according to https://www.smseagle.eu/docs/apiv2/
|
||||
let config = {
|
||||
headers: {
|
||||
"access-token": notification.smseagleToken,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
};
|
||||
|
||||
let encoding = (notification.smseagleEncoding) ? "unicode" : "standard";
|
||||
let priority = (notification.smseaglePriority) ?? 0;
|
||||
|
||||
let postData = {
|
||||
text: msg,
|
||||
encoding: encoding,
|
||||
priority: priority
|
||||
};
|
||||
|
||||
if (notification.smseagleRecipientContact) {
|
||||
postData["contacts"] = notification.smseagleRecipientContact.split(",").map(Number);
|
||||
}
|
||||
if (notification.smseagleRecipientGroup) {
|
||||
postData["groups"] = notification.smseagleRecipientGroup.split(",").map(Number);
|
||||
}
|
||||
if (notification.smseagleRecipientTo) {
|
||||
postData["to"] = notification.smseagleRecipientTo.split(",");
|
||||
}
|
||||
|
||||
let endpoint = "/messages/sms";
|
||||
|
||||
if (notification.smseagleMsgType !== "smseagle-sms") {
|
||||
|
||||
postData["duration"] = notification.smseagleDuration ?? 10;
|
||||
|
||||
if (notification.smseagleMsgType === "smseagle-ring") {
|
||||
endpoint = "/calls/ring";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts") {
|
||||
endpoint = "/calls/tts";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts-advanced") {
|
||||
endpoint = "/calls/tts_advanced";
|
||||
postData["voice_id"] = notification.smseagleTtsModel ?? 1;
|
||||
}
|
||||
}
|
||||
|
||||
let resp = await axios.post(notification.smseagleUrl + "/api/v2" + endpoint, postData, config);
|
||||
|
||||
const queuedCount = resp.data.filter(x => x.status === "queued").length;
|
||||
const unqueuedCount = resp.data.length - queuedCount;
|
||||
|
||||
if (resp.status !== 200 || queuedCount === 0) {
|
||||
if (!resp.data.length) {
|
||||
throw new Error("SMSEagle API returned an empty response");
|
||||
}
|
||||
throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
|
||||
}
|
||||
|
||||
if (unqueuedCount) {
|
||||
return `Sent ${queuedCount}/${resp.data.length} Messages Successfully.`;
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ class SMTP extends NotificationProvider {
|
|||
// default values in case the user does not want to template
|
||||
let subject = msg;
|
||||
let body = msg;
|
||||
let useHTMLBody = false;
|
||||
if (heartbeatJSON) {
|
||||
body = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||
}
|
||||
|
@ -50,11 +51,11 @@ class SMTP extends NotificationProvider {
|
|||
// cannot end with whitespace as this often raises spam scores
|
||||
const customSubject = notification.customSubject?.trim() || "";
|
||||
const customBody = notification.customBody?.trim() || "";
|
||||
|
||||
if (customSubject !== "") {
|
||||
subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
if (customBody !== "") {
|
||||
useHTMLBody = notification.htmlBody || false;
|
||||
body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +68,8 @@ class SMTP extends NotificationProvider {
|
|||
bcc: notification.smtpBCC,
|
||||
to: notification.smtpTo,
|
||||
subject: subject,
|
||||
text: body,
|
||||
// If the email body is custom, and the user wants it, set the email body as HTML
|
||||
[useHTMLBody ? "html" : "text"]: body
|
||||
});
|
||||
|
||||
return okMsg;
|
||||
|
|
37
server/notification-providers/spugpush.js
Normal file
37
server/notification-providers/spugpush.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class SpugPush extends NotificationProvider {
|
||||
|
||||
name = "SpugPush";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
try {
|
||||
let formData = {
|
||||
title: "Uptime Kuma Message",
|
||||
content: msg
|
||||
};
|
||||
if (heartbeatJSON) {
|
||||
if (heartbeatJSON["status"] === UP) {
|
||||
formData.title = `UptimeKuma 「${monitorJSON["name"]}」 is Up`;
|
||||
formData.content = `[✅ Up] ${heartbeatJSON["msg"]}`;
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
formData.title = `UptimeKuma 「${monitorJSON["name"]}」 is Down`;
|
||||
formData.content = `[🔴 Down] ${heartbeatJSON["msg"]}`;
|
||||
}
|
||||
}
|
||||
const apiUrl = `https://push.spug.cc/send/${notification.templateKey}`;
|
||||
await axios.post(apiUrl, formData);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpugPush;
|
|
@ -13,6 +13,7 @@ const DingDing = require("./notification-providers/dingding");
|
|||
const Discord = require("./notification-providers/discord");
|
||||
const Elks = require("./notification-providers/46elks");
|
||||
const Feishu = require("./notification-providers/feishu");
|
||||
const Notifery = require("./notification-providers/notifery");
|
||||
const FreeMobile = require("./notification-providers/freemobile");
|
||||
const GoogleChat = require("./notification-providers/google-chat");
|
||||
const Gorush = require("./notification-providers/gorush");
|
||||
|
@ -30,9 +31,11 @@ const Mattermost = require("./notification-providers/mattermost");
|
|||
const Nostr = require("./notification-providers/nostr");
|
||||
const Ntfy = require("./notification-providers/ntfy");
|
||||
const Octopush = require("./notification-providers/octopush");
|
||||
const OneChat = require("./notification-providers/onechat");
|
||||
const OneBot = require("./notification-providers/onebot");
|
||||
const Opsgenie = require("./notification-providers/opsgenie");
|
||||
const PagerDuty = require("./notification-providers/pagerduty");
|
||||
const Pumble = require("./notification-providers/pumble");
|
||||
const FlashDuty = require("./notification-providers/flashduty");
|
||||
const PagerTree = require("./notification-providers/pagertree");
|
||||
const PromoSMS = require("./notification-providers/promosms");
|
||||
|
@ -72,6 +75,8 @@ const Onesender = require("./notification-providers/onesender");
|
|||
const Wpush = require("./notification-providers/wpush");
|
||||
const SendGrid = require("./notification-providers/send-grid");
|
||||
const YZJ = require("./notification-providers/yzj");
|
||||
const SMSPlanet = require("./notification-providers/sms-planet");
|
||||
const SpugPush = require("./notification-providers/spugpush");
|
||||
|
||||
class Notification {
|
||||
|
||||
|
@ -119,6 +124,7 @@ class Notification {
|
|||
new Nostr(),
|
||||
new Ntfy(),
|
||||
new Octopush(),
|
||||
new OneChat(),
|
||||
new OneBot(),
|
||||
new Onesender(),
|
||||
new Opsgenie(),
|
||||
|
@ -126,6 +132,7 @@ class Notification {
|
|||
new FlashDuty(),
|
||||
new PagerTree(),
|
||||
new PromoSMS(),
|
||||
new Pumble(),
|
||||
new Pushbullet(),
|
||||
new PushDeer(),
|
||||
new Pushover(),
|
||||
|
@ -160,7 +167,10 @@ class Notification {
|
|||
new Cellsynt(),
|
||||
new Wpush(),
|
||||
new SendGrid(),
|
||||
new YZJ()
|
||||
new YZJ(),
|
||||
new SMSPlanet(),
|
||||
new SpugPush(),
|
||||
new Notifery(),
|
||||
];
|
||||
for (let item of list) {
|
||||
if (! item.name) {
|
||||
|
|
|
@ -5,10 +5,10 @@ const saltRounds = 10;
|
|||
/**
|
||||
* Hash a password
|
||||
* @param {string} password Password to hash
|
||||
* @returns {string} Hash
|
||||
* @returns {Promise<string>} Hash
|
||||
*/
|
||||
exports.generate = function (password) {
|
||||
return bcrypt.hashSync(password, saltRounds);
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@ const PrometheusClient = require("prom-client");
|
|||
const { log } = require("../src/util");
|
||||
|
||||
const commonLabels = [
|
||||
"monitor_id",
|
||||
"monitor_name",
|
||||
"monitor_type",
|
||||
"monitor_url",
|
||||
|
@ -40,6 +41,7 @@ class Prometheus {
|
|||
*/
|
||||
constructor(monitor) {
|
||||
this.monitorLabelValues = {
|
||||
monitor_id: monitor.id,
|
||||
monitor_name: monitor.name,
|
||||
monitor_type: monitor.type,
|
||||
monitor_url: monitor.url,
|
||||
|
|
|
@ -50,7 +50,7 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
|||
let msg = request.query.msg || "OK";
|
||||
let ping = parseFloat(request.query.ping) || null;
|
||||
let statusString = request.query.status || "up";
|
||||
let status = (statusString === "up") ? UP : DOWN;
|
||||
const statusFromParam = (statusString === "up") ? UP : DOWN;
|
||||
|
||||
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
|
||||
pushToken
|
||||
|
@ -80,7 +80,7 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
|||
msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else {
|
||||
determineStatus(status, previousHeartbeat, monitor.maxretries, monitor.isUpsideDown(), bean);
|
||||
determineStatus(statusFromParam, previousHeartbeat, monitor.maxretries, monitor.isUpsideDown(), bean);
|
||||
}
|
||||
|
||||
// Calculate uptime
|
||||
|
@ -92,21 +92,21 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
|||
log.debug("router", "PreviousStatus: " + previousHeartbeat?.status);
|
||||
log.debug("router", "Current Status: " + bean.status);
|
||||
|
||||
bean.important = Monitor.isImportantBeat(isFirstBeat, previousHeartbeat?.status, status);
|
||||
bean.important = Monitor.isImportantBeat(isFirstBeat, previousHeartbeat?.status, bean.status);
|
||||
|
||||
if (Monitor.isImportantForNotification(isFirstBeat, previousHeartbeat?.status, status)) {
|
||||
if (Monitor.isImportantForNotification(isFirstBeat, previousHeartbeat?.status, bean.status)) {
|
||||
// Reset down count
|
||||
bean.downCount = 0;
|
||||
|
||||
log.debug("monitor", `[${this.name}] sendNotification`);
|
||||
log.debug("monitor", `[${monitor.name}] sendNotification`);
|
||||
await Monitor.sendNotification(isFirstBeat, monitor, bean);
|
||||
} else {
|
||||
if (bean.status === DOWN && this.resendInterval > 0) {
|
||||
if (bean.status === DOWN && monitor.resendInterval > 0) {
|
||||
++bean.downCount;
|
||||
if (bean.downCount >= this.resendInterval) {
|
||||
if (bean.downCount >= monitor.resendInterval) {
|
||||
// Send notification again, because we are still DOWN
|
||||
log.debug("monitor", `[${this.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
||||
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||
log.debug("monitor", `[${monitor.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${monitor.resendInterval}`);
|
||||
await Monitor.sendNotification(isFirstBeat, monitor, bean);
|
||||
|
||||
// Reset down count
|
||||
bean.downCount = 0;
|
||||
|
|
|
@ -89,7 +89,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
|||
SELECT * FROM heartbeat
|
||||
WHERE monitor_id = ?
|
||||
ORDER BY time DESC
|
||||
LIMIT 50
|
||||
LIMIT 100
|
||||
`, [
|
||||
monitorID,
|
||||
]);
|
||||
|
|
|
@ -674,7 +674,7 @@ let needSetup = false;
|
|||
|
||||
let user = R.dispense("user");
|
||||
user.username = username;
|
||||
user.password = passwordHash.generate(password);
|
||||
user.password = await passwordHash.generate(password);
|
||||
await R.store(user);
|
||||
|
||||
needSetup = false;
|
||||
|
@ -792,6 +792,7 @@ let needSetup = false;
|
|||
bean.url = monitor.url;
|
||||
bean.method = monitor.method;
|
||||
bean.body = monitor.body;
|
||||
bean.ipFamily = monitor.ipFamily;
|
||||
bean.headers = monitor.headers;
|
||||
bean.basic_auth_user = monitor.basic_auth_user;
|
||||
bean.basic_auth_pass = monitor.basic_auth_pass;
|
||||
|
@ -866,6 +867,7 @@ let needSetup = false;
|
|||
monitor.kafkaProducerAllowAutoTopicCreation;
|
||||
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
|
||||
bean.remote_browser = monitor.remote_browser;
|
||||
bean.smtpSecurity = monitor.smtpSecurity;
|
||||
bean.snmpVersion = monitor.snmpVersion;
|
||||
bean.snmpOid = monitor.snmpOid;
|
||||
bean.jsonPathOperator = monitor.jsonPathOperator;
|
||||
|
@ -874,6 +876,12 @@ let needSetup = false;
|
|||
bean.rabbitmqUsername = monitor.rabbitmqUsername;
|
||||
bean.rabbitmqPassword = monitor.rabbitmqPassword;
|
||||
bean.conditions = JSON.stringify(monitor.conditions);
|
||||
bean.manual_status = monitor.manual_status;
|
||||
|
||||
// ping advanced options
|
||||
bean.ping_numeric = monitor.ping_numeric;
|
||||
bean.ping_count = monitor.ping_count;
|
||||
bean.ping_per_request_timeout = monitor.ping_per_request_timeout;
|
||||
|
||||
bean.validate();
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports.apiKeySocketHandler = (socket) => {
|
|||
checkLogin(socket);
|
||||
|
||||
let clearKey = nanoid(40);
|
||||
let hashedKey = passwordHash.generate(clearKey);
|
||||
let hashedKey = await passwordHash.generate(clearKey);
|
||||
key["key"] = hashedKey;
|
||||
let bean = await APIKey.save(key, socket.userID);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports.databaseSocketHandler = (socket) => {
|
|||
checkLogin(socket);
|
||||
callback({
|
||||
ok: true,
|
||||
size: Database.getSize(),
|
||||
size: await Database.getSize(),
|
||||
});
|
||||
} catch (error) {
|
||||
callback({
|
||||
|
|
|
@ -4,7 +4,7 @@ const { sendInfo } = require("../client");
|
|||
const { checkLogin } = require("../util-server");
|
||||
const GameResolver = require("gamedig/lib/GameResolver");
|
||||
const { testChrome } = require("../monitor-types/real-browser-monitor-type");
|
||||
const fs = require("fs");
|
||||
const fsAsync = require("fs").promises;
|
||||
const path = require("path");
|
||||
|
||||
let gameResolver = new GameResolver();
|
||||
|
@ -90,17 +90,29 @@ module.exports.generalSocketHandler = (socket, server) => {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("getPushExample", (language, callback) => {
|
||||
socket.on("getPushExample", async (language, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
if (!/^[a-z-]+$/.test(language)) {
|
||||
throw new Error("Invalid language");
|
||||
}
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let dir = path.join("./extra/push-examples", language);
|
||||
let files = fs.readdirSync(dir);
|
||||
let files = await fsAsync.readdir(dir);
|
||||
|
||||
for (let file of files) {
|
||||
if (file.startsWith("index.")) {
|
||||
callback({
|
||||
ok: true,
|
||||
code: fs.readFileSync(path.join(dir, file), "utf8"),
|
||||
code: await fsAsync.readFile(path.join(dir, file), "utf8"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -211,6 +211,10 @@ module.exports.statusPageSocketHandler = (socket) => {
|
|||
relationBean.send_url = monitor.sendUrl;
|
||||
}
|
||||
|
||||
if (monitor.url !== undefined) {
|
||||
relationBean.custom_url = monitor.url;
|
||||
}
|
||||
|
||||
await R.store(relationBean);
|
||||
}
|
||||
|
||||
|
|
|
@ -113,9 +113,12 @@ class UptimeKumaServer {
|
|||
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
||||
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["smtp"] = new SMTPMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["group"] = new GroupMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["manual"] = new ManualMonitorType();
|
||||
|
||||
// Allow all CORS origins (polling) in development
|
||||
let cors = undefined;
|
||||
|
@ -551,7 +554,10 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
|
|||
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
||||
const { DnsMonitorType } = require("./monitor-types/dns");
|
||||
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
||||
const { SMTPMonitorType } = require("./monitor-types/smtp");
|
||||
const { GroupMonitorType } = require("./monitor-types/group");
|
||||
const { SNMPMonitorType } = require("./monitor-types/snmp");
|
||||
const { MongodbMonitorType } = require("./monitor-types/mongodb");
|
||||
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
|
||||
const { ManualMonitorType } = require("./monitor-types/manual");
|
||||
const Monitor = require("./model/monitor");
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
const tcpp = require("tcp-ping");
|
||||
const ping = require("@louislam/ping");
|
||||
const { R } = require("redbean-node");
|
||||
const { log, genSecret, badgeConstants } = require("../src/util");
|
||||
const {
|
||||
log, genSecret, badgeConstants,
|
||||
PING_PACKET_SIZE_DEFAULT, PING_GLOBAL_TIMEOUT_DEFAULT,
|
||||
PING_COUNT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||
} = require("../src/util");
|
||||
const passwordHash = require("./password-hash");
|
||||
const { Resolver } = require("dns");
|
||||
const iconv = require("iconv-lite");
|
||||
|
@ -47,7 +51,7 @@ exports.initJWTSecret = async () => {
|
|||
jwtSecretBean.key = "jwtSecret";
|
||||
}
|
||||
|
||||
jwtSecretBean.value = passwordHash.generate(genSecret());
|
||||
jwtSecretBean.value = await passwordHash.generate(genSecret());
|
||||
await R.store(jwtSecretBean);
|
||||
return jwtSecretBean;
|
||||
};
|
||||
|
@ -118,20 +122,33 @@ exports.tcping = function (hostname, port) {
|
|||
|
||||
/**
|
||||
* Ping the specified machine
|
||||
* @param {string} hostname Hostname / address of machine
|
||||
* @param {number} size Size of packet to send
|
||||
* @param {string} destAddr Hostname / IP address of machine to ping
|
||||
* @param {number} count Number of packets to send before stopping
|
||||
* @param {string} sourceAddr Source address for sending/receiving echo requests
|
||||
* @param {boolean} numeric If true, IP addresses will be output instead of symbolic hostnames
|
||||
* @param {number} size Size (in bytes) of echo request to send
|
||||
* @param {number} deadline Maximum time in seconds before ping stops, regardless of packets sent
|
||||
* @param {number} timeout Maximum time in seconds to wait for each response
|
||||
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
|
||||
*/
|
||||
exports.ping = async (hostname, size = 56) => {
|
||||
exports.ping = async (
|
||||
destAddr,
|
||||
count = PING_COUNT_DEFAULT,
|
||||
sourceAddr = "",
|
||||
numeric = true,
|
||||
size = PING_PACKET_SIZE_DEFAULT,
|
||||
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
|
||||
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT,
|
||||
) => {
|
||||
try {
|
||||
return await exports.pingAsync(hostname, false, size);
|
||||
return await exports.pingAsync(destAddr, false, count, sourceAddr, numeric, size, deadline, timeout);
|
||||
} catch (e) {
|
||||
// If the host cannot be resolved, try again with ipv6
|
||||
log.debug("ping", "IPv6 error message: " + e.message);
|
||||
|
||||
// As node-ping does not report a specific error for this, try again if it is an empty message with ipv6 no matter what.
|
||||
if (!e.message) {
|
||||
return await exports.pingAsync(hostname, true, size);
|
||||
return await exports.pingAsync(destAddr, true, count, sourceAddr, numeric, size, deadline, timeout);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
@ -140,18 +157,35 @@ exports.ping = async (hostname, size = 56) => {
|
|||
|
||||
/**
|
||||
* Ping the specified machine
|
||||
* @param {string} hostname Hostname / address of machine to ping
|
||||
* @param {string} destAddr Hostname / IP address of machine to ping
|
||||
* @param {boolean} ipv6 Should IPv6 be used?
|
||||
* @param {number} size Size of ping packet to send
|
||||
* @param {number} count Number of packets to send before stopping
|
||||
* @param {string} sourceAddr Source address for sending/receiving echo requests
|
||||
* @param {boolean} numeric If true, IP addresses will be output instead of symbolic hostnames
|
||||
* @param {number} size Size (in bytes) of echo request to send
|
||||
* @param {number} deadline Maximum time in seconds before ping stops, regardless of packets sent
|
||||
* @param {number} timeout Maximum time in seconds to wait for each response
|
||||
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
|
||||
*/
|
||||
exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
|
||||
exports.pingAsync = function (
|
||||
destAddr,
|
||||
ipv6 = false,
|
||||
count = PING_COUNT_DEFAULT,
|
||||
sourceAddr = "",
|
||||
numeric = true,
|
||||
size = PING_PACKET_SIZE_DEFAULT,
|
||||
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
|
||||
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ping.promise.probe(hostname, {
|
||||
ping.promise.probe(destAddr, {
|
||||
v6: ipv6,
|
||||
min_reply: 1,
|
||||
deadline: 10,
|
||||
min_reply: count,
|
||||
sourceAddr: sourceAddr,
|
||||
numeric: numeric,
|
||||
packetSize: size,
|
||||
deadline: deadline,
|
||||
timeout: timeout
|
||||
}).then((res) => {
|
||||
// If ping failed, it will set field to unknown
|
||||
if (res.alive) {
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
<script>
|
||||
import { setPageLocale } from "./util-frontend";
|
||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||
export default {
|
||||
created() {
|
||||
setPageLocale();
|
||||
},
|
||||
};
|
||||
polyfillCountryFlagEmojis();
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
#app {
|
||||
font-family: BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
|
||||
font-family: "Twemoji Country Flags", BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
|
@ -42,6 +42,9 @@ export default {
|
|||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.cleanupModal();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Show the confirm dialog
|
||||
|
@ -58,6 +61,19 @@ export default {
|
|||
this.$emit("added", this.groupName);
|
||||
this.modal.hide();
|
||||
},
|
||||
/**
|
||||
* Clean up modal and restore scroll behavior
|
||||
* @returns {void}
|
||||
*/
|
||||
cleanupModal() {
|
||||
if (this.modal) {
|
||||
try {
|
||||
this.modal.hide();
|
||||
} catch (e) {
|
||||
console.warn("Modal hide failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="col-6 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info">
|
||||
<Uptime :monitor="monitor" type="24" :pill="true" />
|
||||
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
||||
|
@ -22,11 +22,11 @@
|
|||
</span>
|
||||
{{ monitor.name }}
|
||||
</div>
|
||||
<div v-if="monitor.tags.length > 0" class="tags">
|
||||
<div v-if="monitor.tags.length > 0" class="tags gap-1">
|
||||
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-6">
|
||||
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="my-3 form-check">
|
||||
<input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" @click="toggleLink(monitor.group_index, monitor.monitor_index)" />
|
||||
<input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" data-testid="show-clickable-link" @click="toggleLink(monitor.group_index, monitor.monitor_index)" />
|
||||
<label class="form-check-label" for="show-clickable-link">
|
||||
{{ $t("Show Clickable Link") }}
|
||||
</label>
|
||||
|
@ -19,6 +19,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom URL -->
|
||||
<template v-if="monitor.isClickAble">
|
||||
<label for="customUrl" class="form-label">{{ $t("Custom URL") }}</label>
|
||||
<input id="customUrl" :value="monitor.url" type="url" class="form-control" data-testid="custom-url-input" @input="e => changeUrl(monitor.group_index, monitor.monitor_index, e.target!.value)">
|
||||
|
||||
<div class="form-text mb-3">
|
||||
{{ $t("customUrlDescription") }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="btn btn-primary btn-add-group me-2"
|
||||
@click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)"
|
||||
|
@ -29,7 +39,7 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal">
|
||||
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal" data-testid="monitor-settings-close">
|
||||
{{ $t("Close") }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -78,6 +88,7 @@ export default {
|
|||
monitor_index: monitor.index,
|
||||
group_index: group.index,
|
||||
isClickAble: this.showLink(monitor),
|
||||
url: monitor.element.url,
|
||||
};
|
||||
|
||||
this.MonitorSettingDialog.show();
|
||||
|
@ -110,6 +121,17 @@ export default {
|
|||
}
|
||||
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the value of sendUrl
|
||||
* @param {number} groupIndex Index of group monitor is member of
|
||||
* @param {number} index Index of monitor within group
|
||||
* @param {string} value The new value of the url
|
||||
* @returns {void}
|
||||
*/
|
||||
changeUrl(groupIndex, index, value) {
|
||||
this.$root.publicGroupList[groupIndex].monitorList[index].url = value;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -135,11 +135,13 @@ export default {
|
|||
"nostr": "Nostr",
|
||||
"ntfy": "Ntfy",
|
||||
"octopush": "Octopush",
|
||||
"OneChat": "OneChat",
|
||||
"OneBot": "OneBot",
|
||||
"Onesender": "Onesender",
|
||||
"Opsgenie": "Opsgenie",
|
||||
"PagerDuty": "PagerDuty",
|
||||
"PagerTree": "PagerTree",
|
||||
"pumble": "Pumble",
|
||||
"pushbullet": "Pushbullet",
|
||||
"PushByTechulus": "Push by Techulus",
|
||||
"pushover": "Pushover",
|
||||
|
@ -166,7 +168,8 @@ export default {
|
|||
"waha": "WhatsApp (WAHA)",
|
||||
"gtxmessaging": "GtxMessaging",
|
||||
"Cellsynt": "Cellsynt",
|
||||
"SendGrid": "SendGrid"
|
||||
"SendGrid": "SendGrid",
|
||||
"notifery": "Notifery"
|
||||
};
|
||||
|
||||
// Put notifications here if it's not supported in most regions or its documentation is not in English
|
||||
|
@ -183,9 +186,11 @@ export default {
|
|||
"WeCom": "WeCom (企业微信群机器人)",
|
||||
"ServerChan": "ServerChan (Server酱)",
|
||||
"PushPlus": "PushPlus (推送加)",
|
||||
"SpugPush": "SpugPush(Spug推送助手)",
|
||||
"smsc": "SMSC",
|
||||
"WPush": "WPush(wpush.cn)",
|
||||
"YZJ": "YZJ (云之家自定义机器人)"
|
||||
"YZJ": "YZJ (云之家自定义机器人)",
|
||||
"SMSPlanet": "SMSPlanet.pl"
|
||||
};
|
||||
|
||||
// Sort by notification name
|
||||
|
@ -235,6 +240,9 @@ export default {
|
|||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.cleanupModal();
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
|
@ -339,6 +347,20 @@ export default {
|
|||
});
|
||||
} while (this.$root.notificationList.find(it => it.name === name));
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean up modal and restore scroll behavior
|
||||
* @returns {void}
|
||||
*/
|
||||
cleanupModal() {
|
||||
if (this.modal) {
|
||||
try {
|
||||
this.modal.hide();
|
||||
} catch (e) {
|
||||
console.warn("Modal hide failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -125,11 +125,12 @@ export default {
|
|||
}
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.cleanupModal();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Show dialog to confirm deletion
|
||||
|
@ -209,6 +210,20 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean up modal and restore scroll behavior
|
||||
* @returns {void}
|
||||
*/
|
||||
cleanupModal() {
|
||||
if (this.modal) {
|
||||
try {
|
||||
this.modal.hide();
|
||||
} catch (e) {
|
||||
console.warn("Modal hide failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<template #item="monitor">
|
||||
<div class="item" data-testid="monitor">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding">
|
||||
<div class="col-6 small-padding">
|
||||
<div class="info">
|
||||
<font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" />
|
||||
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" />
|
||||
|
@ -58,6 +58,7 @@
|
|||
v-if="editMode"
|
||||
:class="{'link-active': true, 'btn-link': true}"
|
||||
icon="cog" class="action me-3"
|
||||
data-testid="monitor-settings"
|
||||
@click="$refs.monitorSettingDialog.show(group, monitor)"
|
||||
/>
|
||||
</span>
|
||||
|
@ -71,7 +72,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<div :key="$root.userHeartbeatBar" class="col-6">
|
||||
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
'm-2': size == 'normal',
|
||||
'px-2': size == 'sm',
|
||||
'py-0': size == 'sm',
|
||||
'mx-1': size == 'sm',
|
||||
}"
|
||||
:style="{ backgroundColor: item.color, fontSize: size == 'sm' ? '0.7em' : '1em' }"
|
||||
>
|
||||
|
@ -48,7 +47,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
displayText() {
|
||||
if (this.item.value === "" || this.item.value === undefined) {
|
||||
if (this.item.value === "" || this.item.value === undefined || this.item.value === null) {
|
||||
return this.item.name;
|
||||
} else {
|
||||
return `${this.item.name}: ${this.item.value}`;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div v-if="selectedTags.length > 0" class="mb-2 p-1">
|
||||
<tag
|
||||
v-for="item in selectedTags"
|
||||
:key="item.id"
|
||||
:key="`${item.tag_id || item.id}-${item.value || ''}`"
|
||||
:item="item"
|
||||
:remove="deleteTag"
|
||||
/>
|
||||
|
@ -20,10 +20,20 @@
|
|||
<font-awesome-icon class="me-1" icon="plus" /> {{ $t("Add") }}
|
||||
</button>
|
||||
</div>
|
||||
<div ref="modal" class="modal fade" tabindex="-1">
|
||||
<div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<h4 v-if="stagedForBatchAdd.length > 0">{{ $t("Add Tags") }}</h4>
|
||||
<div v-if="stagedForBatchAdd.length > 0" class="mb-3 staging-area" style="max-height: 150px; overflow-y: auto;">
|
||||
<Tag
|
||||
v-for="stagedTag in stagedForBatchAdd"
|
||||
:key="stagedTag.keyForList"
|
||||
:item="mapStagedTagToDisplayItem(stagedTag)"
|
||||
:remove="() => unstageTag(stagedTag)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<vue-multiselect
|
||||
v-model="newDraftTag.select"
|
||||
class="mb-2"
|
||||
|
@ -58,14 +68,11 @@
|
|||
<div class="w-50 pe-2">
|
||||
<input
|
||||
v-model="newDraftTag.name" class="form-control"
|
||||
:class="{'is-invalid': validateDraftTag.nameInvalid}"
|
||||
:class="{'is-invalid': validateDraftTag.invalid && (validateDraftTag.messageKey === 'tagNameColorRequired' || validateDraftTag.messageKey === 'tagNameExists')}"
|
||||
:placeholder="$t('Name')"
|
||||
data-testid="tag-name-input"
|
||||
@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
|
||||
|
@ -104,27 +111,24 @@
|
|||
<div class="mb-2">
|
||||
<input
|
||||
v-model="newDraftTag.value" class="form-control"
|
||||
:class="{'is-invalid': validateDraftTag.valueInvalid}"
|
||||
:class="{'is-invalid': validateDraftTag.invalid && validateDraftTag.messageKey === 'tagAlreadyOnMonitor'}"
|
||||
:placeholder="$t('value (optional)')"
|
||||
data-testid="tag-value-input"
|
||||
@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"
|
||||
data-testid="tag-submit-button"
|
||||
@click.stop="addDraftTag"
|
||||
>
|
||||
{{ $t("Add") }}
|
||||
</button>
|
||||
|
||||
<div v-if="validateDraftTag.invalid && validateDraftTag.messageKey" class="form-text text-danger mb-2">
|
||||
{{ $t(validateDraftTag.messageKey, validateDraftTag.messageParams) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click.stop="clearStagingAndCloseModal">{{ $t("Cancel") }}</button>
|
||||
<button type="button" class="btn btn-outline-primary me-2" :disabled="processing || validateDraftTag.invalid" @click.stop="stageCurrentTag">
|
||||
{{ $t("Add Another Tag") }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" :disabled="processing || (stagedForBatchAdd.length === 0 && validateDraftTag.invalid)" data-testid="add-tags-final-button" @click.stop="confirmAndCommitStagedTags">{{ $t("Done") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -176,71 +180,146 @@ export default {
|
|||
newTags: [],
|
||||
/** @type {Tag[]} */
|
||||
deleteTags: [],
|
||||
/**
|
||||
* @type {Array<object>} Holds tag objects staged for addition.
|
||||
* Each object: { name, color, value, isNewSystemTag, systemTagId, keyForList }
|
||||
*/
|
||||
stagedForBatchAdd: [],
|
||||
newDraftTag: {
|
||||
name: null,
|
||||
select: null,
|
||||
color: null,
|
||||
value: "",
|
||||
invalid: true,
|
||||
nameInvalid: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tagOptions() {
|
||||
const tagOptions = this.existingTags;
|
||||
const tagOptions = [ ...this.existingTags ]; // Create a copy
|
||||
|
||||
// Add tags from newTags
|
||||
for (const tag of this.newTags) {
|
||||
if (!tagOptions.find(t => t.name === tag.name && t.color === tag.color)) {
|
||||
tagOptions.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
// Add newly created system tags from staging area
|
||||
for (const stagedTag of this.stagedForBatchAdd) {
|
||||
if (stagedTag.isNewSystemTag) {
|
||||
// Check if this system tag is already in the options
|
||||
if (!tagOptions.find(t => t.name === stagedTag.name && t.color === stagedTag.color)) {
|
||||
// Create a tag option object for the dropdown
|
||||
tagOptions.push({
|
||||
id: null, // Will be assigned when actually created
|
||||
name: stagedTag.name,
|
||||
color: stagedTag.color
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tagOptions;
|
||||
},
|
||||
selectedTags() {
|
||||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.tag_id === tag.tag_id));
|
||||
// Helper function to normalize tag values for comparison
|
||||
const normalizeValue = (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
return String(value).trim();
|
||||
};
|
||||
|
||||
// Helper function to get tag ID from different structures
|
||||
const getTagId = (tag) => tag.tag_id || tag.id;
|
||||
|
||||
return this.preSelectedTags.concat(this.newTags).filter(tag =>
|
||||
!this.deleteTags.find(monitorTag => {
|
||||
const tagIdMatch = getTagId(monitorTag) === getTagId(tag);
|
||||
const valueMatch = normalizeValue(monitorTag.value) === normalizeValue(tag.value);
|
||||
return tagIdMatch && valueMatch;
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @returns {boolean} True if more new system tags can be staged, false otherwise.
|
||||
*/
|
||||
canStageMoreNewSystemTags() {
|
||||
return true; // Always allow adding more tags, no limit
|
||||
},
|
||||
/**
|
||||
* Provides the color options for the tag color selector.
|
||||
* @returns {Array<object>} Array of color options.
|
||||
*/
|
||||
colorOptions() {
|
||||
return colorOptions(this);
|
||||
},
|
||||
/**
|
||||
* Validates the current draft tag based on several conditions.
|
||||
* @returns {{invalid: boolean, messageKey: string|null, messageParams: object|null}} Object indicating validity, and a message key/params if invalid.
|
||||
*/
|
||||
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 && this.newDraftTag.select == null) {
|
||||
// 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;
|
||||
// If defining a new system tag (newDraftTag.select == null)
|
||||
if (this.newDraftTag.select == null) {
|
||||
if (!this.newDraftTag.name || this.newDraftTag.name.trim() === "" || !this.newDraftTag.color) {
|
||||
// Keep button disabled, but don't show the explicit message for this case
|
||||
return {
|
||||
invalid: true,
|
||||
messageKey: null,
|
||||
messageParams: null,
|
||||
};
|
||||
}
|
||||
if (this.tagOptions.find(opt => opt.name.toLowerCase() === this.newDraftTag.name.trim().toLowerCase())) {
|
||||
return {
|
||||
invalid: true,
|
||||
messageKey: "tagNameExists",
|
||||
messageParams: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// For any tag definition (new or existing system tag + value)
|
||||
const draftTagName = this.newDraftTag.select ? this.newDraftTag.select.name : this.newDraftTag.name.trim();
|
||||
const draftTagValue = this.newDraftTag.value ? this.newDraftTag.value.trim() : ""; // Treat null/undefined value as empty string for comparison
|
||||
|
||||
// Check if (name + value) combination already exists in this.stagedForBatchAdd
|
||||
if (this.stagedForBatchAdd.find(staged => staged.name === draftTagName && staged.value === draftTagValue)) {
|
||||
return {
|
||||
invalid: true,
|
||||
messageKey: "tagAlreadyStaged",
|
||||
messageParams: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if (name + value) combination already exists in this.selectedTags (final list on monitor)
|
||||
// AND it's NOT an "undo delete"
|
||||
const isUndoDelete = this.deleteTags.find(dTag =>
|
||||
dTag.tag_id === (this.newDraftTag.select ? this.newDraftTag.select.id : null) &&
|
||||
dTag.value === draftTagValue
|
||||
);
|
||||
|
||||
if (!isUndoDelete && this.selectedTags.find(sTag => sTag.name === draftTagName && sTag.value === draftTagValue)) {
|
||||
return {
|
||||
invalid: true,
|
||||
messageKey: "tagAlreadyOnMonitor",
|
||||
messageParams: null,
|
||||
};
|
||||
}
|
||||
// If an existing tag is selected at this point, it has passed all relevant checks
|
||||
if (this.newDraftTag.select != null) {
|
||||
return {
|
||||
invalid: false,
|
||||
messageKey: null,
|
||||
messageParams: null,
|
||||
};
|
||||
}
|
||||
|
||||
// If it's a new tag definition, and it passed its specific checks, it's valid.
|
||||
// (This also serves as a final default to valid if other logic paths were missed, though ideally covered above)
|
||||
return {
|
||||
invalid,
|
||||
nameInvalid,
|
||||
valueInvalid,
|
||||
invalid: false,
|
||||
messageKey: null,
|
||||
messageParams: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -248,12 +327,18 @@ export default {
|
|||
this.modal = new Modal(this.$refs.modal);
|
||||
this.getExistingTags();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.cleanupModal();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Show the add tag dialog
|
||||
* @returns {void}
|
||||
*/
|
||||
showAddDialog() {
|
||||
this.stagedForBatchAdd = [];
|
||||
this.clearDraftTag();
|
||||
this.getExistingTags();
|
||||
this.modal.show();
|
||||
},
|
||||
/**
|
||||
|
@ -297,37 +382,6 @@ export default {
|
|||
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a draft tag
|
||||
* @returns {void}
|
||||
*/
|
||||
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();
|
||||
},
|
||||
/**
|
||||
* Remove a draft tag
|
||||
* @returns {void}
|
||||
|
@ -338,10 +392,8 @@ export default {
|
|||
select: null,
|
||||
color: null,
|
||||
value: "",
|
||||
invalid: true,
|
||||
nameInvalid: false,
|
||||
// invalid: true, // Initial validation will be handled by computed prop
|
||||
};
|
||||
this.modal.hide();
|
||||
},
|
||||
/**
|
||||
* Add a tag asynchronously
|
||||
|
@ -383,7 +435,7 @@ export default {
|
|||
*/
|
||||
onEnter() {
|
||||
if (!this.validateDraftTag.invalid) {
|
||||
this.addDraftTag();
|
||||
this.stageCurrentTag();
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -459,7 +511,132 @@ export default {
|
|||
this.newTags = [];
|
||||
this.deleteTags = [];
|
||||
this.processing = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Clean up modal and restore scroll behavior
|
||||
* @returns {void}
|
||||
*/
|
||||
cleanupModal() {
|
||||
if (this.modal) {
|
||||
try {
|
||||
this.modal.hide();
|
||||
} catch (e) {
|
||||
console.warn("Modal hide failed:", e);
|
||||
}
|
||||
}
|
||||
this.stagedForBatchAdd = [];
|
||||
},
|
||||
/**
|
||||
* Stages the current draft tag for batch addition.
|
||||
* @returns {void}
|
||||
*/
|
||||
stageCurrentTag() {
|
||||
if (this.validateDraftTag.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isNew = this.newDraftTag.select == null;
|
||||
const name = isNew ? this.newDraftTag.name.trim() : this.newDraftTag.select.name;
|
||||
const color = isNew ? this.newDraftTag.color.color : this.newDraftTag.select.color;
|
||||
const value = this.newDraftTag.value ? this.newDraftTag.value.trim() : "";
|
||||
|
||||
const stagedTagObject = {
|
||||
name: name,
|
||||
color: color,
|
||||
value: value,
|
||||
isNewSystemTag: isNew,
|
||||
systemTagId: isNew ? null : this.newDraftTag.select.id,
|
||||
keyForList: `staged-${Date.now()}-${Math.random().toString(36).substring(2, 15)}` // Unique key
|
||||
};
|
||||
|
||||
this.stagedForBatchAdd.push(stagedTagObject);
|
||||
this.clearDraftTag(); // Reset input fields for the next tag
|
||||
},
|
||||
/**
|
||||
* Removes a tag from the staged list.
|
||||
* @param {object} tagToUnstage The tag object to remove from staging.
|
||||
* @returns {void}
|
||||
*/
|
||||
unstageTag(tagToUnstage) {
|
||||
this.stagedForBatchAdd = this.stagedForBatchAdd.filter(tag => tag.keyForList !== tagToUnstage.keyForList);
|
||||
},
|
||||
/**
|
||||
* Maps a staged tag object to the structure expected by the Tag component.
|
||||
* @param {object} stagedTag The staged tag object.
|
||||
* @returns {object} Object with name, color, value for the Tag component.
|
||||
*/
|
||||
mapStagedTagToDisplayItem(stagedTag) {
|
||||
return {
|
||||
name: stagedTag.name,
|
||||
color: stagedTag.color,
|
||||
value: stagedTag.value,
|
||||
// id: stagedTag.keyForList, // Pass keyForList as id for the Tag component if it expects an id for display/keying internally beyond v-for key
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Clears the staging list, draft inputs, and closes the modal.
|
||||
* @returns {void}
|
||||
*/
|
||||
clearStagingAndCloseModal() {
|
||||
this.stagedForBatchAdd = [];
|
||||
this.clearDraftTag(); // Clears input fields
|
||||
this.modal.hide();
|
||||
},
|
||||
/**
|
||||
* Processes all staged tags, adds them to the monitor, and closes the modal.
|
||||
* @returns {void}
|
||||
*/
|
||||
confirmAndCommitStagedTags() {
|
||||
// Phase 1: If there's a currently valid newDraftTag that hasn't been staged yet,
|
||||
// (e.g. user typed a full tag and directly clicked the footer "Add"), then stage it now.
|
||||
// stageCurrentTag has its own check for validateDraftTag.invalid and will clear the draft.
|
||||
if (!this.validateDraftTag.invalid) {
|
||||
// Check if newDraftTag actually has content, to avoid staging an empty cleared draft.
|
||||
// A valid draft implies it has content, but double-checking select or name is safer.
|
||||
if (this.newDraftTag.select || (this.newDraftTag.name && this.newDraftTag.color)) {
|
||||
this.stageCurrentTag();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Process everything that is now in stagedForBatchAdd.
|
||||
if (this.stagedForBatchAdd.length === 0) {
|
||||
this.clearDraftTag(); // Ensure draft is clear even if nothing was committed
|
||||
this.modal.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const sTag of this.stagedForBatchAdd) {
|
||||
let isAnUndo = false; // Flag to track if this was an undo
|
||||
// Check if it's an "undo delete"
|
||||
if (sTag.systemTagId) { // Only existing system tags can be an undo delete
|
||||
const undoDeleteIndex = this.deleteTags.findIndex(
|
||||
dTag => dTag.tag_id === sTag.systemTagId && dTag.value === sTag.value
|
||||
);
|
||||
if (undoDeleteIndex > -1) {
|
||||
this.deleteTags.splice(undoDeleteIndex, 1);
|
||||
isAnUndo = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only add to newTags if it's not an "undo delete" operation.
|
||||
// An "undo delete" means the tag is now considered active again from its previous state.
|
||||
if (!isAnUndo) {
|
||||
const tagObjectForNewTags = {
|
||||
id: sTag.systemTagId, // This will be null for brand new system tags
|
||||
color: sTag.color,
|
||||
name: sTag.name,
|
||||
value: sTag.value,
|
||||
new: true, // As per plan, signals new to this monitor transaction
|
||||
};
|
||||
this.newTags.push(tagObjectForNewTags);
|
||||
}
|
||||
}
|
||||
|
||||
// newDraftTag should have been cleared if stageCurrentTag ran in Phase 1, or earlier.
|
||||
// Call clearDraftTag again to be certain the form is reset before closing.
|
||||
this.clearDraftTag();
|
||||
this.modal.hide();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -53,6 +53,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input id="discord-disable-url" v-model="$parent.notification.disableUrl" class="form-check-input" type="checkbox" role="switch">
|
||||
<label class="form-check-label" for="discord-disable-url">{{ $t("Disable URL in Notification") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
@ -60,6 +67,9 @@ export default {
|
|||
if (!this.$parent.notification.discordChannelType) {
|
||||
this.$parent.notification.discordChannelType = "channel";
|
||||
}
|
||||
if (this.$parent.notification.disableUrl === undefined) {
|
||||
this.$parent.notification.disableUrl = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="flashduty-integration-url" class="form-label">Integration Key</label>
|
||||
<HiddenInput id="flashduty-integration-url" v-model="$parent.notification.flashdutyIntegrationKey" autocomplete="false"></HiddenInput>
|
||||
<label for="flashduty-integration-url" class="form-label">{{ $t("FlashDuty Push URL") }} <span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="flashduty-integration-url" v-model="$parent.notification.flashdutyIntegrationKey" autocomplete="false" :placeholder="$t('FlashDuty Push URL Placeholder')" />
|
||||
<div class="form-text">
|
||||
<p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p>
|
||||
</div>
|
||||
<i18n-t tag="div" keypath="wayToGetFlashDutyKey" class="form-text">
|
||||
<a href="https://flashcat.cloud/product/flashduty?from=kuma" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
|
@ -18,7 +21,6 @@
|
|||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{{ $t("matrixDesc1") }}
|
||||
</p>
|
||||
<i18n-t tag="p" keypath="matrixDesc2" style="margin-top: 8px;">
|
||||
<code>curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login"</code>.
|
||||
<code>curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/v3/login"</code>.
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
||||
|
|
49
src/components/notifications/Notifery.vue
Normal file
49
src/components/notifications/Notifery.vue
Normal file
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="notifery-api-key" class="form-label">{{
|
||||
$t("API Key")
|
||||
}}</label>
|
||||
<HiddenInput
|
||||
id="notifery-api-key"
|
||||
v-model="$parent.notification.notiferyApiKey"
|
||||
:required="true"
|
||||
autocomplete="new-password"
|
||||
></HiddenInput>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="notifery-title" class="form-label">{{ $t("Title") }}</label>
|
||||
<input
|
||||
id="notifery-title"
|
||||
v-model="$parent.notification.notiferyTitle"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Uptime Kuma Alert"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="notifery-group" class="form-label">{{ $t("Group") }}</label>
|
||||
<input
|
||||
id="notifery-group"
|
||||
v-model="$parent.notification.notiferyGroup"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:placeholder="$t('Optional')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
<a href="https://docs.notifery.com/api/event/" target="_blank">https://docs.notifery.com/api/event/</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -13,13 +13,20 @@
|
|||
<div class="mb-3">
|
||||
<label for="ntfy-priority" class="form-label">{{ $t("Priority") }}</label>
|
||||
<input id="ntfy-priority" v-model="$parent.notification.ntfyPriority" type="number" class="form-control" required min="1" max="5" step="1">
|
||||
<label for="ntfy-priority-down" class="form-label">{{ $t("ntfyPriorityDown") }}</label>
|
||||
<input id="ntfy-priority-down" v-model="$parent.notification.ntfyPriorityDown" type="number" class="form-control" required min="1" max="5" step="1">
|
||||
<div class="form-text">
|
||||
<p v-if="$parent.notification.ntfyPriority >= 5">
|
||||
<p v-if="$parent.notification.ntfyPriority == $parent.notification.ntfyPriorityDown && $parent.notification.ntfyPriority >= 5">
|
||||
{{ $t("ntfyPriorityHelptextAllEvents") }}
|
||||
</p>
|
||||
<i18n-t v-else-if="$parent.notification.ntfyPriority > $parent.notification.ntfyPriorityDown" tag="p" keypath="ntfyPriorityHelptextPriorityHigherThanDown">
|
||||
<code>DOWN</code>
|
||||
<code>{{ $parent.notification.ntfyPriority }}</code>
|
||||
<code>{{ $parent.notification.ntfyPriorityDown }}</code>
|
||||
</i18n-t>
|
||||
<i18n-t v-else tag="p" keypath="ntfyPriorityHelptextAllExceptDown">
|
||||
<code>DOWN</code>
|
||||
<code>{{ $parent.notification.ntfyPriority + 1 }}</code>
|
||||
<code>{{ $parent.notification.ntfyPriorityDown }}</code>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,6 +76,11 @@ export default {
|
|||
this.$parent.notification.ntfyPriority = 5;
|
||||
}
|
||||
|
||||
// Setting down priority if it's undefined
|
||||
if (typeof this.$parent.notification.ntfyPriorityDown === "undefined") {
|
||||
this.$parent.notification.ntfyPriorityDown = 5;
|
||||
}
|
||||
|
||||
// Handling notifications that added before 1.22.0
|
||||
if (typeof this.$parent.notification.ntfyAuthenticationMethod === "undefined") {
|
||||
if (!this.$parent.notification.ntfyusername) {
|
||||
|
|
64
src/components/notifications/OneChat.vue
Normal file
64
src/components/notifications/OneChat.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<!-- Access Token Input -->
|
||||
<div class="mb-3">
|
||||
<label for="onechat-access-token" class="form-label">
|
||||
OneChat Access Token<span style="color: red;"><sup>*</sup></span>
|
||||
</label>
|
||||
<HiddenInput
|
||||
id="onechat-access-token"
|
||||
v-model="$parent.notification.accessToken"
|
||||
:required="true"
|
||||
>
|
||||
</HiddenInput>
|
||||
<div class="form-text">
|
||||
<p>{{ $t("OneChatAccessToken") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Receiver ID Input -->
|
||||
<div class="mb-3">
|
||||
<label for="onechat-receiver-id" class="form-label">
|
||||
{{ $t("OneChatUserIdOrGroupId") }}<span style="color: red;"><sup>*</sup></span>
|
||||
</label>
|
||||
<input
|
||||
id="onechat-receiver-id"
|
||||
v-model="$parent.notification.recieverId"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Bot ID Input -->
|
||||
<div class="mb-3">
|
||||
<label for="onechat-bot-id" class="form-label">
|
||||
{{ $t("OneChatBotId") }}<span style="color: red;"><sup>*</sup></span>
|
||||
</label>
|
||||
<input
|
||||
id="onechat-bot-id"
|
||||
v-model="$parent.notification.botId"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Document Link -->
|
||||
<div class="form-text">
|
||||
<i18n-t tag="p" keypath="Read more:">
|
||||
<a href="https://chat-develop.one.th/docs" target="_blank">https://chat-develop.one.th/docs</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
9
src/components/notifications/Pumble.vue
Normal file
9
src/components/notifications/Pumble.vue
Normal file
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pumble-webhook-url" class="form-label mb-2">{{ $t("Webhook URL") }}</label><span style="color: red;"><sup>*</sup></span>
|
||||
<input id="pumble-webhook-url" v-model="$parent.notification.webhookURL" type="url" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<a href="https://pumble.com/help/integrations/add-pumble-apps/incoming-webhooks-for-pumble/" target="_blank">{{ $t("documentationOf", ["Pumble Webbhook"]) }}</a>
|
||||
</div>
|
||||
</template>
|
|
@ -16,34 +16,24 @@
|
|||
<option>1</option>
|
||||
<option>2</option>
|
||||
</select>
|
||||
<label for="pushover-sound" class="form-label">{{ $t("Notification Sound") }}</label>
|
||||
<select id="pushover-sound" v-model="$parent.notification.pushoversounds" class="form-select">
|
||||
<option value="pushover">{{ $t("pushoversounds pushover") }}</option>
|
||||
<option value="bike">{{ $t("pushoversounds bike") }}</option>
|
||||
<option value="bugle">{{ $t("pushoversounds bugle") }}</option>
|
||||
<option value="cashregister">{{ $t("pushoversounds cashregister") }}</option>
|
||||
<option value="classical">{{ $t("pushoversounds classical") }}</option>
|
||||
<option value="cosmic">{{ $t("pushoversounds cosmic") }}</option>
|
||||
<option value="falling">{{ $t("pushoversounds falling") }}</option>
|
||||
<option value="gamelan">{{ $t("pushoversounds gamelan") }}</option>
|
||||
<option value="incoming">{{ $t("pushoversounds incoming") }}</option>
|
||||
<option value="intermission">{{ $t("pushoversounds intermission") }}</option>
|
||||
<option value="magic">{{ $t("pushoversounds magic") }}</option>
|
||||
<option value="mechanical">{{ $t("pushoversounds mechanical") }}</option>
|
||||
<option value="pianobar">{{ $t("pushoversounds pianobar") }}</option>
|
||||
<option value="siren">{{ $t("pushoversounds siren") }}</option>
|
||||
<option value="spacealarm">{{ $t("pushoversounds spacealarm") }}</option>
|
||||
<option value="tugboat">{{ $t("pushoversounds tugboat") }}</option>
|
||||
<option value="alien">{{ $t("pushoversounds alien") }}</option>
|
||||
<option value="climb">{{ $t("pushoversounds climb") }}</option>
|
||||
<option value="persistent">{{ $t("pushoversounds persistent") }}</option>
|
||||
<option value="echo">{{ $t("pushoversounds echo") }}</option>
|
||||
<option value="updown">{{ $t("pushoversounds updown") }}</option>
|
||||
<option value="vibrate">{{ $t("pushoversounds vibrate") }}</option>
|
||||
<option value="none">{{ $t("pushoversounds none") }}</option>
|
||||
|
||||
<label for="pushover-sound-down" class="form-label">{{ $t("Notification Sound") }} - Up</label>
|
||||
<select id="pushover-sound-down" v-model="$parent.notification.pushoversounds" class="form-select">
|
||||
<option v-for="sound in soundOptions" :key="sound" :value="sound">
|
||||
{{ $t(`pushoversounds ${sound}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label for="pushover-sound-up" class="form-label">{{ $t("Notification Sound") }} - Down</label>
|
||||
<select id="pushover-sound-up" v-model="$parent.notification.pushoversounds_up" class="form-select">
|
||||
<option v-for="sound in soundOptions" :key="sound" :value="sound">
|
||||
{{ $t(`pushoversounds ${sound}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label for="pushover-ttl" class="form-label">{{ $t("pushoverMessageTtl") }}</label>
|
||||
<input id="pushover-ttl" v-model="$parent.notification.pushoverttl" type="number" min="0" step="1" class="form-control">
|
||||
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
|
@ -66,5 +56,34 @@ export default {
|
|||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
soundOptions: [
|
||||
"pushover",
|
||||
"bike",
|
||||
"bugle",
|
||||
"cashregister",
|
||||
"classical",
|
||||
"cosmic",
|
||||
"falling",
|
||||
"gamelan",
|
||||
"incoming",
|
||||
"intermission",
|
||||
"magic",
|
||||
"mechanical",
|
||||
"pianobar",
|
||||
"siren",
|
||||
"spacealarm",
|
||||
"tugboat",
|
||||
"alien",
|
||||
"climb",
|
||||
"persistent",
|
||||
"echo",
|
||||
"updown",
|
||||
"vibrate",
|
||||
"none",
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,31 +1,123 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-url" class="form-label">{{ $t("smseagleUrl") }}</label>
|
||||
<input id="smseagle-url" v-model="$parent.notification.smseagleUrl" type="text" minlength="7" class="form-control" placeholder="http://127.0.0.1" required>
|
||||
<input
|
||||
id="smseagle-url" v-model="$parent.notification.smseagleUrl" type="text" minlength="7"
|
||||
class="form-control" placeholder="http://127.0.0.1" required
|
||||
>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-token" class="form-label">{{ $t("smseagleToken") }}</label>
|
||||
<HiddenInput id="smseagle-token" v-model="$parent.notification.smseagleToken" :required="true"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient-type" class="form-label">{{ $t("smseagleRecipientType") }}</label>
|
||||
<select id="smseagle-recipient-type" v-model="$parent.notification.smseagleRecipientType" class="form-select">
|
||||
<option value="smseagle-to" selected>{{ $t("smseagleTo") }}</option>
|
||||
<option value="smseagle-group">{{ $t("smseagleGroup") }}</option>
|
||||
<option value="smseagle-contact">{{ $t("smseagleContact") }}</option>
|
||||
<label for="smseagle-api-type" class="form-label">{{ $t("smseagleApiType") }} </label>
|
||||
<select id="smseagle-api-type" v-model="$parent.notification.smseagleApiType" class="form-select">
|
||||
<option value="smseagle-apiv1" selected>{{ $t("smseagleApiv1") }} </option>
|
||||
<option value="smseagle-apiv2">{{ $t("smseagleApiv2") }} </option>
|
||||
</select>
|
||||
<i18n-t tag="div" keypath="smseagleDocs" class="form-text">
|
||||
<a href="https://www.smseagle.eu/api/" target="_blank">https://www.smseagle.eu/api/</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient" class="form-label">{{ $t("smseagleRecipient") }}</label>
|
||||
<input id="smseagle-recipient" v-model="$parent.notification.smseagleRecipient" type="text" class="form-control" required>
|
||||
<div v-if="$parent.notification.smseagleApiType === 'smseagle-apiv1'" class="mb-3">
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient-type" class="form-label">{{ $t("smseagleRecipientType") }}</label>
|
||||
<select
|
||||
id="smseagle-recipient-type" v-model="$parent.notification.smseagleRecipientType"
|
||||
class="form-select"
|
||||
>
|
||||
<!-- phone number -->
|
||||
<option value="smseagle-to" selected>{{ $t("smseagleTo") }}</option>
|
||||
<option value="smseagle-group">{{ $t("smseagleGroup") }}</option>
|
||||
<option value="smseagle-contact">{{ $t("smseagleContact") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient" class="form-label">{{ $t("smseagleRecipient") }}</label>
|
||||
<input id="smseagle-recipient" v-model="$parent.notification.smseagleRecipient" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div
|
||||
v-if="$parent.notification.smseagleMsgType === 'smseagle-sms'
|
||||
|| $parent.notification.smseagleRecipientType !== 'smseagle-to'" class="mb-3"
|
||||
>
|
||||
<label for="smseagle-priority" class="form-label">{{ $t("smseaglePriority") }}</label>
|
||||
<input id="smseagle-priority" v-model="$parent.notification.smseaglePriority" type="number" class="form-control" min="0" max="9" step="1" placeholder="0" required>
|
||||
</div>
|
||||
<div
|
||||
v-if="$parent.notification.smseagleMsgType === 'smseagle-sms'
|
||||
|| $parent.notification.smseagleRecipientType !== 'smseagle-to'" class="mb-3 form-check form-switch"
|
||||
>
|
||||
<label for="smseagle-encoding" class="form-label">{{ $t("smseagleEncoding") }}</label>
|
||||
<input id="smseagle-encoding" v-model="$parent.notification.smseagleEncoding" type="checkbox" class="form-check-input">
|
||||
</div>
|
||||
<div v-if="$parent.notification.smseagleRecipientType === 'smseagle-to'" class="mb-3">
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-msg-type" class="form-label">{{ $t("smseagleMsgType") }} </label>
|
||||
<select id="smseagle-msg-type" v-model="$parent.notification.smseagleMsgType" class="form-select">
|
||||
<option value="smseagle-sms" selected>{{ $t("smseagleMsgSms") }} </option>
|
||||
<option value="smseagle-ring">{{ $t("smseagleMsgRing") }} </option>
|
||||
<option value="smseagle-tts">{{ $t("smseagleMsgTts") }} </option>
|
||||
<option value="smseagle-tts-advanced">{{ $t("smseagleMsgTtsAdvanced") }} </option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
v-if="$parent.notification.smseagleMsgType === 'smseagle-ring'
|
||||
|| $parent.notification.smseagleMsgType === 'smseagle-tts'
|
||||
|| $parent.notification.smseagleMsgType === 'smseagle-tts-advanced'" class="mb-3"
|
||||
>
|
||||
<label for="smseagle-duration" class="form-label">{{ $t("smseagleDuration") }}</label>
|
||||
<input id="smseagle-duration" v-model="$parent.notification.smseagleDuration" type="number" class="form-control" min="0" max="30" step="1" placeholder="10">
|
||||
</div>
|
||||
<div v-if="$parent.notification.smseagleMsgType === 'smseagle-tts-advanced'" class="mb-3">
|
||||
<label for="smseagle-tts-model" class="form-label">{{ $t("smseagleTtsModel") }} </label>
|
||||
<input id="smseagle-tts-model" v-model="$parent.notification.smseagleTtsModel" type="number" class="form-control" placeholder="1" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-priority" class="form-label">{{ $t("smseaglePriority") }}</label>
|
||||
<input id="smseagle-priority" v-model="$parent.notification.smseaglePriority" type="number" class="form-control" min="0" max="9" step="1" placeholder="0">
|
||||
</div>
|
||||
<div class="mb-3 form-check form-switch">
|
||||
<label for="smseagle-encoding" class="form-label">{{ $t("smseagleEncoding") }}</label>
|
||||
<input id="smseagle-encoding" v-model="$parent.notification.smseagleEncoding" type="checkbox" class="form-check-input">
|
||||
|
||||
<div v-if="$parent.notification.smseagleApiType === 'smseagle-apiv2'" class="mb-3">
|
||||
<div class="mb-3">
|
||||
<!-- phone number -->
|
||||
<label for="smseagle-recipient-to" class="form-label">{{ $t("smseagleTo") }}</label>
|
||||
<input id="smseagle-recipient-to" v-model="$parent.notification.smseagleRecipientTo" type="text" class="form-control">
|
||||
<i18n-t tag="div" keypath="smseagleComma" class="form-text" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient-group" class="form-label">{{ $t("smseagleGroupV2") }}</label>
|
||||
<input id="smseagle-recipient-group" v-model="$parent.notification.smseagleRecipientGroup" type="text" class="form-control">
|
||||
<i18n-t tag="div" keypath="smseagleComma" class="form-text" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient-contact" class="form-label">{{ $t("smseagleContactV2") }}</label>
|
||||
<input id="smseagle-recipient-contact" v-model="$parent.notification.smseagleRecipientContact" type="text" class="form-control">
|
||||
<i18n-t tag="div" keypath="smseagleComma" class="form-text" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-priority-v2" class="form-label">{{ $t("smseaglePriority") }}</label>
|
||||
<input id="smseagle-priority-v2" v-model="$parent.notification.smseaglePriority" type="number" class="form-control" min="0" max="9" step="1" placeholder="0">
|
||||
</div>
|
||||
<div class="mb-3 form-check form-switch">
|
||||
<label for="smseagle-encoding-v2" class="form-label">{{ $t("smseagleEncoding") }}</label>
|
||||
<input id="smseagle-encoding-v2" v-model="$parent.notification.smseagleEncoding" type="checkbox" class="form-check-input">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-msg-type-v2" class="form-label">{{ $t("smseagleMsgType") }} </label>
|
||||
<select id="smseagle-msg-type-v2" v-model="$parent.notification.smseagleMsgType" class="form-select">
|
||||
<option value="smseagle-sms" selected>{{ $t("smseagleMsgSms") }} </option>
|
||||
<option value="smseagle-ring">{{ $t("smseagleMsgRing") }} </option>
|
||||
<option value="smseagle-tts">{{ $t("smseagleMsgTts") }} </option>
|
||||
<option value="smseagle-tts-advanced">{{ $t("smseagleMsgTtsAdvanced") }} </option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="$parent.notification.smseagleMsgType && $parent.notification.smseagleMsgType !== 'smseagle-sms'" class="mb-3">
|
||||
<label for="smseagle-duration-v2" class="form-label">{{ $t("smseagleDuration") }}</label>
|
||||
<input id="smseagle-duration-v2" v-model="$parent.notification.smseagleDuration" type="number" class="form-control" min="0" max="30" step="1" placeholder="10">
|
||||
</div>
|
||||
<div v-if="$parent.notification.smseagleMsgType === 'smseagle-tts-advanced'" class="mb-3">
|
||||
<label for="smseagle-tts-model-v2" class="form-label">{{ $t("smseagleTtsModel") }} </label>
|
||||
<input id="smseagle-tts-model-v2" v-model="$parent.notification.smseagleTtsModel" type="number" class="form-control" placeholder="1" required>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -36,5 +128,16 @@ export default {
|
|||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
mounted() {
|
||||
if (!this.$parent.notification.smseagleApiType) {
|
||||
this.$parent.notification.smseagleApiType = "smseagle-apiv1";
|
||||
}
|
||||
if (!this.$parent.notification.smseagleMsgType) {
|
||||
this.$parent.notification.smseagleMsgType = "smseagle-sms";
|
||||
}
|
||||
if (!this.$parent.notification.smseagleRecipientType) {
|
||||
this.$parent.notification.smseagleRecipientType = "smseagle-to";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
46
src/components/notifications/SMSPlanet.vue
Normal file
46
src/components/notifications/SMSPlanet.vue
Normal file
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
|
||||
<HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
|
||||
<i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
|
||||
<template #the_smsplanet_documentation>
|
||||
<a
|
||||
href="https://smsplanet.pl/doc/slate/index.html#introduction"
|
||||
target="_blank"
|
||||
>{{ $t("the smsplanet documentation") }}</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smsplanet-phone-numbers" class="form-label">{{ $t("Phone numbers") }}</label>
|
||||
<textarea
|
||||
id="smsplanet-phone-numbers"
|
||||
v-model="$parent.notification.smsplanetPhoneNumbers"
|
||||
class="form-control"
|
||||
:placeholder="smsplanetPhoneNumbers"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smsplanet-sender-name" class="form-label">{{ $t("Sender name") }}</label>
|
||||
<input id="smsplanet-sender-name" v-model="$parent.notification.smsplanetSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
<div class="form-text">{{ $t("smsplanetNeedToApproveName") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
computed: {
|
||||
smsplanetPhoneNumbers() {
|
||||
return this.$t("Example:", [
|
||||
"+48123456789,+48111222333",
|
||||
]);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -79,6 +79,15 @@
|
|||
<div class="form-text">{{ $t("leave blank for default body") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input id="use-html-body" v-model="$parent.notification.htmlBody" class="form-check-input" type="checkbox" value="">
|
||||
<label class="form-check-label" for="use-html-body">
|
||||
{{ $t("Use HTML for custom E-mail body") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToggleSection :heading="$t('smtpDkimSettings')">
|
||||
<i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3">
|
||||
<a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a>
|
||||
|
|
19
src/components/notifications/SpugPush.vue
Normal file
19
src/components/notifications/SpugPush.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="spugpush-templateKey" class="form-label">{{ $t("SpugPush Template Code") }}</label>
|
||||
<HiddenInput id="spugpush-templateKey" v-model="$parent.notification.templateKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
<a href="https://push.spug.cc/guide/plugin/kuma" rel="noopener noreferrer" target="_blank">https://push.spug.cc</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -4,6 +4,7 @@ import AliyunSMS from "./AliyunSms.vue";
|
|||
import Apprise from "./Apprise.vue";
|
||||
import Bark from "./Bark.vue";
|
||||
import Bitrix24 from "./Bitrix24.vue";
|
||||
import Notifery from "./Notifery.vue";
|
||||
import ClickSendSMS from "./ClickSendSMS.vue";
|
||||
import CallMeBot from "./CallMeBot.vue";
|
||||
import SMSC from "./SMSC.vue";
|
||||
|
@ -29,6 +30,7 @@ import Mattermost from "./Mattermost.vue";
|
|||
import Nostr from "./Nostr.vue";
|
||||
import Ntfy from "./Ntfy.vue";
|
||||
import Octopush from "./Octopush.vue";
|
||||
import OneChat from "./OneChat.vue";
|
||||
import OneBot from "./OneBot.vue";
|
||||
import Onesender from "./Onesender.vue";
|
||||
import Opsgenie from "./Opsgenie.vue";
|
||||
|
@ -36,6 +38,7 @@ import PagerDuty from "./PagerDuty.vue";
|
|||
import FlashDuty from "./FlashDuty.vue";
|
||||
import PagerTree from "./PagerTree.vue";
|
||||
import PromoSMS from "./PromoSMS.vue";
|
||||
import Pumble from "./Pumble.vue";
|
||||
import Pushbullet from "./Pushbullet.vue";
|
||||
import PushDeer from "./PushDeer.vue";
|
||||
import Pushover from "./Pushover.vue";
|
||||
|
@ -62,6 +65,7 @@ import WeCom from "./WeCom.vue";
|
|||
import GoAlert from "./GoAlert.vue";
|
||||
import ZohoCliq from "./ZohoCliq.vue";
|
||||
import Splunk from "./Splunk.vue";
|
||||
import SpugPush from "./SpugPush.vue";
|
||||
import SevenIO from "./SevenIO.vue";
|
||||
import Whapi from "./Whapi.vue";
|
||||
import WAHA from "./WAHA.vue";
|
||||
|
@ -70,6 +74,7 @@ import WPush from "./WPush.vue";
|
|||
import SIGNL4 from "./SIGNL4.vue";
|
||||
import SendGrid from "./SendGrid.vue";
|
||||
import YZJ from "./YZJ.vue";
|
||||
import SMSPlanet from "./SMSPlanet.vue";
|
||||
|
||||
/**
|
||||
* Manage all notification form.
|
||||
|
@ -106,6 +111,7 @@ const NotificationFormList = {
|
|||
"nostr": Nostr,
|
||||
"ntfy": Ntfy,
|
||||
"octopush": Octopush,
|
||||
"OneChat": OneChat,
|
||||
"OneBot": OneBot,
|
||||
"Onesender": Onesender,
|
||||
"Opsgenie": Opsgenie,
|
||||
|
@ -113,6 +119,7 @@ const NotificationFormList = {
|
|||
"FlashDuty": FlashDuty,
|
||||
"PagerTree": PagerTree,
|
||||
"promosms": PromoSMS,
|
||||
"pumble": Pumble,
|
||||
"pushbullet": Pushbullet,
|
||||
"PushByTechulus": TechulusPush,
|
||||
"PushDeer": PushDeer,
|
||||
|
@ -135,6 +142,7 @@ const NotificationFormList = {
|
|||
"threema": Threema,
|
||||
"twilio": Twilio,
|
||||
"Splunk": Splunk,
|
||||
"SpugPush": SpugPush,
|
||||
"webhook": Webhook,
|
||||
"WeCom": WeCom,
|
||||
"GoAlert": GoAlert,
|
||||
|
@ -142,12 +150,14 @@ const NotificationFormList = {
|
|||
"ZohoCliq": ZohoCliq,
|
||||
"SevenIO": SevenIO,
|
||||
"whapi": Whapi,
|
||||
"notifery": Notifery,
|
||||
"waha": WAHA,
|
||||
"gtxmessaging": GtxMessaging,
|
||||
"Cellsynt": Cellsynt,
|
||||
"WPush": WPush,
|
||||
"SendGrid": SendGrid,
|
||||
"YZJ": YZJ,
|
||||
"SMSPlanet": SMSPlanet,
|
||||
};
|
||||
|
||||
export default NotificationFormList;
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
<label><input v-model="settings.checkBeta" type="checkbox" :disabled="!settings.checkUpdate" @change="saveSettings()" /> {{ $t("Also check beta release") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<p>{{ $t("Font Twemoji by Twitter licensed under") }} <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY 4.0</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -145,7 +145,7 @@
|
|||
"Save": "يحفظ",
|
||||
"Notifications": "إشعارات",
|
||||
"Not available, please setup.": "غير متوفر من فضلك الإعداد.",
|
||||
"Setup Notification": "إشعار الإعداد",
|
||||
"Setup Notification": "إشعار جديد",
|
||||
"Light": "نور",
|
||||
"Dark": "داكن",
|
||||
"Auto": "آلي",
|
||||
|
@ -598,7 +598,7 @@
|
|||
"Retry": "إعادة المحاولة",
|
||||
"Topic": "عنوان",
|
||||
"WeCom Bot Key": "WECOM BOT KEY",
|
||||
"Setup Proxy": "وكيل الإعداد",
|
||||
"Setup Proxy": "إعداد الوكيل",
|
||||
"Proxy Protocol": "بروتوكول الوكيل",
|
||||
"Proxy Server": "مخدم بروكسي",
|
||||
"Proxy server has authentication": "خادم الوكيل لديه مصادقة",
|
||||
|
@ -740,5 +740,26 @@
|
|||
"leave blank for default body": "اترك فارغاً ليتم تعيين النص تلقائياً",
|
||||
"emailTemplateServiceName": "اسم الخدمة",
|
||||
"emailTemplateHostnameOrURL": "اسم المضيف أو عنوان URL",
|
||||
"smspartnerPhoneNumber": "رقم الهاتف"
|
||||
"smspartnerPhoneNumber": "رقم الهاتف",
|
||||
"endDateTime": "تاريخ/وقت الإنتهاء",
|
||||
"chromeExecutableAutoDetect": "كشف تلقائي",
|
||||
"Edit Maintenance": "تعديل الصيانة",
|
||||
"smspartnerSenderName": "اسم مرسل الرسائل القصيرة",
|
||||
"telegramServerUrl": "(اختياري) رابط الخادم",
|
||||
"emailCustomisableContent": "محتوى قابل للتخصيص",
|
||||
"emailTemplateMsg": "رسالة الإشعار",
|
||||
"Select message type": "اختر نوع الرسالة",
|
||||
"Your User ID": "معرف المستخدم الخاص بك",
|
||||
"setup a new monitor group": "إنشاء مجموعة مراقبة جديدة",
|
||||
"Expires": "تاريخ الانتهاء",
|
||||
"templateStatus": "الحالة",
|
||||
"telegramUseTemplate": "استخدم قالب رسالة مخصص",
|
||||
"telegramUseTemplateDescription": "إذا تم التفعيل، سيتم إرسال الرسالة باستخدام قالب مخصص.",
|
||||
"now": "الآن",
|
||||
"-year": "-سنة",
|
||||
"and": "و",
|
||||
"Add a domain": "إضافة نطاق",
|
||||
"Remove domain": "إزالة النطاق '{0}'",
|
||||
"time ago": "منذ {0}",
|
||||
"startDateTime": "تاريخ/وقت البدء"
|
||||
}
|
||||
|
|
|
@ -933,5 +933,43 @@
|
|||
"threadForumPostID": "Трэд / ID паста",
|
||||
"whatHappensAtForumPost": "Стварыць новы пост на форуме. Гэта НЕ размяшчае паведамленні ў існуючым пасце. Для публікацыі ў існуючай публікацыі выкарыстоўвайце \"{option}\"",
|
||||
"now": "зараз",
|
||||
"-year": "-год"
|
||||
"-year": "-год",
|
||||
"telegramServerUrl": "(Неабавязкова) URL сервера",
|
||||
"telegramServerUrlDescription": "Каб зняць абмежаванні API бота Telegram або атрымаць доступ у заблакіраваных рэгіёнах (Кітай, Іран і інш.), націсніце {0} для атрымання дадатковай інфармацыі. Значэнне па змаўчанні: {1}",
|
||||
"smspartnerPhoneNumber": "Нумар(ы) тэлефона",
|
||||
"smspartnerSenderName": "Імя адпраўніка SMS",
|
||||
"Command": "Каманда",
|
||||
"mongodbCommandDescription": "Выканаць каманду MongoDB для базы даных. Інфармацыю пра даступныя каманды можна знайсці ў {documentation}",
|
||||
"Community String": "Радок супольнасці",
|
||||
"snmpCommunityStringHelptext": "Гэты радок выконвае функцыю пароля для аўтэнтыфікацыі і кантролю доступу да прылад з падтрымкай SNMP. Павінен адпавядаць канфігурацыі вашай SNMP-прылады.",
|
||||
"OID (Object Identifier)": "OID (Ідэнтыфікатар аб’екта)",
|
||||
"snmpOIDHelptext": "Увядзіце OID для датчыка або статусу, які вы хочаце маніторыць. Выкарыстоўвайце інструменты кіравання сеткай, такія як аглядальнікі MIB або праграмы SNMP, калі вы не ўпэўнены ў патрэбным OID.",
|
||||
"SNMP Version": "Версія SNMP",
|
||||
"Please enter a valid OID.": "Калі ласка, увядзіце карэктны OID.",
|
||||
"wayToGetThreemaGateway": "Вы можаце зарэгістравацца для Threema Gateway {0}.",
|
||||
"threemaRecipient": "Атрымальнік",
|
||||
"templateServiceName": "назва сэрвісу",
|
||||
"templateHostnameOrURL": "імя хоста або URL",
|
||||
"templateStatus": "статус",
|
||||
"telegramUseTemplate": "Выкарыстоўваць уласны шаблон паведамлення",
|
||||
"telegramUseTemplateDescription": "Калі ўключана, паведамленне будзе адпраўлена з выкарыстаннем уласнага шаблона.",
|
||||
"telegramTemplateFormatDescription": "Telegram дазваляе выкарыстоўваць розныя мовы разметкі для паведамленняў, падрабязнасці глядзіце ў Telegram {0}.",
|
||||
"cacheBusterParamDescription": "Выпадкова згенераваны параметр для абыходу кэша.",
|
||||
"Send rich messages": "Адправіць пашыраныя паведамленні",
|
||||
"Bitrix24 Webhook URL": "URL вэбхука Bitrix24",
|
||||
"wayToGetBitrix24Webhook": "Вы можаце стварыць вэбхук, выканаўшы крокі, апісаныя тут: {0}",
|
||||
"bitrix24SupportUserID": "Увядзіце свой ідэнтыфікатар карыстальніка ў Bitrix24. Вы можаце даведацца ідэнтыфікатар па спасылцы, перайшоўшы ў профіль карыстальніка.",
|
||||
"Condition": "Умова",
|
||||
"aboutSlackUsername": "Змяняе адлюстраванае імя адпраўніка паведамлення. Калі вы хочаце згадваць кагосьці, уключыце гэта ў сяброўскае імя замест гэтага.",
|
||||
"time ago": "{0} таму",
|
||||
"Json Query Expression": "Выраз запыту JSON",
|
||||
"and": "i",
|
||||
"smspartnerApiurl": "Вы можаце знайсці свой ключ API на панэлі кіравання па адрасе {0}",
|
||||
"smspartnerSenderNameInfo": "Павінна быць ад 3 да 11 звычайных сімвалаў",
|
||||
"Message format": "Фармат паведамлення",
|
||||
"ignoredTLSError": "Памылкі TLS/SSL былі праігнараваныя",
|
||||
"shrinkDatabaseDescriptionSqlite": "Запусціць аперацыю {vacuum} для SQLite. {auto_vacuum} ужо ўключаны, але ён не дэфрагментуе базу даных і не перапакоўвае асобныя старонкі базы даных так, як гэта робіць каманда {vacuum}.",
|
||||
"wayToGetDiscordThreadId": "Атрыманне ідэнтыфікатара тэмы або паведамлення на форуме аналагічнае атрыманню ідэнтыфікатара канала. Падрабязней пра тое, як атрымаць ідэнтыфікатары, чытайце тут {0}",
|
||||
"smspartnerPhoneNumberHelptext": "Нумар павінен быць у міжнародным фармаце {0}, {1}. Некалькі нумароў павінны быць падзелены {2}",
|
||||
"cacheBusterParam": "Дадайце параметр {0}"
|
||||
}
|
||||
|
|
|
@ -1117,5 +1117,12 @@
|
|||
"wayToWriteWahaChatId": "Телефонният номер с международния префикс, но без знака плюс в началото ({0}), ID на контакта ({1}) или ID на групата ({2}). Известията се изпращат до това чат ID от WAHA сесия.",
|
||||
"wayToGetWahaSession": "От тази сесия WAHA изпраща известия до чат ID. Можете да го намерите в таблото за управление на WAHA.",
|
||||
"telegramServerUrlDescription": "За премахване на API бот ограниченията за Telegram или за получаване на достъп в блокирани зони (Китай, Иран и др.). За повече информация щракнете върху {0}. По подразбиране: {1}",
|
||||
"telegramServerUrl": "(По избор) URL адрес на сървъра"
|
||||
"telegramServerUrl": "(По избор) URL адрес на сървъра",
|
||||
"Font Twemoji by Twitter licensed under": "Шрифт Twemoji от Twitter, лицензиран под",
|
||||
"the smsplanet documentation": "документацията на smsplanet",
|
||||
"Phone numbers": "Телефонни номера",
|
||||
"Sender name": "Име на подател",
|
||||
"smsplanetNeedToApproveName": "Трябва да бъде одобрен в клиентския панел",
|
||||
"smsplanetApiToken": "Токен код за SMSPlanet API",
|
||||
"smsplanetApiDocs": "Подробна информация, за получаване на API токен кодове, можете да намерите в {the_smsplanet_documentation}."
|
||||
}
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"installing": "Instal·lant",
|
||||
"uninstall": "Desinstal·la",
|
||||
"confirmUninstallPlugin": "Estàs segur de desinstal·lar aquest connector?",
|
||||
"notificationRegional": "Regional",
|
||||
"notificationRegional": "Local",
|
||||
"Clone Monitor": "Clona el monitor",
|
||||
"Clone": "Clona",
|
||||
"cloneOf": "Clon de {0}",
|
||||
|
@ -478,7 +478,7 @@
|
|||
"To Email": "Destí email",
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "BCC",
|
||||
"Discord Webhook URL": "Discord Webhook URL",
|
||||
"Discord Webhook URL": "URL del Webhook de Discord",
|
||||
"wayToGetDiscordURL": "Pots rebre aquest per anar a Paràmetres de Servidor -> Integracions -> Vista *Webhooks -> Nou *Webhook",
|
||||
"Bot Display Name": "Nom de pantalla de bot",
|
||||
"Prefix Custom Message": "Prefix de missatge personalitzat",
|
||||
|
@ -560,5 +560,14 @@
|
|||
"templateStatus": "Estat",
|
||||
"telegramUseTemplate": "Fes servir una plantilla de missatge personalitzada",
|
||||
"telegramUseTemplateDescription": "Si s'activa, el missatge s'enviarà fent servir una plantilla personalitzada.",
|
||||
"telegramTemplateFormatDescription": "Telegram permet l'ús de diferents tipus de llenguatges de marcat, llegeix Telegram {0} per més detalls."
|
||||
"telegramTemplateFormatDescription": "Telegram permet l'ús de diferents tipus de llenguatges de marcat, llegeix Telegram {0} per més detalls.",
|
||||
"wayToGetDiscordThreadId": "Obtenir un identificador de publicació del fil/fòrum és semblant a obtenir un identificador de canal. Més informació sobre com obtenir identificadors {0}",
|
||||
"lineDevConsoleTo": "Consola de desenvolupadors de linia - {0}",
|
||||
"Basic Settings": "Configuracions bàsiques",
|
||||
"User ID": "ID d'usuari",
|
||||
"Your User ID": "El teu identificador d'usuari",
|
||||
"Messaging API": "API de missatges",
|
||||
"wayToGetLineChannelToken": "Primer accediu a {0}, creeu un proveïdor i un canal (API de missatgeria) i, a continuació, podeu obtenir el token d'accés al canal i l'identificador d'usuari dels elements del menú esmentats anteriorment.",
|
||||
"Icon URL": "URL de la icona",
|
||||
"aboutIconURL": "Pots donar un enllaç a la imatge a \"URL de la icona\" per sobreposar-la a la imatge de perfil pere defecte. No s'usarà si hi ha una icona d'emoji establerta."
|
||||
}
|
||||
|
|
|
@ -1114,5 +1114,12 @@
|
|||
"wayToGetWahaApiKey": "API-Schlüssel ist der Wert der WHATSAPP_API_KEY-Umgebungsvariable, den du beim Ausführen von WAHA verwendet hast.",
|
||||
"wayToWriteWahaChatId": "Die Telefonnummer mit internationaler Vorwahl, ohne den anfänglichen Pluszeichen ({0}), die Kontakt-ID ({1}) oder die Gruppen-ID ({2}). Die Benachrichtigungen werden an diese Chat-ID von der WAHA-Sitzung gesendet.",
|
||||
"telegramServerUrl": "(Optional) Server URL",
|
||||
"telegramServerUrlDescription": "Um die Telegram-Bot-API-Beschränkungen aufzuheben oder in gesperrten Gebieten (China, Iran usw.) Zugriff zu erhalten. Weitere Informationen findest du unter {0}. Standard: {1}"
|
||||
"telegramServerUrlDescription": "Um die Telegram-Bot-API-Beschränkungen aufzuheben oder in gesperrten Gebieten (China, Iran usw.) Zugriff zu erhalten. Weitere Informationen findest du unter {0}. Standard: {1}",
|
||||
"Font Twemoji by Twitter licensed under": "Schriftart Twemoji von Twitter lizenziert unter",
|
||||
"smsplanetApiToken": "Token für die SMSPlanet API",
|
||||
"smsplanetApiDocs": "Ausführliche Informationen zum Erhalt von API-Tokens findest du in {the_smsplanet_documentation}.",
|
||||
"the smsplanet documentation": "die smsplanet Dokumentation",
|
||||
"Phone numbers": "Telefonnummern",
|
||||
"Sender name": "Absendername",
|
||||
"smsplanetNeedToApproveName": "Muss im Kundenpanel genehmigt werden"
|
||||
}
|
||||
|
|
|
@ -944,7 +944,7 @@
|
|||
"whapiRecipient": "Telefonnummer / Kontakt-ID / Gruppen-ID",
|
||||
"API URL": "API URL",
|
||||
"wayToWriteWhapiRecipient": "Die Rufnummer mit der internationalen Vorwahl, aber ohne das Pluszeichen am Anfang ({0}), die Kontakt-ID ({1}) oder die Gruppen-ID ({2}).",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Gib entweder den Hostnamen des Servers ein, mit dem eine Verbindung hergestellt werden soll, oder {localhost}, wenn ein {local_mta} verwendet werden soll.",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Gib entweder den Hostnamen des Servers ein, mit dem eine Verbindung hergestellt werden soll, oder {localhost}, wenn ein {local_mta} verwendet werden soll",
|
||||
"locally configured mail transfer agent": "Lokal konfigurierter Mail-Transfer-Agent",
|
||||
"Mentioning": "Erwähnung",
|
||||
"Don't mention people": "Keine Personen erwähnen",
|
||||
|
@ -1117,5 +1117,12 @@
|
|||
"wayToGetWahaSession": "Von dieser Sitzung aus sendet WAHA Benachrichtigungen an die Chat-ID. Du kannst sie im WAHA Dashboard finden.",
|
||||
"wayToWriteWahaChatId": "Die Telefonnummer mit internationaler Vorwahl, ohne den anfänglichen Pluszeichen ({0}), die Kontakt-ID ({1}) oder die Gruppen-ID ({2}). Die Benachrichtigungen werden an diese Chat-ID von der WAHA-Sitzung gesendet.",
|
||||
"telegramServerUrlDescription": "Um die Telegram-Bot-API-Beschränkungen aufzuheben oder in gesperrten Gebieten (China, Iran usw.) Zugriff zu erhalten. Weitere Informationen findest du unter {0}. Standard: {1}",
|
||||
"telegramServerUrl": "(Optional) Server URL"
|
||||
"telegramServerUrl": "(Optional) Server URL",
|
||||
"Font Twemoji by Twitter licensed under": "Schriftart Twemoji von Twitter lizenziert unter",
|
||||
"the smsplanet documentation": "die smsplanet Dokumentation",
|
||||
"Phone numbers": "Telefonnummern",
|
||||
"Sender name": "Absendername",
|
||||
"smsplanetNeedToApproveName": "Muss im Kundenpanel genehmigt werden",
|
||||
"smsplanetApiToken": "Token für die SMSPlanet API",
|
||||
"smsplanetApiDocs": "Ausführliche Informationen zum Erhalt von API-Tokens findest du in {the_smsplanet_documentation}."
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"Expected Value": "Expected Value",
|
||||
"Json Query Expression": "Json Query Expression",
|
||||
"Friendly Name": "Friendly Name",
|
||||
"defaultFriendlyName": "New Monitor",
|
||||
"URL": "URL",
|
||||
"Hostname": "Hostname",
|
||||
"Host URL": "Host URL",
|
||||
|
@ -187,9 +188,13 @@
|
|||
"Show URI": "Show URI",
|
||||
"Tags": "Tags",
|
||||
"Add New Tag": "Add New Tag",
|
||||
"Add Tags": "Add Tags",
|
||||
"Add New below or Select...": "Add New below or Select…",
|
||||
"Tag with this name already exist.": "Tag with this name already exists.",
|
||||
"Tag with this value already exist.": "Tag with this value already exists.",
|
||||
"tagAlreadyOnMonitor": "This tag (name and value) is already on the monitor or pending addition.",
|
||||
"tagAlreadyStaged": "This tag (name and value) is already staged for this batch.",
|
||||
"tagNameExists": "A system tag with this name already exists. Select it from the list or use a different name.",
|
||||
"color": "Color",
|
||||
"value (optional)": "value (optional)",
|
||||
"Gray": "Gray",
|
||||
|
@ -517,6 +522,7 @@
|
|||
"Clone": "Clone",
|
||||
"cloneOf": "Clone of {0}",
|
||||
"smtp": "Email (SMTP)",
|
||||
"Use HTML for custom E-mail body": "Use HTML for custom E-mail body",
|
||||
"secureOptionNone": "None / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"Ignore TLS Error": "Ignore TLS Error",
|
||||
|
@ -757,12 +763,26 @@
|
|||
"smseagleTo": "Phone number(s)",
|
||||
"smseagleGroup": "Phonebook group name(s)",
|
||||
"smseagleContact": "Phonebook contact name(s)",
|
||||
"smseagleGroupV2": "Phonebook group ID(s)",
|
||||
"smseagleContactV2": "Phonebook contact ID(s)",
|
||||
"smseagleRecipientType": "Recipient type",
|
||||
"smseagleRecipient": "Recipient(s) (multiple must be separated with comma)",
|
||||
"smseagleToken": "API Access token",
|
||||
"smseagleUrl": "Your SMSEagle device URL",
|
||||
"smseagleEncoding": "Send as Unicode",
|
||||
"smseaglePriority": "Message priority (0-9, default = 0)",
|
||||
"smseagleEncoding": "Send as Unicode (default=GSM-7)",
|
||||
"smseaglePriority": "Message priority (0-9, highest priority = 9)",
|
||||
"smseagleMsgType": "Message type",
|
||||
"smseagleMsgSms": "Sms message (default)",
|
||||
"smseagleMsgRing": "Ring call",
|
||||
"smseagleMsgTts": "Text-to-speech call",
|
||||
"smseagleMsgTtsAdvanced": "Text-to-speech Advanced call",
|
||||
"smseagleDuration": "Duration (in seconds)",
|
||||
"smseagleTtsModel": "Text-to-speech model ID",
|
||||
"smseagleApiType": "API version",
|
||||
"smseagleApiv1": "APIv1 (for existing projects and backward compatibility)",
|
||||
"smseagleApiv2": "APIv2 (recommended for new integrations)",
|
||||
"smseagleDocs": "Check documentation or APIv2 availability: {0}",
|
||||
"smseagleComma": "Multiple must be separated with comma",
|
||||
"smspartnerApiurl": "You can find your API key in your dashboard at {0}",
|
||||
"smspartnerPhoneNumber": "Phone number(s)",
|
||||
"smspartnerPhoneNumberHelptext": "The number must be in the international format {0}, {1}. Multiple numbers must be separated by {2}",
|
||||
|
@ -784,6 +804,7 @@
|
|||
"PushDeer Server": "PushDeer Server",
|
||||
"pushDeerServerDescription": "Leave blank to use the official server",
|
||||
"PushDeer Key": "PushDeer Key",
|
||||
"SpugPush Template Code": "Template Code",
|
||||
"wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
|
||||
"Custom Monitor Type": "Custom Monitor Type",
|
||||
"Google Analytics ID": "Google Analytics ID",
|
||||
|
@ -824,6 +845,8 @@
|
|||
"ntfyAuthenticationMethod": "Authentication Method",
|
||||
"ntfyPriorityHelptextAllEvents": "All events are sent with the maximum priority",
|
||||
"ntfyPriorityHelptextAllExceptDown": "All events are sent with this priority, except {0}-events, which have a priority of {1}",
|
||||
"ntfyPriorityHelptextPriorityHigherThanDown": "Regular priority should be higher than {0} priority. Priority {1} is higher than {0} priority {2}",
|
||||
"ntfyPriorityDown": "Priority for DOWN-events",
|
||||
"ntfyUsernameAndPassword": "Username and Password",
|
||||
"twilioAccountSID": "Account SID",
|
||||
"twilioApiKey": "Api Key (optional)",
|
||||
|
@ -878,8 +901,10 @@
|
|||
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
|
||||
"Close": "Close",
|
||||
"Request Body": "Request Body",
|
||||
"wayToGetFlashDutyKey": "You can go to Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, add a 'Uptime Kuma' to get a push address, copy the Integration Key in the address. For more information, please visit",
|
||||
"wayToGetFlashDutyKey": "To integrate Uptime Kuma with Flashduty: Go to Channels > Select a channel > Integrations > Add a new integration, choose Uptime Kuma, and copy the Push URL.",
|
||||
"FlashDuty Severity": "Severity",
|
||||
"FlashDuty Push URL": "Push URL",
|
||||
"FlashDuty Push URL Placeholder": "Copy from the alerting integration page",
|
||||
"nostrRelays": "Nostr relays",
|
||||
"nostrRelaysHelp": "One relay URL per line",
|
||||
"nostrSender": "Sender Private Key (nsec)",
|
||||
|
@ -1057,6 +1082,21 @@
|
|||
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
|
||||
"SendGrid API Key": "SendGrid API Key",
|
||||
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
|
||||
"pingCountLabel": "Max Packets",
|
||||
"pingCountDescription": "Number of packets to send before stopping",
|
||||
"pingNumericLabel": "Numeric Output",
|
||||
"pingNumericDescription": "If checked, IP addresses will be output instead of symbolic hostnames",
|
||||
"pingGlobalTimeoutLabel": "Global Timeout",
|
||||
"pingGlobalTimeoutDescription": "Total time in seconds before ping stops, regardless of packets sent",
|
||||
"pingPerRequestTimeoutLabel": "Per-Ping Timeout",
|
||||
"pingPerRequestTimeoutDescription": "This is the maximum waiting time (in seconds) before considering a single ping packet lost",
|
||||
"pingIntervalAdjustedInfo": "Interval adjusted based on packet count, global timeout and per-ping timeout",
|
||||
"smtpHelpText": "'SMTPS' tests that SMTP/TLS is working; 'Ignore TLS' connects over plaintext; 'STARTTLS' connects, issues a STARTTLS command and verifies the server certificate. None of these send an email.",
|
||||
"Custom URL": "Custom URL",
|
||||
"customUrlDescription": "Will be used as the clickable URL instead of the monitor's one.",
|
||||
"OneChatAccessToken": "OneChat Access Token",
|
||||
"OneChatUserIdOrGroupId": "OneChat User ID or Group ID",
|
||||
"OneChatBotId": "OneChat Bot ID",
|
||||
"wahaSession": "Session",
|
||||
"wahaChatId": "Chat ID (Phone Number / Contact ID / Group ID)",
|
||||
"wayToGetWahaApiUrl": "Your WAHA Instance URL.",
|
||||
|
@ -1067,5 +1107,21 @@
|
|||
"YZJ Robot Token": "YZJ Robot token",
|
||||
"Plain Text": "Plain Text",
|
||||
"Message Template": "Message Template",
|
||||
"Template Format": "Template Format"
|
||||
"Template Format": "Template Format",
|
||||
"Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under",
|
||||
"smsplanetApiToken": "Token for the SMSPlanet API",
|
||||
"smsplanetApiDocs": "Detailed information on obtaining API tokens can be found in {the_smsplanet_documentation}.",
|
||||
"the smsplanet documentation": "the smsplanet documentation",
|
||||
"Phone numbers": "Phone numbers",
|
||||
"Sender name": "Sender name",
|
||||
"smsplanetNeedToApproveName": "Needs to be approved in the client panel",
|
||||
"Disable URL in Notification": "Disable URL in Notification",
|
||||
"Ip Family": "IP Family",
|
||||
"ipFamilyDescriptionAutoSelect": "Uses the {happyEyeballs} for determining the IP family.",
|
||||
"Happy Eyeballs algorithm": "Happy Eyeballs algorithm",
|
||||
"Add Another Tag": "Add Another Tag",
|
||||
"Staged Tags for Batch Add": "Staged Tags for Batch Add",
|
||||
"Clear Form": "Clear Form",
|
||||
"pause": "Pause",
|
||||
"Manual": "Manual"
|
||||
}
|
||||
|
|
|
@ -341,7 +341,7 @@
|
|||
"Packet Size": "Paketin koko",
|
||||
"telegram": "Telegram",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Bot Token": "Botin token",
|
||||
"Bot Token": "Botin tokeni",
|
||||
"wayToGetTelegramToken": "Voit saada tunnuksen osoitteesta {0}.",
|
||||
"Chat ID": "Chat-tunnus",
|
||||
"wayToGetTelegramChatID": "Saat chat-tunnuksesi lähettämällä viestin botille ja siirtymällä tähän URL-osoitteeseen nähdäksesi chat_id:",
|
||||
|
@ -1096,5 +1096,31 @@
|
|||
"RabbitMQ Nodes": "RabbitMQ-hallintasolmut",
|
||||
"rabbitmqNodesDescription": "Anna URL RabbitMQ-hallintasolmuille sisältäen protokollan ja portin. Esimerkki: {0}",
|
||||
"rabbitmqHelpText": "Jotta voit käyttää seurainta, sinun on otettava hallintalaajennus käyttöön RabbitMQ-asetuksissa. Lisätietoja saat osoitteesta {rabitmq_documentation}.",
|
||||
"aboutSlackUsername": "Muuttaa viestin lähettäjän näyttönimeä. Jos haluat mainita jonkun, lisää se ystävälliseen nimeen."
|
||||
"aboutSlackUsername": "Muuttaa viestin lähettäjän näyttönimeä. Jos haluat mainita jonkun, lisää se ystävälliseen nimeen.",
|
||||
"Font Twemoji by Twitter licensed under": "Twemoji-fontti (Twitter) on lisensoitu seuraavalla lisenssillä",
|
||||
"wayToGetWahaSession": "Tästä istunnosta WAHA lähettää ilmoituksia Chat ID:hen. Löydät sen WAHA kojelaudasta.",
|
||||
"wayToWriteWahaChatId": "Puhelinnumero kansainvälisellä etuliitteellä, mutta ilman plusmerkkiä alussa ({0}), yhteystietotunnusta ({1}) tai ryhmätunnusta ({2}). Ilmoitukset lähetetään tähän chat-tunnukseen WAHA-istunnosta.",
|
||||
"wahaSession": "Istunto",
|
||||
"wahaChatId": "Viesti ID (Puhelinnumero / Yhteystieto ID / Ryhmä ID)",
|
||||
"Template Format": "Malli Muotoilu",
|
||||
"wayToGetWahaApiUrl": "Sinun WAHA instanssin URL.",
|
||||
"YZJ Webhook URL": "YZJ Webhook URL-osoite",
|
||||
"telegramServerUrl": "(Valinnainen) Palvelin Url",
|
||||
"telegramServerUrlDescription": "Telegramin bot-api-rajoitusten poistamiseksi tai pääsyn saamiseksi estetyille alueille (Kiina, Iran jne.). Saat lisätietoja napsauttamalla {0}. Oletus: {1}",
|
||||
"Message Template": "Viesti Malli",
|
||||
"YZJ Robot Token": "YZJ Robotti tokeni",
|
||||
"wayToGetWahaApiKey": "API Key on WHATSAPP_API_KEY ympäristömuuttujan arvo, jota käytit WAHA käynnistämiseen.",
|
||||
"Plain Text": "Pelkkää tekstiä",
|
||||
"templateServiceName": "palvelin nimi",
|
||||
"templateHostnameOrURL": "isäntänimi tai URL",
|
||||
"templateStatus": "tila",
|
||||
"telegramUseTemplate": "Käytä mukautettua viesti mallia",
|
||||
"telegramUseTemplateDescription": "Jos aktivoitu, viesti lähetetään käyttämällä mukautettua mallia.",
|
||||
"telegramTemplateFormatDescription": "Telegram sallii erilaisten merkintäkielien käytön viesteissä, katso Telegram {0} tarkempia tietoja.",
|
||||
"smsplanetApiToken": "SMSPlanet API:n tunnus",
|
||||
"smsplanetApiDocs": "Yksityiskohtaiset tiedot API-tunnusten hankkimisesta löytyvät osoitteesta {the_smsplanet_documentation}.",
|
||||
"the smsplanet documentation": "smsplanetin dokumentaatio",
|
||||
"Phone numbers": "Puhelinnumerot",
|
||||
"Sender name": "Lähettäjän nimi",
|
||||
"smsplanetNeedToApproveName": "On hyväksyttävä asiakaspaneelissa"
|
||||
}
|
||||
|
|
|
@ -1117,5 +1117,12 @@
|
|||
"wayToGetWahaApiKey": "La clé API est la valeur de la variable d'environnement WHATSAPP_API_KEY que vous avez utilisée pour exécuter WAHA.",
|
||||
"wayToWriteWahaChatId": "Le numéro de téléphone avec le préfixe international, mais sans le signe plus ({0}), l'identifiant de contact ({1}) ni l'identifiant de groupe ({2}). Les notifications sont envoyées à cet identifiant de chat depuis la session WAHA.",
|
||||
"telegramServerUrlDescription": "Pour lever les limitations de l’API des bots Telegram ou accéder aux zones bloquées (Chine, Iran, etc.). Pour plus d’informations, cliquez sur {0}. Par défaut : {1}",
|
||||
"telegramServerUrl": "(Facultatif) URL du serveur"
|
||||
"telegramServerUrl": "(Facultatif) URL du serveur",
|
||||
"Font Twemoji by Twitter licensed under": "La police Twemoji de Twitter est sous licence",
|
||||
"the smsplanet documentation": "la documentation de smsplanet",
|
||||
"Phone numbers": "Numéros de téléphone",
|
||||
"Sender name": "Nom de l'expéditeur",
|
||||
"smsplanetNeedToApproveName": "Doit être approuvé dans le panneau client",
|
||||
"smsplanetApiToken": "Jeton pour l'API SMSPlanet",
|
||||
"smsplanetApiDocs": "Des informations détaillées sur l'obtention de jetons API peuvent être trouvées dans {the_smsplanet_documentation}."
|
||||
}
|
||||
|
|
|
@ -1111,5 +1111,12 @@
|
|||
"wayToGetWahaSession": "Iz ove sjednice WAHA šalje obavijesti na identifikator razgovora. Može se pronaći na WAHA nadzornoj ploči.",
|
||||
"wayToWriteWahaChatId": "Telefonski broj s međunarodnim prefiksom, ali bez znaka plus na početku ({0}), identifikator kontakta ({1}) ili identifikator grupe ({2}). Obavijesti se šalju na ovaj identifikator chata iz WAHA sesije.",
|
||||
"telegramServerUrl": "(Neobvezno) URL Poslužitelja",
|
||||
"telegramServerUrlDescription": "Za ukidanje ograničenja API-ja za botove Telegrama ili dobivanje pristupa u blokiranim područjima (Kina, Iran, itd.). Za više informacija kliknite {0}. Zadano: {1}"
|
||||
"telegramServerUrlDescription": "Za ukidanje ograničenja API-ja za botove Telegrama ili dobivanje pristupa u blokiranim područjima (Kina, Iran, itd.). Za više informacija kliknite {0}. Zadano: {1}",
|
||||
"Font Twemoji by Twitter licensed under": "Font Twemoji tvrtke Twitter je pod licencom",
|
||||
"the smsplanet documentation": "dokumentaciji usluge SMSPLANET",
|
||||
"Phone numbers": "Brojevi telefona",
|
||||
"Sender name": "Naziv pošiljatelja",
|
||||
"smsplanetNeedToApproveName": "Potrebno je odobrenje u klijentskoj nadzornoj ploči",
|
||||
"smsplanetApiToken": "Token za pristup SMSPLANET API-ju",
|
||||
"smsplanetApiDocs": "Detaljne informacije o dobivanju tokena za API možete pronaći u {the_smsplanet_documentation}."
|
||||
}
|
||||
|
|
|
@ -1080,5 +1080,24 @@
|
|||
"Fail": "Hiba",
|
||||
"Pop": "Megjelen",
|
||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||
"wayToGetHeiiOnCallDetails": "A Trigger ID és az API kulcsok megszerzésének módja a {dokumentáció}"
|
||||
"wayToGetHeiiOnCallDetails": "A Trigger ID és az API kulcsok megszerzésének módja a {dokumentáció}",
|
||||
"telegramServerUrl": "(Választható) Szerver Url",
|
||||
"telegramServerUrlDescription": "A Telegram bot api korlátozásainak feloldása vagy hozzáférés a blokkolt területekhez (Kína, Irán stb.). További információért kattintson a {0} gombra. Alapértelmezett: {1}",
|
||||
"wahaSession": "Munkamenet",
|
||||
"wahaChatId": "Beszélgetés azonosító (Telefonszám / Kontakt azonosító / Csoport azonosító)",
|
||||
"wayToGetWahaApiUrl": "WAHA példányod URL-je.",
|
||||
"wayToGetWahaApiKey": "Az API-kulcs a WHATSAPP_API_KEY környezeti változó értéke, amelyet a WAHA futtatásához használt.",
|
||||
"wayToWriteWahaChatId": "A telefonszám nemzetközi előtaggal, de az elején lévő pluszjel ({0}), a kapcsolattartó azonosítója ({1}) vagy a csoportazonosító ({2}) nélkül. A WAHA Session értesítéseket küld erre a beszélgetési azonosítóra.",
|
||||
"Plain Text": "Sima Szöveg",
|
||||
"Message Template": "Üzenet Sablon",
|
||||
"Template Format": "Sablon Formátum",
|
||||
"wayToGetWahaSession": "A munkamenetből WAHA küld egy értesítést a Beszélgetés azonosítóra. Az értesítést megtalálhatod a WAHA műszerfalon.",
|
||||
"YZJ Webhook URL": "YZJ Webhook URL",
|
||||
"YZJ Robot Token": "YZJ Robot token",
|
||||
"templateServiceName": "szolgáltatás név",
|
||||
"templateHostnameOrURL": "kiszolgáló név vagy URL",
|
||||
"templateStatus": "státusz",
|
||||
"telegramUseTemplate": "Egyéni üzenetsablon használata",
|
||||
"telegramUseTemplateDescription": "Ha engedélyezve van, az üzenet egy egyéni sablon szerint lesz elküldve.",
|
||||
"telegramTemplateFormatDescription": "Telegram különböző jelölőnyelvek használatát engedi, további információkért lásd {0}."
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
"day": "giorno | giorni",
|
||||
"-day": "-giorni",
|
||||
"hour": "ora",
|
||||
"-hour": "-ore",
|
||||
"-hour": "-ora",
|
||||
"Response": "Risposta",
|
||||
"Ping": "Ping",
|
||||
"Monitor Type": "Modalità di monitoraggio",
|
||||
|
@ -80,7 +80,7 @@
|
|||
"pushOptionalParams": "Parametri aggiuntivi: {0}",
|
||||
"Save": "Salva",
|
||||
"Notifications": "Notifiche",
|
||||
"Not available, please setup.": "Non disponibili, richiesta configurazione manuale.",
|
||||
"Not available, please setup.": "Non disponibile, richiesta configurazione manuale.",
|
||||
"Setup Notification": "Configura le notifiche",
|
||||
"Light": "Chiaro",
|
||||
"Dark": "Scuro",
|
||||
|
@ -308,7 +308,7 @@
|
|||
"clearDataOlderThan": "Mantieni lo storico per {0} giorni.",
|
||||
"PasswordsDoNotMatch": "Le password non corrispondono.",
|
||||
"records": "records",
|
||||
"One record": "One record",
|
||||
"One record": "Un record",
|
||||
"steamApiKeyDescription": "Per monitorare un server di gioco Steam è necessaria una Web-API Key di Steam. È possibile registrarne una qui: ",
|
||||
"Current User": "Utente corrente",
|
||||
"recent": "Recenti",
|
||||
|
@ -738,5 +738,46 @@
|
|||
"invertKeywordDescription": "Cerca la parola chiave essere assente anziché presente.",
|
||||
"octopushAPIKey": "\"API Key\" dalle credenziali API HTTP nel pannello di controllo",
|
||||
"Enable TLS": "Abilita TLS",
|
||||
"ignoredTLSError": "Ignora errori TLS/SSL"
|
||||
"ignoredTLSError": "Ignora errori TLS/SSL",
|
||||
"templateHostnameOrURL": "nome host o URL",
|
||||
"templateStatus": "stato",
|
||||
"templateServiceName": "nome del servizio",
|
||||
"locally configured mail transfer agent": "agente mail configurato localmente",
|
||||
"shrinkDatabaseDescriptionSqlite": "Un record",
|
||||
"pushoversounds cashregister": "Registratore di cassa",
|
||||
"Strategy": "Strategia",
|
||||
"Add a domain": "Aggiungi un dominio",
|
||||
"telegramServerUrl": "(Facoltativo) URL del Server",
|
||||
"pushoversounds magic": "Magico",
|
||||
"pushoversounds mechanical": "Meccanico",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Sirena",
|
||||
"pushoversounds spacealarm": "Allarme spaziale",
|
||||
"pushoversounds alien": "Allarme Alieno (lungo)",
|
||||
"Remove domain": "Rimuovi il dominio '{0}'",
|
||||
"Edit Tag": "Modifica il Tag",
|
||||
"Server Address": "Indirizzo del Server",
|
||||
"Expiry": "Scadenza",
|
||||
"telegramUseTemplateDescription": "Se abilitato, il messaggio sarà spedito usando il template personalizzato.",
|
||||
"high": "alto",
|
||||
"jsonQueryDescription": "Analizza ed estrai dati specifici dalla risposta JSON del server utilizzando una query JSON oppure usa \"$\" per la risposta grezza, se non ti aspetti JSON. Il risultato viene quindi confrontato con il valore previsto, sotto forma di stringhe. Consulta {0} per la documentazione e usa {1} per sperimentare con le query.",
|
||||
"Free Mobile User Identifier": "Identificatore utente mobile gratuito",
|
||||
"telegramServerUrlDescription": "Per rimuovere le limitazioni dell'API bot di Telegram o ottenere l'accesso in aree bloccate (Cina, Iran, ecc.), clicca su {0} per maggiori informazioni. Predefinito: {1}",
|
||||
"octopushLogin": "“Accedi” dalle credenziali API HTTP nel pannello di controllo",
|
||||
"promosmsLogin": "Nome di accesso API",
|
||||
"pushoversounds bike": "Bicicletta",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds classical": "Classico",
|
||||
"pushoversounds cosmic": "Cosmico",
|
||||
"pushoversounds incoming": "In arrivo",
|
||||
"pushoversounds intermission": "Intervallo",
|
||||
"pushoversounds tugboat": "Rimorchiatore",
|
||||
"pushoversounds climb": "Salita (lunga)",
|
||||
"pushoversounds persistent": "Persistente (lungo)",
|
||||
"pushoversounds vibrate": "Solo vibrazione",
|
||||
"wayToGetKookGuildID": "Attivare la “Modalità sviluppatore” nelle impostazioni di Kook e fare clic con il pulsante destro del mouse sulla gilda per ottenere il suo ID.",
|
||||
"Guild ID": "Guild ID",
|
||||
"Free Mobile API Key": "Chiave API mobile gratuita",
|
||||
"telegramUseTemplate": "Utilizza un template di messaggio personalizzato",
|
||||
"telegramTemplateFormatDescription": "Telegram permette l'utilizzo di diversi linguaggi di markup, vedi Telegram {0} per maggiori dettagli."
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"Add New Monitor": "監視の追加",
|
||||
"Quick Stats": "統計",
|
||||
"Up": "正常",
|
||||
"Down": "停止",
|
||||
"Down": "異常",
|
||||
"Pending": "待機中",
|
||||
"Unknown": "不明",
|
||||
"Pause": "一時停止",
|
||||
|
@ -1065,5 +1065,31 @@
|
|||
"Key Added": "追加キー",
|
||||
"Bark Sound": "Bark通知音",
|
||||
"Badge URL": "バッジURL",
|
||||
"pushoversounds intermission": "Intermission"
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"telegramServerUrl": "(任意)サーバーUrl",
|
||||
"telegramServerUrlDescription": "Telegramのボットapiの制限を解除したり、ブロックされた地域(中国、イランなど)でアクセスする。詳しくは {0} をクリックしてください。デフォルト: {1}",
|
||||
"wayToWriteWahaChatId": "電話番号の先頭にプラス記号を付けない国際電話番号({0})、コンタクトID ({1})、またはグループID ({2}) 。WAHAセッションからこのチャットIDに通知が送信されます。",
|
||||
"wahaSession": "セッション",
|
||||
"wahaChatId": "チャットID(電話番号/連絡先ID/グループID)",
|
||||
"wayToGetWahaApiUrl": "WAHAインスタンスのURL。",
|
||||
"wayToGetWahaApiKey": "APIキーはWAHAを実行するために使用したWHATSAPP_API_KEY環境変数の値です。",
|
||||
"wayToGetWahaSession": "このセッションから WAHA はチャット ID に通知を送信します。WAHAダッシュボードで確認できます。",
|
||||
"YZJ Webhook URL": "YZJ ウェブフック URL",
|
||||
"YZJ Robot Token": "YZJ ロボットトークン",
|
||||
"Plain Text": "平文",
|
||||
"Message Template": "メッセージテンプレート",
|
||||
"Template Format": "テンプレート形式",
|
||||
"templateServiceName": "サービス名",
|
||||
"templateHostnameOrURL": "ホスト名またはURL",
|
||||
"templateStatus": "ステータス",
|
||||
"telegramUseTemplate": "カスタムメッセージテンプレートを使用",
|
||||
"telegramUseTemplateDescription": "有効にすると、メッセージはカスタムテンプレートを使って送信されます。",
|
||||
"telegramTemplateFormatDescription": "Telegramではメッセージに異なるマークアップ言語を使用することができます。詳細はTelegram {0} を参照してください。",
|
||||
"Font Twemoji by Twitter licensed under": "TwemojiフォントはTwitterライセンス下でライセンスされています",
|
||||
"the smsplanet documentation": "smsplanetドキュメント",
|
||||
"Phone numbers": "携帯電話番号",
|
||||
"Sender name": "送信者名",
|
||||
"smsplanetNeedToApproveName": "クライアントパネルでの承認が必要",
|
||||
"smsplanetApiToken": "SMSPlanet APIのトークン",
|
||||
"smsplanetApiDocs": "APIトークンの取得に関する詳細な情報は、{the_smsplanet_documentation}にあります。"
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
{
|
||||
"languageName": "한국어",
|
||||
"checkEverySecond": "{0}초마다 확인해요",
|
||||
"retryCheckEverySecond": "{0}초마다 다시 확인해요",
|
||||
"retriesDescription": "서비스가 중단된 후 알림을 보내기 전 최대 재시도 횟수",
|
||||
"ignoreTLSError": "HTTPS 웹사이트에서 TLS/SSL 오류 무시하기",
|
||||
"upsideDownModeDescription": "서버 상태를 반대로 표시해요. 서버가 작동하면 오프라인으로 표시할 거예요.",
|
||||
"maxRedirectDescription": "최대 리다이렉트 횟수예요. 0을 입력하면 리다이렉트를 꺼요.",
|
||||
"checkEverySecond": "{0}초마다 확인",
|
||||
"retryCheckEverySecond": "{0}초마다 재시도",
|
||||
"retriesDescription": "서비스가 다운된 것으로 간주하고 알림을 보내기 전까지의 최대 재시도 횟수",
|
||||
"ignoreTLSError": "HTTPS 웹사이트에서 TLS/SSL 오류 무시",
|
||||
"upsideDownModeDescription": "상태를 반대로 표시합니다. 서비스에 연결 가능하면 '다운'으로 간주됩니다.",
|
||||
"maxRedirectDescription": "최대 리디렉션 허용 횟수. 0으로 설정하면 리디렉션을 사용하지 않습니다.",
|
||||
"acceptedStatusCodesDescription": "응답 성공으로 간주할 상태 코드를 정해요.",
|
||||
"passwordNotMatchMsg": "비밀번호 재입력이 일치하지 않아요.",
|
||||
"notificationDescription": "모니터링에 알림을 설정할 수 있어요.",
|
||||
"notificationDescription": "알림이 동작하려면 기존 모니터에 할당되어야 합니다.",
|
||||
"keywordDescription": "HTML 이나 JSON에서 대소문자를 구분해 키워드를 검색해요.",
|
||||
"pauseDashboardHome": "일시 정지",
|
||||
"deleteMonitorMsg": "정말 이 모니터링을 삭제할까요?",
|
||||
"deleteNotificationMsg": "정말 이 알림을 모든 모니터링에서 삭제할까요?",
|
||||
"pauseDashboardHome": "정지",
|
||||
"deleteMonitorMsg": "이 모니터를 삭제하시겠습니까?",
|
||||
"deleteNotificationMsg": "이 알림을 모든 모니터에서 삭제하시겠습니까?",
|
||||
"resolverserverDescription": "Cloudflare가 기본 서버예요, 원한다면 언제나 다른 Resolver 서버로 변경할 수 있어요.",
|
||||
"rrtypeDescription": "모니터링할 RR-Type을 선택해요",
|
||||
"pauseMonitorMsg": "정말 이 모니터링을 일시 정지할까요?",
|
||||
"enableDefaultNotificationDescription": "새로 추가하는 모든 모니터링에 이 알림을 기본적으로 활성화해요. 각 모니터에 대해 별도로 알림을 비활성화할 수 있어요.",
|
||||
"clearEventsMsg": "정말 이 모니터링에 대한 모든 이벤트를 삭제할까요?",
|
||||
"clearHeartbeatsMsg": "정말 이 모니터링에 대한 모든 하트비트를 삭제할까요?",
|
||||
"rrtypeDescription": "모니터링할 RR Type을 선택하세요.",
|
||||
"pauseMonitorMsg": "이 모니터를 일시 정지하시겠습니까?",
|
||||
"enableDefaultNotificationDescription": "새 모니터에 이 알림을 기본적으로 활성화합니다. 개별 모니터에 대해 알림을 비활성화할 수 있습니다.",
|
||||
"clearEventsMsg": "이 모니터의 모든 이벤트를 삭제하시겠습니까?",
|
||||
"clearHeartbeatsMsg": "이 모니터의 모든 하트비트를 삭제하시겠습니까?",
|
||||
"confirmClearStatisticsMsg": "정말 모든 통계를 삭제할까요?",
|
||||
"importHandleDescription": "이름이 같은 모든 모니터링이나 알림을 건너뛰려면 '기존값 건너뛰기'를 선택해주세요. '덮어쓰기'는 기존의 모든 모니터링과 알림을 삭제해요.",
|
||||
"importHandleDescription": "이름이 같은 모니터나 알림을 건너뛰려면 '기존 항목 건너뛰기'를 선택하세요. '덮어쓰기'를 선택한 경우 존재하는 기존 모니터와 알림을 모두 삭제합니다.",
|
||||
"confirmImportMsg": "정말 백업을 가져올까요? 가져오기 옵션을 제대로 설정했는지 다시 확인해주세요.",
|
||||
"twoFAVerifyLabel": "토큰을 입력해 2단계 인증이 작동하는지 확인해주세요",
|
||||
"tokenValidSettingsMsg": "토큰이 유효해요! 이제 2단계 인증 설정을 저장할 수 있어요.",
|
||||
|
@ -28,17 +28,17 @@
|
|||
"confirmDisableTwoFAMsg": "정말 2단계 인증을 비활성화할까요?",
|
||||
"Settings": "설정",
|
||||
"Dashboard": "대시보드",
|
||||
"New Update": "새로운 업데이트",
|
||||
"New Update": "새 업데이트",
|
||||
"Language": "언어",
|
||||
"Appearance": "디스플레이",
|
||||
"Appearance": "모양",
|
||||
"Theme": "테마",
|
||||
"General": "일반",
|
||||
"Version": "버전",
|
||||
"Check Update On GitHub": "깃허브에서 업데이트 확인",
|
||||
"Check Update On GitHub": "Github에서 업데이트 확인",
|
||||
"List": "목록",
|
||||
"Add": "추가",
|
||||
"Add New Monitor": "새로운 모니터링 추가하기",
|
||||
"Quick Stats": "간단한 정보",
|
||||
"Add New Monitor": "새 모니터 추가",
|
||||
"Quick Stats": "요약",
|
||||
"Up": "온라인",
|
||||
"Down": "오프라인",
|
||||
"Pending": "대기 중",
|
||||
|
@ -50,22 +50,22 @@
|
|||
"Message": "메시지",
|
||||
"No important events": "중요 이벤트 없음",
|
||||
"Resume": "재개",
|
||||
"Edit": "수정",
|
||||
"Edit": "편집",
|
||||
"Delete": "삭제",
|
||||
"Current": "현재",
|
||||
"Uptime": "업타임",
|
||||
"Cert Exp.": "인증서 만료.",
|
||||
"Cert Exp.": "인증서 만료",
|
||||
"day": "일",
|
||||
"-day": "-일",
|
||||
"-day": "일",
|
||||
"hour": "시간",
|
||||
"-hour": "-시간",
|
||||
"-hour": "시간",
|
||||
"Response": "응답",
|
||||
"Ping": "핑",
|
||||
"Monitor Type": "모니터링 종류",
|
||||
"Monitor Type": "모니터 타입",
|
||||
"Keyword": "키워드",
|
||||
"Friendly Name": "이름",
|
||||
"Friendly Name": "별명",
|
||||
"URL": "URL",
|
||||
"Hostname": "호스트네임",
|
||||
"Hostname": "호스트명",
|
||||
"Port": "포트",
|
||||
"Heartbeat Interval": "하트비트 주기",
|
||||
"Retries": "재시도",
|
||||
|
@ -73,46 +73,46 @@
|
|||
"Advanced": "고급",
|
||||
"Upside Down Mode": "상태 반전 모드",
|
||||
"Max. Redirects": "최대 리다이렉트",
|
||||
"Accepted Status Codes": "응답 성공 상태 코드",
|
||||
"Accepted Status Codes": "허용된 상태 코드",
|
||||
"Save": "저장",
|
||||
"Notifications": "알림",
|
||||
"Not available, please setup.": "존재하지 않아요, 새로운 거 하나 만드는 건 어때요?",
|
||||
"Not available, please setup.": "아직 사용할 수 없습니다. 설정이 필요합니다.",
|
||||
"Setup Notification": "알림 설정",
|
||||
"Light": "화이트",
|
||||
"Light": "라이트",
|
||||
"Dark": "다크",
|
||||
"Auto": "자동",
|
||||
"Theme - Heartbeat Bar": "테마 - 하트비트 바",
|
||||
"Normal": "기본값",
|
||||
"Bottom": "가운데",
|
||||
"Bottom": "하단",
|
||||
"None": "없음",
|
||||
"Timezone": "시간대",
|
||||
"Search Engine Visibility": "검색 엔진 활성화",
|
||||
"Search Engine Visibility": "검색 엔진 노출",
|
||||
"Allow indexing": "인덱싱 허용",
|
||||
"Discourage search engines from indexing site": "검색 엔진 인덱싱 거부",
|
||||
"Discourage search engines from indexing site": "검색 엔진의 인덱싱을 허용하지 않음",
|
||||
"Change Password": "비밀번호 변경",
|
||||
"Current Password": "기존 비밀번호",
|
||||
"Current Password": "현재 비밀번호",
|
||||
"New Password": "새 비밀번호",
|
||||
"Repeat New Password": "새로운 비밀번호 재입력",
|
||||
"Repeat New Password": "새 비밀번호 확인",
|
||||
"Update Password": "비밀번호 변경",
|
||||
"Disable Auth": "인증 비활성화",
|
||||
"Enable Auth": "인증 활성화",
|
||||
"disableauth.message1": "정말로 {disableAuth}?",
|
||||
"disable authentication": "인증 기능을 끌까요",
|
||||
"disableauth.message2": "이 기능은 {intendThirdPartyAuth}을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.",
|
||||
"where you intend to implement third-party authentication": "Cloudflare Access와 같은 서드파티 인증",
|
||||
"disableauth.message1": "{disableAuth}하시겠습니까?",
|
||||
"disable authentication": "인증을 비활성화",
|
||||
"disableauth.message2": "이 기능은 Uptime Kuma 앞단에 Cloudflare Access, Authelia 등의 {intendThirdPartyAuth}을 위해 설계되었습니다.",
|
||||
"where you intend to implement third-party authentication": "서드 파티 인증을 구현하는 상황",
|
||||
"Please use this option carefully!": "신중하게 사용하세요!",
|
||||
"Logout": "로그아웃",
|
||||
"Leave": "나가기",
|
||||
"I understand, please disable": "기능에 대해 이해했으니 꺼주세요.",
|
||||
"Leave": "취소",
|
||||
"I understand, please disable": "이해했습니다. 비활성화합니다.",
|
||||
"Confirm": "확인",
|
||||
"Yes": "확인",
|
||||
"No": "취소",
|
||||
"Username": "이름",
|
||||
"Yes": "예",
|
||||
"No": "아니요",
|
||||
"Username": "사용자명",
|
||||
"Password": "비밀번호",
|
||||
"Remember me": "비밀번호 기억하기",
|
||||
"Remember me": "로그인 상태 유지",
|
||||
"Login": "로그인",
|
||||
"No Monitors, please": "모니터링이 현재 없어요,",
|
||||
"add one": "한번 추가해보실래요?",
|
||||
"No Monitors, please": "등록된 모니터가 없습니다.",
|
||||
"add one": "추가하기",
|
||||
"Notification Type": "알림 종류",
|
||||
"Email": "이메일",
|
||||
"Test": "테스트",
|
||||
|
@ -120,33 +120,33 @@
|
|||
"Resolver Server": "Resolver 서버",
|
||||
"Resource Record Type": "리소스 레코드 유형",
|
||||
"Last Result": "최근 결과",
|
||||
"Create your admin account": "관리자 계정 만들기",
|
||||
"Repeat Password": "비밀번호 재입력",
|
||||
"Create your admin account": "관리자 계정 생성",
|
||||
"Repeat Password": "비밀번호 확인",
|
||||
"Import Backup": "백업 가져오기",
|
||||
"Export Backup": "백업 내보내기",
|
||||
"Export": "내보내기",
|
||||
"Import": "가져오기",
|
||||
"respTime": "응답 시간 (ms)",
|
||||
"notAvailableShort": "N/A",
|
||||
"Default enabled": "기본 알림으로 설정",
|
||||
"Apply on all existing monitors": "기존 모니터링에 모두 적용하기",
|
||||
"Create": "생성하기",
|
||||
"Default enabled": "기본적으로 활성화",
|
||||
"Apply on all existing monitors": "기존 모니터에 모두 적용",
|
||||
"Create": "생성",
|
||||
"Clear Data": "데이터 삭제",
|
||||
"Events": "이벤트",
|
||||
"Heartbeats": "하트비트",
|
||||
"Auto Get": "자동 Get",
|
||||
"backupDescription": "모든 모니터링과 알림을 JSON 파일 형식에 저장할 수 있어요.",
|
||||
"Auto Get": "Auto Get",
|
||||
"backupDescription": "모든 모니터와 알림을 JSON 파일에 백업할 수 있습니다.",
|
||||
"backupDescription2": "히스토리와 이벤트 데이터는 포함되어 있지 않아요.",
|
||||
"backupDescription3": "알림 토큰과 같은 보안 데이터가 내보내기 파일에 포함되어 있으므로 관리에 주의해주세요.",
|
||||
"alertNoFile": "가져오기를 하기 위해 파일을 선택해주세요.",
|
||||
"alertWrongFileType": "JSON 파일을 선택해주세요.",
|
||||
"Clear all statistics": "모든 통계치 삭제",
|
||||
"Skip existing": "기존값 건너뛰기",
|
||||
"alertNoFile": "가져올 파일을 선택하세요.",
|
||||
"alertWrongFileType": "JSON 파일을 선택하세요.",
|
||||
"Clear all statistics": "모든 통계 삭제",
|
||||
"Skip existing": "기존 항목 건너뛰기",
|
||||
"Overwrite": "덮어쓰기",
|
||||
"Options": "옵션",
|
||||
"Keep both": "두개 모두 보존",
|
||||
"Keep both": "모두 보존",
|
||||
"Verify Token": "토큰 검증",
|
||||
"Setup 2FA": "2단계 인증 설정하기",
|
||||
"Setup 2FA": "2단계 인증 설정",
|
||||
"Enable 2FA": "2단계 인증 활성화",
|
||||
"Disable 2FA": "2단계 인증 비활성화",
|
||||
"2FA Settings": "2단계 인증 설정",
|
||||
|
@ -154,34 +154,34 @@
|
|||
"Active": "활성화",
|
||||
"Inactive": "비활성화",
|
||||
"Token": "토큰",
|
||||
"Show URI": "URI 보기",
|
||||
"Show URI": "URI 표시",
|
||||
"Tags": "태그",
|
||||
"Add New below or Select...": "아래 새롭게 추가 또는 선택…",
|
||||
"Tag with this name already exist.": "같은 태그 이름이 이미 존재해요.",
|
||||
"Tag with this value already exist.": "같은 값을 가진 태그가 이미 존재해요.",
|
||||
"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": "핑크색",
|
||||
"Red": "빨강",
|
||||
"Orange": "주황",
|
||||
"Green": "초록",
|
||||
"Blue": "파랑",
|
||||
"Indigo": "인디고",
|
||||
"Purple": "보라",
|
||||
"Pink": "핑크",
|
||||
"Search...": "검색…",
|
||||
"Avg. Ping": "평균 핑",
|
||||
"Avg. Response": "평균 응답",
|
||||
"Entry Page": "첫 페이지",
|
||||
"statusPageNothing": "아무것도 없어요. 새로운 그룹 또는 모니터링을 추가해주세요.",
|
||||
"statusPageNothing": "아무것도 없습니다. 새 그룹이나 모니터를 추가하세요.",
|
||||
"No Services": "서비스 없음",
|
||||
"All Systems Operational": "모든 시스템 정상",
|
||||
"Partially Degraded Service": "일부 시스템 비정상",
|
||||
"Degraded Service": "모든 시스템 비정상",
|
||||
"All Systems Operational": "모든 시스템 작동 중",
|
||||
"Partially Degraded Service": "일부 서비스 불안정",
|
||||
"Degraded Service": "서비스 불안정",
|
||||
"Add Group": "그룹 추가",
|
||||
"Add a monitor": "모니터링 추가",
|
||||
"Edit Status Page": "상태 페이지 수정",
|
||||
"Go to Dashboard": "대시보드로 가기",
|
||||
"Add a monitor": "모니터 추가",
|
||||
"Edit Status Page": "상태 페이지 편집",
|
||||
"Go to Dashboard": "대시보드로",
|
||||
"Status Page": "상태 페이지",
|
||||
"Status Pages": "상태 페이지",
|
||||
"defaultNotificationName": "내 {notification} 알림 ({number})",
|
||||
|
@ -198,8 +198,8 @@
|
|||
"webhook": "Webhook",
|
||||
"Post URL": "Post URL",
|
||||
"Content Type": "Content Type",
|
||||
"webhookJsonDesc": "{0}은 Express.js와 같은 최신 HTTP 서버에 적합해요",
|
||||
"webhookFormDataDesc": "{multipart}은 PHP에 적합해요. {decodeFunction}를 기준으로 JSON을 디코딩하면 되어요",
|
||||
"webhookJsonDesc": "{0}은(는) Express.js와 같은 모던 HTTP 서버에 적합합니다.",
|
||||
"webhookFormDataDesc": "{multipart}는 PHP에 적합합니다. JSON은 {decodeFunction}을 사용해 파싱해야 합니다.",
|
||||
"smtp": "Email (SMTP)",
|
||||
"secureOptionNone": "없음 / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
|
@ -215,26 +215,26 @@
|
|||
"Prefix Custom Message": "접두사 메시지",
|
||||
"Hello @everyone is...": "{'@'}everyone 서버 상태 알림이에요…",
|
||||
"teams": "Microsoft Teams",
|
||||
"Webhook URL": "웹훅 URL",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"wayToGetTeamsURL": "{0}에서 Webhook을 어떻게 만드는지 알아보세요.",
|
||||
"signal": "Signal",
|
||||
"Number": "숫자",
|
||||
"Recipients": "받는 사람",
|
||||
"needSignalAPI": "REST API를 사용하는 Signal 클라이언트가 있어야 해요.",
|
||||
"wayToCheckSignalURL": "밑에 URL을 확인해 URL 설정 방법을 볼 수 있어요:",
|
||||
"signalImportant": "경고: 받는 사람의 그룹과 숫자는 섞을 수 없어요!",
|
||||
"signalImportant": "중요: 수신자 그룹과 숫자는 섞을 수 없습니다!",
|
||||
"gotify": "Gotify",
|
||||
"Application Token": "애플리케이션 토큰",
|
||||
"Server URL": "서버 URL",
|
||||
"Priority": "우선 순위",
|
||||
"slack": "Slack",
|
||||
"Icon Emoji": "아이콘 이모지",
|
||||
"Channel Name": "채널 이름",
|
||||
"Channel Name": "채널명",
|
||||
"Uptime Kuma URL": "Uptime Kuma URL",
|
||||
"aboutWebhooks": "Webhook에 대한 설명: {0}",
|
||||
"aboutChannelName": "Webhook 채널을 무시하려면 {0} 채널 이름칸에 채널 이름을 입력해주세요. 예: #기타-채널",
|
||||
"aboutKumaURL": "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Github Project 페이지로 설정해요.",
|
||||
"emojiCheatSheet": "이모지 목록 시트: {0}",
|
||||
"aboutWebhooks": "Webhook에 대한 자세한 내용: {0}",
|
||||
"aboutChannelName": "Webhook 채널을 바이패스하려면 {0}에 채널 이름을 입력하세요. 예: #기타-채널",
|
||||
"aboutKumaURL": "Uptime Kuma URL 필드를 공백으로 두면 기본적으로 Github Project 페이지로 설정합니다,",
|
||||
"emojiCheatSheet": "이모지 목록: {0}",
|
||||
"rocket.chat": "Rocket.chat",
|
||||
"pushover": "Pushover",
|
||||
"pushy": "Pushy",
|
||||
|
@ -263,9 +263,9 @@
|
|||
"Example:": "예: {0}",
|
||||
"Read more:": "더 보기: {0}",
|
||||
"Status:": "상태: {0}",
|
||||
"Read more": "더 보기",
|
||||
"appriseInstalled": "Apprise가 설치되어있어요.",
|
||||
"appriseNotInstalled": "Apprise가 설치되어있지 않아요. {0}",
|
||||
"Read more": "더보기",
|
||||
"appriseInstalled": "Apprise가 설치되어 있습니다.",
|
||||
"appriseNotInstalled": "Apprise가 설치되지 않았습니다. {0}",
|
||||
"Access Token": "액세스 토큰",
|
||||
"Channel access token": "채널 액세스 토큰",
|
||||
"Line Developers Console": "Line 개발자 콘솔",
|
||||
|
@ -284,10 +284,10 @@
|
|||
"promosmsTypeSpeed": "SMS SPEED - 시스템에서 가장 높은 우선순위예요. 매우 빠르고 신뢰할 수 있지만 비용이 많이 들어요 (SMS 전체 가격의 약 두 배).",
|
||||
"promosmsPhoneNumber": "전화 번호 (폴란드 수신자라면 지역번호를 적지 않아도 되어요.)",
|
||||
"promosmsSMSSender": "SMS 보내는 사람 이름 : 미리 등록된 이름 혹은 기본값 중 하나예요: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
"Primary Base URL": "기본 URL",
|
||||
"Primary Base URL": "주 베이스 URL",
|
||||
"Push URL": "Push URL",
|
||||
"needPushEvery": "이 URL을 {0} 초 마다 호출할 수 있어요.",
|
||||
"pushOptionalParams": "선택적 파라미터: {0}",
|
||||
"needPushEvery": "이 URL을 {0}초 마다 호출할 수 있습니다.",
|
||||
"pushOptionalParams": "추가 파라미터: {0}",
|
||||
"emailCustomSubject": "커스텀 주제",
|
||||
"clicksendsms": "ClickSend SMS",
|
||||
"checkPrice": "{0} 가격 확인:",
|
||||
|
@ -297,110 +297,110 @@
|
|||
"matrixHomeserverURL": "Homeserver URL (http(s):// 와 함께 적어주세요. 그리고 포트 번호는 선택적 입니다.)",
|
||||
"Internal Room Id": "내부 방 ID",
|
||||
"matrixDesc1": "Matrix 클라이언트 방 설정의 고급 섹션에서 내부 방 ID를 찾을 수 있어요. 내부 방 ID는 이렇게 생겼답니다: !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
"matrixDesc2": "사용자의 모든 방에 대한 엑세스가 허용될 수 있어서 새로운 사용자를 만들고 원하는 방에만 초대한 후 엑세스 토큰을 사용하는 것이 좋아요. {0} 이 명령어를 통해 엑세스 토큰을 얻을 수 있어요",
|
||||
"Method": "메서드",
|
||||
"matrixDesc2": "개인 Matrix 사용자 계정의 액세스 토큰을 사용하는 것은 계정 전체와 참여 중인 모든 방에 완전한 접근 권한을 부여하게 되므로 권장되지 않습니다. 대신 새로운 사용자를 생성한 후 알림을 받을 방에만 초대하는 것을 권장합니다. 액세스 토큰은 {0} 명령어를 실행하여 얻을 수 있습니다.",
|
||||
"Method": "Method",
|
||||
"Body": "Body",
|
||||
"Headers": "헤더",
|
||||
"PushUrl": "Push URL",
|
||||
"HeadersInvalidFormat": "요청 Headers의 JSON 형식이 올바르지 않아요: ",
|
||||
"BodyInvalidFormat": "요청 Body의 JSON 형식이 올바르지 않아요: ",
|
||||
"Monitor History": "모니터링 기록",
|
||||
"clearDataOlderThan": "모니터링 기록을 {0}일 동안 저장해요.",
|
||||
"PasswordsDoNotMatch": "비밀번호가 일치하지 않아요.",
|
||||
"records": "records",
|
||||
"HeadersInvalidFormat": "요청 헤더의 JSON 형식이 올바르지 않음: ",
|
||||
"BodyInvalidFormat": "요청 본문의 JSON 형식이 올바르지 않음: ",
|
||||
"Monitor History": "모니터 기록",
|
||||
"clearDataOlderThan": "모니터 기록을 {0}일간 저장합니다.",
|
||||
"PasswordsDoNotMatch": "비밀번호가 일치하지 않습니다.",
|
||||
"records": "레코드",
|
||||
"One record": "One record",
|
||||
"steamApiKeyDescription": "스팀 게임 서버를 모니터링하려면 Steam Web API 키가 필요해요. API 키는 하단 웹사이트에서 등록할 수 있어요: ",
|
||||
"steamApiKeyDescription": "Steam 게임 서버를 모니터링하려면 Steam Web-API 키가 필요합니다. 여기서 API 키를 등록하세요: ",
|
||||
"Current User": "현재 사용자",
|
||||
"recent": "최근",
|
||||
"Done": "완료",
|
||||
"Info": "정보",
|
||||
"Security": "보안",
|
||||
"Steam API Key": "스팀 API 키",
|
||||
"Steam API Key": "Steam API 키",
|
||||
"Shrink Database": "데이터베이스 축소",
|
||||
"Pick a RR-Type...": "RR-Type을 골라주세요…",
|
||||
"Pick Accepted Status Codes...": "상태 코드를 골라주세요…",
|
||||
"Pick a RR-Type...": "RR-Type 선택…",
|
||||
"Pick Accepted Status Codes...": "성공 상태 코드 선택…",
|
||||
"Default": "기본",
|
||||
"HTTP Options": "HTTP 옵션",
|
||||
"Create Incident": "인시던트 만들기",
|
||||
"Create Incident": "인시던트 생성",
|
||||
"Title": "제목",
|
||||
"Content": "내용",
|
||||
"Style": "스타일",
|
||||
"info": "정보",
|
||||
"warning": "주의",
|
||||
"danger": "경고",
|
||||
"warning": "경고",
|
||||
"danger": "위험",
|
||||
"primary": "기본",
|
||||
"light": "화이트",
|
||||
"light": "라이트",
|
||||
"dark": "다크",
|
||||
"Post": "게시",
|
||||
"Please input title and content": "제목과 내용을 작성해주세요",
|
||||
"Created": "생성 날짜",
|
||||
"Last Updated": "마지막 업데이트",
|
||||
"Please input title and content": "제목 및 내용을 입력하세요.",
|
||||
"Created": "생성일",
|
||||
"Last Updated": "최근 수정",
|
||||
"Unpin": "제거",
|
||||
"Switch to Light Theme": "화이트 테마로 전환",
|
||||
"Switch to Light Theme": "라이트 테마로 전환",
|
||||
"Switch to Dark Theme": "다크 테마로 전환",
|
||||
"Show Tags": "태그 보이기",
|
||||
"Show Tags": "태그 보기",
|
||||
"Hide Tags": "태그 숨기기",
|
||||
"Description": "설명",
|
||||
"No monitors available.": "모니터링이 없어요.",
|
||||
"No monitors available.": "사용 가능한 모니터가 없습니다.",
|
||||
"Add one": "추가하기",
|
||||
"No Monitors": "모니터링 없음",
|
||||
"Untitled Group": "이름없는 그룹",
|
||||
"No Monitors": "모니터 없음",
|
||||
"Untitled Group": "제목 없는 그룹",
|
||||
"Services": "서비스",
|
||||
"Discard": "취소",
|
||||
"Cancel": "취소",
|
||||
"Cancel": "닫기",
|
||||
"Powered by": "Powered by",
|
||||
"serwersms": "SerwerSMS.pl",
|
||||
"serwersmsAPIUser": "API Usename (webapi_ 접두사 포함)",
|
||||
"serwersmsAPIUser": "API 사용자명 (webapi_ 접두사 포함)",
|
||||
"serwersmsAPIPassword": "API 비밀번호",
|
||||
"serwersmsPhoneNumber": "휴대전화 번호",
|
||||
"serwersmsSenderName": "보내는 사람 이름 (customer portal를 통해 가입된 정보)",
|
||||
"serwersmsPhoneNumber": "휴대폰 번호",
|
||||
"serwersmsSenderName": "SMS 발신자명 (customer portal로 가입된 정보)",
|
||||
"stackfield": "Stackfield",
|
||||
"dnsPortDescription": "DNS 서버 포트, 기본값은 53 이에요. 포트는 언제나 변경할 수 있어요.",
|
||||
"PushByTechulus": "Push by Techulus",
|
||||
"GoogleChat": "Google Chat (Google Workspace only)",
|
||||
"topic": "Topic",
|
||||
"topicExplanation": "모니터링할 MQTT Topic",
|
||||
"topicExplanation": "모니터링할 MQTT 토픽",
|
||||
"successMessage": "성공 메시지",
|
||||
"successMessageExplanation": "성공으로 간주되는 MQTT 메시지",
|
||||
"error": "오류",
|
||||
"critical": "크리티컬",
|
||||
"Customize": "커스터마이즈",
|
||||
"Custom Footer": "커스텀 Footer",
|
||||
"Custom CSS": "커스텀 CSS",
|
||||
"critical": "중대",
|
||||
"Customize": "사용자화",
|
||||
"Custom Footer": "사용자 지정 푸터",
|
||||
"Custom CSS": "사용자 지정 CSS",
|
||||
"smtpDkimSettings": "DKIM 설정",
|
||||
"smtpDkimDesc": "사용 방법은 DKIM {0}를 참조하세요.",
|
||||
"smtpDkimDesc": "사용 방법은 Nodemailer DKIM {0}을(를) 참조하세요.",
|
||||
"documentation": "문서",
|
||||
"smtpDkimDomain": "도메인 이름",
|
||||
"smtpDkimKeySelector": "Key Selector",
|
||||
"smtpDkimPrivateKey": "Private Key",
|
||||
"smtpDkimPrivateKey": "비밀 키",
|
||||
"smtpDkimHashAlgo": "해시 알고리즘 (선택)",
|
||||
"smtpDkimheaderFieldNames": "서명할 헤더 키 (선택)",
|
||||
"smtpDkimskipFields": "서명하지 않을 헤더 키 (선택)",
|
||||
"wayToGetPagerDutyKey": "Service -> Service Directory -> (서비스 선택) -> Integrations -> Add integration. 에서 찾을 수 있어요. 자세히 알아보려면 {0}에서 \"Events API V2\"를 검색해봐요",
|
||||
"wayToGetPagerDutyKey": "\"Events API V2\"는 Service -> Service Directory -> (서비스 선택) -> Integrations -> Add integration. 에서 찾을 수 있습니다. 자세한 내용 {0}",
|
||||
"Integration Key": "Integration 키",
|
||||
"Integration URL": "Integration URL",
|
||||
"Auto resolve or acknowledged": "자동 해결 혹은 승인",
|
||||
"Auto resolve or acknowledged": "자동 해결 또는 승인",
|
||||
"do nothing": "아무것도 하지 않기",
|
||||
"auto acknowledged": "자동 승인 (acknowledged)",
|
||||
"auto resolve": "자동 해결 (resolve)",
|
||||
"gorush": "Gorush",
|
||||
"alerta": "Alerta",
|
||||
"alertaApiEndpoint": "API Endpoint",
|
||||
"alertaEnvironment": "환경변수",
|
||||
"alertaApiEndpoint": "API 엔드포인트",
|
||||
"alertaEnvironment": "환경",
|
||||
"alertaApiKey": "API 키",
|
||||
"alertaAlertState": "경고 상태",
|
||||
"alertaRecoverState": "해결된 상태",
|
||||
"deleteStatusPageMsg": "정말 이 상태 페이지를 삭제할까요?",
|
||||
"alertaAlertState": "알림 상태",
|
||||
"alertaRecoverState": "복구 상태",
|
||||
"deleteStatusPageMsg": "이 상태 페이지를 삭제하시겠습니까?",
|
||||
"Proxies": "프록시",
|
||||
"default": "Default",
|
||||
"default": "기본",
|
||||
"enabled": "활성화",
|
||||
"setAsDefault": "기본 프록시로 설정",
|
||||
"deleteProxyMsg": "정말 이 프록시를 모든 모니터링에서 삭제할까요?",
|
||||
"proxyDescription": "프록시가 작동하려면 모니터에 할당되어야 해요.",
|
||||
"enableProxyDescription": "이 프록시는 활성화될 때까지 영향을 미치지 않아요. 활성화 상태에 따라 모든 모니터에서 프록시를 일시정지할 수 있어요.",
|
||||
"setAsDefaultProxyDescription": "새로 추가하는 모든 모니터링에 이 프록시를 기본적으로 활성화해요. 각 모니터에 대해 별도로 프록시를 비활성화할 수 있어요.",
|
||||
"setAsDefault": "기본으로 설정",
|
||||
"deleteProxyMsg": "이 프록시를 모든 모니터에서 삭제하시겠습니까?",
|
||||
"proxyDescription": "프록시가 작동하려면 모니터에 할당되어야 합니다.",
|
||||
"enableProxyDescription": "이 프록시는 활성화될 때까지 모니터의 요청에 미치지 않습니다. 활성화 상태를 통해 모든 모니터에서 프록시를 일시 정지할 수 있습니다.",
|
||||
"setAsDefaultProxyDescription": "새 모니터에 이 프록시를 기본적으로 활성화합니다. 개별 모니터에 대해 프록시를 비활성화할 수 있습니다.",
|
||||
"Certificate Chain": "인증서 체인",
|
||||
"Valid": "유효",
|
||||
"Valid": "유효함",
|
||||
"Invalid": "유효하지 않음",
|
||||
"AccessKeyId": "AccessKey ID",
|
||||
"SecretAccessKey": "AccessKey Secret",
|
||||
|
@ -425,17 +425,17 @@
|
|||
"Proxy server has authentication": "프록시 서버에 인증 절차가 있음",
|
||||
"User": "사용자",
|
||||
"Installed": "설치됨",
|
||||
"Not installed": "설치되어 있지 않음",
|
||||
"Not installed": "설치되지 않음",
|
||||
"Running": "작동 중",
|
||||
"Not running": "작동하고 있지 않음",
|
||||
"Remove Token": "토큰 삭제",
|
||||
"Not running": "작동 중이 아님",
|
||||
"Remove Token": "토큰 제거",
|
||||
"Start": "시작",
|
||||
"Stop": "정지",
|
||||
"Uptime Kuma": "Uptime Kuma",
|
||||
"Add New Status Page": "새로운 상태 페이지 만들기",
|
||||
"Slug": "주소",
|
||||
"Add New Status Page": "새 상태 페이지 추가",
|
||||
"Slug": "Slug",
|
||||
"Accept characters:": "허용되는 문자열:",
|
||||
"startOrEndWithOnly": "{0} 로 시작하거나 끝나야 해요",
|
||||
"startOrEndWithOnly": "{0}로 시작하거나 끝나야 합니다.",
|
||||
"No consecutive dashes": "연속되는 대시는 허용되지 않아요",
|
||||
"Next": "다음",
|
||||
"The slug is already taken. Please choose another slug.": "이미 존재하는 주소에요. 다른 주소를 사용해 주세요.",
|
||||
|
@ -469,7 +469,7 @@
|
|||
"onebotGroupMessage": "그룹 메시지",
|
||||
"onebotPrivateMessage": "개인 메시지",
|
||||
"onebotUserOrGroupId": "그룹/사용자 ID",
|
||||
"onebotSafetyTips": "안전을 위해 Access 토큰을 설정하세요",
|
||||
"onebotSafetyTips": "보안을 위해 Access 토큰을 설정하세요.",
|
||||
"PushDeer Key": "PushDeer 키",
|
||||
"Footer Text": "Footer 문구",
|
||||
"Show Powered By": "Powered By 문구 표시하기",
|
||||
|
@ -479,9 +479,9 @@
|
|||
"Certificate Expiry Notification": "인증서 만료 알림",
|
||||
"API Username": "API 사용자 이름",
|
||||
"API Key": "API 키",
|
||||
"Recipient Number": "받는 사람 번호",
|
||||
"Recipient Number": "수신자 번호",
|
||||
"From Name/Number": "발신자 이름/번호",
|
||||
"Leave blank to use a shared sender number.": "공유 발신자 번호를 사용하려면 공백으로 두세요.",
|
||||
"Leave blank to use a shared sender number.": "공유 발신 번호를 사용하려면 공백으로 두세요.",
|
||||
"Octopush API Version": "Octopush API 버전",
|
||||
"Legacy Octopush-DM": "레거시 Octopush-DM",
|
||||
"endpoint": "endpoint",
|
||||
|
@ -526,9 +526,9 @@
|
|||
"Retype the address.": "주소 다시 입력하기.",
|
||||
"Go back to the previous page.": "이전 페이지로 돌아가기.",
|
||||
"Coming Soon": "Coming Soon",
|
||||
"wayToGetClickSendSMSToken": "{0}에서 API 사용자 이름과 키를 얻을 수 있어요.",
|
||||
"Custom Monitor Type": "커스텀 모니터링",
|
||||
"deleteDockerHostMsg": "정말 이 도커 호스트를 모든 모니터링에서 삭제할까요?",
|
||||
"wayToGetClickSendSMSToken": "{0}에서 API 사용자명과 키를 얻을 수 있습니다.",
|
||||
"Custom Monitor Type": "커스텀 모니터",
|
||||
"deleteDockerHostMsg": "이 Docker 호스트를 모든 모니터에서 삭제하시겠습니까?",
|
||||
"trustProxyDescription": "'X-Forwarded-*' 헤더를 신뢰해요. 올바른 클라이언트 IP를 얻어야하고Uptime Kuma가 Nginx나 Apache 같은 프록시 뒤에 있다면 이 기능을 활성화해야 해요.",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "프로필 이름(왼쪽 아래)을 클릭하고 아래로 스크롤한 다음 토큰 만들기를 클릭하여 장기 액세스 토큰을 만들 수 있어요. ",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "그런 다음 동작을 선택해요, 예를 들어 장면을 RGB 조명이 빨간색인 곳으로 전환해요.",
|
||||
|
@ -541,12 +541,12 @@
|
|||
"You can divide numbers with": "다음과 같이 숫자를 구분할 수 있어요:",
|
||||
"goAlertInfo": "GoAlert는 온콜 스케줄링, 자동 에스컬레이션 및 알림(SMS 또는 음성 통화와 같은)을 위한 오픈 소스 응용 프로그램이에요. 올바른 사람, 올바른 방법, 적절한 시간에 자동으로 참여하세요! {0}",
|
||||
"smseagle": "SMSEagle",
|
||||
"smseagleTo": "휴대전화 번호",
|
||||
"smseagleRecipient": "받는 사람 (쉼표로 구분)",
|
||||
"smseagleTo": "휴대폰 번호",
|
||||
"smseagleRecipient": "수신자 (여러 명인 경우 쉼표로 구분)",
|
||||
"Maintenance": "점검",
|
||||
"statusMaintenance": "점검 중",
|
||||
"resendEveryXTimes": "{0}번마다 다시 보내요",
|
||||
"resendDisabled": "다시 보내지 않아요",
|
||||
"resendEveryXTimes": "{0}번마다 재전송",
|
||||
"resendDisabled": "재전송하지 않음",
|
||||
"loadingError": "데이터를 가져올 수 없어요, 나중에 다시 시도하세요.",
|
||||
"plugin": "플러그인",
|
||||
"install": "설치",
|
||||
|
@ -575,14 +575,14 @@
|
|||
"Bark Group": "Bark 그룹",
|
||||
"Bark Sound": "Bark 소리",
|
||||
"promosmsAllowLongSMS": "긴 SMS 허용",
|
||||
"smseagleGroup": "전화번호부 그룹 이름",
|
||||
"smseagleContact": "전화번호부 연락처 이름",
|
||||
"smseagleRecipientType": "받는 사람 종류",
|
||||
"smseagleToken": "API 엑세스 토큰",
|
||||
"smseagleGroup": "연락처 그룹명 목록",
|
||||
"smseagleContact": "연락처 이름 목록",
|
||||
"smseagleRecipientType": "수신자 종류",
|
||||
"smseagleToken": "API 액세스 토큰",
|
||||
"smseagleUrl": "SMSEagle 기기 URL",
|
||||
"smseagleEncoding": "유니코드로 보내기",
|
||||
"smseaglePriority": "메시지 우선 순위 (0-9, 기본값= 0)",
|
||||
"ntfy Topic": "ntfy 주제",
|
||||
"ntfy Topic": "ntfy 토픽",
|
||||
"HomeAssistant": "홈 어시스턴트",
|
||||
"RadiusSecretDescription": "클라이언트와 서버 간의 비밀 키",
|
||||
"RadiusSecret": "Radius 비밀 키",
|
||||
|
@ -594,7 +594,7 @@
|
|||
"Request Timeout": "요청 타임아웃",
|
||||
"Query": "쿼리",
|
||||
"settingsCertificateExpiry": "TLS 인증서 만료",
|
||||
"certificationExpiryDescription": "HTTPS 모니터링 TLS 인증서가 만료되면 알림을 활성화해요:",
|
||||
"certificationExpiryDescription": "TLS 인증서가 설정된 기간 내에 만료될 경우, HTTPS 모니터가 알림을 전송합니다:",
|
||||
"Setup Docker Host": "도커 호스트 설정",
|
||||
"Docker Daemon": "도커 데몬",
|
||||
"socket": "소켓",
|
||||
|
@ -623,7 +623,7 @@
|
|||
"Event data:": "이벤트 데이터:",
|
||||
"Frontend Version": "프론트엔드 버전",
|
||||
"Frontend Version do not match backend version!": "프론트엔드 버전이 백엔드 버전과 일치하지 않아요!",
|
||||
"confirmDeleteTagMsg": "정말 이 태그를 삭제할까요? 이 태그와 연결된 모니터링은 삭제되지 않아요.",
|
||||
"confirmDeleteTagMsg": "이 태그를 삭제하시겠습니까? 이 태그와 연결된 모니터는 삭제되지 않습니다.",
|
||||
"infiniteRetention": "무한히 저장하려면 0으로 설정하세요.",
|
||||
"backupRecommend": "대신 볼륨 또는 데이터 폴더 (./data/) 를 직접 백업하세요.",
|
||||
"Optional": "선택",
|
||||
|
@ -670,64 +670,64 @@
|
|||
"grpcMethodDescription": "메서드 이름은 sayHello, check와 같은 카멜 케이스로 변환되어요.",
|
||||
"deleteMaintenanceMsg": "정말 이 점검을 삭제할까요?",
|
||||
"recurringIntervalMessage": "매일 한 번 실행 | {0}일마다 한 번 실행",
|
||||
"affectedMonitorsDescription": "현재 점검에 영향을 받는 모니터링 선택하기",
|
||||
"affectedMonitorsDescription": "현재 유지보수에 영향을 받는 모니터를 선택하세요.",
|
||||
"affectedStatusPages": "점검 메시지를 표시할 상태 페이지 선택하기",
|
||||
"Kook": "Kook",
|
||||
"atLeastOneMonitor": "최소 1개의 모니터링을 선택하세요",
|
||||
"atLeastOneMonitor": "적어도 1개 이상의 모니터를 선택하세요.",
|
||||
"wayToGetKookBotToken": "{0} 에서 애플리케이션을 만들고 봇 토큰을 얻어요",
|
||||
"Help": "도움말",
|
||||
"Game": "게임",
|
||||
"General Monitor Type": "일반 모니터링",
|
||||
"Passive Monitor Type": "수동 모니터링",
|
||||
"Specific Monitor Type": "특정 모니터링",
|
||||
"General Monitor Type": "일반 모니터 유형",
|
||||
"Passive Monitor Type": "수동 모니터 유형",
|
||||
"Specific Monitor Type": "특정 모니터 유형",
|
||||
"Monitor": "모니터",
|
||||
"Resend Notification if Down X times consecutively": "X번 중단될 경우 알림 다시 보내기",
|
||||
"Schedule maintenance": "점검 예약하기",
|
||||
"Affected Monitors": "영향을 받는 모니터링",
|
||||
"Pick Affected Monitors...": "영향을 받는 모니터링 선택하기…",
|
||||
"Resend Notification if Down X times consecutively": "연속적인 다운으로 판단해 알림을 재전송할 기준 횟수",
|
||||
"Schedule maintenance": "유지보수 예약",
|
||||
"Affected Monitors": "영향을 받는 모니터",
|
||||
"Pick Affected Monitors...": "영향을 받는 모니터 선택…",
|
||||
"Start of maintenance": "점검 시작",
|
||||
"All Status Pages": "모든 상태 페이지",
|
||||
"Select status pages...": "상태 페이지 선택하기…",
|
||||
"Custom": "커스텀",
|
||||
"Select status pages...": "상태 페이지 선택…",
|
||||
"Custom": "사용자 지정",
|
||||
"webhookAdditionalHeadersTitle": "추가 헤더",
|
||||
"webhookAdditionalHeadersDesc": "웹훅과 함께 전송될 추가 헤더를 설정해요. 각각의 헤더는 JSON 키/값으로 구성되어야 해요.",
|
||||
"webhookAdditionalHeadersDesc": "Webhook과 함께 전송되는 추가 헤더를 설정합니다. 각각의 헤더는 JSON 키/값으로 이루어져야 합니다.",
|
||||
"HTTP Headers": "HTTP 헤더",
|
||||
"Trust Proxy": "프록시 신뢰",
|
||||
"API Keys": "API 키",
|
||||
"markdownSupported": "Markdown 문법이 지원됨",
|
||||
"markdownSupported": "마크다운 문법 사용 가능",
|
||||
"telegramMessageThreadID": "(선택) 메시지 스레드 ID",
|
||||
"Clone": "복제",
|
||||
"cloneOf": "{0}의 복제본",
|
||||
"Clone Monitor": "모니터링 복제",
|
||||
"Clone Monitor": "모니터 복제",
|
||||
"telegramProtectContent": "포워딩/저장 보호",
|
||||
"telegramProtectContentDescription": "활성화 할경우 텔레그램 봇 메시지는 포워딩 및 저장으로부터 보호됩니다.",
|
||||
"telegramSendSilentlyDescription": "조용히 메시지를 보냅니다. 사용자들은 무음으로 알림을 받습니다.",
|
||||
"telegramSendSilently": "무음 알림",
|
||||
"Add New Tag": "태그 추가",
|
||||
"Edit Tag": "태그 수정",
|
||||
"Add New Tag": "새 태그 추가",
|
||||
"Edit Tag": "태그 편집",
|
||||
"Server Address": "서버 주소",
|
||||
"Learn More": "자세히 알아보기",
|
||||
"Continue": "계속",
|
||||
"Key Added": "키 추가됨",
|
||||
"No API Keys": "API 키 없음",
|
||||
"disableAPIKeyMsg": "이 API키를 정말로 비활성화하시겠습니까?",
|
||||
"deleteAPIKeyMsg": "이 API키를 정말로 삭제하시겠습니까?",
|
||||
"disableAPIKeyMsg": "이 API 키를 비활성화하시겠습니까?",
|
||||
"deleteAPIKeyMsg": "이 API 키를 삭제하시겠습니까?",
|
||||
"Generate": "생성",
|
||||
"Body Encoding": "Body 인코딩",
|
||||
"Body Encoding": "본문(Body) 인코딩",
|
||||
"Expiry": "만료",
|
||||
"Expiry date": "만료 날짜",
|
||||
"Expiry date": "만료일",
|
||||
"Don't expire": "만료되지 않음",
|
||||
"notificationRegional": "지역별",
|
||||
"Google Analytics ID": "Google Analytics ID",
|
||||
"Google Analytics ID": "Google 애널리틱스 ID",
|
||||
"Add API Key": "API 키 추가",
|
||||
"apiKeyAddedMsg": "API 키가 추가되었습니다. 다시 표시되지 않을 것이므로 메모해 두세요.",
|
||||
"apiKeyAddedMsg": "API 키가 추가되었습니다. 다시 표시되지 않으므로 메모해 두세요.",
|
||||
"pagertreeCritical": "긴급",
|
||||
"apiKey-active": "사용 가능",
|
||||
"apiKey-active": "활성",
|
||||
"lunaseaUserID": "사용자 ID",
|
||||
"apiKey-expired": "만료됨",
|
||||
"Expires": "만료일",
|
||||
"Expires": "만료",
|
||||
"twilioAuthToken": "인증 토큰 / API 키 시크릿",
|
||||
"twilioFromNumber": "번호에서",
|
||||
"twilioFromNumber": "발신 번호",
|
||||
"twilioToNumber": "번호에서",
|
||||
"twilioAccountSID": "계정 SID",
|
||||
"pagertreeUrgency": "긴급",
|
||||
|
@ -739,63 +739,110 @@
|
|||
"invalidCronExpression": "알수없는 Cron 값입니다: {0}",
|
||||
"Add Another": "다른 항목 추가",
|
||||
"apiKey-inactive": "비활성화",
|
||||
"pagertreeIntegrationUrl": "Integration 링크",
|
||||
"pagertreeIntegrationUrl": "Integration URL",
|
||||
"pagertreeLow": "낮음",
|
||||
"pagertreeMedium": "중간",
|
||||
"pagertreeHigh": "높음",
|
||||
"pagertreeResolve": "자동으로 해결하기",
|
||||
"pagertreeResolve": "자동으로 해결",
|
||||
"pagertreeDoNothing": "아무것도 하지 않음",
|
||||
"wayToGetPagerTreeIntegrationURL": "PagerTree에서 Uptime Kuma 통합을 생성한 후 Endpoint를 복사합니다. 전체 세부 정보 보기 {0}",
|
||||
"wayToGetPagerTreeIntegrationURL": "PagerTree에서 Uptime Kuma 통합을 생성한 후 엔드포인트를 복사합니다. 세부 정보 보기 {0}",
|
||||
"lunaseaTarget": "대상",
|
||||
"lunaseaDeviceID": "기기 ID",
|
||||
"lunaseaDeviceID": "디바이스 ID",
|
||||
"statusPageRefreshIn": "{0} 후 새로고침",
|
||||
"telegramMessageThreadIDDescription": "포럼의 대상 메시지 쓰레드(주제)에 대한 선택적 고유 식별인, 포럼 관리자 그룹에만 해당",
|
||||
"pagertreeSilent": "없음",
|
||||
"setupDatabaseChooseDatabase": "어떤 데이터베이스를 사용하시겠습니까?",
|
||||
"setupDatabaseEmbeddedMariaDB": "추가 설정은 필요 없습니다. 이 도커 이미지에는 MariaDB가 내장되어 구성되어 있습니다. Uptime Kuma는 Unix Socket을 통해 데이터베이스에 연결합니다.",
|
||||
"setupDatabaseEmbeddedMariaDB": "추가 설정이 필요하지 않습니다. 이 도커 이미지에는 MariaDB가 자동으로 포함 및 구성되어 있으며, Uptime Kuma는 유닉스 소켓을 통해 데이터베이스에 연결합니다.",
|
||||
"setupDatabaseMariaDB": "외부 MariaDB 데이터베이스에 연결합니다. 데이터베이스 연결 정보를 설정해야 합니다.",
|
||||
"setupDatabaseSQLite": "소규모 배포에 권장되는 간단한 데이터베이스 파일입니다. v2.0.0 이전에는 Uptime Kuma가 SQLite를 기본 데이터베이스로 사용했습니다.",
|
||||
"setupDatabaseSQLite": "소규모 배포에 권장되는 간단한 데이터베이스 파일입니다. Uptime Kuma는 v2.0.0 이전까지 SQLite를 기본 데이터베이스로 사용했습니다.",
|
||||
"dbName": "데이터베이스 이름",
|
||||
"filterActive": "활성화",
|
||||
"filterActivePaused": "일시중지",
|
||||
"filterActive": "활성",
|
||||
"filterActivePaused": "일시 정지",
|
||||
"Home": "홈",
|
||||
"Cannot connect to the socket server": "소켓 서버에 연결 할 수 없습니다",
|
||||
"Reconnecting...": "재 연결중...",
|
||||
"Cannot connect to the socket server": "소켓 서버에 연결할 수 없습니다.",
|
||||
"Reconnecting...": "다시 연결하는 중...",
|
||||
"Json Query": "JSON 쿼리",
|
||||
"settingUpDatabaseMSG": "데이터베이스를 설정하는 중입니다. 시간이 걸릴 수 있으니 기다려 주세요.",
|
||||
"settingUpDatabaseMSG": "데이터베이스를 설정하는 중입니다. 시간이 걸릴 수 있으니 잠시만 기다려 주세요.",
|
||||
"enableNSCD": "모든 DNS 요청을 캐싱하기 위해 NSCD (Name Service Cache Daemon) 활성화",
|
||||
"pushOthers": "기타",
|
||||
"programmingLanguages": "프로그래밍 언어",
|
||||
"Select": "선택",
|
||||
"Edit Maintenance": "점검 수정하기",
|
||||
"styleElapsedTime": "하트비트 바 밑의 지난 시간 표시",
|
||||
"styleElapsedTimeShowNoLine": "보이기 (선 없음)",
|
||||
"styleElapsedTimeShowWithLine": "보이기 (선 있음)",
|
||||
"styleElapsedTime": "하트비트 바 아래 표시되는 경과 시간",
|
||||
"styleElapsedTimeShowNoLine": "표시 (선 없음)",
|
||||
"styleElapsedTimeShowWithLine": "표시 (선 있음)",
|
||||
"chromeExecutable": "Chrome/Chromium 실행 파일",
|
||||
"chromeExecutableAutoDetect": "자동 감지",
|
||||
"Invert Keyword": "키워드 반전",
|
||||
"Expected Value": "기대값",
|
||||
"Expected Value": "기댓값",
|
||||
"Add a domain": "도메인 추가",
|
||||
"Remove domain": "도메인 '{0}' 제거",
|
||||
"Monitor Group": "모니터링 그룹",
|
||||
"Monitor Group": "모니터 그룹",
|
||||
"Monitor Setting": "{0}의 모니터 설정",
|
||||
"now": "지금",
|
||||
"time ago": "{0} 전",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "연결하려는 서버의 호스트 이름을 입력하거나 {local_mta}를 사용하려는 경우 {localhost}를 입력합니다",
|
||||
"-year": "-연도",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "연결하려는 서버의 호스트 이름을 입력하거나, {local_mta}를 사용하려는 경우 {localhost}를 입력합니다.",
|
||||
"-year": "년",
|
||||
"Json Query Expression": "Json 쿼리 표현식",
|
||||
"Host URL": "호스트 URL",
|
||||
"locally configured mail transfer agent": "로컬로 구성된 메일 전송 에이전트",
|
||||
"ignoreTLSErrorGeneral": "연결에 TLS/SSL 오류 무시하기",
|
||||
"locally configured mail transfer agent": "로컬에 구성된 메일 전송 에이전트",
|
||||
"ignoreTLSErrorGeneral": "연결 중 TLS/SSL 오류 무시",
|
||||
"ignoredTLSError": "TLS/SSL 오류가 무시되었습니다",
|
||||
"liquidIntroduction": "템플릿 생성은 Liquid 템플릿 언어를 통해 이루어집니다. 사용 지침은 {0}을 참조하세요. 사용 가능한 변수는 다음과 같습니다:",
|
||||
"liquidIntroduction": "템플릿은 Liquid 템플릿 언어를 통해 생성됩니다. 사용법은 {0}을 참조하세요. 사용 가능한 변수는 다음과 같습니다:",
|
||||
"templateMsg": "알림 메시지",
|
||||
"templateLimitedToUpDownCertNotifications": "업/다운/인증서 만료 알림에만 사용 가능",
|
||||
"templateLimitedToUpDownNotifications": "UP/DOWN 알림에만 사용 가능",
|
||||
"webhookBodyPresetOption": "프리셋 - {0}",
|
||||
"templateLimitedToUpDownCertNotifications": "온라인/오프라인/인증서 만료 알림에만 사용 가능",
|
||||
"templateLimitedToUpDownNotifications": "온라인/오프라인 알림에만 사용 가능",
|
||||
"webhookBodyPresetOption": "사전 설정 - {0}",
|
||||
"successKeyword": "성공 키워드",
|
||||
"successKeywordExplanation": "성공으로 간주되는 MQTT 키워드",
|
||||
"successKeywordExplanation": "성공으로 간주할 MQTT 키워드",
|
||||
"Reset Token": "토큰 초기화",
|
||||
"Check/Uncheck": "체크/체크 해제",
|
||||
"pushViewCode": "푸시 모니터링는 어떻게 사용하나요? (코드 보기)"
|
||||
"pushViewCode": "푸시 모니터는 어떻게 사용하나요? (코드 보기)",
|
||||
"Search monitored sites": "모니터링 중인 사이트 검색",
|
||||
"templateHeartbeatJSON": "하트비트를 설명하는 오브젝트",
|
||||
"shrinkDatabaseDescriptionSqlite": "SQLite 데이터베이스에 대해 {vacuum}을(를) 트리거합니다. {auto_vacuum}이 이미 활성화되어 있지만, 이는 데이터베이스를 조각 모음하거나 {vacuum} 명령어처럼 개별 데이터베이스 페이지를 다시 정리하지는 않습니다.",
|
||||
"statusPageSpecialSlugDesc": "특별한 주소 {0}: 아무런 주소도 입력되지 않으면 이 페이지가 보여요",
|
||||
"Add a new expiry notification day": "새 만료 알림 날짜 추가",
|
||||
"Refresh Interval Description": "이 상태 페이지는 {0}초마다 완전 새로고침(F5) 돼요",
|
||||
"telegramServerUrlDescription": "텔레그램 봇 API의 제한을 해제하거나, 차단된 지역(중국, 이란 등)에서 액세스하려면 {0}을 클릭하세요. 기본값: {1}",
|
||||
"chromeExecutableDescription": "Docker 사용자의 경우, Chromium이 아직 설치되지 않았다면 이를 설치하고 테스트 결과를 표시하는 데 몇 분이 걸릴 수 있어요. 1GB의 디스크 공간을 사용해요.",
|
||||
"templateMonitorJSON": "모니터를 설명하는 오브젝트",
|
||||
"webhookBodyCustomOption": "사용자 지정 본문 (Body)",
|
||||
"telegramServerUrl": "(선택) 서버 URL",
|
||||
"and": "그리고",
|
||||
"emailCustomisableContent": "사용자 지정 가능한 콘텐츠",
|
||||
"smtpLiquidIntroduction": "다음 두 개 필드는 Liquid 템플릿 언어를 통해 템플릿화할 수 있습니다. 사용 지침은 {0}을 참조하세요. 사용 가능한 변수는 다음과 같습니다:",
|
||||
"leave blank for default subject": "기본값을 사용하려면 비워두세요",
|
||||
"emailCustomBody": "커스텀 Body",
|
||||
"leave blank for default body": "기본값을 사용하려면 비워두세요",
|
||||
"templateServiceName": "서비스명",
|
||||
"templateHostnameOrURL": "호스트명 또는 URL",
|
||||
"templateStatus": "상태",
|
||||
"selectedMonitorCount": "선택됨: {0}",
|
||||
"Remove the expiry notification": "만료 알림 날짜 제거",
|
||||
"Refresh Interval": "새로고침 주기",
|
||||
"noDockerHostMsg": "사용할 수 없습니다. 먼저 도커 호스트를 설정하세요.",
|
||||
"DockerHostRequired": "이 모니터를 위한 Docker 호스트를 설정해 주세요.",
|
||||
"tailscalePingWarning": "Tailscale Ping 모니터를 사용하려면 Docker를 사용하지 않고 Uptime Kuma를 설치해야 하며, 서버에 Tailscale 클라이언트도 설치해야 합니다.",
|
||||
"telegramUseTemplate": "커스텀 메시지 템플릿 사용",
|
||||
"telegramUseTemplateDescription": "활성화하면 메시지를 보낼 때 커스텀 템플릿을 사용해요.",
|
||||
"telegramTemplateFormatDescription": "텔레그램은 메시지에 다양한 마크업 언어를 사용할 수 있어요. 자세한 내용은 텔레그램 {0}을 참조하세요.",
|
||||
"RabbitMQ Username": "RabbitMQ 사용자명",
|
||||
"RabbitMQ Password": "RabbitMQ 비밀번호",
|
||||
"wahaSession": "세션",
|
||||
"emailTemplateMsg": "알림 메시지",
|
||||
"Select message type": "메시지 유형 선택",
|
||||
"Send to channel": "채널로 전송",
|
||||
"Create new forum post": "새 포럼 게시물 만들기",
|
||||
"Your User ID": "사용자 ID",
|
||||
"emailTemplateMonitorJSON": "모니터를 설명하는 객체",
|
||||
"postToExistingThread": "기존 스레드/포럼 게시물에 게시",
|
||||
"forumPostName": "포럼 게시물 이름",
|
||||
"threadForumPostID": "스레드 / 포럼 게시물 ID",
|
||||
"e.g. {discordThreadID}": "예: {discordThreadID}",
|
||||
"whatHappensAtForumPost": "새 포럼 게시물을 만드세요. 기존 게시물에는 메시지가 게시되지 않습니다. 기존 게시물에 게시하려면 \"{option}\"을 사용하세요",
|
||||
"wayToGetDiscordThreadId": "스레드/포럼 게시물 ID를 얻는 것은 채널 ID를 얻는 것과 비슷합니다. ID를 얻는 방법에 대해 자세히 알아보세요. {0}",
|
||||
"Channel access token (Long-lived)": "채널 액세스 토큰(장기)",
|
||||
"invertKeywordDescription": "키워드가 존재하지 않는지 살펴보세요.",
|
||||
"emailTemplateLimitedToUpDownNotification": "UP/DOWN 하트비트에만 사용 가능, 그렇지 않으면 null"
|
||||
}
|
||||
|
|
7
src/lang/lv.json
Normal file
7
src/lang/lv.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"languageName": "Latviešu",
|
||||
"setupDatabaseChooseDatabase": "Kuru datubāzi izmantosiet?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Jums nav nekas jādara. Docker imidžā ir iebūvēta un automātiski konfigurēta MariaDB datubāze. Uptime Kuma pieslēgsies šai datubāzei izmantojot unix soketu.",
|
||||
"setupDatabaseSQLite": "Vienkāršs datu bāzes fails, iesakāms maza izmēra risinājumiem. Pirms versijas v2.0.0 SQLite bija noklusējuma datubāze.",
|
||||
"setupDatabaseMariaDB": "Pieslēgties ārējai MariaDB datubāzei. Jums būs jākonfigurē datubāzes pieslēgšanās informācija."
|
||||
}
|
|
@ -7,24 +7,24 @@
|
|||
"Game": "Permainan",
|
||||
"Primary Base URL": "URL Pangkalan Utama",
|
||||
"Version": "Versi",
|
||||
"Add": "Menambah",
|
||||
"Quick Stats": "Statistik ringkas",
|
||||
"Up": "Dalam talian",
|
||||
"Down": "Luar talian",
|
||||
"Pending": "Belum selesai",
|
||||
"statusMaintenance": "Membaiki",
|
||||
"Maintenance": "Membaiki",
|
||||
"Unknown": "Tidak ketahui",
|
||||
"General Monitor Type": "Jenis monitor umum",
|
||||
"Check Update On GitHub": "Semak kemas kini dalam GitHub",
|
||||
"Add": "Tambah",
|
||||
"Quick Stats": "Statistik Pantas",
|
||||
"Up": "Atas",
|
||||
"Down": "Bawah",
|
||||
"Pending": "Dalam Proses",
|
||||
"statusMaintenance": "Penyelenggaraan",
|
||||
"Maintenance": "Penyelenggaraan",
|
||||
"Unknown": "Tidak Diketahui",
|
||||
"General Monitor Type": "Jenis Monitor Umum",
|
||||
"Check Update On GitHub": "Semak kemas kini di GitHub",
|
||||
"List": "Senarai",
|
||||
"Specific Monitor Type": "Jenis monitor spesifik",
|
||||
"Specific Monitor Type": "Jenis Monitor Spesifik",
|
||||
"markdownSupported": "Sintaks markdown disokong",
|
||||
"languageName": "Bahasa inggeris",
|
||||
"Dashboard": "Papan pemuka",
|
||||
"Language": "Bahasa",
|
||||
"Add New Monitor": "Tambah monitor baharu",
|
||||
"Passive Monitor Type": "Jenis monitor pasif",
|
||||
"Add New Monitor": "Tambah Monitor Baharu",
|
||||
"Passive Monitor Type": "Jenis Monitor Pasif",
|
||||
"No Services": "Tiada Servis",
|
||||
"Add a monitor": "Tambah Monitor",
|
||||
"High": "Tinggi",
|
||||
|
@ -49,5 +49,41 @@
|
|||
"Content Type": "Jenis Content",
|
||||
"Home": "Laman Utama",
|
||||
"Settings": "Tetapan",
|
||||
"Save": "Simpan"
|
||||
"Save": "Simpan",
|
||||
"Cannot connect to the socket server": "Tidak dapat disambungkan kepada pelayan soket",
|
||||
"Resume": "Sambung",
|
||||
"Current": "Terkini",
|
||||
"Uptime": "Uptime",
|
||||
"Cert Exp.": "Tamat Sijil",
|
||||
"now": "sekarang",
|
||||
"setupDatabaseMariaDB": "Sambungan kepada pangkalan data MariaDB secara luaran. Anda perlu tetapkan maklumat sambungan pangkalan data.",
|
||||
"hour": "jam",
|
||||
"Ping": "Ping",
|
||||
"settingUpDatabaseMSG": "Pangkalan data sedang ditetapkan. Sila tunggu sebentar.",
|
||||
"Reconnecting...": "Penyambungan...",
|
||||
"Message": "Mesej",
|
||||
"No important events": "Tiada info penting",
|
||||
"Edit": "Sunting",
|
||||
"Delete": "Padam",
|
||||
"Monitor": "Monitor | Monitors",
|
||||
"time ago": "{0} yang lepas",
|
||||
"day": "hari | hari",
|
||||
"-day": "-hari",
|
||||
"-year": "-tahun",
|
||||
"Pause": "Rehat",
|
||||
"Status": "Status",
|
||||
"DateTime": "TarikhMasa",
|
||||
"dbName": "Nama Pangkalan Data",
|
||||
"-hour": "-jam",
|
||||
"Response": "Tindakbalas",
|
||||
"Monitor Type": "Jenis Monitor",
|
||||
"Keyword": "Katakunci",
|
||||
"pauseDashboardHome": "Rehat",
|
||||
"Name": "Nama",
|
||||
"setupDatabaseChooseDatabase": "Pangkalan Data yang mana hendak digunakan ?",
|
||||
"Host URL": "URL Host",
|
||||
"URL": "URL",
|
||||
"Expected Value": "Nilai Sepatutnya",
|
||||
"Friendly Name": "Nama Mudah",
|
||||
"Hostname": "Nama Host"
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
"Create": "Aanmaken",
|
||||
"Clear Data": "Data wissen",
|
||||
"Events": "Gebeurtenissen",
|
||||
"Heartbeats": "Heartbeats",
|
||||
"Heartbeats": "Hartslagen",
|
||||
"Auto Get": "Auto Get",
|
||||
"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.",
|
||||
|
@ -464,24 +464,24 @@
|
|||
"Show Powered By": "Laat \"Mogeljik gemaakt door\" zien",
|
||||
"Domain Names": "Domein Namen",
|
||||
"pushoversounds pushover": "Pushover (standaard)",
|
||||
"pushoversounds bike": "Bike",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds cashregister": "Cash Register",
|
||||
"pushoversounds bike": "Fiets",
|
||||
"pushoversounds bugle": "Trompet",
|
||||
"pushoversounds cashregister": "Kassa",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds cosmic": "Buitenaards",
|
||||
"pushoversounds falling": "Vallend",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds magic": "Magic",
|
||||
"pushoversounds mechanical": "Mechanical",
|
||||
"pushoversounds incoming": "Inkomend",
|
||||
"pushoversounds intermission": "Pauze",
|
||||
"pushoversounds magic": "Magie",
|
||||
"pushoversounds mechanical": "Mechanisch",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Siren",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds siren": "Sirene",
|
||||
"pushoversounds spacealarm": "Ruimte Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm (long)",
|
||||
"pushoversounds climb": "Climb (long)",
|
||||
"pushoversounds persistent": "Persistent (long)",
|
||||
"pushoversounds persistent": "Aanhoudend (lang)",
|
||||
"pushoversounds echo": "Pushover Echo (long)",
|
||||
"pushoversounds updown": "Up Down (long)",
|
||||
"pushoversounds vibrate": "Alleen trillen",
|
||||
|
@ -649,8 +649,8 @@
|
|||
"smseagleTo": "Telefoonnummer(s)",
|
||||
"Custom Monitor Type": "Custom Monitor Type",
|
||||
"trustProxyDescription": "'X-Forwarded-*' headers vertrouwen. Als je de correcte client IP wilt krijgen en de Uptime Kuma installatie is achter een proxy zoals Nginx of Apache, schakel dan dit in.",
|
||||
"RadiusCalledStationId": "Called Station Id",
|
||||
"RadiusCalledStationIdDescription": "Identifier of the called device",
|
||||
"RadiusCalledStationId": "Genoemde stations ID",
|
||||
"RadiusCalledStationIdDescription": "Identificatie van het genoemde apparaat",
|
||||
"RadiusCallingStationId": "Calling Station Id",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Long-Lived Access Token": "Long-Lived Access Token",
|
||||
|
@ -789,7 +789,7 @@
|
|||
"Badge Warn Days": "Badge Waarschuwing dagen",
|
||||
"Badge Down Days": "Badge Offline dagen",
|
||||
"Badge Style": "Badge stijl",
|
||||
"chromeExecutable": "Chrome/Chromium Executable",
|
||||
"chromeExecutable": "Chrome/Chromium Uitvoerbaar bestand",
|
||||
"chromeExecutableAutoDetect": "Automatisch detecteren",
|
||||
"Edit Maintenance": "Onderhoud bewerken",
|
||||
"Badge Label": "Badge Label",
|
||||
|
@ -1039,36 +1039,36 @@
|
|||
"ends with": "eindigt met",
|
||||
"not ends with": "eindigt niet met",
|
||||
"less than": "minder dan",
|
||||
"greater than": "meer dan",
|
||||
"greater than": "groter dan",
|
||||
"record": "dossier",
|
||||
"jsonQueryDescription": "Parseer en haal specifieke gegevens uit de JSON-respons van de server met behulp van JSON-query of gebruik \"$\" voor de onbewerkte respons, als u geen JSON verwacht. Het resultaat wordt vervolgens vergeleken met de verwachte waarde, als strings. Zie {0} voor documentatie en gebruik {1} om te experimenteren met query's.",
|
||||
"rabbitmqNodesDescription": "Voer het URL voor de RabbitMQ beheerkooppunt inclusief protocol en poort in. Bijvoorbeeld: {0}",
|
||||
"rabbitmqNodesDescription": "Voer het URL voor de RabbitMQ beheer nodes inclusief protocol en poort in. Bijvoorbeeld: {0}",
|
||||
"rabbitmqNodesRequired": "Aub stel de knooppunten voor deze monitor in.",
|
||||
"rabbitmqNodesInvalid": "Stel gebruik een volledig gekwalificeerde (beginnend met 'http') URL voor RabbitMQ-knooppunten.",
|
||||
"rabbitmqNodesInvalid": "Gebruik een volledig gekwalificeerde (beginnend met 'http') URL voor de RabbitMQ nodes.",
|
||||
"RabbitMQ Username": "RabbitMQ gebruikersnaam",
|
||||
"RabbitMQ Password": "RabbitMQ wachtwoord",
|
||||
"rabbitmqHelpText": "Om gebruik te maken van de monitor moet je de Management Plugin in de RabbitMQ setup aanzetten. Voor meer informatie zie de {rabitmq_documentatie}.",
|
||||
"SendGrid API Key": "SendGrid API sleutel",
|
||||
"Separate multiple email addresses with commas": "Splits meerdere emailadressen met kommas",
|
||||
"RabbitMQ Nodes": "RabbitMQ Beheerknoppunten",
|
||||
"RabbitMQ Nodes": "RabbitMQ beheer Nodes",
|
||||
"shrinkDatabaseDescriptionSqlite": "Trigger database {vacuum} voor SQLite. {auto_vacuum} is al ingeschakeld, maar hiermee wordt de database niet gedefragmenteerd en worden ook databasepagina's niet afzonderlijke opnieuw ingepakt zoals de opdracht {vacuum} dat doet.",
|
||||
"aboutSlackUsername": "Wijzigt de weergavenaam van de afzender van het bericht. Als je iemand wilt vermelden, voer in dat geval de naam in als vriendelijke naam.",
|
||||
"cacheBusterParam": "Voeg de parameter {0} toe",
|
||||
"Form Data Body": "Formulier Gegevens Content",
|
||||
"Optional: Space separated list of scopes": "Optioneel: Reikwijdte door spaties gescheiden lijst",
|
||||
"Alphanumerical string and hyphens only": "Alleen alfanumerieke tekenreeksen en koppeltekens",
|
||||
"aboutSlackUsername": "Verandert de weergavenaam van de afzender. Als je iemand wil vermelden, voeg dit dan aan de vriendelijke naam toe.",
|
||||
"cacheBusterParam": "Voeg de {0} parameter",
|
||||
"Form Data Body": "Formulier Gegevens Body",
|
||||
"Optional: Space separated list of scopes": "Optioneel: door spaties gescheiden lijst met scopes",
|
||||
"Alphanumerical string and hyphens only": "Alleen alfanumerieke tekens en streepjes",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Tijdsgevoelige meldingen worden meteen afgeleverd, zelfs als het apparaat in niet storen modus staat.",
|
||||
"Message format": "Berichtformaat",
|
||||
"Send rich messages": "Stuur rijke berichten",
|
||||
"OAuth Scope": "OAuth Reikwijdte",
|
||||
"equals": "gelijk aan",
|
||||
"Message format": "Bericht opmaak",
|
||||
"Send rich messages": "Verstuur berichten met opmaak",
|
||||
"OAuth Scope": "OAuth bereik",
|
||||
"equals": "hetzelfde als",
|
||||
"not equals": "niet gelijk aan",
|
||||
"less than or equal to": "kleiner dan of gelijk aan",
|
||||
"greater than or equal to": "groter dan of gelijk aan",
|
||||
"Notification Channel": "Meldingskanaal",
|
||||
"less than or equal to": "minder dan of gelijk aan",
|
||||
"greater than or equal to": "meer dan of gelijk aan",
|
||||
"Notification Channel": "Notificatie kanaal",
|
||||
"Sound": "Geluid",
|
||||
"Arcade": "Speelhal",
|
||||
"Correct": "Juist",
|
||||
"Correct": "Goed",
|
||||
"Fail": "Mislukt",
|
||||
"Harp": "Harp",
|
||||
"Reveal": "Laat zien",
|
||||
|
@ -1082,12 +1082,38 @@
|
|||
"Time Sensitive (iOS Only)": "Tijdsgevoelig (alleen voor iOs)",
|
||||
"From": "Van",
|
||||
"Can be found on:": "Kan gevonden worden op: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Het telefoonnummer van de ontvanger in E.164 formaat",
|
||||
"The phone number of the recipient in E.164 format.": "Het telefoonnummer van de ontvanger in E.164 formaat.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Ofwel een sms zender ID of een telefoonnummer in E.164 formaat als je reacties wil ontvangen.",
|
||||
"Clear": "Helder",
|
||||
"Elevator": "Lift",
|
||||
"Pop": "Pop",
|
||||
"Community String": "Gemeenschap Tekst",
|
||||
"Json Query Expression": "Json Query Expressie",
|
||||
"ignoredTLSError": "TLS/SSL-fouten zijn genegeerd"
|
||||
"Community String": "Gemeenschapsreeks",
|
||||
"Json Query Expression": "JSON Query Expressie",
|
||||
"ignoredTLSError": "TLS/SSL-fouten zijn genegeerd",
|
||||
"telegramServerUrl": "(Optioneel) Server Url",
|
||||
"telegramServerUrlDescription": "Om de beperkingen van Telegram's bot api op te heffen of toegang te krijgen in geblokkeerde gebieden (China, Iran, enz.). Klik voor meer informatie op {0}. Standaard: {1}",
|
||||
"wahaSession": "Sessie",
|
||||
"wahaChatId": "Chat-ID (telefoonnummer / contact-ID / groeps-ID)",
|
||||
"wayToGetWahaApiUrl": "Je WAHA Instance URL.",
|
||||
"wayToGetWahaApiKey": "API Key is de WHATSAPP_API_KEY omgevingsvariabele die je hebt gebruikt om WAHA uit te voeren.",
|
||||
"wayToGetWahaSession": "Vanaf deze sessie stuurt WAHA meldingen naar Chat ID. Je kunt deze vinden in WAHA Dashboard.",
|
||||
"wayToWriteWahaChatId": "Het telefoonnummer met het internationale voorvoegsel, maar zonder het plusteken aan het begin ({0}), de contact-ID ({1}) of de groeps-ID ({2}). Vanuit WAHA Sessie worden meldingen naar deze Chat-ID verzonden.",
|
||||
"YZJ Robot Token": "YZJ Robot token",
|
||||
"Plain Text": "Platte tekst",
|
||||
"Message Template": "Bericht Sjabloon",
|
||||
"YZJ Webhook URL": "YZJ Webhook URL",
|
||||
"Template Format": "Sjabloonformaat",
|
||||
"templateServiceName": "service naam",
|
||||
"templateHostnameOrURL": "hostnaam of url",
|
||||
"templateStatus": "status",
|
||||
"telegramUseTemplate": "Gebruik aangepaste bericht sjabloon",
|
||||
"telegramTemplateFormatDescription": "Telegram staat het gebruik van verschillende opmaaktalen voor berichten toe, zie Telegram {0} voor specifieke details.",
|
||||
"telegramUseTemplateDescription": "Indien ingeschakeld, wordt het bericht verzonden met een aangepaste sjabloon.",
|
||||
"Font Twemoji by Twitter licensed under": "Lettertype Twemoji van Twitter gelicentieerd onder",
|
||||
"the smsplanet documentation": "de smsplanet documentatie",
|
||||
"Phone numbers": "Telefoonnummers",
|
||||
"Sender name": "Naam afzender",
|
||||
"smsplanetNeedToApproveName": "Moet worden goedgekeurd in het clientpaneel",
|
||||
"smsplanetApiToken": "Token voor de SMSPlanet API",
|
||||
"smsplanetApiDocs": "Gedetailleerde informatie over het verkrijgen van API-tokens vindt u op {the_smsplanet_documentation}."
|
||||
}
|
||||
|
|
|
@ -1098,5 +1098,31 @@
|
|||
"RabbitMQ Username": "Nazwa użytkownika RabbitMQ",
|
||||
"RabbitMQ Password": "Hasło RabbitMQ",
|
||||
"SendGrid API Key": "Klucz API SendGrid",
|
||||
"Separate multiple email addresses with commas": "Oddziel wiele adresów e-mail przecinkami"
|
||||
"Separate multiple email addresses with commas": "Oddziel wiele adresów e-mail przecinkami",
|
||||
"templateServiceName": "service name",
|
||||
"telegramServerUrlDescription": "Aby znieść ograniczenia api bota Telegrama lub uzyskać dostęp w zablokowanych obszarach (Chiny, Iran itp.). Aby uzyskać więcej informacji, kliknij {0}. Domyślnie: {1}",
|
||||
"wayToGetWahaSession": "Z tej sesji WAHA wysyła powiadomienia do Chat ID. Można go znaleźć w WAHA Dashboard.",
|
||||
"wayToWriteWahaChatId": "Numer telefonu z prefiksem międzynarodowym, ale bez znaku plus na początku ({0}), identyfikator kontaktu ({1}) lub identyfikator grupy ({2}). Powiadomienia są wysyłane do tego identyfikatora czatu z sesji WAHA.",
|
||||
"wahaSession": "Sesja",
|
||||
"wahaChatId": "Identyfikator czatu (numer telefonu / identyfikator kontaktu / identyfikator grupy)",
|
||||
"wayToGetWahaApiUrl": "Adres URL instancji WAHA.",
|
||||
"wayToGetWahaApiKey": "Klucz API to wartość zmiennej środowiskowej WHATSAPP_API_KEY użytej do uruchomienia WAHA.",
|
||||
"YZJ Robot Token": "Token robota YZJ",
|
||||
"YZJ Webhook URL": "Adres URL usługi YZJ Webhook",
|
||||
"telegramServerUrl": "(Opcjonalnie) Adres URL serwera",
|
||||
"Plain Text": "Zwykły tekst",
|
||||
"Message Template": "Szablon wiadomości",
|
||||
"Template Format": "Format szablonu",
|
||||
"templateHostnameOrURL": "nazwa hosta lub adres URL",
|
||||
"templateStatus": "status",
|
||||
"telegramUseTemplate": "Użyj niestandardowego szablonu wiadomości",
|
||||
"telegramUseTemplateDescription": "Jeśli opcja ta jest włączona, wiadomość zostanie wysłana przy użyciu niestandardowego szablonu.",
|
||||
"telegramTemplateFormatDescription": "Telegram pozwala na używanie różnych języków znaczników dla wiadomości, zobacz Telegram {0}, aby uzyskać szczegółowe informacje.",
|
||||
"Font Twemoji by Twitter licensed under": "Czcionka Twemoji autorstwa Twitter na licencji",
|
||||
"smsplanetApiToken": "Token dla API SMSPlanet",
|
||||
"smsplanetApiDocs": "Szczegółowe informacje na temat uzyskiwania tokenów API można znaleźć w {the_smsplanet_documentation}.",
|
||||
"the smsplanet documentation": "dokumentacja smsplanet",
|
||||
"Phone numbers": "Numery telefonów",
|
||||
"Sender name": "Nazwa nadawcy",
|
||||
"smsplanetNeedToApproveName": "Wymaga zatwierdzenia w panelu klienta"
|
||||
}
|
||||
|
|
|
@ -978,7 +978,7 @@
|
|||
"Add Remote Browser": "Adicionar Navegador Remoto",
|
||||
"New Group": "Novo Grupo",
|
||||
"Group Name": "Nome do Grupo",
|
||||
"OAuth2: Client Credentials": "OAuth2: Client Credentials",
|
||||
"OAuth2: Client Credentials": "OAuth2: Credenciais do Cliente",
|
||||
"Authentication Method": "Método de Autenticação",
|
||||
"Authorization Header": "Header de Autorização",
|
||||
"ignoredTLSError": "Erros TLS/SSL foram ignorados",
|
||||
|
@ -1068,5 +1068,25 @@
|
|||
"telegramTemplateFormatDescription": "O Telegram permite o uso de diferentes linguagens de marcação para mensagens. Veja o Telegram {0} para detalhes específicos.",
|
||||
"templateHostnameOrURL": "hostname ou URL",
|
||||
"templateStatus": "status",
|
||||
"telegramUseTemplateDescription": "Se habilitado, a mensagem será enviada usando um template personalizado."
|
||||
"telegramUseTemplateDescription": "Se habilitado, a mensagem será enviada usando um template personalizado.",
|
||||
"telegramServerUrlDescription": "Para suspender as limitações da API de bots do Telegram ou obter acesso em áreas bloqueadas (China, Irã, etc). Para mais informações, clique em {0}. Padrão: {1}",
|
||||
"wahaSession": "Sessão",
|
||||
"wayToGetWahaApiUrl": "URL da sua instância WAHA.",
|
||||
"wayToGetWahaApiKey": "API Key é o valor da variável de ambiente WHATSAPP_API_KEY que você usou para executar o WAHA.",
|
||||
"wayToGetWahaSession": "A partir desta sessão, o WAHA envia notificações para o Chat ID. Você pode encontrá-lo no WAHA Dashboard.",
|
||||
"wayToWriteWahaChatId": "O número de telefone com o prefixo internacional, mas sem o sinal de mais no início ({0}), o Contact ID ({1}) ou o Group ID ({2}). As notificações são enviadas para este Chat ID da sessão WAHA.",
|
||||
"Plain Text": "Texto Simples",
|
||||
"wahaChatId": "Chat ID (Número de Telefone / Contact ID / Group ID)",
|
||||
"YZJ Webhook URL": "YZJ Webhook URL",
|
||||
"YZJ Robot Token": "YZJ Robot token",
|
||||
"telegramServerUrl": "(Opcional) URL do Servidor",
|
||||
"Message Template": "Modelo de Mensagem",
|
||||
"Template Format": "Formato do Modelo",
|
||||
"Font Twemoji by Twitter licensed under": "Fonte Twemoji do Twitter licenciada sob",
|
||||
"the smsplanet documentation": "a documentação do smsplanet",
|
||||
"Phone numbers": "Números de telefone",
|
||||
"Sender name": "Nome do remetente",
|
||||
"smsplanetNeedToApproveName": "Precisa ser aprovado no painel do cliente",
|
||||
"smsplanetApiToken": "Token para a API SMSPlanet",
|
||||
"smsplanetApiDocs": "Informações detalhadas sobre a obtenção de tokens de API podem ser encontradas em {the_smsplanet_documentation}."
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue