Merge branch 'master' into master

This commit is contained in:
Frank Elsinga 2025-05-18 00:57:07 +02:00 committed by GitHub
commit d179a468a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
136 changed files with 7377 additions and 20931 deletions

View file

@ -32,7 +32,6 @@ tsconfig.json
/extra/healthcheck.exe
/extra/healthcheck
/extra/exe-builder
/extra/push-examples
/extra/uptime-kuma-push
# Comment the following line if you want to rebuild the healthcheck binary

View file

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

View file

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

2
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,2 @@
---
blank_issues_enabled: false

View file

@ -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: "..."

View 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: |
...

View file

@ -1,17 +0,0 @@
---
name: "Security Issue"
about: "Just for alerting @louislam, do not provide any details here"
title: "Security Issue"
ref: "main"
labels:
- security
---
DO NOT PROVIDE ANY DETAILS HERE. Please privately report to https://github.com/louislam/uptime-kuma/security/advisories/new.
Why need this issue? It is because GitHub Advisory do not send a notification to @louislam, it is a workaround to do so.
Your GitHub Advisory URL:

View file

@ -0,0 +1,45 @@
---
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", "P1-high"]
assignees: [louislam]
body:
- type: markdown
attributes:
value: |
## ❗ 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.**
**Note**: This issue is only for notifying the maintainers of the repository, as the GitHub Security Advisory does not automatically send notifications.
- **Confidentiality**: The information you provide in the GitHub Security Advisory will initially remain confidential. However, once the vulnerability is addressed, the advisory will be publicly disclosed on GitHub.
- **Access and Visibility**: Until the advisory is published, it will only be visible to the maintainers of the repository and invited collaborators.
- **Credit**: You will be automatically credited as a contributor for identifying and reporting the vulnerability. Your contribution will be reflected in the MITRE Credit System.
- **Important Reminder**: **Do not include any sensitive or detailed vulnerability information in this issue.** This issue is only for sharing the advisory URL to notify the maintainers of the repository, not for discussing the vulnerability itself.
**Thank you for helping us keep Uptime Kuma secure!**
## **Step 1: Submit a GitHub Security Advisory**
Right-click the link below and select `Open link in new tab` to access the page. This will keep the security issue open, allowing you to easily return and paste the Advisory URL here later.
➡️ [Create a New Security Advisory](https://github.com/louislam/uptime-kuma/security/advisories/new)
## **Step 2: Share the Advisory URL**
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
id: github-advisory-url
validations:
required: true
attributes:
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

View file

@ -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 dont 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` | ![Before](image-link) | ![After](image-link) |
| `DOWN` | ![Before](image-link) | ![After](image-link) |
| Certificate-expiry | ![Before](image-link) | ![After](image-link) |
| Testing | ![Before](image-link) | ![After](image-link) |
## 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
View 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.

View file

@ -78,7 +78,7 @@ jobs:
e2e-test:
needs: [ ]
runs-on: ARM64
runs-on: ubuntu-24.04-arm
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- uses: actions/checkout@v4

View file

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

View file

@ -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
- 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>
<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>
- <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).
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)
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>
<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>
- <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);
}
```
- `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.
- `server/notification-providers/PROVIDER_NAME.js` is where the heart of the
notification provider lives.
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)
- Both `monitorJSON` and `heartbeatJSON` can be `null` for some events. If
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 |
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` | ![Before](image-link) | ![After](image-link) |
| `DOWN` | ![Before](image-link) | ![After](image-link) |
| Certificate-expiry | ![Before](image-link) | ![After](image-link) |
| Testing | ![Before](image-link) | ![After](image-link) |
```
<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>
- <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.
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>
<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>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`
@ -481,7 +761,8 @@ We have a few procedures we follow. These are documented here:
</p>
</details>
- <details><summary>Release Wiki</summary>
- <details><summary><b>Release Wiki</b> (click to expand)</summary>
<p>
**Setup Repo**
@ -501,7 +782,8 @@ We have a few procedures we follow. These are documented here:
</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

View file

@ -62,6 +62,7 @@ Requirements:
- Platform
- ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
- ❌ FreeBSD / OpenBSD / NetBSD
- ❌ Replit / Heroku
- [Node.js](https://nodejs.org/en/download/) 18 / 20.4
- [npm](https://docs.npmjs.com/cli/) 9

View file

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

View file

@ -0,0 +1,13 @@
// Fix #5721: Change proxy port column type to integer to support larger port numbers
exports.up = function (knex) {
return knex.schema
.alterTable("proxy", function (table) {
table.integer("port").alter();
});
};
exports.down = function (knex) {
return knex.schema.alterTable("proxy", function (table) {
table.smallint("port").alter();
});
};

View 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");
});
};

View file

@ -101,12 +101,30 @@ export function buildImage(repoNames, tags, target, buildArgs = "", dockerfile =
/**
* Check if the version already exists on Docker Hub
* TODO: use semver to compare versions if it is greater than the previous?
* @param {string} repoName Docker Hub repository name
* @param {string[]} repoNames repository name (Only check the name with single slash)
* @param {string} version Version to check
* @returns {void}
*/
export async function checkTagExists(repoName, version) {
console.log(`Checking if version ${version} exists on Docker Hub`);
export async function checkTagExists(repoNames, version) {
// Skip if the tag is not on Docker Hub
// louislam/uptime-kuma
let dockerHubRepoNames = repoNames.filter((name) => {
return name.split("/").length === 2;
});
for (let repoName of dockerHubRepoNames) {
await checkTagExistsSingle(repoName, version);
}
}
/**
* Check if the version already exists on Docker Hub
* @param {string} repoName repository name
* @param {string} version Version to check
* @returns {Promise<void>}
*/
export async function checkTagExistsSingle(repoName, version) {
console.log(`Checking if version ${version} exists on Docker Hub:`, repoName);
// Get a list of tags from the Docker Hub repository
let tags = [];

View file

@ -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: ");
}

19659
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "2.0.0-beta.0",
"version": "2.0.0-beta.2",
"license": "MIT",
"repository": {
"type": "git",
@ -41,7 +41,7 @@
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --push",
"upload-artifacts": "node extra/release/upload-artifacts.mjs",
"upload-artifacts-beta": "node extra/release/upload-artifacts-beta.mjs",
"setup": "git checkout 1.23.15 && npm ci --production && npm run download-dist",
"setup": "git checkout 1.23.16 && npm ci --omit dev && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js",
@ -72,7 +72,7 @@
"@louislam/sqlite3": "15.1.6",
"@vvo/tzdb": "^6.125.0",
"args-parser": "~1.3.0",
"axios": "~0.28.1",
"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",
@ -95,8 +96,8 @@
"html-escaper": "^3.0.3",
"http-cookie-agent": "~5.0.4",
"http-graceful-shutdown": "~3.1.7",
"http-proxy-agent": "~5.0.0",
"https-proxy-agent": "~5.0.1",
"http-proxy-agent": "~7.0.2",
"https-proxy-agent": "~7.0.6",
"iconv-lite": "~0.6.3",
"isomorphic-ws": "^5.0.0",
"jsesc": "~3.0.2",
@ -118,7 +119,7 @@
"node-cloudflared-tunnel": "~1.0.9",
"node-radius-client": "~1.0.0",
"nodemailer": "~6.9.13",
"nostr-tools": "^1.13.1",
"nostr-tools": "^2.10.4",
"notp": "~2.0.3",
"openid-client": "^5.4.2",
"password-hash": "~1.2.2",
@ -135,7 +136,7 @@
"semver": "~7.5.4",
"socket.io": "~4.8.0",
"socket.io-client": "~4.8.0",
"socks-proxy-agent": "6.1.1",
"socks-proxy-agent": "~8.0.5",
"tar": "~6.2.1",
"tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2",
@ -144,7 +145,7 @@
"ws": "^8.13.0"
},
"devDependencies": {
"@actions/github": "~5.1.1",
"@actions/github": "~6.0.0",
"@fortawesome/fontawesome-svg-core": "~1.2.36",
"@fortawesome/free-regular-svg-icons": "~5.15.4",
"@fortawesome/free-solid-svg-icons": "~5.15.4",
@ -171,7 +172,7 @@
"cross-env": "~7.0.3",
"delay": "^5.0.0",
"dns2": "~2.0.1",
"dompurify": "~3.1.7",
"dompurify": "~3.2.4",
"eslint": "~8.14.0",
"eslint-plugin-jsdoc": "~46.4.6",
"eslint-plugin-vue": "~8.7.1",
@ -181,7 +182,7 @@
"postcss-html": "~1.5.0",
"postcss-rtlcss": "~3.7.2",
"postcss-scss": "~4.0.4",
"prismjs": "~1.29.0",
"prismjs": "~1.30.0",
"qrcode": "~1.5.0",
"rollup-plugin-visualizer": "^5.6.0",
"sass": "~1.42.1",
@ -192,7 +193,7 @@
"testcontainers": "^10.13.1",
"typescript": "~4.4.4",
"v-pagination-3": "~0.1.7",
"vite": "~5.2.8",
"vite": "~5.4.15",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-vue-devtools": "^7.0.15",
@ -200,7 +201,7 @@
"vue-chartjs": "~5.2.0",
"vue-confirm-dialog": "~1.0.2",
"vue-contenteditable": "~3.0.4",
"vue-i18n": "~9.2.2",
"vue-i18n": "~9.14.3",
"vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2",
"vue-prism-editor": "~2.0.0-alpha.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -736,7 +736,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)";
}
}

View file

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

View file

@ -53,7 +53,7 @@ class Monitor extends BeanModel {
};
if (this.sendUrl) {
obj.url = this.url;
obj.url = this.customUrl ?? this.url;
}
if (showTags) {
@ -380,39 +380,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();
@ -1327,7 +1294,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.
@ -1434,7 +1402,7 @@ class Monitor extends BeanModel {
for (let notification of notificationList) {
try {
log.debug("monitor", "Sending to " + notification.name);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will expire in ${daysRemaining} days`);
sent = true;
} catch (e) {
log.error("monitor", "Cannot send cert notification to " + notification.name);
@ -1625,7 +1593,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(`

View file

@ -485,7 +485,7 @@ function ApiCache() {
}
if (typeof duration === "string") {
let split = duration.match(/^([\d\.,]+)\s?(\w+)$/);
let split = duration.match(/^([\d\.,]+)\s?([a-zA-Z]+)$/);
if (split.length === 3) {
let len = parseFloat(split[1]);

View file

@ -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] });

View 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,
};

View file

@ -240,6 +240,14 @@ class RealBrowserMonitorType extends MonitorType {
const context = await browser.newContext();
const page = await context.newPage();
// Prevent Local File Inclusion
// Accept only http:// and https://
// https://github.com/louislam/uptime-kuma/security/advisories/GHSA-2qgm-m29m-cj2h
let url = new URL(monitor.url);
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new Error("Invalid url protocol, only http and https are allowed.");
}
const res = await page.goto(monitor.url, {
waitUntil: "networkidle",
timeout: monitor.interval * 1000 * 0.8,

View file

@ -17,7 +17,7 @@ class AliyunSMS extends NotificationProvider {
if (heartbeatJSON != null) {
let msgBody = JSON.stringify({
name: monitorJSON["name"],
time: heartbeatJSON["time"],
time: heartbeatJSON["localDateTime"],
status: this.statusToString(heartbeatJSON["status"]),
msg: heartbeatJSON["msg"],
});

View file

@ -46,10 +46,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"],
@ -83,10 +83,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"],

View file

@ -72,6 +72,7 @@ class GoogleChat extends NotificationProvider {
// construct json data
let data = {
fallbackText: chatHeader["title"],
cardsV2: [
{
card: {

View file

@ -1,11 +1,10 @@
const NotificationProvider = require("./notification-provider");
const {
relayInit,
getPublicKey,
getEventHash,
getSignature,
finalizeEvent,
Relay,
kinds,
nip04,
nip19
nip19,
} = require("nostr-tools");
// polyfills for node versions
@ -31,7 +30,6 @@ class Nostr extends NotificationProvider {
const createdAt = Math.floor(Date.now() / 1000);
const senderPrivateKey = await this.getPrivateKey(notification.sender);
const senderPublicKey = getPublicKey(senderPrivateKey);
const recipientsPublicKeys = await this.getPublicKeys(notification.recipients);
// Create NIP-04 encrypted direct message event for each recipient
@ -39,34 +37,41 @@ class Nostr extends NotificationProvider {
for (const recipientPublicKey of recipientsPublicKeys) {
const ciphertext = await nip04.encrypt(senderPrivateKey, recipientPublicKey, msg);
let event = {
kind: 4,
pubkey: senderPublicKey,
kind: kinds.EncryptedDirectMessage,
created_at: createdAt,
tags: [[ "p", recipientPublicKey ]],
content: ciphertext,
};
event.id = getEventHash(event);
event.sig = getSignature(event, senderPrivateKey);
events.push(event);
const signedEvent = finalizeEvent(event, senderPrivateKey);
events.push(signedEvent);
}
// Publish events to each relay
const relays = notification.relays.split("\n");
let successfulRelays = 0;
// Connect to each relay
for (const relayUrl of relays) {
const relay = relayInit(relayUrl);
try {
await relay.connect();
successfulRelays++;
const relay = await Relay.connect(relayUrl);
let eventIndex = 0;
// Publish events
for (const event of events) {
relay.publish(event);
}
// Authenticate to the relay, if required
try {
await relay.publish(events[0]);
eventIndex = 1;
} catch (error) {
continue;
if (relay.challenge) {
await relay.auth(async (evt) => {
return finalizeEvent(evt, senderPrivateKey);
});
}
}
try {
for (let i = eventIndex; i < events.length; i++) {
await relay.publish(events[i]);
}
successfulRelays++;
} catch (error) {
console.error(`Failed to publish event to ${relayUrl}:`, error);
} finally {
relay.close();
}
@ -90,7 +95,7 @@ class Nostr extends NotificationProvider {
const { data } = senderDecodeResult;
return data;
} catch (error) {
throw new Error(`Failed to get private key: ${error.message}`);
throw new Error(`Failed to decode private key for sender ${sender}: ${error.message}`);
}
}
@ -109,10 +114,10 @@ class Nostr extends NotificationProvider {
if (type === "npub") {
publicKeys.push(data);
} else {
throw new Error("not an npub");
throw new Error(`Recipient ${recipient} is not an npub`);
}
} catch (error) {
throw new Error(`Error decoding recipient: ${error}`);
throw new Error(`Error decoding recipient ${recipient}: ${error}`);
}
}
return publicKeys;

View 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;

View file

@ -1,3 +1,6 @@
const { Liquid } = require("liquidjs");
const { DOWN } = require("../../src/util");
class NotificationProvider {
/**
@ -49,6 +52,50 @@ class NotificationProvider {
}
}
/**
* Renders a message template with notification context
* @param {string} template the template
* @param {string} msg the message that will be included in the context
* @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only)
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {Promise<string>} rendered template
*/
async renderTemplate(template, msg, monitorJSON, heartbeatJSON) {
const engine = new Liquid();
const parsedTpl = engine.parse(template);
// Let's start with dummy values to simplify code
let monitorName = "Monitor Name not available";
let monitorHostnameOrURL = "testing.hostname";
if (monitorJSON !== null) {
monitorName = monitorJSON["name"];
monitorHostnameOrURL = this.extractAddress(monitorJSON);
}
let serviceStatus = "⚠️ Test";
if (heartbeatJSON !== null) {
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
}
const context = {
// for v1 compatibility, to be removed in v3
"STATUS": serviceStatus,
"NAME": monitorName,
"HOSTNAME_OR_URL": monitorHostnameOrURL,
// variables which are officially supported
"status": serviceStatus,
"name": monitorName,
"hostnameOrURL": monitorHostnameOrURL,
monitorJSON,
heartbeatJSON,
msg,
};
return engine.render(parsedTpl, context);
}
/**
* Throws an error
* @param {any} error The error to throw

View 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;

View 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;

View file

@ -11,7 +11,8 @@ class PushDeer extends NotificationProvider {
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
const serverUrl = notification.pushdeerServer || "https://api2.pushdeer.com";
const url = `${serverUrl.trim().replace(/\/*$/, "")}/message/push`;
// capture group below is nessesary to prevent an ReDOS-attack
const url = `${serverUrl.trim().replace(/([^/])\/+$/, "$1")}/message/push`;
let valid = msg != null && monitorJSON != null && heartbeatJSON != null;

View file

@ -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);
}
}
}

View file

@ -0,0 +1,56 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class PushPlus extends NotificationProvider {
name = "PushPlus";
/**
* @inheritdoc
* @param {BeanModel} notification Notification object
* @param {string} msg Message content
* @param {?object} monitorJSON Monitor details
* @param {?object} heartbeatJSON Heartbeat details
* @returns {Promise<string>} Success message
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
const url = "https://www.pushplus.plus/send";
try {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const params = {
"token": notification.pushPlusSendKey,
"title": this.checkStatus(heartbeatJSON, monitorJSON),
"content": msg,
"template": "html"
};
await axios.post(url, params, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* Get the formatted title for message
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
* @param {?object} monitorJSON Monitor details (For Up/Down only)
* @returns {string} Formatted title
*/
checkStatus(heartbeatJSON, monitorJSON) {
let title = "UptimeKuma Message";
if (heartbeatJSON != null && heartbeatJSON["status"] === UP) {
title = "UptimeKuma Monitor Up " + monitorJSON["name"];
}
if (heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
title = "UptimeKuma Monitor Down " + monitorJSON["name"];
}
return title;
}
}
module.exports = PushPlus;

View file

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

View 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;

View file

@ -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);
}

View file

@ -1,7 +1,5 @@
const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider");
const { DOWN } = require("../../src/util");
const { Liquid } = require("liquidjs");
class SMTP extends NotificationProvider {
name = "smtp";
@ -44,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"]}`;
}
@ -52,16 +51,12 @@ class SMTP extends NotificationProvider {
// cannot end with whitespace as this often raises spam scores
const customSubject = notification.customSubject?.trim() || "";
const customBody = notification.customBody?.trim() || "";
const context = this.generateContext(msg, monitorJSON, heartbeatJSON);
const engine = new Liquid();
if (customSubject !== "") {
const tpl = engine.parse(customSubject);
subject = await engine.render(tpl, context);
subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON);
}
if (customBody !== "") {
const tpl = engine.parse(customBody);
body = await engine.render(tpl, context);
useHTMLBody = notification.htmlBody || false;
body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON);
}
}
@ -73,48 +68,12 @@ 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;
}
/**
* Generate context for LiquidJS
* @param {string} msg the message that will be included in the context
* @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only)
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {{STATUS: string, status: string, HOSTNAME_OR_URL: string, hostnameOrUrl: string, NAME: string, name: string, monitorJSON: ?object, heartbeatJSON: ?object, msg: string}} the context
*/
generateContext(msg, monitorJSON, heartbeatJSON) {
// Let's start with dummy values to simplify code
let monitorName = "Monitor Name not available";
let monitorHostnameOrURL = "testing.hostname";
if (monitorJSON !== null) {
monitorName = monitorJSON["name"];
monitorHostnameOrURL = this.extractAddress(monitorJSON);
}
let serviceStatus = "⚠️ Test";
if (heartbeatJSON !== null) {
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
}
return {
// for v1 compatibility, to be removed in v3
"STATUS": serviceStatus,
"NAME": monitorName,
"HOSTNAME_OR_URL": monitorHostnameOrURL,
// variables which are officially supported
"status": serviceStatus,
"name": monitorName,
"hostnameOrURL": monitorHostnameOrURL,
monitorJSON,
heartbeatJSON,
msg,
};
}
}
module.exports = SMTP;

View 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;

View file

@ -9,7 +9,7 @@ class Telegram extends NotificationProvider {
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
const url = "https://api.telegram.org";
const url = notification.telegramServerUrl ?? "https://api.telegram.org";
try {
let params = {
@ -22,6 +22,14 @@ class Telegram extends NotificationProvider {
params.message_thread_id = notification.telegramMessageThreadID;
}
if (notification.telegramUseTemplate) {
params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON);
if (notification.telegramTemplateParseMode !== "plain") {
params.parse_mode = notification.telegramTemplateParseMode;
}
}
await axios.get(`${url}/bot${notification.telegramBotToken}/sendMessage`, {
params: params,
});

View file

@ -0,0 +1,40 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class WAHA extends NotificationProvider {
name = "waha";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
try {
const config = {
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"X-Api-Key": notification.wahaApiKey,
}
};
let data = {
"session": notification.wahaSession,
"chatId": notification.wahaChatId,
"text": msg,
};
let url = notification.wahaApiUrl.replace(/([^/])\/+$/, "$1") + "/api/sendText";
await axios.post(url, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = WAHA;

View file

@ -1,7 +1,6 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const FormData = require("form-data");
const { Liquid } = require("liquidjs");
class Webhook extends NotificationProvider {
name = "webhook";
@ -28,17 +27,7 @@ class Webhook extends NotificationProvider {
config.headers = formData.getHeaders();
data = formData;
} else if (notification.webhookContentType === "custom") {
// Initialize LiquidJS and parse the custom Body Template
const engine = new Liquid();
const tpl = engine.parse(notification.webhookCustomBody);
// Insert templated values into Body
data = await engine.render(tpl,
{
msg,
heartbeatJSON,
monitorJSON
});
data = await this.renderTemplate(notification.webhookCustomBody, msg, monitorJSON, heartbeatJSON);
}
if (notification.webhookAdditionalHeaders) {

View file

@ -24,7 +24,7 @@ class Whapi extends NotificationProvider {
"body": msg,
};
let url = (notification.whapiApiUrl || "https://gate.whapi.cloud/").replace(/\/+$/, "") + "/messages/text";
let url = (notification.whapiApiUrl || "https://gate.whapi.cloud/").replace(/([^/])\/+$/, "$1") + "/messages/text";
await axios.post(url, data, config);

View file

@ -0,0 +1,57 @@
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
const { default: axios } = require("axios");
class YZJ extends NotificationProvider {
name = "YZJ";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
if (heartbeatJSON !== null) {
msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
}
const config = {
headers: {
"Content-Type": "application/json",
},
};
const params = {
content: msg
};
// yzjtype=0 => general robot
const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`;
const result = await axios.post(url, params, config);
if (!result.data?.success) {
throw new Error(result.data?.errmsg ?? "yzj's server did not respond with the expected result");
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* Convert status constant to string
* @param {string} status The status constant
* @returns {string} status
*/
statusToString(status) {
switch (status) {
case DOWN:
return "❌";
case UP:
return "✅";
default:
return status;
}
}
}
module.exports = YZJ;

View file

@ -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,15 +31,18 @@ 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");
const Pushbullet = require("./notification-providers/pushbullet");
const PushDeer = require("./notification-providers/pushdeer");
const Pushover = require("./notification-providers/pushover");
const PushPlus = require("./notification-providers/pushplus");
const Pushy = require("./notification-providers/pushy");
const RocketChat = require("./notification-providers/rocket-chat");
const SerwerSMS = require("./notification-providers/serwersms");
@ -64,12 +68,16 @@ const ServerChan = require("./notification-providers/serverchan");
const ZohoCliq = require("./notification-providers/zoho-cliq");
const SevenIO = require("./notification-providers/sevenio");
const Whapi = require("./notification-providers/whapi");
const WAHA = require("./notification-providers/waha");
const GtxMessaging = require("./notification-providers/gtx-messaging");
const Cellsynt = require("./notification-providers/cellsynt");
const Onesender = require("./notification-providers/onesender");
const Wpush = require("./notification-providers/wpush");
const SendGrid = require("./notification-providers/send-grid");
const Webpush = require("./notification-providers/Webpush");
const YZJ = require("./notification-providers/yzj");
const SMSPlanet = require("./notification-providers/sms-planet");
const SpugPush = require("./notification-providers/spugpush");
class Notification {
@ -117,6 +125,7 @@ class Notification {
new Nostr(),
new Ntfy(),
new Octopush(),
new OneChat(),
new OneBot(),
new Onesender(),
new Opsgenie(),
@ -124,9 +133,11 @@ class Notification {
new FlashDuty(),
new PagerTree(),
new PromoSMS(),
new Pumble(),
new Pushbullet(),
new PushDeer(),
new Pushover(),
new PushPlus(),
new Pushy(),
new RocketChat(),
new ServerChan(),
@ -152,11 +163,16 @@ class Notification {
new ZohoCliq(),
new SevenIO(),
new Whapi(),
new WAHA(),
new GtxMessaging(),
new Cellsynt(),
new Wpush(),
new SendGrid(),
new Webpush(),
new YZJ(),
new SMSPlanet(),
new SpugPush(),
new Notifery(),
];
for (let item of list) {
if (! item.name) {

View file

@ -1,7 +1,7 @@
const { R } = require("redbean-node");
const HttpProxyAgent = require("http-proxy-agent");
const HttpsProxyAgent = require("https-proxy-agent");
const SocksProxyAgent = require("socks-proxy-agent");
const { HttpProxyAgent } = require("http-proxy-agent");
const { HttpsProxyAgent } = require("https-proxy-agent");
const { SocksProxyAgent } = require("socks-proxy-agent");
const { debug } = require("../src/util");
const { UptimeKumaServer } = require("./uptime-kuma-server");
const { CookieJar } = require("tough-cookie");
@ -100,17 +100,17 @@ class Proxy {
let jar = new CookieJar();
const proxyOptions = {
protocol: proxy.protocol,
host: proxy.host,
port: proxy.port,
cookies: { jar },
};
const proxyUrl = new URL(`${proxy.protocol}://${proxy.host}:${proxy.port}`);
if (proxy.auth) {
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
proxyUrl.username = proxy.username;
proxyUrl.password = proxy.password;
}
debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
debug(`Proxy URL: ${proxyUrl.toString()}`);
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
@ -122,15 +122,15 @@ class Proxy {
// eslint-disable-next-line no-case-declarations
const HttpsCookieProxyAgent = createCookieAgent(HttpsProxyAgent);
httpAgent = new HttpCookieProxyAgent({
...httpAgentOptions || {},
httpAgent = new HttpCookieProxyAgent(proxyUrl.toString(), {
...(httpAgentOptions || {}),
...proxyOptions,
});
httpsAgent = new HttpsCookieProxyAgent(proxyUrl.toString(), {
...(httpsAgentOptions || {}),
...proxyOptions,
});
httpsAgent = new HttpsCookieProxyAgent({
...httpsAgentOptions || {},
...proxyOptions,
});
break;
case "socks":
case "socks5":
@ -138,10 +138,9 @@ class Proxy {
case "socks4":
// eslint-disable-next-line no-case-declarations
const SocksCookieProxyAgent = createCookieAgent(SocksProxyAgent);
agent = new SocksCookieProxyAgent({
agent = new SocksCookieProxyAgent(proxyUrl.toString(), {
...httpAgentOptions,
...httpsAgentOptions,
...proxyOptions,
tls: {
rejectUnauthorized: httpsAgentOptions.rejectUnauthorized,
},

View file

@ -98,15 +98,15 @@ router.all("/api/push/:pushToken", async (request, response) => {
// 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;

View file

@ -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,
]);

View file

@ -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);
}

View file

@ -113,6 +113,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
UptimeKumaServer.monitorTypeList["group"] = new GroupMonitorType();
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
@ -551,6 +552,7 @@ 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 { GroupMonitorType } = require("./monitor-types/group");
const { SNMPMonitorType } = require("./monitor-types/snmp");
const { MongodbMonitorType } = require("./monitor-types/mongodb");
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");

View file

@ -4,9 +4,11 @@
<script>
import { setPageLocale } from "./util-frontend";
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
export default {
created() {
setPageLocale();
},
};
polyfillCountryFlagEmojis();
</script>

View file

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

View file

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

View file

@ -73,12 +73,6 @@ export default {
}
},
created() {
if (this.model.length === 0) {
this.addCondition();
}
},
methods: {
getNewGroup() {
return {

View file

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

View file

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

View file

@ -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",
@ -163,10 +165,12 @@ export default {
"ZohoCliq": "ZohoCliq",
"SevenIO": "SevenIO",
"whapi": "WhatsApp (Whapi)",
"waha": "WhatsApp (WAHA)",
"gtxmessaging": "GtxMessaging",
"Cellsynt": "Cellsynt",
"SendGrid": "SendGrid",
"Webpush": "Webpush",
"notifery": "Notifery"
};
// Put notifications here if it's not supported in most regions or its documentation is not in English
@ -182,8 +186,12 @@ export default {
"SMSManager": "SmsManager (smsmanager.cz)",
"WeCom": "WeCom (企业微信群机器人)",
"ServerChan": "ServerChan (Server酱)",
"PushPlus": "PushPlus (推送加)",
"SpugPush": "SpugPushSpug推送助手",
"smsc": "SMSC",
"WPush": "WPush(wpush.cn)",
"YZJ": "YZJ (云之家自定义机器人)",
"SMSPlanet": "SMSPlanet.pl"
};
// Sort by notification name
@ -233,6 +241,9 @@ export default {
mounted() {
this.modal = new Modal(this.$refs.modal);
},
beforeUnmount() {
this.cleanupModal();
},
methods: {
/**
@ -337,6 +348,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);
}
}
}
},
};

View file

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

View file

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

View file

@ -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}`;

View file

@ -197,7 +197,7 @@ export default {
return tagOptions;
},
selectedTags() {
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id === tag.id));
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.tag_id === tag.tag_id));
},
colorOptions() {
return colorOptions(this);
@ -248,6 +248,9 @@ export default {
this.modal = new Modal(this.$refs.modal);
this.getExistingTags();
},
beforeUnmount() {
this.cleanupModal();
},
methods: {
/**
* Show the add tag dialog
@ -459,6 +462,19 @@ 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);
}
}
}
},
};

View file

@ -0,0 +1,75 @@
<template>
<div class="form-text mb-2">
<i18n-t tag="div" keypath="liquidIntroduction">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{ msg }}</code>: {{ $t("templateMsg") }}<br />
<code v-pre>{{ name }}</code>: {{ $t("templateServiceName") }}<br />
<code v-pre>{{ status }}</code>: {{ $t("templateStatus") }}<br />
<code v-pre>{{ hostnameOrURL }}</code>: {{ $t("templateHostnameOrURL") }}<br />
<code v-pre>{{ heartbeatJSON }}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
<code v-pre>{{ monitorJSON }}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
</div>
<input
:id="id"
ref="templatedInput"
v-model="model"
type="text"
class="form-control"
:placeholder="placeholder"
:required="required"
autocomplete="false"
>
</template>
<script>
export default {
props: {
/**
* The value of the templated input.
*/
modelValue: {
type: String,
default: ""
},
/**
* id for the templated input.
*/
id: {
type: String,
required: true,
},
/**
* Whether the templated input is required.
* @example true
*/
required: {
type: Boolean,
required: true,
},
/**
* Placeholder text for the templated input.
*/
placeholder: {
type: String,
default: ""
},
},
emits: [ "update:modelValue" ],
computed: {
/**
* Send value update to parent on change.
*/
model: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
}
}
},
};
</script>

View file

@ -0,0 +1,80 @@
<template>
<div class="form-text mb-2">
<i18n-t tag="div" keypath="liquidIntroduction">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{ msg }}</code>: {{ $t("templateMsg") }}<br />
<code v-pre>{{ name }}</code>: {{ $t("templateServiceName") }}<br />
<code v-pre>{{ status }}</code>: {{ $t("templateStatus") }}<br />
<code v-pre>{{ hostnameOrURL }}</code>: {{ $t("templateHostnameOrURL") }}<br />
<code v-pre>{{ heartbeatJSON }}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
<code v-pre>{{ monitorJSON }}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
</div>
<textarea
:id="id"
ref="templatedTextarea"
v-model="model"
class="form-control"
:placeholder="placeholder"
:required="required"
autocomplete="false"
></textarea>
</template>
<script>
export default {
props: {
/**
* The value of the templated textarea.
*/
modelValue: {
type: String,
default: ""
},
/**
* id for the templated textarea.
*/
id: {
type: String,
required: true,
},
/**
* Whether the templated textarea is required.
* @example true
*/
required: {
type: Boolean,
required: true,
},
/**
* Placeholder text for the templated textarea.
*/
placeholder: {
type: String,
default: ""
},
},
emits: [ "update:modelValue" ],
computed: {
/**
* Send value update to parent on change.
*/
model: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
}
}
},
};
</script>
<style lang="scss" scoped>
textarea {
min-height: 150px;
}
</style>

View file

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

View file

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

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

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

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

View file

@ -0,0 +1,19 @@
<template>
<div class="mb-3">
<label for="pushPlus-sendkey" class="form-label">{{ $t("SendKey") }}</label>
<HiddenInput id="pushPlus-sendkey" v-model="$parent.notification.pushPlusSendKey" :required="true" autocomplete="new-password"></HiddenInput>
</div>
<i18n-t tag="div" keypath="More info on:" class="mb-3 form-text">
<a href="https://www.pushplus.plus/" target="_blank">https://www.pushplus.plus/</a>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View file

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

View file

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

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

View file

@ -67,28 +67,27 @@
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</div>
<p class="form-text">
<i18n-t tag="div" keypath="smtpLiquidIntroduction" class="form-text mb-3">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{name}}</code>: {{ $t("emailTemplateServiceName") }}<br />
<code v-pre>{{msg}}</code>: {{ $t("emailTemplateMsg") }}<br />
<code v-pre>{{status}}</code>: {{ $t("emailTemplateStatus") }}<br />
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("emailTemplateHeartbeatJSON") }}<b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
<code v-pre>{{monitorJSON}}</code>: {{ $t("emailTemplateMonitorJSON") }} <b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
<code v-pre>{{hostnameOrURL}}</code>: {{ $t("emailTemplateHostnameOrURL") }}<br />
</p>
<div class="mb-3">
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<TemplatedInput id="subject-email" v-model="$parent.notification.customSubject" :required="false" placeholder=""></TemplatedInput>
<div class="form-text">{{ $t("leave blank for default subject") }}</div>
</div>
<div class="mb-3">
<label for="body-email" class="form-label">{{ $t("emailCustomBody") }}</label>
<textarea id="body-email" v-model="$parent.notification.customBody" type="text" class="form-control" autocomplete="false" placeholder=""></textarea>
<TemplatedTextarea id="body-email" v-model="$parent.notification.customBody" :required="false" placeholder=""></TemplatedTextarea>
<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>
@ -124,11 +123,15 @@
<script>
import HiddenInput from "../HiddenInput.vue";
import TemplatedInput from "../TemplatedInput.vue";
import TemplatedTextarea from "../TemplatedTextarea.vue";
import ToggleSection from "../ToggleSection.vue";
export default {
components: {
HiddenInput,
TemplatedInput,
TemplatedTextarea,
ToggleSection,
},
computed: {

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

View file

@ -33,6 +33,56 @@
<input id="message_thread_id" v-model="$parent.notification.telegramMessageThreadID" type="text" class="form-control">
<p class="form-text">{{ $t("telegramMessageThreadIDDescription") }}</p>
<label for="server_url" class="form-label">{{ $t("telegramServerUrl") }}</label>
<input id="server_url" v-model="$parent.notification.telegramServerUrl" type="text" class="form-control">
<div class="form-text">
<i18n-t keypath="telegramServerUrlDescription">
<a
href="https://core.telegram.org/bots/api#using-a-local-bot-api-server"
target="_blank"
>{{ $t("here") }}</a>
<a
href="https://api.telegram.org"
target="_blank"
>https://api.telegram.org</a>
</i18n-t>
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.telegramUseTemplate" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("telegramUseTemplate") }}</label>
</div>
<div class="form-text">
{{ $t("telegramUseTemplateDescription") }}
</div>
</div>
<template v-if="$parent.notification.telegramUseTemplate">
<div class="mb-3">
<label class="form-label" for="message_parse_mode">{{ $t("Message Format") }}</label>
<select
id="message_parse_mode"
v-model="$parent.notification.telegramTemplateParseMode"
class="form-select"
required
>
<option value="plain">{{ $t("Plain Text") }}</option>
<option value="HTML">HTML</option>
<option value="MarkdownV2">MarkdownV2</option>
</select>
<i18n-t tag="p" keypath="telegramTemplateFormatDescription" class="form-text">
<a href="https://core.telegram.org/bots/api#formatting-options" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<label class="form-label" for="message_template">{{ $t('Message Template') }}</label>
<TemplatedTextarea id="message_template" v-model="$parent.notification.telegramTemplate" :required="true" :placeholder="telegramTemplatedTextareaPlaceholder"></TemplatedTextarea>
</div>
</template>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.telegramSendSilently" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("telegramSendSilently") }}</label>
@ -57,11 +107,27 @@
<script>
import HiddenInput from "../HiddenInput.vue";
import TemplatedTextarea from "../TemplatedTextarea.vue";
import axios from "axios";
export default {
components: {
HiddenInput,
TemplatedTextarea,
},
computed: {
telegramTemplatedTextareaPlaceholder() {
return this.$t("Example:", [
`
Uptime Kuma Alert{% if monitorJSON %} - {{ monitorJSON['name'] }}{% endif %}
{{ msg }}
`,
]);
}
},
mounted() {
this.$parent.notification.telegramServerUrl ||= "https://api.telegram.org";
},
methods: {
/**
@ -80,7 +146,7 @@ export default {
}
}
return `https://api.telegram.org/bot${token}/getUpdates`;
return `${this.$parent.notification.telegramServerUrl}/bot${token}/getUpdates`;
},
/**
@ -115,3 +181,9 @@ export default {
}
};
</script>
<style lang="scss" scoped>
textarea {
min-height: 150px;
}
</style>

View file

@ -0,0 +1,38 @@
<template>
<div class="mb-3">
<label for="waha-api-url" class="form-label">{{ $t("API URL") }}</label>
<input id="waha-api-url" v-model="$parent.notification.wahaApiUrl" placeholder="http://localhost:3000/" type="url" class="form-control" required>
<div class="form-text">{{ $t("wayToGetWahaApiUrl") }}</div>
</div>
<div class="mb-3">
<label for="waha-api-key" class="form-label">{{ $t("API Key") }}</label>
<HiddenInput id="waha-api-key" v-model="$parent.notification.wahaApiKey" :required="false" autocomplete="new-password"></HiddenInput>
<div class="form-text">{{ $t("wayToGetWahaApiKey") }}</div>
</div>
<div class="mb-3">
<label for="waha-session" class="form-label">{{ $t("wahaSession") }}</label>
<input id="waha-session" v-model="$parent.notification.wahaSession" type="text" placeholder="default" class="form-control" required>
<div class="form-text">{{ $t("wayToGetWahaSession") }}</div>
</div>
<div class="mb-3">
<label for="waha-chat-id" class="form-label">{{ $t("wahaChatId") }}</label>
<input id="waha-chat-id" v-model="$parent.notification.wahaChatId" type="text" pattern="^[\d-]{10,31}$" class="form-control" required>
<div class="form-text">{{ $t("wayToWriteWahaChatId", ["00117612345678", "00117612345678@c.us", "123456789012345678@g.us"]) }}</div>
</div>
<i18n-t tag="div" keypath="More info on:" class="mb-3 form-text">
<a href="https://waha.devlike.pro/" target="_blank">https://waha.devlike.pro/</a>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
}
};
</script>

View file

@ -32,20 +32,7 @@
</template>
</i18n-t>
<template v-else-if="$parent.notification.webhookContentType == 'custom'">
<i18n-t tag="div" keypath="liquidIntroduction" class="form-text">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{msg}}</code>: {{ $t("templateMsg") }}<br />
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
<code v-pre>{{monitorJSON}}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
<textarea
id="customBody"
v-model="$parent.notification.webhookCustomBody"
class="form-control"
:placeholder="customBodyPlaceholder"
required
></textarea>
<TemplatedTextarea id="customBody" v-model="$parent.notification.webhookCustomBody" :required="true" :placeholder="customBodyPlaceholder"></TemplatedTextarea>
</template>
</div>
@ -67,7 +54,12 @@
</template>
<script>
import TemplatedTextarea from "../TemplatedTextarea.vue";
export default {
components: {
TemplatedTextarea,
},
data() {
return {
showAdditionalHeadersField: this.$parent.notification.webhookAdditionalHeaders != null,

View file

@ -0,0 +1,19 @@
<template>
<div class="mb-3">
<label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
<i18n-t class="form-text" keypath="wayToGetTeamsURL">
<a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
</i18n-t>
</div>
<div class="mb-3">
<label for="yzjToken" class="form-label">{{ $t("YZJ Robot Token") }}<span style="color: red;"><sup>*</sup></span></label>
<HiddenInput id="yzjToken" v-model="$parent.notification.yzjToken" :required="true" autocomplete="new-password"></HiddenInput>
<i18n-t class="form-text" keypath="wayToGetLineNotifyToken">
<a href="https://www.yunzhijia.com/opendocs/docs.html#/server-api/im/index?id=%e6%8e%a5%e5%8f%a3%e5%9c%b0%e5%9d%80%e5%92%8c%e6%8e%88%e6%9d%83%e7%a0%81" target="_blank">{{ $t("here") }}</a>
</i18n-t>
</div>
</template>
<script setup lang="ts">
import HiddenInput from "../HiddenInput.vue";
</script>

View file

@ -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,9 +38,11 @@ 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";
import PushPlus from "./PushPlus.vue";
import Pushy from "./Pushy.vue";
import RocketChat from "./RocketChat.vue";
import ServerChan from "./ServerChan.vue";
@ -61,13 +65,17 @@ 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";
import Cellsynt from "./Cellsynt.vue";
import WPush from "./WPush.vue";
import SIGNL4 from "./SIGNL4.vue";
import SendGrid from "./SendGrid.vue";
import Webpush from "./Webpush.vue";
import YZJ from "./YZJ.vue";
import SMSPlanet from "./SMSPlanet.vue";
/**
* Manage all notification form.
@ -104,6 +112,7 @@ const NotificationFormList = {
"nostr": Nostr,
"ntfy": Ntfy,
"octopush": Octopush,
"OneChat": OneChat,
"OneBot": OneBot,
"Onesender": Onesender,
"Opsgenie": Opsgenie,
@ -111,10 +120,12 @@ const NotificationFormList = {
"FlashDuty": FlashDuty,
"PagerTree": PagerTree,
"promosms": PromoSMS,
"pumble": Pumble,
"pushbullet": Pushbullet,
"PushByTechulus": TechulusPush,
"PushDeer": PushDeer,
"pushover": Pushover,
"PushPlus": PushPlus,
"pushy": Pushy,
"rocket.chat": RocketChat,
"serwersms": SerwerSMS,
@ -132,6 +143,7 @@ const NotificationFormList = {
"threema": Threema,
"twilio": Twilio,
"Splunk": Splunk,
"SpugPush": SpugPush,
"webhook": Webhook,
"WeCom": WeCom,
"GoAlert": GoAlert,
@ -139,11 +151,15 @@ const NotificationFormList = {
"ZohoCliq": ZohoCliq,
"SevenIO": SevenIO,
"whapi": Whapi,
"notifery": Notifery,
"waha": WAHA,
"gtxmessaging": GtxMessaging,
"Cellsynt": Cellsynt,
"WPush": WPush,
"SendGrid": SendGrid,
"Webpush": Webpush,
"YZJ": YZJ,
"SMSPlanet": SMSPlanet,
};
export default NotificationFormList;

View file

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

View file

@ -31,6 +31,7 @@ const languageList = {
"sv-SE": "Svenska",
"tr-TR": "Türkçe",
"ko-KR": "한국어",
"lt": "Lietuvių",
"ru-RU": "Русский",
"zh-CN": "简体中文",
"pl": "Polski",

View file

@ -733,5 +733,12 @@
"Remove the expiry notification": "إزالة تنبيه يوم تاريخ الإنتهاء",
"tailscalePingWarning": "من أجل استخدام مراقبة Tailscale Ping، تحتاج إلى تثبيت Uptime Kuma بدون Docker وكذلك تثبيت عميل Tailscale على الخادم الخاص بك.",
"Clone Monitor": "نسخ المراقبة",
"telegramMessageThreadIDDescription": "معرف فريد اختياري لسلسلة الرسائل المستهدفة (الموضوع) في المنتدى؛ لمجموعات المنتدى الكبرى فقط"
"telegramMessageThreadIDDescription": "معرف فريد اختياري لسلسلة الرسائل المستهدفة (الموضوع) في المنتدى؛ لمجموعات المنتدى الكبرى فقط",
"emailCustomBody": "نص مخصص",
"emailTemplateStatus": "الحالة",
"leave blank for default subject": "اترك فارغاً ليتم تعيين الموضوع تلقائياً",
"leave blank for default body": "اترك فارغاً ليتم تعيين النص تلقائياً",
"emailTemplateServiceName": "اسم الخدمة",
"emailTemplateHostnameOrURL": "اسم المضيف أو عنوان URL",
"smspartnerPhoneNumber": "رقم الهاتف"
}

View file

@ -1097,5 +1097,26 @@
"Separate multiple email addresses with commas": "Разделяйте отделните имейл адреси със запетаи",
"RabbitMQ Nodes": "Възли за управление на RabbitMQ",
"rabbitmqNodesDescription": "Въведете URL адреса на възлите за управление на RabbitMQ, включително протокол и порт. Пример: {0}",
"rabbitmqHelpText": "За да използвате монитора, ще трябва да активирате добавката за управление във вашата настройка на RabbitMQ. За повече информация моля, вижте {rabitmq_documentation}."
"rabbitmqHelpText": "За да използвате монитора, ще трябва да активирате добавката за управление във вашата настройка на RabbitMQ. За повече информация моля, вижте {rabitmq_documentation}.",
"aboutSlackUsername": "Променя показваното име на подателя на съобщението. Ако желаете да споменете някого, вместо това го включете в приятелското име.",
"YZJ Robot Token": "YZJ Robot токен код",
"YZJ Webhook URL": "YZJ Уеб кука URL адрес",
"templateServiceName": "име на услугата",
"templateHostnameOrURL": "име на хоста или URL адрес",
"Plain Text": "Обикновен текст",
"Message Template": "Шаблон за съобщение",
"templateStatus": "статус",
"telegramUseTemplate": "Използвай персонализиран шаблон за съобщение",
"telegramUseTemplateDescription": "Ако е активирано, съобщението ще бъде изпратено чрез персонализиран шаблон.",
"telegramTemplateFormatDescription": "Telegram позволява използването на различни \"markup\" езици за съобщенията. Вижте Telegram {0} за детайли.",
"Template Format": "Формат на шаблона",
"wayToGetWahaApiUrl": "Вашият WAHA URL адрес.",
"wahaSession": "Сесия",
"wahaChatId": "Чат ID (телефонен номер / ID на контакт / ID на група)",
"wayToGetWahaApiKey": "API ключът, е стойността на променливата WHATSAPP_API_KEY, която сте използвали за стартиране на WAHA.",
"wayToWriteWahaChatId": "Телефонният номер с международния префикс, но без знака плюс в началото ({0}), ID на контакта ({1}) или ID на групата ({2}). Известията се изпращат до това чат ID от WAHA сесия.",
"wayToGetWahaSession": "От тази сесия WAHA изпраща известия до чат ID. Можете да го намерите в таблото за управление на WAHA.",
"telegramServerUrlDescription": "За премахване на API бот ограниченията за Telegram или за получаване на достъп в блокирани зони (Китай, Иран и др.). За повече информация щракнете върху {0}. По подразбиране: {1}",
"telegramServerUrl": "(По избор) URL адрес на сървъра",
"Font Twemoji by Twitter licensed under": "Шрифт Twemoji от Twitter, лицензиран под"
}

View file

@ -1,10 +1,10 @@
{
"setupDatabaseChooseDatabase": "আপনি কোন ডাটাবেজটি ব্যবহার করতে চান?",
"setupDatabaseEmbeddedMariaDB": "আপনাকে কিছু নিযুক্ত করতে হবে না। এই ডকার ইমেজটি (Docker image) স্বয়ংক্রিয়ভাবে আপনার জন্য মারিয়া ডিবি (MariaDB) বসিয়েছে এবং প্রস্তুত করেছে।Uptime Kuma ইউনিক্স সকেটের (Unix Socket) মাধ্যমে এই ডাটাবেসের সাথে সংযুক্ত হবে।",
"setupDatabaseMariaDB": "একটি বহিরাগত মারিয়া ডিবি (MariaDB) ডাটাবেসের সাথে সংযোগ করুন। আপনাকে ডাটাবেস সংযোগ তথ্য নিযুক্ত করতে হবে।",
"setupDatabaseEmbeddedMariaDB": "আপনাকে কিছু সেটআপ করার প্রয়োজন নেই। এই ডকার ইমেজে স্বয়ংক্রিয়ভাবে MariaDB এম্বেড এবং কনফিগার করা হয়েছে। Uptime Kuma এই ডাটাবেজের সাথে ইউনিক্স সকেটের মাধ্যমে সংযুক্ত হবে।",
"setupDatabaseMariaDB": "বহিরাগত MariaDB ডাটাবেজের সাথে সংযোগ করতে হবে। আপনাকে ডাটাবেজের সংযোগ তথ্য সেট করতে হবে।",
"Add": "সংযোগ করুন",
"dbName": "ডাটাবেজের নাম",
"languageName": "ইংরেজি",
"languageName": "বাংলা",
"Settings": "সেটিংস",
"Dashboard": "ড্যাশবোর্ড",
"Help": "সাহায্য",
@ -14,5 +14,216 @@
"Check Update On GitHub": "GitHub-এ আপডেট চেক করুন",
"List": "তালিকা",
"General": "সাধারণ",
"Game": "খেলা"
"Game": "খেলা",
"disable authentication": "প্রমাণীকরণ বন্ধ করুন",
"pauseDashboardHome": "থামুন",
"disableauth.message2": "এটি Uptime Kuma এর সামনে {intendThirdPartyAuth} এর মতো পরিস্থিতির জন্য ডিজাইন করা হয়েছে, যেমন Cloudflare Access, Authelia অথবা অন্যান্য প্রমাণীকরণ ব্যবস্থা।",
"I understand, please disable": "আমি বুঝতে পারছি, দয়া করে অক্ষম করুন",
"Certificate Info": "সার্টিফিকেট তথ্য",
"Create your admin account": "আপনার অ্যাডমিন অ্যাকাউন্ট তৈরি করুন",
"Import": "ইমপোর্ট",
"Default enabled": "ডিফল্ট সক্রিয়",
"Heartbeats": "হার্টবিট",
"Affected Monitors": "প্রভাবিত মনিটরগুলো",
"All Status Pages": "সমস্ত স্ট্যাটাস পেজ",
"alertNoFile": "অনুগ্রহ করে আমদানির জন্য একটি ফাইল নির্বাচন করুন।",
"alertWrongFileType": "অনুগ্রহ করে একটি JSON ফাইল নির্বাচন করুন।",
"Clear all statistics": "সব পরিসংখ্যান মুছে ফেলুন",
"Setup 2FA": "2FA সেট আপ করুন",
"Two Factor Authentication": "দুই ফ্যাক্টর প্রমাণীকরণ",
"Add New Tag": "নতুন ট্যাগ যোগ করুন",
"Tag with this name already exist.": "এই নামের ট্যাগ আগে থেকেই আছে।",
"Pink": "গোলাপী",
"Search monitored sites": "পর্যবেক্ষণ করা সাইট অনুসন্ধান",
"statusPageNothing": "এখানে কিছুই নেই, অনুগ্রহ করে একটি গ্রুপ বা মনিটর যোগ করুন।",
"Degraded Service": "অধঃপতন সেবা",
"Go to Dashboard": "ড্যাশবোর্ডে যান",
"defaultNotificationName": "আমার {বিজ্ঞপ্তি} সতর্কতা ({number})",
"webhookJsonDesc": "{0} যেকোনো আধুনিক HTTP সার্ভার যেমন Express.js-এর জন্য ভালো",
"liquidIntroduction": "টেম্পলেটবিলিটি লিকুইড টেমপ্লেটিং ভাষার মাধ্যমে অর্জন করা হয়। ব্যবহারের নির্দেশাবলীর জন্য অনুগ্রহ করে {0} দেখুন। এই উপলব্ধ ভেরিয়েবল হল:",
"Notifications": "নোটিফিকেশন",
"Setup Notification": "নোটিফিকেশন সেটআপ করুন",
"Light": "আলো",
"Auto": "স্বয়ংক্রিয়",
"Theme - Heartbeat Bar": "থিম - হার্টবিট বার",
"styleElapsedTime": "হার্টবিট বারের নিচে ব্যবহৃত সময়",
"styleElapsedTimeShowNoLine": "শোর করুন (কোনো লাইন নেই)",
"styleElapsedTimeShowWithLine": "শো করুন (লাইন সহ)",
"Normal": "স্বাভাবিক",
"Bottom": "নীচে",
"Timezone": "টাইমজোন",
"Search Engine Visibility": "সার্চ ইঞ্জিন দৃশ্যমানতা",
"Allow indexing": "ইন্ডেক্সিং অনুমোদন করুন",
"Discourage search engines from indexing site": "সার্চ ইঞ্জিনগুলোকে সাইটের ইন্ডেক্সিং থেকে বিরত রাখুন",
"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
"Current Password": "বর্তমান পাসওয়ার্ড",
"New Password": "নতুন পাসওয়ার্ড",
"Repeat New Password": "নতুন পাসওয়ার্ডটি আবার দিন",
"Update Password": "পাসওয়ার্ড আপডেট করুন",
"Enable Auth": "প্রমাণীকরণ চালু করুন",
"disableauth.message1": "আপনি কি সত্যিই {disableAuth} করতে চান?",
"where you intend to implement third-party authentication": "যেখানে আপনি তৃতীয় পক্ষের প্রমাণীকরণ বাস্তবায়ন করতে চান",
"Please use this option carefully!": "দয়া করে এই অপশনটি সাবধানে ব্যবহার করুন!",
"Logout": "লগ আউট",
"Leave": "লিভ",
"Confirm": "নিশ্চিত",
"Yes": "হ্যাঁ",
"No": "না",
"Username": "ইউজার নেম",
"Password": "পাসওয়ার্ড",
"Remember me": "আমাকে মনে রাখুন",
"Login": "লগ ইন",
"add one": "একটি যোগ করুন",
"Notification Type": "নোটিফিকেশনের ধরন",
"Email": "ইমেইল",
"Test": "পরীক্ষা",
"Resolver Server": "সমাধানকারী সার্ভার",
"Resource Record Type": "রিসোর্স রেকর্ড টাইপ",
"Last Result": "শেষ ফলাফল",
"Repeat Password": "পাসওয়ার্ড পুনরায় দিন",
"Import Backup": "ইমপোর্ট ব্যাকআপ",
"Export Backup": "এক্সপোর্ট ব্যাকআপ",
"Export": "এক্সপোর্ট",
"respTime": "প্রতিক্রিয়া সময় (ms)",
"notAvailableShort": "প্রযোজ্য নয়",
"Apply on all existing monitors": "সমস্ত বিদ্যমান মনিটরে প্রয়োগ করুন",
"Create": "তৈরি",
"Clear Data": "ডেটা সাফ",
"Events": "ইভেন্ট সমূহ",
"Auto Get": "অটো গ্রহণ",
"Schedule maintenance": "রক্ষণাবেক্ষণ সূচী করুন",
"Pick Affected Monitors...": "প্রভাবিত মনিটর নির্বাচন করুন…",
"Start of maintenance": "রক্ষণাবেক্ষণের শুরু",
"Select status pages...": "স্ট্যাটাস পেজগুলো নির্বাচন করুন…",
"Skip existing": "বিদ্যমান এড়িয়ে যান",
"Overwrite": "ওভাররাইট",
"Options": "অপশন",
"Keep both": "দুটোই রাখুন",
"Verify Token": "টোকেন যাচাই করুন",
"Enable 2FA": "2FA সক্ষম করুন",
"Disable 2FA": "2FA নিষ্ক্রিয় করুন",
"2FA Settings": "2FA সেটিংস",
"filterActive": "সক্রিয়",
"filterActivePaused": "বিরতি",
"Active": "সক্রিয়",
"Inactive": "নিষ্ক্রিয়",
"Token": "টোকেন",
"Show URI": "URI দেখান",
"Tags": "ট্যাগ",
"Add New below or Select...": "নীচে নতুন যোগ করুন বা নির্বাচন করুন…",
"Tag with this value already exist.": "এই মান সহ ট্যাগ ইতিমধ্যেই বিদ্যমান।",
"color": "রং",
"value (optional)": "মান (ঐচ্ছিক)",
"Red": "লাল",
"Orange": "কমলা",
"Green": "সবুজ",
"Blue": "নীল",
"Indigo": "নীল",
"Purple": "বেগুনি",
"Custom": "কাস্টম",
"Search...": "অনুসন্ধান…",
"Avg. Ping": "গড় পিং",
"Avg. Response": "গড় প্রতিক্রিয়া",
"Entry Page": "প্রবেশ পৃষ্ঠা",
"statusPageRefreshIn": "রিফ্রেশ হবে: {0}",
"No Services": "কোনো পরিষেবা নেই",
"All Systems Operational": "সমস্ত সিস্টেম অপারেশনাল",
"Partially Degraded Service": "আংশিকভাবে অবনমিত পরিষেবা",
"Add Group": "গ্রুপ যোগ করুন",
"Add a monitor": "একটি মনিটর যোগ করুন",
"Edit Status Page": "এডিট স্টেটাস পেজ",
"Status Page": "স্ট্যাটাস পেজ",
"Status Pages": "স্ট্যাটাস পেজ সমূহ",
"here": "এখানে",
"Required": "প্রয়োজন",
"Post URL": "পোস্ট URL",
"Content Type": "বিষয়বস্তুর প্রকার",
"webhookFormDataDesc": "{multipart} PHP এর জন্য ভালো। JSON-কে {decodeFunction} দিয়ে পার্স করতে হবে",
"templateMsg": "বিজ্ঞপ্তির বার্তা",
"templateHeartbeatJSON": "হৃদস্পন্দন বর্ণনাকারী বস্তু",
"templateMonitorJSON": "মনিটরের বর্ণনাকারী বস্তু",
"templateLimitedToUpDownNotifications": "শুধুমাত্র UP/DOWN বিজ্ঞপ্তির জন্য উপলব্ধ",
"Gray": "ধূসর",
"Add New Monitor": "নতুন আপটাইম মনিটর যোগ করুন",
"statusMaintenance": "রক্ষণাবেক্ষণ",
"Friendly Name": "বন্ধুত্বপূর্ণ নাম",
"URL": "ইউআরএল",
"settingUpDatabaseMSG": "ডাটাবেস সেটআপ করা হচ্ছে। এটি কিছুটা সময় নিতে পারে, অনুগ্রহ করে ধৈর্য ধরুন।",
"Response": "রেসপন্স",
"Ping": "পিং",
"Keyword": "কীওয়ার্ড",
"Invert Keyword": "ইনভার্ট কীওয়ার্ড",
"setupDatabaseSQLite": "একটি সাধারণ ডাটাবেস ফাইল, যা ছোট পরিসরের ডিপ্লয়মেন্টের জন্য সুপারিশ করা হয়। v2.0.0 এর পূর্বে, Uptime Kuma ডিফল্ট ডাটাবেস হিসেবে SQLite ব্যবহার করত।",
"Unknown": "অজানা",
"Cannot connect to the socket server": "সকেট সার্ভারের সাথে সংযোগ স্থাপন করা যাচ্ছে না",
"Reconnecting...": "পুনরায় সংযোগ স্থাপন করা হচ্ছে...",
"Passive Monitor Type": "নিষ্ক্রিয় মনিটরের ধরন",
"markdownSupported": "মার্কডাউন সিনট্যাক্স সাপোর্ট",
"Pause": "থামুন",
"-day": "-দিন",
"hour": "ঘন্টা",
"Host URL": "হোস্ট ইউআরএল",
"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} লিখুন",
"ignoreTLSErrorGeneral": "সংযোগের জন্য TLS/SSL ত্রুটি উপেক্ষা করুন",
"Upside Down Mode": "আপসাইড ডাউন মোড",
"Pending": "প্রক্রিয়াধীন",
"Push URL": "পুশ URL",
"needPushEvery": "আপনাকে এই URL টি প্রতি {0} সেকেন্ডে কল করতে হবে।",
"Dark": "অন্ধকার",
"Up": "আপ",
"webhookAdditionalHeadersTitle": "অতিরিক্ত শিরোনাম",
"pushOthers": "অন্যান্য",
"programmingLanguages": "প্রোগ্রামিং ভাষাসমূহ",
"Not available, please setup.": "উপলব্ধ নয়, অনুগ্রহ করে সেটআপ করুন।",
"Down": "ডাউন",
"Monitor Type": "মনিটরের ধরন",
"Expected Value": "প্রত্যাশিত মান",
"Home": "হোম",
"Maintenance": "রক্ষণাবেক্ষণ",
"General Monitor Type": "সাধারণ মনিটরের ধরন",
"Specific Monitor Type": "নির্দিষ্ট মনিটরের ধরন",
"Monitor": "মনিটর | মনিটরগুলো",
"day": "দিন | দিনগুলো",
"-hour": "-ঘন্টা",
"Hostname": "হোস্টনেম",
"locally configured mail transfer agent": "লোকালি কনফিগারড মেইল ট্রান্সফার এজেন্ট",
"Port": "পোর্ট",
"Heartbeat Interval": "হার্টবিট ইন্টারভাল",
"Max. Redirects": "সর্বোচ্চ রিডাইরেক্ট",
"Primary Base URL": "প্রাথমিক বেস URL",
"Message": "বার্তা",
"Resume": "পুনরায় শুরু",
"Delete": "মুছে ফেলুন",
"now": "এখন",
"time ago": "{} বয়স",
"-year": "-বছর",
"Json Query Expression": "JSON কোয়েরি এক্সপ্রেশন",
"timeoutAfter": "{0} সেকেন্ড পর টাইমআউট",
"Retries": "পুনরায় চেষ্টা করে",
"checkEverySecond": "প্রতিটি {0} সেকেন্ডে চেক করুন",
"retryCheckEverySecond": "প্রতিটি {0} সেকেন্ডে পুনরায় চেষ্টা করুন",
"resendEveryXTimes": "প্রতিটি {0} বার পুনরায় পাঠান",
"pushOptionalParams": "ঐচ্ছিক প্যারামিটারসমূহ: {0}",
"Save": "সংরক্ষণ",
"ignoredTLSError": "TLS/SSL ত্রুটিগুলি উপেক্ষা করা হয়েছে",
"Accepted Status Codes": "গ্রহণযোগ্য স্ট্যাটাস কোডসমূহ",
"Disable Auth": "প্রমাণীকরণ বন্ধ করুন",
"Theme": "থিম",
"Name": "নাম",
"Status": "অবস্থা",
"DateTime": "তারিখ ও সময়",
"No important events": "কোনো গুরুত্বপূর্ণ ইভেন্ট নেই",
"Edit": "সম্পাদনা",
"Current": "বর্তমান",
"Uptime": "আপটাইম",
"Request Timeout": "রিকোয়েস্ট টাইমআউট",
"Heartbeat Retry Interval": "হার্টবিট রিট্রাই ইন্টারভাল",
"Advanced": "অ্যাডভান্স",
"retriesDescription": "সার্ভিসটি ডাউন হিসেবে চিহ্নিত হওয়ার আগে এবং একটি নোটিফিকেশন পাঠানোর জন্য সর্বোচ্চ পুনরায় চেষ্টা করার সংখ্যা",
"upsideDownModeDescription": "স্ট্যাটাসটি উল্টো করে দিন। যদি সার্ভিসটি পৌঁছানো যায়, তবে এটি DOWN হবে।",
"maxRedirectDescription": "অনুসরণ করার জন্য সর্বোচ্চ রিডাইরেক্ট সংখ্যা। রিডাইরেক্ট নিষ্ক্রিয় করতে 0 সেট করুন।",
"ignoreTLSError": "HTTPS ওয়েবসাইটগুলির জন্য TLS/SSL ত্রুটিগুলি উপেক্ষা করুন",
"pushViewCode": "পুশ মনিটর কীভাবে ব্যবহার করবেন? (কোড দেখুন)",
"Appearance": "দেখানোর ধরন",
"Quick Stats": "তাৎক্ষণিক পরিসংখ্যান"
}

View file

@ -49,7 +49,7 @@
"Delete": "Eliminar",
"Current": "Actual",
"Uptime": "Temps actiu",
"Cert Exp.": "Caducitat del certificat",
"Cert Exp.": "Caducitat del certificat.",
"Monitor": "Monitor | Monitors",
"day": "dia | dies",
"-day": "-dia",
@ -65,7 +65,7 @@
"URL": "URL",
"Hostname": "Nom del servidor",
"Port": "Port",
"timeoutAfter": "¡Timeout' després de {0",
"timeoutAfter": "¡Timeout' després de {0} segons",
"locally configured mail transfer agent": "Agent de transferència de correu configurat localment",
"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": "Introduïu el nom del servidor al qual voleu connectar-vos o {localhost} si voleu utilitzar un {local_mta}",
"Host URL": "URL del servidor",
@ -213,5 +213,361 @@
"Email": "Correu",
"Last Result": "Darrer Resultat",
"Add New Tag": "Afegir nova etiqueta",
"Tag with this value already exist.": "Ja existeix una etiqueta amb aquest valor."
"Tag with this value already exist.": "Ja existeix una etiqueta amb aquest valor.",
"defaultNotificationName": "La meva {notification} Alerta ({number})",
"Required": "Obligatori",
"Post URL": "Posar URL",
"Content Type": "Content Type",
"Json Query Expression": "Expressió de consulta Json",
"now": "ara",
"-year": "-any",
"Status Pages": "Pàgines d'estat",
"here": "aquí",
"time ago": "fa {0}",
"ignoredTLSError": "Errors TLS/SSL ignorats",
"webhookFormDataDesc": "{multipart} es bo per PHP. El JSON haurà d'analitzar-se amb {decodeFunction}",
"webhookJsonDesc": "{0} es bo per qualsevol servidor HTTP modern com Express.js",
"templateMsg": "missatge de la notificació",
"webhookAdditionalHeadersTitle": "Capçaleres addicionals",
"Application Token": "Testimoni d'aplicació",
"Server URL": "URL del servidor",
"Priority": "Prioritat",
"Webhook URL": "URL del Webhook",
"emojiCheatSheet": "Full de trampa d'emoji: {0}",
"Read more": "Llegeix més",
"appriseInstalled": "S'ha instal·lat l'apèndix.",
"templateHeartbeatJSON": "objecte que descriu el batec del cor",
"templateMonitorJSON": "objecte que descriu el monitor",
"templateLimitedToUpDownCertNotifications": "només disponible per a notificacions de venciment UP/DOWN/Certificate",
"templateLimitedToUpDownNotifications": "només disponible per a notificacions UP/DOWN",
"webhookAdditionalHeadersDesc": "Estableix les capçaleres addicionals enviades amb el webhook. Cada capçalera s'ha de definir com una clau/valor JSON.",
"webhookBodyPresetOption": "Predefinit - {0}",
"webhookBodyCustomOption": "Cos personalitzat",
"Headers": "Capçaleres",
"Monitor History": "Historial del monitor",
"PasswordsDoNotMatch": "Les contrasenyes no coincideixen.",
"records": "registres",
"One record": "Un registre",
"Current User": "Usuari actual",
"topic": "Tema",
"topicExplanation": "tema MQTT a monitorar",
"successKeyword": "Paraula clau d'èxit",
"successKeywordExplanation": "Paraula clau MQTT que es considerarà un èxit",
"recent": "Recent",
"Reset Token": "Restableix el testimoni",
"Done": "Fet",
"Info": "Info",
"Steam API Key": "Clau API de Steam",
"Shrink Database": "Redueix la base de dades",
"Pick a RR-Type...": "Trieu un tipus RR…",
"Default": "Per defecte",
"HTTP Options": "Opcions HTTP",
"Create Incident": "Crear Incident",
"Title": "Títol",
"Content": "Contingut",
"Style": "Estil",
"info": "info",
"warning": "avís",
"danger": "perill",
"primary": "primària",
"light": "lleuger",
"dark": "fosc",
"Post": "Post",
"Created": "Creat",
"Last Updated": "Darrera actualització",
"Switch to Light Theme": "Canvia a tema clar",
"Switch to Dark Theme": "Canviar a tema fosc",
"Show Tags": "Mostra les etiquetes",
"Hide Tags": "Amaga les etiquetes",
"Description": "Descripció",
"No monitors available.": "No hi ha monitors disponibles.",
"Add one": "Afegeix-ne un",
"No Monitors": "Sense monitors",
"Untitled Group": "Grup sense títol",
"Services": "Serveis",
"Discard": "Descarta",
"Cancel": "Canel·la",
"Select": "Selecciona",
"Check/Uncheck": "Comprova/desmarca",
"Powered by": "Funciona amb",
"Customize": "Personalitza",
"Custom Footer": "Peu de pàgina personalitzat",
"Custom CSS": "CSS personalitzat",
"default": "Per defecte",
"enabled": "Habilitat",
"setAsDefault": "Estableix com a predeterminat",
"deleteProxyMsg": "Esteu segur que voleu suprimir aquest servidor intermediari per a tots els monitors?",
"setAsDefaultProxyDescription": "Aquest servidor intermediari s'habilitarà de manera predeterminada per als monitors nous. Encara podeu desactivar el servidor intermediari per separat per a cada monitor.",
"Certificate Chain": "Cadena de certificats",
"Valid": "Vàlid",
"Invalid": "Invàlid",
"User": "Usuari",
"Installed": "Instal·lat",
"Not installed": "No instal·lat",
"Running": "En execució",
"Not running": "No en execució",
"Remove Token": "Elimina el testimoni",
"Start": "Inicia",
"Stop": "Aturar",
"Add New Status Page": "Afegeix una pàgina d'estat nova",
"Slug": "Àlies",
"Accept characters:": "Accepta caràcters:",
"startOrEndWithOnly": "Inicia o acaba només amb {0}",
"No consecutive dashes": "Sense guions consecutius",
"Next": "Següent",
"The slug is already taken. Please choose another slug.": "L'àlias ja està agafat. Si us plau, escolliu un altre àlies.",
"No Proxy": "Sense servidor intermediari",
"Proxies": "Servidors intermediaris",
"HTTP Basic Auth": "Autenticació bàsica HTTP",
"New Status Page": "Pàgina d'estat nova",
"Reverse Proxy": "Servidor intermediari invertit",
"Backup": "Còpia de seguretat",
"About": "Quant a",
"wayToGetCloudflaredURL": "(Descarrega de cloudfared des de {0})",
"cloudflareWebsite": "Lloc web de Cloudflare",
"Message:": "Missatge:",
"HTTP Headers": "Capçaleres HTTP",
"Trust Proxy": "Confia en el servidor intermediari",
"Other Software": "Un altre programari",
"For example: nginx, Apache and Traefik.": "Per exemple: nginx, Apache i Traefik.",
"Please read": "Llegiu",
"Subject:": "Assumpte:",
"Valid To:": "Vàlid per a:",
"Days Remaining:": "Dies restants:",
"Issuer:": "Emissor:",
"Fingerprint:": "Empremta digital:",
"No status pages": "Sense pàgines d'estat",
"Domain Name Expiry Notification": "Notificació de venciment del nom de domini",
"Add a new expiry notification day": "Notificació de venciment del nom de domini",
"Remove the expiry notification": "Elimina el dia de notificació de venciment",
"Proxy": "Servidor intermediari",
"Date Created": "Data de creació",
"Footer Text": "Text del peu de pàgina",
"Refresh Interval Description": "La pàgina d'estat farà una actualització completa del lloc cada {0} segons",
"Show Powered By": "Mostra Impulsat per",
"Domain Names": "Noms de domini",
"signedInDisp": "Sessió iniciada com a {0}",
"RadiusSecret": "Secret de Radius",
"RadiusSecretDescription": "Secret compartit entre client i servidor",
"RadiusCalledStationId": "Id de l'estació cridada",
"RadiusCallingStationId": "Id de l'estació de trucada",
"RadiusCallingStationIdDescription": "Identificador del dispositiu de crida",
"Certificate Expiry Notification": "Notificació de venciment del certificat",
"API Username": "Nom d'usuari API",
"API Key": "Clau API",
"Show update if available": "Mostra l'actualització si està disponible",
"Using a Reverse Proxy?": "Usar un servidor intermediari invers?",
"Check how to config it for WebSocket": "Comprova com configurar-lo per a WebSocket",
"Steam Game Server": "Servidor de jocs Steam",
"Most likely causes:": "Causes més probables:",
"There might be a typing error in the address.": "Pot haver-hi un error d'escriptura a l'adreça.",
"What you can try:": "Què podeu provar:",
"Retype the address.": "Torneu a teclejar l'adreça.",
"Go back to the previous page.": "Torna a la pàgina anterior.",
"Coming Soon": "Properament",
"Connection String": "Cadena de connexió",
"Query": "Consulta",
"settingsCertificateExpiry": "Caducitat del certificat TLS",
"Setup Docker Host": "Configura l'amfitrió Docker",
"Connection Type": "Tipus de connexió",
"Docker Daemon": "Dimoni Docker",
"noDockerHostMsg": "No disponible. Primer configureu un amfitrió Docker.",
"DockerHostRequired": "Establiu l'amfitrió Docker per a aquest monitor.",
"socket": "Sòcol",
"tcp": "TCP / HTTP",
"Docker Container": "Contenidor Docker",
"Container Name / ID": "Nom del contenidor / ID",
"Docker Host": "Amfitrió Docker",
"Docker Hosts": "Amfitrions Docker",
"Domain": "Domini",
"Workstation": "Estació de treball",
"Packet Size": "Mida del paquet",
"Bot Token": "Testimoni de bot",
"wayToGetTelegramToken": "Podeu obtenir un testimoni de {0}.",
"Chat ID": "ID de xat",
"telegramMessageThreadID": "(Opcional) ID del fil del missatge",
"telegramMessageThreadIDDescription": "Identificador únic opcional per al fil del missatge de destinació (tema) del fòrum; només per als supergrups del fòrum",
"telegramSendSilently": "Envia silenciosament",
"telegramProtectContent": "Protegeix la reenviament/desament",
"supportTelegramChatID": "Admet xat directe / grup / ID de xat del canal",
"YOUR BOT TOKEN HERE": "EL VOSTRE TESTIMONI DE BOT AQUÍ",
"chatIDNotFound": "No s'ha trobat l'ID del xat; primer envieu un missatge a aquest bot",
"disableCloudflaredNoAuthMsg": "Esteu en mode No Auth, no cal una contrasenya.",
"wayToGetLineNotifyToken": "Podeu obtenir un testimoni d'accés des de {0}",
"Examples": "Exemples",
"Home Assistant URL": "URL de l'assistent d'inici",
"Long-Lived Access Token": "Testimoni d'accés viu",
"Notification Service": "Servei de notificacions",
"Automations can optionally be triggered in Home Assistant:": "Les automatitzacions es poden activar opcionalment a l'assistent d'inici:",
"Trigger type:": "Tipus d'activador:",
"Event type:": "Tipus d'esdeveniment:",
"Event data:": "Dades de l'esdeveniment:",
"Then choose an action, for example switch the scene to where an RGB light is red.": "A continuació, trieu una acció, per exemple, canvieu l'escena a on una llum RGB és vermella.",
"Frontend Version": "Versió del frontal",
"Frontend Version do not match backend version!": "La versió frontal no coincideix amb la versió del dorsal!",
"backupRecommend": "Si us plau, feu una còpia de seguretat del volum o de la carpeta de dades (./data/) directament.",
"Optional": "Opcional",
"and": "i",
"startDateTime": "Data/hora d'inici",
"endDateTime": "Data/hora final",
"cronExpression": "Expressió Cron",
"cronSchedule": "Planificació: ",
"invalidCronExpression": "Expressió Cron no vàlida: {0}",
"recurringInterval": "Interval",
"Recurring": "Recurrència",
"strategyManual": "Activa/Inactiva manualment",
"warningTimezone": "Està utilitzant la zona horària del servidor",
"weekdayShortMon": "Dill",
"weekdayShortTue": "Dim",
"weekdayShortWed": "Dim",
"weekdayShortThu": "Dij",
"weekdayShortFri": "Div",
"weekdayShortSat": "Diss",
"weekdayShortSun": "Dg",
"dayOfWeek": "Dia de la setmana",
"dayOfMonth": "Dia del mes",
"lastDay": "Últim dia",
"lastDay2": "2n últim dia del mes",
"lastDay3": "3r Darrer Dia del Mes",
"lastDay4": "4t Darrer Dia del Mes",
"No Maintenance": "Sense manteniment",
"maintenanceStatus-under-maintenance": "Sota manteniment",
"maintenanceStatus-inactive": "Inactiu",
"maintenanceStatus-scheduled": "Programat",
"maintenanceStatus-unknown": "Desconegut",
"Server Timezone": "Zona horària del servidor",
"statusPageMaintenanceEndDate": "Final",
"IconUrl": "URL de la icona",
"Enable DNS Cache": "(Obsolet) Habilita la memòria cau DNS per als monitors HTTP(s)",
"Enable": "Habilita",
"Disable": "Desactiva",
"chromeExecutable": "Executable Chrome/Chromium",
"chromeExecutableAutoDetect": "Detecció automàtica",
"dnsCacheDescription": "Pot ser que no funcioni treballant amb entorns IPv6, desactiva'l si detectes qualsevol problema.",
"Single Maintenance Window": "Finestra de Manteniment únic",
"Maintenance Time Window of a Day": "Finestra de Temps del manteniment d'un Dia",
"Effective Date Range": "Rang de data eficaç (Opcional)",
"Schedule Maintenance": "Programa de manteniment",
"Edit Maintenance": "Edita el manteniment",
"Date and Time": "Data i hora",
"DateTime Range": "Rang de data i temps",
"loadingError": "Impossible obtenir la data, si us plau prova-ho més endavant.",
"plugin": "Connector | Connectors",
"install": "Instal·la",
"installing": "Instal·lant",
"uninstall": "Desinstal·la",
"confirmUninstallPlugin": "Estàs segur de desinstal·lar aquest connector?",
"notificationRegional": "Local",
"Clone Monitor": "Clona el monitor",
"Clone": "Clona",
"cloneOf": "Clon de {0}",
"secureOptionNone": "Cap / STARTTLS (25, 587)",
"secureOptionTLS": "TLS (465)",
"Ignore TLS Error": "Ignora error TLS",
"From Email": "Des de Correu",
"emailCustomisableContent": "Contingut personalitzable",
"emailCustomSubject": "Tema personalitzable",
"leave blank for default subject": "deixar en blanc per tema per defecte",
"emailCustomBody": "Cos personalitzat",
"leave blank for default body": "deixa en blanc per un cos per defecte",
"emailTemplateServiceName": "Nom de servei",
"emailTemplateHostnameOrURL": "Nom de host o URL",
"emailTemplateStatus": "Estat",
"emailTemplateMonitorJSON": "objecte que descriu el monitor",
"emailTemplateHeartbeatJSON": "objecte que descriu el batec del cor",
"To Email": "Destí email",
"smtpCC": "CC",
"smtpBCC": "BCC",
"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",
"Hello @everyone is...": "Hola {'@'} a tothom …",
"Send to channel": "Envia al canal",
"Create new forum post": "Crea una nova publicació",
"postToExistingThread": "Publica a un fil existent",
"forumPostName": "Nom de publicació de fòrum",
"threadForumPostID": "Fil / identificador de fòrum",
"e.g. {discordThreadID}": "exemple {discordThreadID}",
"wayToGetTeamsURL": "Pot aprendre com crear una URL de webhook {0}.",
"wayToGetZohoCliqURL": "Pot aprendre com crear una URL de webhook {0}.",
"needSignalAPI": "Necessites tenir una senyal de client amb REST API.",
"wayToCheckSignalURL": "Pot comprovar aquesta URL per veure com configurar:",
"Number": "Número",
"Recipients": "Receptors",
"Access Token": "Fitxa d'accés",
"Channel access token (Long-lived)": "Fitxa d'accés del canal (de llarga vida)",
"Line Developers Console": "Consola de Desenvolupadors de la línia",
"appriseNotInstalled": "L'apèndix no està instal·lat. {0}",
"Method": "Mètode",
"clearDataOlderThan": "Conserva les dades de l'historial del monitor durant {0} dies.",
"steamApiKeyDescription": "Per a monitoritzar un servidor de jocs de vapor, necessiteu una clau Steam Web-API. Podeu registrar la vostra clau API aquí: ",
"shrinkDatabaseDescriptionSqlite": "Activa la base de dades {vacuum} per a SQLite. {auto.vacuum} ja està activat, però això no desfragmenta la base de dades ni reempaqueta les pàgines individuals de la base de dades de la manera com ho fa l'ordre {vacuum}.",
"liquidIntroduction": "S'aconsegueix la flexibilitat mitjançant el llenguatge de templatació líquid. Consulteu el {0} per a les instruccions d'ús. Aquestes són les variables disponibles:",
"selectedMonitorCount": "Seleccionat: {0}",
"deleteStatusPageMsg": "Esteu segur que voleu suprimir aquesta pàgina d'estat?",
"proxyDescription": "Els intermediaris s'han d'assignar a un monitor perquè funcioni.",
"enableProxyDescription": "Aquest servidor intermediari no afectarà les sol·licituds del monitor fins que estigui activat. Podeu controlar temporalment desactivar el servidor intermediari de tots els monitors per l'estat d'activació.",
"statusPageSpecialSlugDesc": "Àlies especial {0}: aquesta pàgina es mostrarà quan no es proporcioni l'àlies",
"Authentication": "Autenticació",
"Page Not Found": "Pàgina no trobada",
"Don't know how to get the token? Please read the guide:": "No saps com aconseguir el testimoni? Si us plau, llegiu la guia:",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "La connexió actual es pot perdre si esteu connectant a través del túnel Cloudflare. Segur que voleu aturar-ho? Escriviu la contrasenya actual per confirmar-la.",
"Refresh Interval": "Interval de refresc",
"signedInDispDisabled": "Autenticació desactivada.",
"RadiusCalledStationIdDescription": "Identificador del dispositiu anomenat",
"Also check beta release": "Comprova també la versió beta",
"The resource is no longer available.": "El recurs ja no està disponible.",
"certificationExpiryDescription": "Els monitors HTTPS activen la notificació quan el certificat TLS caduca a:",
"deleteDockerHostMsg": "Esteu segur que voleu suprimir aquest amfitrió de l'acoblador per a tots els monitors?",
"tailscalePingWarning": "Per utilitzar el monitor de Ping Tailscale, heu d'instal·lar el Kuma Uptime sense Docker i també instal·lar el client Tailscale al vostre servidor.",
"telegramSendSilentlyDescription": "Envia el missatge en silenci. Els usuaris rebran una notificació sense so.",
"telegramProtectContentDescription": "Si està activat, els missatges del bot del Telegram estaran protegits contra reenviaments i desaments.",
"wayToGetTelegramChatID": "Podeu obtenir el vostre ID de xat enviant un missatge al bot i anant a aquest URL per veure el xat id:",
"trustProxyDescription": "Confia en les capçaleres «X-Forwarded-*». Si voleu obtenir la IP del client correcta i el vostre Kuma Uptime està darrere d'un servidor intermediari com Nginx o Apache, hauríeu d'activar-ho.",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Es pot crear un testimoni d'accés de llarga durada fent clic al nom del vostre perfil (a baix a l'esquerra) i desplaçant-vos a la part inferior i després feu clic a Crea un testimoni. ",
"default: notify all devices": "per defecte: notifica tots els dispositius",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Podeu trobar una llista dels Serveis de Notificació a l'assistent d'inici a «Eines de revelador . Serveis» cerca «notificació» per trobar el nom del vostre dispositiu/telèfon.",
"backupOutdatedWarning": "Obsolet: Atès que s'han afegit moltes característiques i aquesta funció de còpia de seguretat és una mica inexistent, no pot generar o restaurar una còpia de seguretat completa.",
"lastDay1": "L'últim dia del mes",
"pauseMaintenanceMsg": "Segur que voleu fer una pausa?",
"maintenanceStatus-ended": "Finalitzat",
"Display Timezone": "Mostra la zona horària",
"enableNSCD": "Habilita NSCD (Dimoni de memòria cau de Servei del Nom ) per accelerar les sol·licituds de DNS",
"chromeExecutableDescription": "Per a usuaris de Docker, si el Chrome no està encara instal·lat, pot dur uns quants minuts per instal·lar i mostrar el resultat de prova. Duu 1*GB d'espai de disc.",
"uninstalling": "Desinstal·lant",
"smtp": "Correu electrònic (SMTP)",
"smtpLiquidIntroduction": "Els següents dos camps són personalitzables via \"Liquid templating Language\". Per favor refereix al {0} per a instruccions d'ús. Aquests són les variables disponibles:",
"emailTemplateMsg": "missatge de la notificació",
"emailTemplateLimitedToUpDownNotification": "només disponible per estats UP/Down, altrament null",
"Select message type": "Selecciona el tipus de missatge",
"whatHappensAtForumPost": "Crea una nova publicació de fòrum. Això no publica un missatge a un fòrum existent. Per publicar un fil existent utilitza \"{option}\"",
"Channel access token": "Fitxa d'accés del canal",
"Body": "Cos",
"or": "o",
"PushUrl": "URL de captura",
"HeadersInvalidFormat": "Les capçaleres de sol·licitud no són JSON vàlides: ",
"BodyInvalidFormat": "El cos de petició no és JSON vàlid: ",
"Security": "Seguretat",
"Pick Accepted Status Codes...": "Trieu els codis d'estat acceptats…",
"error": "error",
"critical": "crítica",
"Please input title and content": "Introduïu el títol i el contingut",
"telegramServerUrl": "(Opcional) Url del servidor",
"telegramServerUrlDescription": "Per saltar-se les limitacions del bot de Telegram o tenir accés a regions bloquejades (China, Iran, etc). Per a més informació fes click {0}. Per defecte {1}",
"templateServiceName": "Nom del servei",
"templateHostnameOrURL": "Adreça URL o nom del host",
"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.",
"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."
}

View file

@ -802,8 +802,8 @@
"Json Query": "Json dotaz",
"Badge Duration (in hours)": "Zobrazení odznaku (v hodinách)",
"Badge Preview": "Náhled odznaku",
"Notify Channel": "Kanál nofitikací",
"aboutNotifyChannel": "Upozornění kanálu spustí upozornění na počítači nebo v mobilu pro všechny členy kanálu, ať už jsou dostupní nebo ne.",
"Notify Channel": "Upozornit kanál",
"aboutNotifyChannel": "Upozornění na kanál spustí upozornění na počítači nebo v mobilním telefonu pro všechny členy kanálu, ať už je jejich dostupnost nastavena na aktivní nebo na nepřítomnost.",
"filterActive": "Aktivní",
"filterActivePaused": "Pozastaveno",
"Enter the list of brokers": "Vytvořte seznam zprostředkovatelů",
@ -1055,5 +1055,41 @@
"Debug": "Ladění",
"CopyToClipboardError": "Nelze zkopírovat do schránky: {error}",
"ignoredTLSError": "Chyby TLS/SSL byly ignorovány",
"Copy": "Kopírovat"
"Copy": "Kopírovat",
"aboutSlackUsername": "Mění zobrazované jméno odesílatele zprávy. Pokud někoho chcete zmínit, použijte raději pole obecný název.",
"Message format": "Formát zprávy",
"Notification Channel": "Kanál notifikací",
"Alphanumerical string and hyphens only": "Pouze alfanumerické řetězce a pomlčky",
"Sound": "Zvuk",
"Custom sound to override default notification sound": "Použít vlastní zvuk místo výchozího zvuku upozornění",
"Correct": "Správně",
"Fail": "Chyba",
"Reveal": "Odhalení",
"Doorbell": "Zvonek",
"Flute": "Flétna",
"Money": "Peníze",
"Elevator": "Výtah",
"Guitar": "Kytara",
"Time Sensitive (iOS Only)": "Časově kritické (pouze pro iOS)",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Časově kritická upozornění budou doručena okamžitě, i když je zařízení v režimu nerušit.",
"From": "Od",
"Can be found on:": "Lze nalézt na: {0}",
"The phone number of the recipient in E.164 format.": "Telefonní číslo příjemce ve formátu E.164 (+420...).",
"RabbitMQ Nodes": "RabbitMQ uzly pro správu",
"rabbitmqNodesDescription": "URL RabbitMQ uzlů pro správu zadávejte včetně protokolu a portu. Příklad: {0}",
"rabbitmqNodesRequired": "Prosím, definujte uzly pro tento dohled.",
"rabbitmqNodesInvalid": "Prosím, použijte plně kvalifikované URL (začínající na 'http)' RabbitMQ uzlů.",
"RabbitMQ Username": "RabbitMQ uživatelské jméno",
"Send rich messages": "Poslat rozsáhlé zprávy",
"Arcade": "Arkáda",
"Harp": "Harfa",
"Bubble": "Bublinky",
"Scifi": "Scifi",
"Pop": "Prasknutí",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Pro zajištění příjmu odpovědí zadejte ID odesílatele textu nebo telefonní číslo ve formátu E.164.",
"RabbitMQ Password": "RabbitMQ heslo",
"rabbitmqHelpText": "Abyste mohli používat tento monitor, musíte v nastavení RabbitMQ povolit modul pro správu. Další informace naleznete na adrese {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API klíč",
"Separate multiple email addresses with commas": "Více e-mailových adres oddělte čárkami",
"Clear": "Odstranění"
}

View file

@ -45,7 +45,7 @@
"Port": "Port",
"Heartbeat Interval": "Hjerteslag interval",
"Retries": "Gentagelser",
"retriesDescription": "Maksimalt antal gentagelser, før tjenesten markeres som inaktiv og sender en meddelelse.",
"retriesDescription": "Maksimalt antal gentagelser, før tjenesten markeres som inaktiv og sender en meddelelse",
"Advanced": "Avanceret",
"ignoreTLSError": "Ignorér TLS/SSL fejl for HTTPS websteder",
"Upside Down Mode": "Omvendt tilstand",
@ -99,7 +99,7 @@
"deleteNotificationMsg": "Er du sikker på, at du vil slette denne underretning for alle overvågere?",
"resolverserverDescription": "Cloudflare er standardserveren, den kan til enhver tid ændres.",
"Resolver Server": "Navne-server",
"rrtypeDescription": "Vælg den type RR, du vil overvåge.",
"rrtypeDescription": "Vælg den type RR, du vil overvåge",
"Last Result": "Seneste resultat",
"pauseMonitorMsg": "Er du sikker på at du vil standse overvågningen?",
"Create your admin account": "Opret din administratorkonto",
@ -125,7 +125,7 @@
"backupDescription3": "Følsom data, f.eks. underretnings-tokener, er inkluderet i eksportfilen. Gem den sikkert.",
"alertNoFile": "Vælg en fil der skal importeres.",
"alertWrongFileType": "Vælg venligst en JSON-fil.",
"twoFAVerifyLabel": "Indtast venligst dit token for at bekræfte, at 2FA fungerer",
"twoFAVerifyLabel": "Indtast venligst dit token for at bekræfte, at 2FA fungerer:",
"tokenValidSettingsMsg": "Token er gyldigt! Du kan nu gemme 2FA -indstillingerne.",
"confirmEnableTwoFAMsg": "Er du sikker på at du vil aktivere 2FA?",
"confirmDisableTwoFAMsg": "Er du sikker på at du vil deaktivere 2FA?",
@ -141,7 +141,7 @@
"Token": "Token",
"Show URI": "Vis URI",
"Clear all statistics": "Ryd alle Statistikker",
"retryCheckEverySecond": "Prøv igen hvert {0} sekund.",
"retryCheckEverySecond": "Prøv igen hvert {0} sekund",
"importHandleDescription": "Vælg 'Spring over eksisterende', hvis du vil springe over hver overvåger eller underretning med samme navn. 'Overskriv' sletter alle eksisterende overvågere og underretninger.",
"confirmImportMsg": "Er du sikker på at importere sikkerhedskopien? Sørg for, at du har valgt den rigtige importindstilling.",
"Heartbeat Retry Interval": "Hjerteslag gentagelsesinterval",
@ -277,7 +277,7 @@
"lineDevConsoleTo": "Line Udviklerkonsol - {0}",
"Basic Settings": "Basisindstillinger",
"User ID": "Bruger-ID",
"Messaging API": "Messaging API",
"Messaging API": "Besked API",
"wayToGetLineChannelToken": "Tilgå først {0}, opret en udbyder og kanal (Messaging API), så kan du få kanaladgangstoken'et og bruger-ID'et fra de ovennævnte menupunkter.",
"Icon URL": "Ikon URL",
"aboutIconURL": "Du kan angive et link til et billede i \"Ikon URL\" for at tilsidesætte standardprofilbilledet. Vil ikke blive brugt, hvis Ikon Emoji er angivet.",
@ -415,7 +415,7 @@
"signedInDisp": "Logget ind som {0}",
"Certificate Expiry Notification": "Meddelelse om udløbsdato for certifikatet",
"API Username": "API Brugernavn",
"API Key": "API Key",
"API Key": "API Nøgle",
"Steam Game Server": "Steam Game Server",
"What you can try:": "Hvad du kan prøve:",
"Go back to the previous page.": "Gå tilbage til forrige side.",
@ -621,11 +621,11 @@
"confirmDeleteTagMsg": "Er du sikker på at du vil slette denne etiket? Overvågninger med denne etiket vil ikke blive slettet.",
"resendEveryXTimes": "Gensend hver {0} gang",
"resendDisabled": "Gensendelse deaktiveret",
"Reconnecting...": "Genforbinder...",
"Reconnecting...": "Genopretter forbindelse...",
"successPaused": "Standset med succes.",
"Reset Token": "Nulstil nøgle",
"Show Clickable Link": "Vis klikbart link",
"atLeastOneMonitor": "Vælg mindst én berørt overvågning.",
"atLeastOneMonitor": "Vælg mindst én berørt overvågning",
"authInvalidToken": "Ugyldig nøgle.",
"authIncorrectCreds": "Brugernavn eller adgangskode er ikke korrekt.",
"2faAlreadyEnabled": "2FA er allerede aktiveret.",
@ -656,5 +656,147 @@
"telegramSendSilentlyDescription": "Send beskeden lydløst. Brugerne vil modtage en notifikation uden lyd.",
"pushoverMessageTtl": "Message TTL (Sekunder)",
"setupDatabaseEmbeddedMariaDB": "Du skal ikke opsætte noget. Docker billedet har integreret og konfigureret MariaDB automatisk. Uptime Kuma vil forbinde til databasen vha. en Unix socket.",
"setupDatabaseMariaDB": "Oprette forbindelse til en ekstern MariaDB database. Du skal indstille databasens forbindelsesinformation."
"setupDatabaseMariaDB": "Oprette forbindelse til en ekstern MariaDB database. Du skal indstille databasens forbindelsesinformation.",
"styleElapsedTimeShowWithLine": "Vis (med linjer)",
"styleElapsedTime": "Forløbet tid under heartbeat-bjælken",
"templateMsg": "besked fra notifikationen",
"templateHeartbeatJSON": "objekt, der beskriver heartbeat",
"templateMonitorJSON": "objekt, der beskriver monitoren",
"templateLimitedToUpDownNotifications": "kun tilgængelig for UP/DOWN notifikationer",
"webhookAdditionalHeadersTitle": "Yderligere Headers",
"webhookAdditionalHeadersDesc": "Angiver yderligere headers, der sendes med webhooken. Hver header skal defineres som en JSON-nøgle/værdi.",
"webhookBodyCustomOption": "Brugerdefineret Body",
"successKeyword": "Succesnøgleord",
"startOrEndWithOnly": "Kun start eller slut med {0}",
"setAsDefaultProxyDescription": "Denne proxy vil som standard være aktiveret for nye monitorer. Du kan stadig deaktivere proxyen individuelt for hver monitor.",
"No consecutive dashes": "Ingen på hinanden følgende bindestreger",
"statusPageSpecialSlugDesc": "Speciel slug {0}: Denne side vises, når der ikke angives en slug",
"Query": "Kø",
"Add a new expiry notification day": "Tilføj en ny udløbsnotifikationsdag",
"Remove the expiry notification": "Fjern udløbsnotifikationsdagen",
"Refresh Interval": "Opdateringsinterval",
"Refresh Interval Description": "Statussiden vil udføre en fuld opdatering af sitet hvert {0} sekunder",
"RadiusSecret": "Radius-hemmelighed",
"RadiusCalledStationId": "Called Station ID",
"RadiusCalledStationIdDescription": "Identifikator for den kaldte enhed",
"RadiusCallingStationId": "Calling Station Id",
"RadiusCallingStationIdDescription": "Identifikator for den kaldende enhed",
"Check how to config it for WebSocket": "Tjek, hvordan det konfigureres til WebSocket",
"Connection String": "Forbindelsesstreng",
"Workstation": "Arbejdsstation",
"telegramMessageThreadID": "(Valgfrit) Beskedtråd-ID",
"telegramMessageThreadIDDescription": "Valgfri unik identifikator for målbeskedtråden (emnet) i forummet; kun for forum-supergrupper",
"telegramProtectContent": "Beskyt videresendelse/gemning",
"telegramProtectContentDescription": "Hvis aktiveret, vil bot-beskeder i Telegram være beskyttet mod videresendelse og gemning.",
"disableCloudflaredNoAuthMsg": "Du er i No Auth-tilstand, en adgangskode er ikke påkrævet.",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "En liste over notifikationstjenester kan findes i Home Assistant under “Developer Tools > Services”. Søg efter “notification” for at finde navnet på din enhed/telefon.",
"Then choose an action, for example switch the scene to where an RGB light is red.": "Vælg derefter en handling, for eksempel at skifte scenen til en, hvor et RGB-lys er rødt.",
"backupRecommend": "Sikkerhedskopier venligst volumen eller data-mappen (./data/) direkte i stedet.",
"and": "og",
"startDateTime": "Startdato/-tidspunkt",
"endDateTime": "Slutdato/-tidspunkt",
"cronExpression": "Cron-udtryk",
"cronSchedule": "Tidsplan: ",
"warningTimezone": "Den bruger serverens tidszone",
"enableNSCD": "Aktivér NSCD (Name Service Cache Daemon) for at cache alle DNS-forespørgsler",
"chromeExecutable": "Chrome/Chromium-eksekverbar fil",
"Single Maintenance Window": "Enkelt vedligeholdelsesvindue",
"Edit Maintenance": "Rediger vedligeholdelse",
"emailCustomisableContent": "Brugerdefinerbart indhold",
"leave blank for default subject": "lad stå tomt for standardemne",
"emailCustomBody": "Brugerdefineret body",
"leave blank for default body": "lad stå tomt for standard-body",
"emailTemplateServiceName": "Servicenavn",
"emailTemplateHostnameOrURL": "Værtsnavn eller URL",
"emailTemplateStatus": "Status",
"emailTemplateMonitorJSON": "objekt, der beskriver monitoren",
"emailTemplateHeartbeatJSON": "objekt, der beskriver heartbeat",
"emailTemplateMsg": "besked fra notifikationen",
"emailTemplateLimitedToUpDownNotification": "kun tilgængelig for UP/DOWN heartbeats, ellers null",
"Select message type": "Vælg beskedtype",
"Send to channel": "Send til kanal",
"Create new forum post": "Opret nyt forumpost",
"postToExistingThread": "Send til eksisterende tråd / forumpost",
"forumPostName": "Forumpost-navn",
"threadForumPostID": "Tråd- / Forumpost-ID",
"e.g. {discordThreadID}": "f.eks. {discordThreadID}",
"wayToGetDiscordThreadId": "At hente en tråd- / forumpost-ID ligner processen for at hente en kanal-ID. Læs mere om, hvordan du får IDs {0}",
"Channel access token (Long-lived)": "Kanaladgangstoken (langvarig)",
"Your User ID": "Din bruger-ID",
"dataRetentionTimeError": "Opbevaringsperioden skal være 0 eller højere",
"infiniteRetention": "Sæt til 0 for uendelig opbevaring.",
"enableGRPCTls": "Tillad afsendelse af gRPC-forespørgsel med TLS-forbindelse",
"affectedStatusPages": "Vis denne vedligeholdelsesbesked på udvalgte statussider",
"invertKeywordDescription": "Se efter, at nøgleordet mangler i stedet for at være til stede.",
"octopushAPIKey": "“API-nøgle” fra HTTP API-legitimationsoplysninger i kontrolpanelet",
"octopushLogin": "“Login” fra HTTP API-legitimationsoplysninger i kontrolpanelet",
"pushoversounds pushover": "Pushover (standard)",
"pushoversounds bike": "Cykel",
"pushoversounds bugle": "Signalhorn",
"pushoversounds cashregister": "Kasseapparat",
"pushoversounds cosmic": "Kosmisk",
"pushoversounds falling": "Faldende",
"pushoversounds gamelan": "Gamelan",
"pushoversounds incoming": "Indkommende",
"pushoversounds intermission": "Pause",
"pushoversounds spacealarm": "Rumalarm",
"pushoversounds tugboat": "Slæbebåd",
"pushoversounds climb": "Klatring (lang)",
"pushoversounds persistent": "Vedvarende (lang)",
"pushoversounds echo": "Pushover Echo (lang)",
"pushoversounds updown": "Op Ned (lang)",
"GoogleChat": "Google Chat (kun Google Workspace)",
"styleElapsedTimeShowNoLine": "Vis (ingen linjer)",
"Slug": "Slug",
"The slug is already taken. Please choose another slug.": "Denne slug er allerede i brug. Vælg venligst en anden.",
"There might be a typing error in the address.": "Der er muligvis en stavfejl i adressen.",
"Long-Lived Access Token": "Long-Lived Access Token",
"Search monitored sites": "Søg overvågede sites",
"liquidIntroduction": "Templatability opnås via Liquid-templeringssproget. Se venligst {0} for brugsanvisninger. Her er de tilgængelige variabler:",
"templateLimitedToUpDownCertNotifications": "kun tilgængelig for UP/DOWN/Certifikatudløb notifikationer",
"shrinkDatabaseDescriptionSqlite": "Udløs database-{vacuum} for SQLite. {auto_vacuum} er allerede aktiveret, men dette defragmenterer ikke databasen eller ompakker de enkelte database-sider på samme måde som {vacuum}-kommandoen gør.",
"Check/Uncheck": "Markér/Afjern markering",
"enableProxyDescription": "Denne proxy vil ikke påvirke monitor-forespørgsler, før den er aktiveret. Du kan midlertidigt deaktivere proxyen for alle monitorer via aktiveringsstatus.",
"RadiusSecretDescription": "Delt hemmelighed mellem klient og server",
"noDockerHostMsg": "Ikke tilgængelig. Opsæt en Docker-host først.",
"DockerHostRequired": "Angiv venligst Docker-hosten for denne monitor.",
"tailscalePingWarning": "For at bruge Tailscale Ping-monitoren skal du installere Uptime Kuma uden Docker og også installere Tailscale-klienten på din server.",
"trustProxyDescription": "Tillid til X-Forwarded-* headers. Hvis du vil hente den korrekte klient-IP, og din Uptime Kuma er bag en proxy såsom Nginx eller Apache, bør du aktivere dette.",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "En langvarig adgangstoken kan oprettes ved at klikke på dit profilnavn (nederst til venstre), rulle ned til bunden og derefter klikke på “Opret token”. ",
"backupOutdatedWarning": "Forældet: Da mange funktioner er blevet tilføjet, og denne backup-funktion er lidt vedligeholdelsesfri, kan den ikke generere eller gendanne en komplet backup.",
"invalidCronExpression": "Ugyldigt Cron-udtryk: {0}",
"chromeExecutableDescription": "For Docker-brugere: Hvis Chromium endnu ikke er installeret, kan det tage et par minutter at installere og vise testresultatet. Det kræver 1 GB diskplads.",
"Effective Date Range": "Gyldig datointerval (Valgfrit)",
"DateTime Range": "Dato-/tidsinterval",
"notificationRegional": "Regional",
"smtpLiquidIntroduction": "De følgende to felter kan tilpasses via Liquid-templateringssproget. Se venligst {0} for brugsanvisninger. Her er de tilgængelige variabler:",
"whatHappensAtForumPost": "Opret en ny forumpost. Dette sender IKKE beskeder i en eksisterende post. For at sende i en eksisterende post, brug ”{option}”",
"grpcMethodDescription": "Metodenavnet konverteres til camelCase-format, såsom sayHello, check osv.",
"affectedMonitorsDescription": "Vælg de monitorer, der er påvirket af den aktuelle vedligeholdelse",
"jsonQueryDescription": "Parse og udtræk specifikke data fra serverens JSON-svar ved hjælp af en JSON-forespørgsel, eller brug ”$” for det rå svar, hvis JSON ikke forventes. Resultatet sammenlignes derefter med den forventede værdi som strenge. Se {0} for dokumentation, og brug {1} til at eksperimentere med forespørgsler.",
"pushoversounds classical": "Klassisk",
"pushoversounds pianobar": "Piano Bar",
"wayToGetKookBotToken": "Opret en applikation og få din bot-token på {0}",
"wayToGetKookGuildID": "Tænd for Developer Mode i Kook-indstillingerne, og højreklik på guilden for at få dens ID",
"successKeywordExplanation": "MQTT-nøgleord, der vil blive betragtet som succes",
"endpoint": "endpoint",
"topicExplanation": "MQTT-emne til overvågning",
"settingUpDatabaseMSG": "Opsætter databasen. Det kan tage et stykke tid, så vær tålmodig.",
"now": "nu",
"Json Query Expression": "Json Query Expression",
"locally configured mail transfer agent": "lokalt konfigureret mail overførsels agent",
"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": "Indtast enten værtsnavnet på serveren du vil forbinde til eller {localhost} hvis du planlægger at bruge en {local_mta}",
"timeoutAfter": "Timeout efter {0} sekunder",
"ignoredTLSError": "TLS/SSL-fejl er blevet ignoreret",
"Invert Keyword": "Inverter Nøgleord",
"Resend Notification if Down X times consecutively": "Send meddelelse igen, hvis Ned X gange i træk",
"Expected Value": "Forventede Værdi",
"setupDatabaseSQLite": "En simpel database-fil, anbefalet til mindre implementeringer. Før v2.0.0, brugte Uptime Kuma SQLite som standard database.",
"ignoreTLSErrorGeneral": "Ignorér TLS/SSL-fejl for forbindelsen",
"time ago": "{0} siden",
"-year": "-år",
"Host URL": "Host URL",
"Request Timeout": "Anmod Timeout",
"Cannot connect to the socket server": "Kan ikke oprette forbindelse til socket serveren",
"pushViewCode": "Hvordan bruger man Push Monitor? (Se kode)"
}

View file

@ -1095,5 +1095,25 @@
"Separate multiple email addresses with commas": "Mehrere E-Mail-Adressen mit Kommas trennen",
"rabbitmqNodesInvalid": "Benutze eine vollständig qualifizierte URL (beginnend mit 'http') für RabbitMQ-Knoten.",
"rabbitmqHelpText": "Um den Monitor zu benutzen, musst du das Management Plugin in deinem RabbitMQ-Setup aktivieren. Weitere Informationen siehe {rabitmq_documentation}.",
"aboutSlackUsername": "Ändert den Anzeigenamen des Absenders. Wenn du jemanden erwähnen möchtest, füge ihn stattdessen in den Namen ein."
"aboutSlackUsername": "Ändert den Anzeigenamen des Absenders. Wenn du jemanden erwähnen möchtest, füge ihn stattdessen in den Namen ein.",
"templateHostnameOrURL": "Hostname oder URL",
"telegramUseTemplate": "Benutzerdefinierte Nachrichtenvorlage verwenden",
"telegramUseTemplateDescription": "Wenn diese Option aktiviert ist, wird die Nachricht unter Verwendung einer benutzerdefinierten Vorlage gesendet.",
"templateServiceName": "Service-Name",
"YZJ Webhook URL": "YZJ Webhook URL",
"YZJ Robot Token": "YZJ Robot Token",
"templateStatus": "Status",
"telegramTemplateFormatDescription": "Telegram ermöglicht die Verwendung verschiedener Markup-Sprachen für Nachrichten, siehe Telegram {0} für spezifische Details.",
"Plain Text": "Nur Text",
"Message Template": "Nachrichtenvorlage",
"Template Format": "Vorlagenformat",
"wayToGetWahaApiUrl": "Die URL deiner WAHA-Instanz.",
"wayToGetWahaSession": "Von dieser Sitzung aus sendet WAHA Benachrichtigungen an die Chat-ID. Du kannst sie im WAHA Dashboard finden.",
"wahaSession": "Sitzung",
"wahaChatId": "Chat-ID (Telefonnummer / Kontakt-ID / Gruppen-ID)",
"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}",
"Font Twemoji by Twitter licensed under": "Schriftart Twemoji von Twitter lizenziert unter"
}

View file

@ -1098,5 +1098,25 @@
"rabbitmqNodesDescription": "Gib die URL für die RabbitMQ-Verwaltungsknoten einschließlich Protokoll und Port ein. Beispiel: {0}",
"rabbitmqNodesInvalid": "Benutze eine vollständig qualifizierte URL (beginnend mit 'http') für RabbitMQ-Knoten.",
"rabbitmqHelpText": "Um den Monitor zu benutzen, musst du das Management Plugin in deinem RabbitMQ-Setup aktivieren. Weitere Informationen siehe {rabitmq_documentation}.",
"aboutSlackUsername": "Ändert den Anzeigenamen des Absenders. Wenn du jemanden erwähnen möchtest, füge ihn stattdessen in den Namen ein."
"aboutSlackUsername": "Ändert den Anzeigenamen des Absenders. Wenn du jemanden erwähnen möchtest, füge ihn stattdessen in den Namen ein.",
"templateHostnameOrURL": "Hostname oder URL",
"telegramUseTemplate": "Benutzerdefinierte Nachrichtenvorlage verwenden",
"telegramTemplateFormatDescription": "Telegram ermöglicht die Verwendung verschiedener Markup-Sprachen für Nachrichten, siehe Telegram {0} für spezifische Details.",
"Plain Text": "Nur Text",
"templateServiceName": "Service-Name",
"YZJ Webhook URL": "YZJ Webhook URL",
"YZJ Robot Token": "YZJ Robot Token",
"templateStatus": "Status",
"telegramUseTemplateDescription": "Wenn diese Option aktiviert ist, wird die Nachricht unter Verwendung einer benutzerdefinierten Vorlage gesendet.",
"Message Template": "Nachrichtenvorlage",
"Template Format": "Vorlagenformat",
"wayToGetWahaApiUrl": "Die URL deiner WAHA-Instanz.",
"wahaSession": "Sitzung",
"wahaChatId": "Chat-ID (Telefonnummer / Kontakt-ID / Gruppen-ID)",
"wayToGetWahaApiKey": "API-Schlüssel ist der Wert der WHATSAPP_API_KEY-Umgebungsvariable, den du beim Ausführen von WAHA verwendet hast.",
"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",
"Font Twemoji by Twitter licensed under": "Schriftart Twemoji von Twitter lizenziert unter"
}

View file

@ -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",
@ -231,6 +232,9 @@
"templateMonitorJSON": "object describing the monitor",
"templateLimitedToUpDownCertNotifications": "only available for UP/DOWN/Certificate expiry notifications",
"templateLimitedToUpDownNotifications": "only available for UP/DOWN notifications",
"templateServiceName": "service name",
"templateHostnameOrURL": "hostname or URL",
"templateStatus": "status",
"webhookAdditionalHeadersTitle": "Additional Headers",
"webhookAdditionalHeadersDesc": "Sets additional headers sent with the webhook. Each header should be defined as a JSON key/value.",
"webhookBodyPresetOption": "Preset - {0}",
@ -421,8 +425,13 @@
"telegramSendSilentlyDescription": "Sends the message silently. Users will receive a notification with no sound.",
"telegramProtectContent": "Protect Forwarding/Saving",
"telegramProtectContentDescription": "If enabled, the bot messages in Telegram will be protected from forwarding and saving.",
"telegramUseTemplate": "Use custom message template",
"telegramUseTemplateDescription": "If enabled, the message will be sent using a custom template.",
"telegramTemplateFormatDescription": "Telegram allows using different markup languages for messages, see Telegram {0} for specifc details.",
"supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID",
"wayToGetTelegramChatID": "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:",
"telegramServerUrl": "(Optional) Server Url",
"telegramServerUrlDescription": "To lift Telegram's bot api limitations or gain access in blocked areas (China, Iran, etc). For more information click {0}. Default: {1}",
"YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
"chatIDNotFound": "Chat ID is not found; please send a message to this bot first",
"disableCloudflaredNoAuthMsg": "You are in No Auth mode, a password is not required.",
@ -509,6 +518,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",
@ -519,9 +529,6 @@
"leave blank for default subject": "leave blank for default subject",
"emailCustomBody": "Custom Body",
"leave blank for default body": "leave blank for default body",
"emailTemplateServiceName": "Service Name",
"emailTemplateHostnameOrURL": "Hostname or URL",
"emailTemplateStatus": "Status",
"emailTemplateMonitorJSON": "object describing the monitor",
"emailTemplateHeartbeatJSON": "object describing the heartbeat",
"emailTemplateMsg": "message of the notification",
@ -752,12 +759,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}",
@ -779,6 +800,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",
@ -1056,5 +1078,29 @@
"Allow Notifications": "Allow Notifications",
"Browser not supported": "Browser not supported",
"Unable to get permission to notify": "Unable to get permission to notify (request either denied or ignored).",
"Webpush Helptext": "Web push only works with SSL (HTTPS) connections. For iOS devices, webpage must be added to homescreen beforehand."
"Webpush Helptext": "Web push only works with SSL (HTTPS) connections. For iOS devices, webpage must be added to homescreen beforehand.",
"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.",
"wayToGetWahaApiKey": "API Key is WHATSAPP_API_KEY environment variable value you used to run WAHA.",
"wayToGetWahaSession": "From this session WAHA sends notifications to Chat ID. You can find it in WAHA Dashboard.",
"wayToWriteWahaChatId": "The phone number with the international prefix, but without the plus sign at the start ({0}), the Contact ID ({1}) or the Group ID ({2}). Notifications are sent to this Chat ID from WAHA Session.",
"YZJ Webhook URL": "YZJ Webhook URL",
"YZJ Robot Token": "YZJ Robot token",
"Plain Text": "Plain Text",
"Message Template": "Message Template",
"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"
}

Some files were not shown because too many files have changed in this diff Show more