LouisLam
3 years ago
137 changed files with 22175 additions and 8724 deletions
@ -1,11 +0,0 @@ |
|||||
spec: |
|
||||
name: uptime-kuma |
|
||||
services: |
|
||||
- name: server |
|
||||
git: |
|
||||
repo_clone_url: https://github.com/louislam/uptime-kuma |
|
||||
branch: master |
|
||||
http_port: 3001 |
|
||||
build_command: npm run setup |
|
||||
run_command: npm run start-server |
|
||||
|
|
@ -0,0 +1,12 @@ |
|||||
|
# These are supported funding model platforms |
||||
|
|
||||
|
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] |
||||
|
#patreon: # Replace with a single Patreon username |
||||
|
open_collective: uptime-kuma # Replace with a single Open Collective username |
||||
|
#ko_fi: # Replace with a single Ko-fi username |
||||
|
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel |
||||
|
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry |
||||
|
#liberapay: # Replace with a single Liberapay username |
||||
|
#issuehunt: # Replace with a single IssueHunt username |
||||
|
#otechie: # Replace with a single Otechie username |
||||
|
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] |
@ -1,71 +0,0 @@ |
|||||
# For most projects, this workflow file will not need changing; you simply need |
|
||||
# to commit it to your repository. |
|
||||
# |
|
||||
# You may wish to alter this file to override the set of languages analyzed, |
|
||||
# or to provide custom queries or build logic. |
|
||||
# |
|
||||
# ******** NOTE ******** |
|
||||
# We have attempted to detect the languages in your repository. Please check |
|
||||
# the `language` matrix defined below to confirm you have the correct set of |
|
||||
# supported CodeQL languages. |
|
||||
# |
|
||||
name: "CodeQL" |
|
||||
|
|
||||
on: |
|
||||
push: |
|
||||
branches: [ master ] |
|
||||
pull_request: |
|
||||
# The branches below must be a subset of the branches above |
|
||||
branches: [ master ] |
|
||||
schedule: |
|
||||
- cron: '35 5 * * 2' |
|
||||
|
|
||||
jobs: |
|
||||
analyze: |
|
||||
name: Analyze |
|
||||
runs-on: ubuntu-latest |
|
||||
permissions: |
|
||||
actions: read |
|
||||
contents: read |
|
||||
security-events: write |
|
||||
|
|
||||
strategy: |
|
||||
fail-fast: false |
|
||||
matrix: |
|
||||
language: [ 'javascript' ] |
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] |
|
||||
# Learn more: |
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed |
|
||||
|
|
||||
steps: |
|
||||
- name: Checkout repository |
|
||||
uses: actions/checkout@v2 |
|
||||
|
|
||||
# Initializes the CodeQL tools for scanning. |
|
||||
- name: Initialize CodeQL |
|
||||
uses: github/codeql-action/init@v1 |
|
||||
with: |
|
||||
languages: ${{ matrix.language }} |
|
||||
# If you wish to specify custom queries, you can do so here or in a config file. |
|
||||
# By default, queries listed here will override any specified in a config file. |
|
||||
# Prefix the list here with "+" to use these queries and those in the config file. |
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main |
|
||||
|
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). |
|
||||
# If this step fails, then you should remove it and run the build manually (see below) |
|
||||
- name: Autobuild |
|
||||
uses: github/codeql-action/autobuild@v1 |
|
||||
|
|
||||
# ℹ️ Command-line programs to run using the OS shell. |
|
||||
# 📚 https://git.io/JvXDl |
|
||||
|
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines |
|
||||
# and modify them (or add more) to build your code if your project |
|
||||
# uses a compiled language |
|
||||
|
|
||||
#- run: | |
|
||||
# make bootstrap |
|
||||
# make release |
|
||||
|
|
||||
- name: Perform CodeQL Analysis |
|
||||
uses: github/codeql-action/analyze@v1 |
|
@ -1,3 +1,9 @@ |
|||||
{ |
{ |
||||
"extends": "stylelint-config-recommended", |
"extends": "stylelint-config-standard", |
||||
|
"rules": { |
||||
|
"indentation": 4, |
||||
|
"no-descending-specificity": null, |
||||
|
"selector-list-comma-newline-after": null, |
||||
|
"declaration-empty-line-before": null |
||||
|
} |
||||
} |
} |
||||
|
@ -0,0 +1 @@ |
|||||
|
git.kuma.pet |
@ -0,0 +1,128 @@ |
|||||
|
# Contributor Covenant Code of Conduct |
||||
|
|
||||
|
## Our Pledge |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
We pledge to act and interact in ways that contribute to an open, welcoming, |
||||
|
diverse, inclusive, and healthy community. |
||||
|
|
||||
|
## Our Standards |
||||
|
|
||||
|
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, |
||||
|
and learning from the experience |
||||
|
* 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 |
||||
|
professional setting |
||||
|
|
||||
|
## Enforcement Responsibilities |
||||
|
|
||||
|
Community leaders are responsible for clarifying and enforcing our standards of |
||||
|
acceptable behavior and will take appropriate and fair corrective action in |
||||
|
response to any behavior that they deem inappropriate, threatening, offensive, |
||||
|
or harmful. |
||||
|
|
||||
|
Community leaders have the right and responsibility to remove, edit, or reject |
||||
|
comments, commits, code, wiki edits, issues, and other contributions that are |
||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation |
||||
|
decisions when appropriate. |
||||
|
|
||||
|
## Scope |
||||
|
|
||||
|
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, |
||||
|
posting via an official social media account, or acting as an appointed |
||||
|
representative at an online or offline event. |
||||
|
|
||||
|
## Enforcement |
||||
|
|
||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
||||
|
reported to the community leaders responsible for enforcement at |
||||
|
louis@uptimekuma.louislam.net. |
||||
|
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. |
||||
|
|
||||
|
## Enforcement Guidelines |
||||
|
|
||||
|
Community leaders will follow these Community Impact Guidelines in determining |
||||
|
the consequences for any action they deem in violation of this Code of Conduct: |
||||
|
|
||||
|
### 1. Correction |
||||
|
|
||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed |
||||
|
unprofessional or unwelcome in the community. |
||||
|
|
||||
|
**Consequence**: A private, written warning from community leaders, providing |
||||
|
clarity around the nature of the violation and an explanation of why the |
||||
|
behavior was inappropriate. A public apology may be requested. |
||||
|
|
||||
|
### 2. Warning |
||||
|
|
||||
|
**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. |
||||
|
|
||||
|
### 3. Temporary Ban |
||||
|
|
||||
|
**Community Impact**: A serious violation of community standards, including |
||||
|
sustained inappropriate behavior. |
||||
|
|
||||
|
**Consequence**: A temporary ban from any sort of interaction or public |
||||
|
communication with the community for a specified period of time. No public or |
||||
|
private interaction with the people involved, including unsolicited interaction |
||||
|
with those enforcing the Code of Conduct, is allowed during this period. |
||||
|
Violating these terms may lead to a permanent ban. |
||||
|
|
||||
|
### 4. Permanent Ban |
||||
|
|
||||
|
**Community Impact**: Demonstrating a pattern of violation of community |
||||
|
standards, including sustained inappropriate behavior, harassment of an |
||||
|
individual, or aggression toward or disparagement of classes of individuals. |
||||
|
|
||||
|
**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. |
||||
|
|
||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct |
||||
|
enforcement ladder](https://github.com/mozilla/diversity). |
||||
|
|
||||
|
[homepage]: https://www.contributor-covenant.org |
||||
|
|
||||
|
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. |
@ -0,0 +1,152 @@ |
|||||
|
# Project Info |
||||
|
|
||||
|
First of all, thank you everyone who made pull requests for Uptime Kuma, I never thought GitHub Community can be that nice! And also because of this, I also never thought other people actually read my code and edit my code. It is not structed and commented so well, lol. Sorry about that. |
||||
|
|
||||
|
The project was created with vite.js (vue3). Then I created a sub-directory called "server" for server part. Both frontend and backend share the same package.json. |
||||
|
|
||||
|
The frontend code build into "dist" directory. The server uses "dist" as root. This is how production is working. |
||||
|
|
||||
|
# Can I create a pull request for Uptime Kuma? |
||||
|
|
||||
|
Generally, if the pull request is working fine and it do not affect any existing logic, workflow and perfomance, I will merge to the master branch once it is tested. |
||||
|
|
||||
|
If you are not sure, feel free to create an empty pull request draft first. |
||||
|
|
||||
|
## Pull Request Examples |
||||
|
|
||||
|
### ✅ High - Medium Priority |
||||
|
|
||||
|
- Add a new notification |
||||
|
- Add a chart |
||||
|
- Fix a bug |
||||
|
|
||||
|
### *️⃣ Requires one more reviewer |
||||
|
|
||||
|
I do not have such knowledge to test it. |
||||
|
|
||||
|
- Add k8s supports |
||||
|
|
||||
|
### *️⃣ Low Priority |
||||
|
|
||||
|
It changed my current workflow and require further studies. |
||||
|
|
||||
|
- Change my release approach |
||||
|
|
||||
|
### ❌ Won't Merge |
||||
|
|
||||
|
- Duplicated pull request |
||||
|
- Buggy |
||||
|
- Existing logic is completely modified or deleted |
||||
|
- A function that is completely out of scope |
||||
|
|
||||
|
# Project Styles |
||||
|
|
||||
|
I personally do not like something need to learn so much and need to config so much before you can finally start the app. |
||||
|
|
||||
|
For example, recently, because I am not a python expert, I spent a 2 hours to resolve all problems in order to install and use the Apprise cli. Apprise requires so many hidden requirements, I have to figure out myself how to solve the problems by Google search for my OS. That is painful. I do not want Uptime Kuma to be like this way, so: |
||||
|
|
||||
|
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort to get it run |
||||
|
- Single container for Docker users, no very complex docker-composer file. Just map the volume and expose the port, then good to go |
||||
|
- All settings in frontend. |
||||
|
- Easy to use |
||||
|
|
||||
|
# Coding Styles |
||||
|
|
||||
|
- Follow .editorconfig |
||||
|
- Follow eslint |
||||
|
|
||||
|
## Name convention |
||||
|
|
||||
|
- Javascript/Typescript: camelCaseType |
||||
|
- SQLite: underscore_type |
||||
|
- CSS/SCSS: dash-type |
||||
|
|
||||
|
# Tools |
||||
|
- Node.js >= 14 |
||||
|
- Git |
||||
|
- IDE that supports .editorconfig and eslint (I am using Intellji Idea) |
||||
|
- A SQLite tool (I am using SQLite Expert Personal) |
||||
|
|
||||
|
# Install dependencies |
||||
|
|
||||
|
```bash |
||||
|
npm install --dev |
||||
|
``` |
||||
|
|
||||
|
For npm@7, you need --legacy-peer-deps |
||||
|
|
||||
|
``` |
||||
|
npm install --legacy-peer-deps --dev |
||||
|
``` |
||||
|
|
||||
|
# Backend Dev |
||||
|
|
||||
|
```bash |
||||
|
npm run start-server |
||||
|
|
||||
|
# Or |
||||
|
|
||||
|
node server/server.js |
||||
|
``` |
||||
|
|
||||
|
It binds to 0.0.0.0:3001 by default. |
||||
|
|
||||
|
|
||||
|
## Backend Details |
||||
|
|
||||
|
It is mainly a socket.io app + express.js. |
||||
|
|
||||
|
express.js is just used for serving the frontend built files (index.html, .js and .css etc.) |
||||
|
|
||||
|
# Frontend Dev |
||||
|
|
||||
|
Start frontend dev server. Hot-reload enabled in this way. It binds to 0.0.0.0:3000. |
||||
|
|
||||
|
```bash |
||||
|
npm run dev |
||||
|
``` |
||||
|
|
||||
|
PS: You can ignore those scss warnings, those warnings are from Bootstrap that I cannot fix. |
||||
|
|
||||
|
You can use Vue Devtool Chrome extension for debugging. |
||||
|
|
||||
|
After the frontend server started. It cannot connect to the websocket server even you have started the server. You need to tell the frontend that is a dev env by running this in DevTool console and refresh: |
||||
|
|
||||
|
```javascript |
||||
|
localStorage.dev = "dev"; |
||||
|
``` |
||||
|
|
||||
|
So that the frontend will try to connect websocket server in 3001. |
||||
|
|
||||
|
Alternately, you can specific NODE_ENV to "development". |
||||
|
|
||||
|
|
||||
|
## Build the frontend |
||||
|
|
||||
|
```bash |
||||
|
npm run build |
||||
|
``` |
||||
|
|
||||
|
## Frontend Details |
||||
|
|
||||
|
Uptime Kuma Frontend is a single page application (SPA). Most paths are handled by Vue Router. |
||||
|
|
||||
|
The router in "src/main.js" |
||||
|
|
||||
|
As you can see, most data in frontend is stored in root level, even though you changed the current router to any other pages. |
||||
|
|
||||
|
The data and socket logic in "src/mixins/socket.js" |
||||
|
|
||||
|
# Database Migration |
||||
|
|
||||
|
1. create `patch{num}.sql` in `./db/` |
||||
|
1. update `latestVersion` in `./server/database.js` |
||||
|
|
||||
|
# Unit Test |
||||
|
|
||||
|
Yes, no unit test for now. I know it is very important, but at the same time my spare time is very limited. I want to implement my ideas first. I will go back to this in some points. |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,14 @@ |
|||||
|
# Security Policy |
||||
|
|
||||
|
## Supported Versions |
||||
|
|
||||
|
Use this section to tell people about which versions of your project are |
||||
|
currently being supported with security updates. |
||||
|
|
||||
|
| Version | Supported | |
||||
|
| ------- | ------------------ | |
||||
|
| 1.x.x | :white_check_mark: | |
||||
|
|
||||
|
## Reporting a Vulnerability |
||||
|
|
||||
|
https://github.com/louislam/uptime-kuma/issues |
@ -1,7 +0,0 @@ |
|||||
{ |
|
||||
"name": "Uptime Kuma", |
|
||||
"description": "A fancy self-hosted monitoring tool", |
|
||||
"repository": "https://github.com/louislam/uptime-kuma", |
|
||||
"logo": "https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png", |
|
||||
"keywords": ["node", "express", "socket-io", "uptime-kuma", "uptime"] |
|
||||
} |
|
Binary file not shown.
@ -0,0 +1,10 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
-- For sendHeartbeatList |
||||
|
CREATE INDEX monitor_time_index ON heartbeat (monitor_id, time); |
||||
|
|
||||
|
-- For sendImportantHeartbeatList |
||||
|
CREATE INDEX monitor_important_time_index ON heartbeat (monitor_id, important,time); |
||||
|
|
||||
|
COMMIT; |
@ -0,0 +1,22 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
-- Generated by Intellij IDEA |
||||
|
create table setting_dg_tmp |
||||
|
( |
||||
|
id INTEGER |
||||
|
primary key autoincrement, |
||||
|
key VARCHAR(200) not null |
||||
|
unique, |
||||
|
value TEXT, |
||||
|
type VARCHAR(20) |
||||
|
); |
||||
|
|
||||
|
insert into setting_dg_tmp(id, key, value, type) select id, key, value, type from setting; |
||||
|
|
||||
|
drop table setting; |
||||
|
|
||||
|
alter table setting_dg_tmp rename to setting; |
||||
|
|
||||
|
|
||||
|
COMMIT; |
@ -0,0 +1,70 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
PRAGMA foreign_keys = off; |
||||
|
|
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
create table monitor_dg_tmp ( |
||||
|
id INTEGER not null primary key autoincrement, |
||||
|
name VARCHAR(150), |
||||
|
active BOOLEAN default 1 not null, |
||||
|
user_id INTEGER references user on update cascade on delete |
||||
|
set |
||||
|
null, |
||||
|
interval INTEGER default 20 not null, |
||||
|
url TEXT, |
||||
|
type VARCHAR(20), |
||||
|
weight INTEGER default 2000, |
||||
|
hostname VARCHAR(255), |
||||
|
port INTEGER, |
||||
|
created_date DATETIME default (DATETIME('now')) not null, |
||||
|
keyword VARCHAR(255), |
||||
|
maxretries INTEGER NOT NULL DEFAULT 0, |
||||
|
ignore_tls BOOLEAN default 0 not null, |
||||
|
upside_down BOOLEAN default 0 not null |
||||
|
); |
||||
|
|
||||
|
insert into |
||||
|
monitor_dg_tmp( |
||||
|
id, |
||||
|
name, |
||||
|
active, |
||||
|
user_id, |
||||
|
interval, |
||||
|
url, |
||||
|
type, |
||||
|
weight, |
||||
|
hostname, |
||||
|
port, |
||||
|
keyword, |
||||
|
maxretries, |
||||
|
ignore_tls, |
||||
|
upside_down |
||||
|
) |
||||
|
select |
||||
|
id, |
||||
|
name, |
||||
|
active, |
||||
|
user_id, |
||||
|
interval, |
||||
|
url, |
||||
|
type, |
||||
|
weight, |
||||
|
hostname, |
||||
|
port, |
||||
|
keyword, |
||||
|
maxretries, |
||||
|
ignore_tls, |
||||
|
upside_down |
||||
|
from |
||||
|
monitor; |
||||
|
|
||||
|
drop table monitor; |
||||
|
|
||||
|
alter table |
||||
|
monitor_dg_tmp rename to monitor; |
||||
|
|
||||
|
create index user_id on monitor (user_id); |
||||
|
|
||||
|
COMMIT; |
||||
|
|
||||
|
PRAGMA foreign_keys = on; |
@ -0,0 +1,74 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
PRAGMA foreign_keys = off; |
||||
|
|
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
create table monitor_dg_tmp ( |
||||
|
id INTEGER not null primary key autoincrement, |
||||
|
name VARCHAR(150), |
||||
|
active BOOLEAN default 1 not null, |
||||
|
user_id INTEGER references user on update cascade on delete |
||||
|
set |
||||
|
null, |
||||
|
interval INTEGER default 20 not null, |
||||
|
url TEXT, |
||||
|
type VARCHAR(20), |
||||
|
weight INTEGER default 2000, |
||||
|
hostname VARCHAR(255), |
||||
|
port INTEGER, |
||||
|
created_date DATETIME default (DATETIME('now')) not null, |
||||
|
keyword VARCHAR(255), |
||||
|
maxretries INTEGER NOT NULL DEFAULT 0, |
||||
|
ignore_tls BOOLEAN default 0 not null, |
||||
|
upside_down BOOLEAN default 0 not null, |
||||
|
maxredirects INTEGER default 10 not null, |
||||
|
accepted_statuscodes_json TEXT default '["200-299"]' not null |
||||
|
); |
||||
|
|
||||
|
insert into |
||||
|
monitor_dg_tmp( |
||||
|
id, |
||||
|
name, |
||||
|
active, |
||||
|
user_id, |
||||
|
interval, |
||||
|
url, |
||||
|
type, |
||||
|
weight, |
||||
|
hostname, |
||||
|
port, |
||||
|
created_date, |
||||
|
keyword, |
||||
|
maxretries, |
||||
|
ignore_tls, |
||||
|
upside_down |
||||
|
) |
||||
|
select |
||||
|
id, |
||||
|
name, |
||||
|
active, |
||||
|
user_id, |
||||
|
interval, |
||||
|
url, |
||||
|
type, |
||||
|
weight, |
||||
|
hostname, |
||||
|
port, |
||||
|
created_date, |
||||
|
keyword, |
||||
|
maxretries, |
||||
|
ignore_tls, |
||||
|
upside_down |
||||
|
from |
||||
|
monitor; |
||||
|
|
||||
|
drop table monitor; |
||||
|
|
||||
|
alter table |
||||
|
monitor_dg_tmp rename to monitor; |
||||
|
|
||||
|
create index user_id on monitor (user_id); |
||||
|
|
||||
|
COMMIT; |
||||
|
|
||||
|
PRAGMA foreign_keys = on; |
@ -0,0 +1,10 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
ALTER TABLE monitor |
||||
|
ADD dns_resolve_type VARCHAR(5); |
||||
|
|
||||
|
ALTER TABLE monitor |
||||
|
ADD dns_resolve_server VARCHAR(255); |
||||
|
|
||||
|
COMMIT; |
@ -0,0 +1,7 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
ALTER TABLE monitor |
||||
|
ADD dns_last_result VARCHAR(255); |
||||
|
|
||||
|
COMMIT; |
@ -0,0 +1,7 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
ALTER TABLE notification |
||||
|
ADD is_default BOOLEAN default 0 NOT NULL; |
||||
|
|
||||
|
COMMIT; |
@ -1,42 +1,37 @@ |
|||||
# DON'T UPDATE TO alpine3.13, 1.14, see #41. |
# DON'T UPDATE TO node:14-bullseye-slim, see #372. |
||||
FROM node:14-alpine3.12 AS release |
FROM node:14-buster-slim AS build |
||||
WORKDIR /app |
WORKDIR /app |
||||
|
|
||||
# split the sqlite install here, so that it can caches the arm prebuilt |
# split the sqlite install here, so that it can caches the arm prebuilt |
||||
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \ |
# do not modify it, since we don't want to re-compile the arm prebuilt again |
||||
|
RUN apt update && \ |
||||
|
apt --yes install python3 python3-pip python3-dev git g++ make && \ |
||||
ln -s /usr/bin/python3 /usr/bin/python && \ |
ln -s /usr/bin/python3 /usr/bin/python && \ |
||||
npm install sqlite3@5.0.2 bcrypt@5.0.1 && \ |
npm install mapbox/node-sqlite3#593c9d --build-from-source |
||||
apk del .build-deps |
|
||||
|
|
||||
# Touching above code may causes sqlite3 re-compile again, painful slow. |
COPY . . |
||||
|
RUN npm install --legacy-peer-deps && npm run build && npm prune --production |
||||
|
|
||||
# Install apprise |
FROM node:14-bullseye-slim AS release |
||||
# Hate pip!!! I never run pip install successfully in first run for anything in my life without Google :/ |
WORKDIR /app |
||||
# Compilation Fail 1 => Google Search "alpine ffi.h" => Add libffi-dev |
|
||||
# Compilation Fail 2 => Google Search "alpine cargo" => Add cargo |
|
||||
# Compilation Fail 3 => Google Search "alpine opensslv.h" => Add openssl-dev |
|
||||
# Compilation Fail 4 => Google Search "alpine opensslv.h" again => Change to libressl-dev musl-dev |
|
||||
# Compilation Fail 5 => Google Search "ERROR: libressl3.3-libtls-3.3.3-r0: trying to overwrite usr/lib/libtls.so.20 owned by libretls-3.3.3-r0." again => Change back to openssl-dev with musl-dev |
|
||||
# Runtime Error => ModuleNotFoundError: No module named 'six' => pip3 install six |
|
||||
# Runtime Error 2 => ModuleNotFoundError: No module named 'six' => apk add py3-six |
|
||||
ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 |
|
||||
RUN apk add --no-cache python3 py3-pip py3-six cargo |
|
||||
RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev python3-dev && \ |
|
||||
pip3 install apprise && \ |
|
||||
pip3 cache purge && \ |
|
||||
rm -rf /root/.cache && \ |
|
||||
apk del .build-deps |
|
||||
RUN apprise --version |
|
||||
|
|
||||
# New things add here |
# Install Apprise, |
||||
|
# add sqlite3 cli for debugging in the future |
||||
|
# iputils-ping for ping |
||||
|
RUN apt update && \ |
||||
|
apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ |
||||
|
sqlite3 \ |
||||
|
iputils-ping && \ |
||||
|
pip3 --no-cache-dir install apprise && \ |
||||
|
rm -rf /var/lib/apt/lists/* |
||||
|
|
||||
COPY . . |
# Copy app files from build layer |
||||
RUN npm install && npm run build && npm prune |
COPY --from=build /app /app |
||||
|
|
||||
EXPOSE 3001 |
EXPOSE 3001 |
||||
VOLUME ["/app/data"] |
VOLUME ["/app/data"] |
||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js |
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js |
||||
CMD ["npm", "run", "start-server"] |
CMD ["node", "server/server.js"] |
||||
|
|
||||
FROM release AS nightly |
FROM release AS nightly |
||||
RUN npm run mark-as-nightly |
RUN npm run mark-as-nightly |
||||
|
@ -0,0 +1,33 @@ |
|||||
|
# DON'T UPDATE TO alpine3.13, 1.14, see #41. |
||||
|
FROM node:14-alpine3.12 AS build |
||||
|
WORKDIR /app |
||||
|
|
||||
|
# split the sqlite install here, so that it can caches the arm prebuilt |
||||
|
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \ |
||||
|
ln -s /usr/bin/python3 /usr/bin/python && \ |
||||
|
npm install mapbox/node-sqlite3#593c9d && \ |
||||
|
apk del .build-deps && \ |
||||
|
rm -f /usr/bin/python |
||||
|
|
||||
|
COPY . . |
||||
|
RUN npm install --legacy-peer-deps && npm run build && npm prune --production |
||||
|
|
||||
|
|
||||
|
FROM node:14-alpine3.12 AS release |
||||
|
WORKDIR /app |
||||
|
|
||||
|
# Install apprise |
||||
|
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ |
||||
|
pip3 --no-cache-dir install apprise && \ |
||||
|
rm -rf /root/.cache |
||||
|
|
||||
|
# Copy app files from build layer |
||||
|
COPY --from=build /app /app |
||||
|
|
||||
|
EXPOSE 3001 |
||||
|
VOLUME ["/app/data"] |
||||
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js |
||||
|
CMD ["node", "server/server.js"] |
||||
|
|
||||
|
FROM release AS nightly |
||||
|
RUN npm run mark-as-nightly |
@ -0,0 +1,2 @@ |
|||||
|
# Must enable File Sharing in Docker Desktop |
||||
|
docker run -it --rm -v ${pwd}:/app louislam/batsh /usr/bin/batsh bash --output ./install.sh ./extra/install.batsh |
@ -1,19 +1,34 @@ |
|||||
var http = require("http"); |
/* |
||||
var options = { |
* This script should be run after a period of time (180s), because the server may need some time to prepare. |
||||
host: "localhost", |
*/ |
||||
port: "3001", |
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; |
||||
timeout: 2000, |
|
||||
|
let client; |
||||
|
|
||||
|
if (process.env.SSL_KEY && process.env.SSL_CERT) { |
||||
|
client = require("https"); |
||||
|
} else { |
||||
|
client = require("http"); |
||||
|
} |
||||
|
|
||||
|
let options = { |
||||
|
host: process.env.HOST || "127.0.0.1", |
||||
|
port: parseInt(process.env.PORT) || 3001, |
||||
|
timeout: 28 * 1000, |
||||
}; |
}; |
||||
var request = http.request(options, (res) => { |
|
||||
console.log(`STATUS: ${res.statusCode}`); |
let request = client.request(options, (res) => { |
||||
if (res.statusCode == 200) { |
console.log(`Health Check OK [Res Code: ${res.statusCode}]`); |
||||
process.exit(0); |
if (res.statusCode === 200) { |
||||
} else { |
process.exit(0); |
||||
process.exit(1); |
} else { |
||||
} |
process.exit(1); |
||||
|
} |
||||
}); |
}); |
||||
|
|
||||
request.on("error", function (err) { |
request.on("error", function (err) { |
||||
console.log("ERROR"); |
console.error("Health Check ERROR"); |
||||
process.exit(1); |
process.exit(1); |
||||
}); |
}); |
||||
|
|
||||
request.end(); |
request.end(); |
||||
|
@ -0,0 +1,245 @@ |
|||||
|
// install.sh is generated by ./extra/install.batsh, do not modify it directly. |
||||
|
// "npm run compile-install-script" to compile install.sh |
||||
|
// The command is working on Windows PowerShell and Docker for Windows only. |
||||
|
|
||||
|
|
||||
|
// curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh |
||||
|
println("====================="); |
||||
|
println("Uptime Kuma Installer"); |
||||
|
println("====================="); |
||||
|
println("Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian"); |
||||
|
println("---------------------------------------"); |
||||
|
println("This script is designed for Linux and basic usage."); |
||||
|
println("For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation"); |
||||
|
println("---------------------------------------"); |
||||
|
println(""); |
||||
|
println("Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2"); |
||||
|
println("Docker - Install Uptime Kuma Docker container"); |
||||
|
println(""); |
||||
|
|
||||
|
if ("$1" != "") { |
||||
|
type = "$1"; |
||||
|
} else { |
||||
|
call("read", "-p", "Which installation method do you prefer? [DOCKER/local]: ", "type"); |
||||
|
} |
||||
|
|
||||
|
defaultPort = "3001"; |
||||
|
|
||||
|
function checkNode() { |
||||
|
bash("nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')"); |
||||
|
println("Node Version: " ++ nodeVersion); |
||||
|
|
||||
|
if (nodeVersion < "12") { |
||||
|
println("Error: Required Node.js 14"); |
||||
|
call("exit", "1"); |
||||
|
} |
||||
|
|
||||
|
if (nodeVersion == "12") { |
||||
|
println("Warning: NodeJS " ++ nodeVersion ++ " is not tested."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function deb() { |
||||
|
bash("nodeCheck=$(node -v)"); |
||||
|
bash("apt --yes update"); |
||||
|
|
||||
|
if (nodeCheck != "") { |
||||
|
checkNode(); |
||||
|
} else { |
||||
|
|
||||
|
// Old nodejs binary name is "nodejs" |
||||
|
bash("check=$(nodejs --version)"); |
||||
|
if (check != "") { |
||||
|
println("Error: 'node' command is not found, but 'nodejs' command is found. Your NodeJS should be too old."); |
||||
|
bash("exit 1"); |
||||
|
} |
||||
|
|
||||
|
bash("curlCheck=$(curl --version)"); |
||||
|
if (curlCheck == "") { |
||||
|
println("Installing Curl"); |
||||
|
bash("apt --yes install curl"); |
||||
|
} |
||||
|
|
||||
|
println("Installing Node.js 14"); |
||||
|
bash("curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt"); |
||||
|
bash("apt --yes install nodejs"); |
||||
|
bash("node -v"); |
||||
|
|
||||
|
bash("nodeCheckAgain=$(node -v)"); |
||||
|
|
||||
|
if (nodeCheckAgain == "") { |
||||
|
println("Error during Node.js installation"); |
||||
|
bash("exit 1"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bash("check=$(git --version)"); |
||||
|
if (check == "") { |
||||
|
println("Installing Git"); |
||||
|
bash("apt --yes install git"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (type == "local") { |
||||
|
defaultInstallPath = "/opt/uptime-kuma"; |
||||
|
|
||||
|
if (exists("/etc/redhat-release")) { |
||||
|
os = call("cat", "/etc/redhat-release"); |
||||
|
distribution = "rhel"; |
||||
|
|
||||
|
} else if (exists("/etc/issue")) { |
||||
|
bash("os=$(head -n1 /etc/issue | cut -f 1 -d ' ')"); |
||||
|
if (os == "Ubuntu") { |
||||
|
distribution = "ubuntu"; |
||||
|
} |
||||
|
if (os == "Debian") { |
||||
|
distribution = "debian"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bash("arch=$(uname -i)"); |
||||
|
|
||||
|
println("Your OS: " ++ os); |
||||
|
println("Distribution: " ++ distribution); |
||||
|
println("Arch: " ++ arch); |
||||
|
|
||||
|
if ("$3" != "") { |
||||
|
port = "$3"; |
||||
|
} else { |
||||
|
call("read", "-p", "Listening Port [$defaultPort]: ", "port"); |
||||
|
|
||||
|
if (port == "") { |
||||
|
port = defaultPort; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ("$2" != "") { |
||||
|
installPath = "$2"; |
||||
|
} else { |
||||
|
call("read", "-p", "Installation Path [$defaultInstallPath]: ", "installPath"); |
||||
|
|
||||
|
if (installPath == "") { |
||||
|
installPath = defaultInstallPath; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// CentOS |
||||
|
if (distribution == "rhel") { |
||||
|
bash("nodeCheck=$(node -v)"); |
||||
|
|
||||
|
if (nodeCheck != "") { |
||||
|
checkNode(); |
||||
|
} else { |
||||
|
|
||||
|
bash("curlCheck=$(curl --version)"); |
||||
|
if (curlCheck == "") { |
||||
|
println("Installing Curl"); |
||||
|
bash("yum -y -q install curl"); |
||||
|
} |
||||
|
|
||||
|
println("Installing Node.js 14"); |
||||
|
bash("curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt"); |
||||
|
bash("yum install -y -q nodejs"); |
||||
|
bash("node -v"); |
||||
|
|
||||
|
bash("nodeCheckAgain=$(node -v)"); |
||||
|
|
||||
|
if (nodeCheckAgain == "") { |
||||
|
println("Error during Node.js installation"); |
||||
|
bash("exit 1"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bash("check=$(git --version)"); |
||||
|
if (check == "") { |
||||
|
println("Installing Git"); |
||||
|
bash("yum -y -q install git"); |
||||
|
} |
||||
|
|
||||
|
// Ubuntu |
||||
|
} else if (distribution == "ubuntu") { |
||||
|
deb(); |
||||
|
|
||||
|
// Debian |
||||
|
} else if (distribution == "debian") { |
||||
|
deb(); |
||||
|
|
||||
|
} else { |
||||
|
// Unknown distribution |
||||
|
error = 0; |
||||
|
|
||||
|
bash("check=$(git --version)"); |
||||
|
if (check == "") { |
||||
|
error = 1; |
||||
|
println("Error: git is missing"); |
||||
|
} |
||||
|
|
||||
|
bash("check=$(node -v)"); |
||||
|
if (check == "") { |
||||
|
error = 1; |
||||
|
println("Error: node is missing"); |
||||
|
} |
||||
|
|
||||
|
if (error > 0) { |
||||
|
println("Please install above missing software"); |
||||
|
bash("exit 1"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bash("check=$(pm2 --version)"); |
||||
|
if (check == "") { |
||||
|
println("Installing PM2"); |
||||
|
bash("npm install pm2 -g"); |
||||
|
bash("pm2 startup"); |
||||
|
} |
||||
|
|
||||
|
bash("mkdir -p $installPath"); |
||||
|
bash("cd $installPath"); |
||||
|
bash("git clone https://github.com/louislam/uptime-kuma.git ."); |
||||
|
bash("npm run setup"); |
||||
|
|
||||
|
bash("pm2 start server/server.js --name uptime-kuma -- --port=$port"); |
||||
|
|
||||
|
} else { |
||||
|
defaultVolume = "uptime-kuma"; |
||||
|
|
||||
|
bash("check=$(docker -v)"); |
||||
|
if (check == "") { |
||||
|
println("Error: docker is not found!"); |
||||
|
bash("exit 1"); |
||||
|
} |
||||
|
|
||||
|
bash("check=$(docker info)"); |
||||
|
|
||||
|
bash("if [[ \"$check\" == *\"Is the docker daemon running\"* ]]; then |
||||
|
\"echo\" \"Error: docker is not running\" |
||||
|
\"exit\" \"1\" |
||||
|
fi"); |
||||
|
|
||||
|
if ("$3" != "") { |
||||
|
port = "$3"; |
||||
|
} else { |
||||
|
call("read", "-p", "Expose Port [$defaultPort]: ", "port"); |
||||
|
|
||||
|
if (port == "") { |
||||
|
port = defaultPort; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ("$2" != "") { |
||||
|
volume = "$2"; |
||||
|
} else { |
||||
|
call("read", "-p", "Volume Name [$defaultVolume]: ", "volume"); |
||||
|
|
||||
|
if (volume == "") { |
||||
|
volume = defaultVolume; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
println("Port: $port"); |
||||
|
println("Volume: $volume"); |
||||
|
bash("docker volume create $volume"); |
||||
|
bash("docker run -d --restart=always -p $port:3001 -v $volume:/app/data --name uptime-kuma louislam/uptime-kuma:1"); |
||||
|
} |
||||
|
|
||||
|
println("http://localhost:$port"); |
@ -0,0 +1,59 @@ |
|||||
|
console.log("== Uptime Kuma Reset Password Tool =="); |
||||
|
|
||||
|
console.log("Loading the database"); |
||||
|
|
||||
|
const Database = require("../server/database"); |
||||
|
const { R } = require("redbean-node"); |
||||
|
const readline = require("readline"); |
||||
|
const { initJWTSecret } = require("../server/util-server"); |
||||
|
const rl = readline.createInterface({ |
||||
|
input: process.stdin, |
||||
|
output: process.stdout |
||||
|
}); |
||||
|
|
||||
|
(async () => { |
||||
|
await Database.connect(); |
||||
|
|
||||
|
try { |
||||
|
const user = await R.findOne("user"); |
||||
|
|
||||
|
if (! user) { |
||||
|
throw new Error("user not found, have you installed?"); |
||||
|
} |
||||
|
|
||||
|
console.log("Found user: " + user.username); |
||||
|
|
||||
|
while (true) { |
||||
|
let password = await question("New Password: "); |
||||
|
let confirmPassword = await question("Confirm New Password: "); |
||||
|
|
||||
|
if (password === confirmPassword) { |
||||
|
await user.resetPassword(password); |
||||
|
|
||||
|
// Reset all sessions by reset jwt secret
|
||||
|
await initJWTSecret(); |
||||
|
|
||||
|
rl.close(); |
||||
|
break; |
||||
|
} else { |
||||
|
console.log("Passwords do not match, please try again."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
console.log("Password reset successfully."); |
||||
|
} catch (e) { |
||||
|
console.error("Error: " + e.message); |
||||
|
} |
||||
|
|
||||
|
await Database.close(); |
||||
|
|
||||
|
console.log("Finished. You should restart the Uptime Kuma server.") |
||||
|
})(); |
||||
|
|
||||
|
function question(question) { |
||||
|
return new Promise((resolve) => { |
||||
|
rl.question(question, (answer) => { |
||||
|
resolve(answer); |
||||
|
}) |
||||
|
}); |
||||
|
} |
@ -0,0 +1,144 @@ |
|||||
|
/* |
||||
|
* Simple DNS Server |
||||
|
* For testing DNS monitoring type, dev only |
||||
|
*/ |
||||
|
const dns2 = require("dns2"); |
||||
|
|
||||
|
const { Packet } = dns2; |
||||
|
|
||||
|
const server = dns2.createServer({ |
||||
|
udp: true |
||||
|
}); |
||||
|
|
||||
|
server.on("request", (request, send, rinfo) => { |
||||
|
for (let question of request.questions) { |
||||
|
console.log(question.name, type(question.type), question.class); |
||||
|
|
||||
|
const response = Packet.createResponseFromRequest(request); |
||||
|
|
||||
|
if (question.name === "existing.com") { |
||||
|
|
||||
|
if (question.type === Packet.TYPE.A) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
address: "1.2.3.4" |
||||
|
}); |
||||
|
} if (question.type === Packet.TYPE.AAAA) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
address: "fe80::::1234:5678:abcd:ef00", |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.CNAME) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
domain: "cname1.existing.com", |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.MX) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
exchange: "mx1.existing.com", |
||||
|
priority: 5 |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.NS) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
ns: "ns1.existing.com", |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.SOA) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
primary: "existing.com", |
||||
|
admin: "admin@existing.com", |
||||
|
serial: 2021082701, |
||||
|
refresh: 300, |
||||
|
retry: 3, |
||||
|
expiration: 10, |
||||
|
minimum: 10, |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.SRV) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
priority: 5, |
||||
|
weight: 5, |
||||
|
port: 8080, |
||||
|
target: "srv1.existing.com", |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.TXT) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
data: "#v=spf1 include:_spf.existing.com ~all", |
||||
|
}); |
||||
|
} else if (question.type === Packet.TYPE.CAA) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
flags: 0, |
||||
|
tag: "issue", |
||||
|
value: "ca.existing.com", |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
if (question.name === "4.3.2.1.in-addr.arpa") { |
||||
|
if (question.type === Packet.TYPE.PTR) { |
||||
|
response.answers.push({ |
||||
|
name: question.name, |
||||
|
type: question.type, |
||||
|
class: question.class, |
||||
|
ttl: 300, |
||||
|
domain: "ptr1.existing.com", |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
send(response); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
server.on("listening", () => { |
||||
|
console.log("Listening"); |
||||
|
console.log(server.addresses()); |
||||
|
}); |
||||
|
|
||||
|
server.on("close", () => { |
||||
|
console.log("server closed"); |
||||
|
}); |
||||
|
|
||||
|
server.listen({ |
||||
|
udp: 5300 |
||||
|
}); |
||||
|
|
||||
|
function type(code) { |
||||
|
for (let name in Packet.TYPE) { |
||||
|
if (Packet.TYPE[name] === code) { |
||||
|
return name; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
package-lock.json |
||||
|
test.js |
||||
|
languages/ |
@ -0,0 +1,78 @@ |
|||||
|
// Need to use es6 to read language files
|
||||
|
|
||||
|
import fs from "fs"; |
||||
|
import path from "path"; |
||||
|
import util from "util"; |
||||
|
|
||||
|
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
|
||||
|
/** |
||||
|
* Look ma, it's cp -R. |
||||
|
* @param {string} src The path to the thing to copy. |
||||
|
* @param {string} dest The path to the new copy. |
||||
|
*/ |
||||
|
const copyRecursiveSync = function (src, dest) { |
||||
|
let exists = fs.existsSync(src); |
||||
|
let stats = exists && fs.statSync(src); |
||||
|
let isDirectory = exists && stats.isDirectory(); |
||||
|
if (isDirectory) { |
||||
|
fs.mkdirSync(dest); |
||||
|
fs.readdirSync(src).forEach(function (childItemName) { |
||||
|
copyRecursiveSync(path.join(src, childItemName), |
||||
|
path.join(dest, childItemName)); |
||||
|
}); |
||||
|
} else { |
||||
|
fs.copyFileSync(src, dest); |
||||
|
} |
||||
|
}; |
||||
|
console.log(process.argv) |
||||
|
const baseLangCode = process.argv[2] || "zh-HK"; |
||||
|
console.log("Base Lang: " + baseLangCode); |
||||
|
fs.rmdirSync("./languages", { recursive: true }); |
||||
|
copyRecursiveSync("../../src/languages", "./languages"); |
||||
|
|
||||
|
const en = (await import("./languages/en.js")).default; |
||||
|
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default; |
||||
|
const files = fs.readdirSync("./languages"); |
||||
|
console.log(files); |
||||
|
for (const file of files) { |
||||
|
if (file.endsWith(".js")) { |
||||
|
console.log("Processing " + file); |
||||
|
const lang = await import("./languages/" + file); |
||||
|
|
||||
|
let obj; |
||||
|
|
||||
|
if (lang.default) { |
||||
|
console.log("is js module"); |
||||
|
obj = lang.default; |
||||
|
} else { |
||||
|
console.log("empty file"); |
||||
|
obj = { |
||||
|
languageName: "<Your Language name in your language (not in English)>" |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// En first
|
||||
|
for (const key in en) { |
||||
|
if (! obj[key]) { |
||||
|
obj[key] = en[key]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Base second
|
||||
|
for (const key in baseLang) { |
||||
|
if (! obj[key]) { |
||||
|
obj[key] = key; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const code = "export default " + util.inspect(obj, { |
||||
|
depth: null, |
||||
|
}); |
||||
|
|
||||
|
fs.writeFileSync(`../../src/languages/${file}`, code); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fs.rmdirSync("./languages", { recursive: true }); |
||||
|
console.log("Done, fix the format by eslint now"); |
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"name": "update-language-files", |
||||
|
"type": "module", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "index.js", |
||||
|
"scripts": { |
||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||
|
}, |
||||
|
"author": "", |
||||
|
"license": "ISC" |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
const pkg = require("../package.json"); |
||||
|
const fs = require("fs"); |
||||
|
const child_process = require("child_process"); |
||||
|
const util = require("../src/util"); |
||||
|
|
||||
|
util.polyfill(); |
||||
|
|
||||
|
const oldVersion = pkg.version; |
||||
|
const newVersion = process.argv[2]; |
||||
|
|
||||
|
console.log("Old Version: " + oldVersion); |
||||
|
console.log("New Version: " + newVersion); |
||||
|
|
||||
|
if (! newVersion) { |
||||
|
console.error("invalid version"); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
|
||||
|
const exists = tagExists(newVersion); |
||||
|
|
||||
|
if (! exists) { |
||||
|
// Process package.json
|
||||
|
pkg.version = newVersion; |
||||
|
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion); |
||||
|
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion); |
||||
|
pkg.scripts["build-docker-alpine"] = pkg.scripts["build-docker-alpine"].replaceAll(oldVersion, newVersion); |
||||
|
pkg.scripts["build-docker-debian"] = pkg.scripts["build-docker-debian"].replaceAll(oldVersion, newVersion); |
||||
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); |
||||
|
|
||||
|
commit(newVersion); |
||||
|
tag(newVersion); |
||||
|
} else { |
||||
|
console.log("version exists") |
||||
|
} |
||||
|
|
||||
|
function commit(version) { |
||||
|
let msg = "update to " + version; |
||||
|
|
||||
|
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]); |
||||
|
let stdout = res.stdout.toString().trim(); |
||||
|
console.log(stdout) |
||||
|
|
||||
|
if (stdout.includes("no changes added to commit")) { |
||||
|
throw new Error("commit error") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function tag(version) { |
||||
|
let res = child_process.spawnSync("git", ["tag", version]); |
||||
|
console.log(res.stdout.toString().trim()) |
||||
|
} |
||||
|
|
||||
|
function tagExists(version) { |
||||
|
if (! version) { |
||||
|
throw new Error("invalid version"); |
||||
|
} |
||||
|
|
||||
|
let res = child_process.spawnSync("git", ["tag", "-l", version]); |
||||
|
|
||||
|
return res.stdout.toString().trim() === version; |
||||
|
} |
@ -1,39 +0,0 @@ |
|||||
/** |
|
||||
* String.prototype.replaceAll() polyfill |
|
||||
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
|
||||
* @author Chris Ferdinandi |
|
||||
* @license MIT |
|
||||
*/ |
|
||||
if (!String.prototype.replaceAll) { |
|
||||
String.prototype.replaceAll = function(str, newStr){ |
|
||||
|
|
||||
// If a regex pattern
|
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') { |
|
||||
return this.replace(str, newStr); |
|
||||
} |
|
||||
|
|
||||
// If a string
|
|
||||
return this.replace(new RegExp(str, 'g'), newStr); |
|
||||
|
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
const pkg = require('../package.json'); |
|
||||
const fs = require("fs"); |
|
||||
const oldVersion = pkg.version |
|
||||
const newVersion = process.argv[2] |
|
||||
|
|
||||
console.log("Old Version: " + oldVersion) |
|
||||
console.log("New Version: " + newVersion) |
|
||||
|
|
||||
if (newVersion) { |
|
||||
// Process package.json
|
|
||||
pkg.version = newVersion |
|
||||
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion) |
|
||||
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion) |
|
||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") |
|
||||
|
|
||||
// Process README.md
|
|
||||
fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion)) |
|
||||
} |
|
||||
|
|
@ -1,16 +1,16 @@ |
|||||
<!DOCTYPE html> |
<!DOCTYPE html> |
||||
<html lang="en"> |
<html lang="en"> |
||||
<head> |
<head> |
||||
<meta charset="UTF-8" /> |
<meta charset="UTF-8" /> |
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" /> |
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<meta name="theme-color" content="#5cdd8b" /> |
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> |
||||
|
<link rel="icon" type="image/svg+xml" href="/icon.svg" /> |
||||
|
<meta name="theme-color" id="theme-color" content="" /> |
||||
<meta name="description" content="Uptime Kuma monitoring tool" /> |
<meta name="description" content="Uptime Kuma monitoring tool" /> |
||||
<title>Uptime Kuma</title> |
<title>Uptime Kuma</title> |
||||
</head> |
</head> |
||||
<body> |
<body> |
||||
<div id="app"></div> |
<div id="app"></div> |
||||
<script type="module" src="/src/main.js"></script> |
<script type="module" src="/src/main.js"></script> |
||||
</body> |
</body> |
||||
</html> |
</html> |
||||
|
@ -0,0 +1,203 @@ |
|||||
|
# install.sh is generated by ./extra/install.batsh, do not modify it directly. |
||||
|
# "npm run compile-install-script" to compile install.sh |
||||
|
# The command is working on Windows PowerShell and Docker for Windows only. |
||||
|
# curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh |
||||
|
"echo" "-e" "=====================" |
||||
|
"echo" "-e" "Uptime Kuma Installer" |
||||
|
"echo" "-e" "=====================" |
||||
|
"echo" "-e" "Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian" |
||||
|
"echo" "-e" "---------------------------------------" |
||||
|
"echo" "-e" "This script is designed for Linux and basic usage." |
||||
|
"echo" "-e" "For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation" |
||||
|
"echo" "-e" "---------------------------------------" |
||||
|
"echo" "-e" "" |
||||
|
"echo" "-e" "Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2" |
||||
|
"echo" "-e" "Docker - Install Uptime Kuma Docker container" |
||||
|
"echo" "-e" "" |
||||
|
if [ "$1" != "" ]; then |
||||
|
type="$1" |
||||
|
else |
||||
|
"read" "-p" "Which installation method do you prefer? [DOCKER/local]: " "type" |
||||
|
fi |
||||
|
defaultPort="3001" |
||||
|
function checkNode { |
||||
|
local _0 |
||||
|
nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])') |
||||
|
"echo" "-e" "Node Version: ""$nodeVersion" |
||||
|
_0="12" |
||||
|
if [ $(($nodeVersion < $_0)) == 1 ]; then |
||||
|
"echo" "-e" "Error: Required Node.js 14" |
||||
|
"exit" "1" |
||||
|
fi |
||||
|
if [ "$nodeVersion" == "12" ]; then |
||||
|
"echo" "-e" "Warning: NodeJS ""$nodeVersion"" is not tested." |
||||
|
fi |
||||
|
} |
||||
|
function deb { |
||||
|
nodeCheck=$(node -v) |
||||
|
apt --yes update |
||||
|
if [ "$nodeCheck" != "" ]; then |
||||
|
"checkNode" |
||||
|
else |
||||
|
# Old nodejs binary name is "nodejs" |
||||
|
check=$(nodejs --version) |
||||
|
if [ "$check" != "" ]; then |
||||
|
"echo" "-e" "Error: 'node' command is not found, but 'nodejs' command is found. Your NodeJS should be too old." |
||||
|
exit 1 |
||||
|
fi |
||||
|
curlCheck=$(curl --version) |
||||
|
if [ "$curlCheck" == "" ]; then |
||||
|
"echo" "-e" "Installing Curl" |
||||
|
apt --yes install curl |
||||
|
fi |
||||
|
"echo" "-e" "Installing Node.js 14" |
||||
|
curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt |
||||
|
apt --yes install nodejs |
||||
|
node -v |
||||
|
nodeCheckAgain=$(node -v) |
||||
|
if [ "$nodeCheckAgain" == "" ]; then |
||||
|
"echo" "-e" "Error during Node.js installation" |
||||
|
exit 1 |
||||
|
fi |
||||
|
fi |
||||
|
check=$(git --version) |
||||
|
if [ "$check" == "" ]; then |
||||
|
"echo" "-e" "Installing Git" |
||||
|
apt --yes install git |
||||
|
fi |
||||
|
} |
||||
|
if [ "$type" == "local" ]; then |
||||
|
defaultInstallPath="/opt/uptime-kuma" |
||||
|
if [ -e "/etc/redhat-release" ]; then |
||||
|
os=$("cat" "/etc/redhat-release") |
||||
|
distribution="rhel" |
||||
|
else |
||||
|
if [ -e "/etc/issue" ]; then |
||||
|
os=$(head -n1 /etc/issue | cut -f 1 -d ' ') |
||||
|
if [ "$os" == "Ubuntu" ]; then |
||||
|
distribution="ubuntu" |
||||
|
fi |
||||
|
if [ "$os" == "Debian" ]; then |
||||
|
distribution="debian" |
||||
|
fi |
||||
|
fi |
||||
|
fi |
||||
|
arch=$(uname -i) |
||||
|
"echo" "-e" "Your OS: ""$os" |
||||
|
"echo" "-e" "Distribution: ""$distribution" |
||||
|
"echo" "-e" "Arch: ""$arch" |
||||
|
if [ "$3" != "" ]; then |
||||
|
port="$3" |
||||
|
else |
||||
|
"read" "-p" "Listening Port [$defaultPort]: " "port" |
||||
|
if [ "$port" == "" ]; then |
||||
|
port="$defaultPort" |
||||
|
fi |
||||
|
fi |
||||
|
if [ "$2" != "" ]; then |
||||
|
installPath="$2" |
||||
|
else |
||||
|
"read" "-p" "Installation Path [$defaultInstallPath]: " "installPath" |
||||
|
if [ "$installPath" == "" ]; then |
||||
|
installPath="$defaultInstallPath" |
||||
|
fi |
||||
|
fi |
||||
|
# CentOS |
||||
|
if [ "$distribution" == "rhel" ]; then |
||||
|
nodeCheck=$(node -v) |
||||
|
if [ "$nodeCheck" != "" ]; then |
||||
|
"checkNode" |
||||
|
else |
||||
|
curlCheck=$(curl --version) |
||||
|
if [ "$curlCheck" == "" ]; then |
||||
|
"echo" "-e" "Installing Curl" |
||||
|
yum -y -q install curl |
||||
|
fi |
||||
|
"echo" "-e" "Installing Node.js 14" |
||||
|
curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt |
||||
|
yum install -y -q nodejs |
||||
|
node -v |
||||
|
nodeCheckAgain=$(node -v) |
||||
|
if [ "$nodeCheckAgain" == "" ]; then |
||||
|
"echo" "-e" "Error during Node.js installation" |
||||
|
exit 1 |
||||
|
fi |
||||
|
fi |
||||
|
check=$(git --version) |
||||
|
if [ "$check" == "" ]; then |
||||
|
"echo" "-e" "Installing Git" |
||||
|
yum -y -q install git |
||||
|
fi |
||||
|
# Ubuntu |
||||
|
else |
||||
|
if [ "$distribution" == "ubuntu" ]; then |
||||
|
"deb" |
||||
|
# Debian |
||||
|
else |
||||
|
if [ "$distribution" == "debian" ]; then |
||||
|
"deb" |
||||
|
else |
||||
|
# Unknown distribution |
||||
|
error=$((0)) |
||||
|
check=$(git --version) |
||||
|
if [ "$check" == "" ]; then |
||||
|
error=$((1)) |
||||
|
"echo" "-e" "Error: git is missing" |
||||
|
fi |
||||
|
check=$(node -v) |
||||
|
if [ "$check" == "" ]; then |
||||
|
error=$((1)) |
||||
|
"echo" "-e" "Error: node is missing" |
||||
|
fi |
||||
|
if [ $(($error > 0)) == 1 ]; then |
||||
|
"echo" "-e" "Please install above missing software" |
||||
|
exit 1 |
||||
|
fi |
||||
|
fi |
||||
|
fi |
||||
|
fi |
||||
|
check=$(pm2 --version) |
||||
|
if [ "$check" == "" ]; then |
||||
|
"echo" "-e" "Installing PM2" |
||||
|
npm install pm2 -g |
||||
|
pm2 startup |
||||
|
fi |
||||
|
mkdir -p $installPath |
||||
|
cd $installPath |
||||
|
git clone https://github.com/louislam/uptime-kuma.git . |
||||
|
npm run setup |
||||
|
pm2 start server/server.js --name uptime-kuma -- --port=$port |
||||
|
else |
||||
|
defaultVolume="uptime-kuma" |
||||
|
check=$(docker -v) |
||||
|
if [ "$check" == "" ]; then |
||||
|
"echo" "-e" "Error: docker is not found!" |
||||
|
exit 1 |
||||
|
fi |
||||
|
check=$(docker info) |
||||
|
if [[ "$check" == *"Is the docker daemon running"* ]]; then |
||||
|
"echo" "Error: docker is not running" |
||||
|
"exit" "1" |
||||
|
fi |
||||
|
if [ "$3" != "" ]; then |
||||
|
port="$3" |
||||
|
else |
||||
|
"read" "-p" "Expose Port [$defaultPort]: " "port" |
||||
|
if [ "$port" == "" ]; then |
||||
|
port="$defaultPort" |
||||
|
fi |
||||
|
fi |
||||
|
if [ "$2" != "" ]; then |
||||
|
volume="$2" |
||||
|
else |
||||
|
"read" "-p" "Volume Name [$defaultVolume]: " "volume" |
||||
|
if [ "$volume" == "" ]; then |
||||
|
volume="$defaultVolume" |
||||
|
fi |
||||
|
fi |
||||
|
"echo" "-e" "Port: $port" |
||||
|
"echo" "-e" "Volume: $volume" |
||||
|
docker volume create $volume |
||||
|
docker run -d --restart=always -p $port:3001 -v $volume:/app/data --name uptime-kuma louislam/uptime-kuma:1 |
||||
|
fi |
||||
|
"echo" "-e" "http://localhost:$port" |
@ -0,0 +1,31 @@ |
|||||
|
# Uptime-Kuma K8s Deployment |
||||
|
|
||||
|
⚠ Warning: K8s deployment is provided by contributors. I have no experience with K8s and I can't fix error in the future. I only test Docker and Node.js. Use at your own risk. |
||||
|
|
||||
|
## How does it work? |
||||
|
|
||||
|
Kustomize is a tool which builds a complete deployment file for all config elements. |
||||
|
You can edit the files in the ```uptime-kuma``` folder except the ```kustomization.yml``` until you know what you're doing. |
||||
|
If you want to choose another namespace you can edit the ```kustomization.yml``` in the ```kubernetes```-Folder and change the ```namespace: uptime-kuma``` to something you like. |
||||
|
|
||||
|
It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service |
||||
|
|
||||
|
## What do i have to edit? |
||||
|
You have to edit the ```ingressroute.yml``` to your needs. |
||||
|
This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.github.io/ingress-nginx/) in combination with the [cert-manager](https://cert-manager.io/). |
||||
|
|
||||
|
- host |
||||
|
- secrets and secret names |
||||
|
- (Cluster)Issuer (optional) |
||||
|
- the Version in the Deployment-File |
||||
|
- update: |
||||
|
- change to newer version and run the above commands, it will update the pods one after another |
||||
|
|
||||
|
## How To use: |
||||
|
|
||||
|
- install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/) |
||||
|
- Edit files mentioned above to your needs |
||||
|
- run ```kustomize build > apply.yml``` |
||||
|
- run ```kubectl apply -f apply.yml``` |
||||
|
|
||||
|
Now you should see some k8s magic and Uptime-Kuma should be available at the specified address. |
@ -0,0 +1,10 @@ |
|||||
|
namespace: uptime-kuma |
||||
|
namePrefix: uptime-kuma- |
||||
|
|
||||
|
commonLabels: |
||||
|
app: uptime-kuma |
||||
|
|
||||
|
bases: |
||||
|
- uptime-kuma |
||||
|
|
||||
|
|
@ -0,0 +1,45 @@ |
|||||
|
apiVersion: apps/v1 |
||||
|
kind: Deployment |
||||
|
metadata: |
||||
|
labels: |
||||
|
component: uptime-kuma |
||||
|
name: deployment |
||||
|
spec: |
||||
|
selector: |
||||
|
matchLabels: |
||||
|
component: uptime-kuma |
||||
|
replicas: 1 |
||||
|
strategy: |
||||
|
type: Recreate |
||||
|
|
||||
|
template: |
||||
|
metadata: |
||||
|
labels: |
||||
|
component: uptime-kuma |
||||
|
spec: |
||||
|
containers: |
||||
|
- name: app |
||||
|
image: louislam/uptime-kuma:1 |
||||
|
ports: |
||||
|
- containerPort: 3001 |
||||
|
volumeMounts: |
||||
|
- mountPath: /app/data |
||||
|
name: storage |
||||
|
livenessProbe: |
||||
|
exec: |
||||
|
command: |
||||
|
- node |
||||
|
- extra/healthcheck.js |
||||
|
initialDelaySeconds: 180 |
||||
|
periodSeconds: 60 |
||||
|
timeoutSeconds: 30 |
||||
|
readinessProbe: |
||||
|
httpGet: |
||||
|
path: / |
||||
|
port: 3001 |
||||
|
scheme: HTTP |
||||
|
|
||||
|
volumes: |
||||
|
- name: storage |
||||
|
persistentVolumeClaim: |
||||
|
claimName: pvc |
@ -0,0 +1,39 @@ |
|||||
|
apiVersion: networking.k8s.io/v1 |
||||
|
kind: Ingress |
||||
|
metadata: |
||||
|
annotations: |
||||
|
kubernetes.io/ingress.class: nginx |
||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod |
||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" |
||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" |
||||
|
nginx.ingress.kubernetes.io/server-snippets: | |
||||
|
location / { |
||||
|
proxy_set_header Upgrade $http_upgrade; |
||||
|
proxy_http_version 1.1; |
||||
|
proxy_set_header X-Forwarded-Host $http_host; |
||||
|
proxy_set_header X-Forwarded-Proto $scheme; |
||||
|
proxy_set_header X-Forwarded-For $remote_addr; |
||||
|
proxy_set_header Host $host; |
||||
|
proxy_set_header Connection "upgrade"; |
||||
|
proxy_set_header X-Real-IP $remote_addr; |
||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
|
proxy_set_header Upgrade $http_upgrade; |
||||
|
proxy_cache_bypass $http_upgrade; |
||||
|
} |
||||
|
name: ingress |
||||
|
spec: |
||||
|
tls: |
||||
|
- hosts: |
||||
|
- example.com |
||||
|
secretName: example-com-tls |
||||
|
rules: |
||||
|
- host: example.com |
||||
|
http: |
||||
|
paths: |
||||
|
- path: / |
||||
|
pathType: Prefix |
||||
|
backend: |
||||
|
service: |
||||
|
name: service |
||||
|
port: |
||||
|
number: 3001 |
@ -0,0 +1,5 @@ |
|||||
|
resources: |
||||
|
- deployment.yml |
||||
|
- service.yml |
||||
|
- ingressroute.yml |
||||
|
- pvc.yml |
@ -0,0 +1,10 @@ |
|||||
|
apiVersion: v1 |
||||
|
kind: PersistentVolumeClaim |
||||
|
metadata: |
||||
|
name: pvc |
||||
|
spec: |
||||
|
accessModes: |
||||
|
- ReadWriteOnce |
||||
|
resources: |
||||
|
requests: |
||||
|
storage: 4Gi |
@ -0,0 +1,13 @@ |
|||||
|
apiVersion: v1 |
||||
|
kind: Service |
||||
|
metadata: |
||||
|
name: service |
||||
|
spec: |
||||
|
selector: |
||||
|
component: uptime-kuma |
||||
|
type: ClusterIP |
||||
|
ports: |
||||
|
- name: http |
||||
|
port: 3001 |
||||
|
targetPort: 3001 |
||||
|
protocol: TCP |
File diff suppressed because it is too large
After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 15 KiB |
@ -1,3 +0,0 @@ |
|||||
# https://www.robotstxt.org/robotstxt.html |
|
||||
User-agent: * |
|
||||
Disallow: |
|
@ -0,0 +1,44 @@ |
|||||
|
const { setSetting } = require("./util-server"); |
||||
|
const axios = require("axios"); |
||||
|
const { isDev } = require("../src/util"); |
||||
|
|
||||
|
exports.version = require("../package.json").version; |
||||
|
exports.latestVersion = null; |
||||
|
|
||||
|
let interval; |
||||
|
|
||||
|
exports.startInterval = () => { |
||||
|
let check = async () => { |
||||
|
try { |
||||
|
const res = await axios.get("https://raw.githubusercontent.com/louislam/uptime-kuma/master/package.json"); |
||||
|
|
||||
|
if (typeof res.data === "string") { |
||||
|
res.data = JSON.parse(res.data); |
||||
|
} |
||||
|
|
||||
|
// For debug
|
||||
|
if (process.env.TEST_CHECK_VERSION === "1") { |
||||
|
res.data.version = "1000.0.0" |
||||
|
} |
||||
|
|
||||
|
exports.latestVersion = res.data.version; |
||||
|
console.log("Latest Version: " + exports.latestVersion); |
||||
|
} catch (_) { } |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
check(); |
||||
|
interval = setInterval(check, 3600 * 1000 * 48); |
||||
|
}; |
||||
|
|
||||
|
exports.enableCheckUpdate = async (value) => { |
||||
|
await setSetting("checkUpdate", value); |
||||
|
|
||||
|
clearInterval(interval); |
||||
|
|
||||
|
if (value) { |
||||
|
exports.startInterval(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
exports.socket = null; |
@ -0,0 +1,88 @@ |
|||||
|
/* |
||||
|
* For Client Socket |
||||
|
*/ |
||||
|
const { TimeLogger } = require("../src/util"); |
||||
|
const { R } = require("redbean-node"); |
||||
|
const { io } = require("./server"); |
||||
|
|
||||
|
async function sendNotificationList(socket) { |
||||
|
const timeLogger = new TimeLogger(); |
||||
|
|
||||
|
let result = []; |
||||
|
let list = await R.find("notification", " user_id = ? ", [ |
||||
|
socket.userID, |
||||
|
]); |
||||
|
|
||||
|
for (let bean of list) { |
||||
|
result.push(bean.export()) |
||||
|
} |
||||
|
|
||||
|
io.to(socket.userID).emit("notificationList", result) |
||||
|
|
||||
|
timeLogger.print("Send Notification List"); |
||||
|
|
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Send Heartbeat History list to socket |
||||
|
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only |
||||
|
* @param overwrite Overwrite client-side's heartbeat list |
||||
|
*/ |
||||
|
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { |
||||
|
const timeLogger = new TimeLogger(); |
||||
|
|
||||
|
let list = await R.getAll(` |
||||
|
SELECT * FROM heartbeat |
||||
|
WHERE monitor_id = ? |
||||
|
ORDER BY time DESC |
||||
|
LIMIT 100 |
||||
|
`, [
|
||||
|
monitorID, |
||||
|
]) |
||||
|
|
||||
|
let result = list.reverse(); |
||||
|
|
||||
|
if (toUser) { |
||||
|
io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); |
||||
|
} else { |
||||
|
socket.emit("heartbeatList", monitorID, result, overwrite); |
||||
|
} |
||||
|
|
||||
|
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Important Heart beat list (aka event list) |
||||
|
* @param socket |
||||
|
* @param monitorID |
||||
|
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only |
||||
|
* @param overwrite Overwrite client-side's heartbeat list |
||||
|
*/ |
||||
|
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { |
||||
|
const timeLogger = new TimeLogger(); |
||||
|
|
||||
|
let list = await R.find("heartbeat", ` |
||||
|
monitor_id = ? |
||||
|
AND important = 1 |
||||
|
ORDER BY time DESC |
||||
|
LIMIT 500 |
||||
|
`, [
|
||||
|
monitorID, |
||||
|
]) |
||||
|
|
||||
|
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); |
||||
|
|
||||
|
if (toUser) { |
||||
|
io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite); |
||||
|
} else { |
||||
|
socket.emit("importantHeartbeatList", monitorID, list, overwrite); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
sendNotificationList, |
||||
|
sendImportantHeartbeatList, |
||||
|
sendHeartbeatList, |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
const { BeanModel } = require("redbean-node/dist/bean-model"); |
||||
|
const passwordHash = require("../password-hash"); |
||||
|
const { R } = require("redbean-node"); |
||||
|
|
||||
|
class User extends BeanModel { |
||||
|
|
||||
|
/** |
||||
|
* Direct execute, no need R.store() |
||||
|
* @param newPassword |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
async resetPassword(newPassword) { |
||||
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ |
||||
|
passwordHash.generate(newPassword), |
||||
|
this.id |
||||
|
]); |
||||
|
this.password = newPassword; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = User; |
@ -0,0 +1,26 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const child_process = require("child_process"); |
||||
|
|
||||
|
class Apprise extends NotificationProvider { |
||||
|
|
||||
|
name = "apprise"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) |
||||
|
|
||||
|
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; |
||||
|
|
||||
|
if (output) { |
||||
|
|
||||
|
if (! output.includes("ERROR")) { |
||||
|
return "Sent Successfully"; |
||||
|
} |
||||
|
|
||||
|
throw new Error(output) |
||||
|
} else { |
||||
|
return "No output from apprise"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Apprise; |
@ -0,0 +1,105 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
const { DOWN, UP } = require("../../src/util"); |
||||
|
|
||||
|
class Discord extends NotificationProvider { |
||||
|
|
||||
|
name = "discord"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
const discordDisplayName = notification.discordUsername || "Uptime Kuma"; |
||||
|
|
||||
|
// If heartbeatJSON is null, assume we're testing.
|
||||
|
if (heartbeatJSON == null) { |
||||
|
let discordtestdata = { |
||||
|
username: discordDisplayName, |
||||
|
content: msg, |
||||
|
} |
||||
|
await axios.post(notification.discordWebhookUrl, discordtestdata) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
let url; |
||||
|
|
||||
|
if (monitorJSON["type"] === "port") { |
||||
|
url = monitorJSON["hostname"]; |
||||
|
if (monitorJSON["port"]) { |
||||
|
url += ":" + monitorJSON["port"]; |
||||
|
} |
||||
|
|
||||
|
} else { |
||||
|
url = monitorJSON["url"]; |
||||
|
} |
||||
|
|
||||
|
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
|
if (heartbeatJSON["status"] == DOWN) { |
||||
|
let discorddowndata = { |
||||
|
username: discordDisplayName, |
||||
|
embeds: [{ |
||||
|
title: "❌ Your service " + monitorJSON["name"] + " went down. ❌", |
||||
|
color: 16711680, |
||||
|
timestamp: heartbeatJSON["time"], |
||||
|
fields: [ |
||||
|
{ |
||||
|
name: "Service Name", |
||||
|
value: monitorJSON["name"], |
||||
|
}, |
||||
|
{ |
||||
|
name: "Service URL", |
||||
|
value: url, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Time (UTC)", |
||||
|
value: heartbeatJSON["time"], |
||||
|
}, |
||||
|
{ |
||||
|
name: "Error", |
||||
|
value: heartbeatJSON["msg"], |
||||
|
}, |
||||
|
], |
||||
|
}], |
||||
|
} |
||||
|
await axios.post(notification.discordWebhookUrl, discorddowndata) |
||||
|
return okMsg; |
||||
|
|
||||
|
} else if (heartbeatJSON["status"] == UP) { |
||||
|
let discordupdata = { |
||||
|
username: discordDisplayName, |
||||
|
embeds: [{ |
||||
|
title: "✅ Your service " + monitorJSON["name"] + " is up! ✅", |
||||
|
color: 65280, |
||||
|
timestamp: heartbeatJSON["time"], |
||||
|
fields: [ |
||||
|
{ |
||||
|
name: "Service Name", |
||||
|
value: monitorJSON["name"], |
||||
|
}, |
||||
|
{ |
||||
|
name: "Service URL", |
||||
|
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Time (UTC)", |
||||
|
value: heartbeatJSON["time"], |
||||
|
}, |
||||
|
{ |
||||
|
name: "Ping", |
||||
|
value: heartbeatJSON["ping"] + "ms", |
||||
|
}, |
||||
|
], |
||||
|
}], |
||||
|
} |
||||
|
await axios.post(notification.discordWebhookUrl, discordupdata) |
||||
|
return okMsg; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
module.exports = Discord; |
@ -0,0 +1,28 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Gotify extends NotificationProvider { |
||||
|
|
||||
|
name = "gotify"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
try { |
||||
|
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { |
||||
|
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); |
||||
|
} |
||||
|
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { |
||||
|
"message": msg, |
||||
|
"priority": notification.gotifyPriority || 8, |
||||
|
"title": "Uptime-Kuma", |
||||
|
}) |
||||
|
|
||||
|
return okMsg; |
||||
|
|
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Gotify; |
@ -0,0 +1,60 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
const { DOWN, UP } = require("../../src/util"); |
||||
|
|
||||
|
class Line extends NotificationProvider { |
||||
|
|
||||
|
name = "line"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
try { |
||||
|
let lineAPIUrl = "https://api.line.me/v2/bot/message/push"; |
||||
|
let config = { |
||||
|
headers: { |
||||
|
"Content-Type": "application/json", |
||||
|
"Authorization": "Bearer " + notification.lineChannelAccessToken |
||||
|
} |
||||
|
}; |
||||
|
if (heartbeatJSON == null) { |
||||
|
let testMessage = { |
||||
|
"to": notification.lineUserID, |
||||
|
"messages": [ |
||||
|
{ |
||||
|
"type": "text", |
||||
|
"text": "Test Successful!" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
await axios.post(lineAPIUrl, testMessage, config) |
||||
|
} else if (heartbeatJSON["status"] == DOWN) { |
||||
|
let downMessage = { |
||||
|
"to": notification.lineUserID, |
||||
|
"messages": [ |
||||
|
{ |
||||
|
"type": "text", |
||||
|
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
await axios.post(lineAPIUrl, downMessage, config) |
||||
|
} else if (heartbeatJSON["status"] == UP) { |
||||
|
let upMessage = { |
||||
|
"to": notification.lineUserID, |
||||
|
"messages": [ |
||||
|
{ |
||||
|
"type": "text", |
||||
|
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
await axios.post(lineAPIUrl, upMessage, config) |
||||
|
} |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Line; |
@ -0,0 +1,48 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
const { DOWN, UP } = require("../../src/util"); |
||||
|
|
||||
|
class LunaSea extends NotificationProvider { |
||||
|
|
||||
|
name = "lunasea"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice |
||||
|
|
||||
|
try { |
||||
|
if (heartbeatJSON == null) { |
||||
|
let testdata = { |
||||
|
"title": "Uptime Kuma Alert", |
||||
|
"body": "Testing Successful.", |
||||
|
} |
||||
|
await axios.post(lunaseadevice, testdata) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
if (heartbeatJSON["status"] == DOWN) { |
||||
|
let downdata = { |
||||
|
"title": "UptimeKuma Alert: " + monitorJSON["name"], |
||||
|
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |
||||
|
} |
||||
|
await axios.post(lunaseadevice, downdata) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
if (heartbeatJSON["status"] == UP) { |
||||
|
let updata = { |
||||
|
"title": "UptimeKuma Alert: " + monitorJSON["name"], |
||||
|
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |
||||
|
} |
||||
|
await axios.post(lunaseadevice, updata) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = LunaSea; |
@ -0,0 +1,123 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
const { DOWN, UP } = require("../../src/util"); |
||||
|
|
||||
|
class Mattermost extends NotificationProvider { |
||||
|
|
||||
|
name = "mattermost"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
try { |
||||
|
const mattermostUserName = notification.mattermostusername || "Uptime Kuma"; |
||||
|
// If heartbeatJSON is null, assume we're testing.
|
||||
|
if (heartbeatJSON == null) { |
||||
|
let mattermostTestData = { |
||||
|
username: mattermostUserName, |
||||
|
text: msg, |
||||
|
} |
||||
|
await axios.post(notification.mattermostWebhookUrl, mattermostTestData) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
const mattermostChannel = notification.mattermostchannel; |
||||
|
const mattermostIconEmoji = notification.mattermosticonemo; |
||||
|
const mattermostIconUrl = notification.mattermosticonurl; |
||||
|
|
||||
|
if (heartbeatJSON["status"] == DOWN) { |
||||
|
let mattermostdowndata = { |
||||
|
username: mattermostUserName, |
||||
|
text: "Uptime Kuma Alert", |
||||
|
channel: mattermostChannel, |
||||
|
icon_emoji: mattermostIconEmoji, |
||||
|
icon_url: mattermostIconUrl, |
||||
|
attachments: [ |
||||
|
{ |
||||
|
fallback: |
||||
|
"Your " + |
||||
|
monitorJSON["name"] + |
||||
|
" service went down.", |
||||
|
color: "#FF0000", |
||||
|
title: |
||||
|
"❌ " + |
||||
|
monitorJSON["name"] + |
||||
|
" service went down. ❌", |
||||
|
title_link: monitorJSON["url"], |
||||
|
fields: [ |
||||
|
{ |
||||
|
short: true, |
||||
|
title: "Service Name", |
||||
|
value: monitorJSON["name"], |
||||
|
}, |
||||
|
{ |
||||
|
short: true, |
||||
|
title: "Time (UTC)", |
||||
|
value: heartbeatJSON["time"], |
||||
|
}, |
||||
|
{ |
||||
|
short: false, |
||||
|
title: "Error", |
||||
|
value: heartbeatJSON["msg"], |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
await axios.post( |
||||
|
notification.mattermostWebhookUrl, |
||||
|
mattermostdowndata |
||||
|
); |
||||
|
return okMsg; |
||||
|
} else if (heartbeatJSON["status"] == UP) { |
||||
|
let mattermostupdata = { |
||||
|
username: mattermostUserName, |
||||
|
text: "Uptime Kuma Alert", |
||||
|
channel: mattermostChannel, |
||||
|
icon_emoji: mattermostIconEmoji, |
||||
|
icon_url: mattermostIconUrl, |
||||
|
attachments: [ |
||||
|
{ |
||||
|
fallback: |
||||
|
"Your " + |
||||
|
monitorJSON["name"] + |
||||
|
" service went up!", |
||||
|
color: "#32CD32", |
||||
|
title: |
||||
|
"✅ " + |
||||
|
monitorJSON["name"] + |
||||
|
" service went up! ✅", |
||||
|
title_link: monitorJSON["url"], |
||||
|
fields: [ |
||||
|
{ |
||||
|
short: true, |
||||
|
title: "Service Name", |
||||
|
value: monitorJSON["name"], |
||||
|
}, |
||||
|
{ |
||||
|
short: true, |
||||
|
title: "Time (UTC)", |
||||
|
value: heartbeatJSON["time"], |
||||
|
}, |
||||
|
{ |
||||
|
short: false, |
||||
|
title: "Ping", |
||||
|
value: heartbeatJSON["ping"] + "ms", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
await axios.post( |
||||
|
notification.mattermostWebhookUrl, |
||||
|
mattermostupdata |
||||
|
); |
||||
|
return okMsg; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Mattermost; |
@ -0,0 +1,36 @@ |
|||||
|
class NotificationProvider { |
||||
|
|
||||
|
/** |
||||
|
* Notification Provider Name |
||||
|
* @type string |
||||
|
*/ |
||||
|
name = undefined; |
||||
|
|
||||
|
/** |
||||
|
* @param notification : BeanModel |
||||
|
* @param msg : string General Message |
||||
|
* @param monitorJSON : object Monitor details (For Up/Down only) |
||||
|
* @param heartbeatJSON : object Heartbeat details (For Up/Down only) |
||||
|
* @returns {Promise<string>} Return Successful Message |
||||
|
* Throw Error with fail msg |
||||
|
*/ |
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
throw new Error("Have to override Notification.send(...)"); |
||||
|
} |
||||
|
|
||||
|
throwGeneralAxiosError(error) { |
||||
|
let msg = "Error: " + error + " "; |
||||
|
|
||||
|
if (error.response && error.response.data) { |
||||
|
if (typeof error.response.data === "string") { |
||||
|
msg += error.response.data; |
||||
|
} else { |
||||
|
msg += JSON.stringify(error.response.data) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new Error(msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = NotificationProvider; |
@ -0,0 +1,40 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Octopush extends NotificationProvider { |
||||
|
|
||||
|
name = "octopush"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
let config = { |
||||
|
headers: { |
||||
|
"api-key": notification.octopushAPIKey, |
||||
|
"api-login": notification.octopushLogin, |
||||
|
"cache-control": "no-cache" |
||||
|
} |
||||
|
}; |
||||
|
let data = { |
||||
|
"recipients": [ |
||||
|
{ |
||||
|
"phone_number": notification.octopushPhoneNumber |
||||
|
} |
||||
|
], |
||||
|
//octopush not supporting non ascii char
|
||||
|
"text": msg.replace(/[^\x00-\x7F]/g, ""), |
||||
|
"type": notification.octopushSMSType, |
||||
|
"purpose": "alert", |
||||
|
"sender": notification.octopushSenderName |
||||
|
}; |
||||
|
|
||||
|
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Octopush; |
@ -0,0 +1,50 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
const { DOWN, UP } = require("../../src/util"); |
||||
|
|
||||
|
class Pushbullet extends NotificationProvider { |
||||
|
|
||||
|
name = "pushbullet"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
let pushbulletUrl = "https://api.pushbullet.com/v2/pushes"; |
||||
|
let config = { |
||||
|
headers: { |
||||
|
"Access-Token": notification.pushbulletAccessToken, |
||||
|
"Content-Type": "application/json" |
||||
|
} |
||||
|
}; |
||||
|
if (heartbeatJSON == null) { |
||||
|
let testdata = { |
||||
|
"type": "note", |
||||
|
"title": "Uptime Kuma Alert", |
||||
|
"body": "Testing Successful.", |
||||
|
} |
||||
|
await axios.post(pushbulletUrl, testdata, config) |
||||
|
} else if (heartbeatJSON["status"] == DOWN) { |
||||
|
let downdata = { |
||||
|
"type": "note", |
||||
|
"title": "UptimeKuma Alert: " + monitorJSON["name"], |
||||
|
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |
||||
|
} |
||||
|
await axios.post(pushbulletUrl, downdata, config) |
||||
|
} else if (heartbeatJSON["status"] == UP) { |
||||
|
let updata = { |
||||
|
"type": "note", |
||||
|
"title": "UptimeKuma Alert: " + monitorJSON["name"], |
||||
|
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], |
||||
|
} |
||||
|
await axios.post(pushbulletUrl, updata, config) |
||||
|
} |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Pushbullet; |
@ -0,0 +1,49 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Pushover extends NotificationProvider { |
||||
|
|
||||
|
name = "pushover"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
let pushoverlink = "https://api.pushover.net/1/messages.json" |
||||
|
|
||||
|
try { |
||||
|
if (heartbeatJSON == null) { |
||||
|
let data = { |
||||
|
"message": "<b>Uptime Kuma Pushover testing successful.</b>", |
||||
|
"user": notification.pushoveruserkey, |
||||
|
"token": notification.pushoverapptoken, |
||||
|
"sound": notification.pushoversounds, |
||||
|
"priority": notification.pushoverpriority, |
||||
|
"title": notification.pushovertitle, |
||||
|
"retry": "30", |
||||
|
"expire": "3600", |
||||
|
"html": 1, |
||||
|
} |
||||
|
await axios.post(pushoverlink, data) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
let data = { |
||||
|
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"], |
||||
|
"user": notification.pushoveruserkey, |
||||
|
"token": notification.pushoverapptoken, |
||||
|
"sound": notification.pushoversounds, |
||||
|
"priority": notification.pushoverpriority, |
||||
|
"title": notification.pushovertitle, |
||||
|
"retry": "30", |
||||
|
"expire": "3600", |
||||
|
"html": 1, |
||||
|
} |
||||
|
await axios.post(pushoverlink, data) |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Pushover; |
@ -0,0 +1,30 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Pushy extends NotificationProvider { |
||||
|
|
||||
|
name = "pushy"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, { |
||||
|
"to": notification.pushyToken, |
||||
|
"data": { |
||||
|
"message": "Uptime-Kuma" |
||||
|
}, |
||||
|
"notification": { |
||||
|
"body": msg, |
||||
|
"badge": 1, |
||||
|
"sound": "ping.aiff" |
||||
|
} |
||||
|
}) |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Pushy; |
@ -0,0 +1,46 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class RocketChat extends NotificationProvider { |
||||
|
|
||||
|
name = "rocket.chat"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
try { |
||||
|
if (heartbeatJSON == null) { |
||||
|
let data = { |
||||
|
"text": "Uptime Kuma Rocket.chat testing successful.", |
||||
|
"channel": notification.rocketchannel, |
||||
|
"username": notification.rocketusername, |
||||
|
"icon_emoji": notification.rocketiconemo, |
||||
|
} |
||||
|
await axios.post(notification.rocketwebhookURL, data) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
const time = heartbeatJSON["time"]; |
||||
|
let data = { |
||||
|
"text": "Uptime Kuma Alert", |
||||
|
"channel": notification.rocketchannel, |
||||
|
"username": notification.rocketusername, |
||||
|
"icon_emoji": notification.rocketiconemo, |
||||
|
"attachments": [ |
||||
|
{ |
||||
|
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time, |
||||
|
"title_link": notification.rocketbutton, |
||||
|
"text": "*Message*\n" + msg, |
||||
|
"color": "#32cd32" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
await axios.post(notification.rocketwebhookURL, data) |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = RocketChat; |
@ -0,0 +1,27 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Signal extends NotificationProvider { |
||||
|
|
||||
|
name = "signal"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
let data = { |
||||
|
"message": msg, |
||||
|
"number": notification.signalNumber, |
||||
|
"recipients": notification.signalRecipients.replace(/\s/g, "").split(","), |
||||
|
}; |
||||
|
let config = {}; |
||||
|
|
||||
|
await axios.post(notification.signalURL, data, config) |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Signal; |
@ -0,0 +1,70 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Slack extends NotificationProvider { |
||||
|
|
||||
|
name = "slack"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
try { |
||||
|
if (heartbeatJSON == null) { |
||||
|
let data = { |
||||
|
"text": "Uptime Kuma Slack testing successful.", |
||||
|
"channel": notification.slackchannel, |
||||
|
"username": notification.slackusername, |
||||
|
"icon_emoji": notification.slackiconemo, |
||||
|
} |
||||
|
await axios.post(notification.slackwebhookURL, data) |
||||
|
return okMsg; |
||||
|
} |
||||
|
|
||||
|
const time = heartbeatJSON["time"]; |
||||
|
let data = { |
||||
|
"text": "Uptime Kuma Alert", |
||||
|
"channel": notification.slackchannel, |
||||
|
"username": notification.slackusername, |
||||
|
"icon_emoji": notification.slackiconemo, |
||||
|
"blocks": [{ |
||||
|
"type": "header", |
||||
|
"text": { |
||||
|
"type": "plain_text", |
||||
|
"text": "Uptime Kuma Alert", |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"type": "section", |
||||
|
"fields": [{ |
||||
|
"type": "mrkdwn", |
||||
|
"text": "*Message*\n" + msg, |
||||
|
}, |
||||
|
{ |
||||
|
"type": "mrkdwn", |
||||
|
"text": "*Time (UTC)*\n" + time, |
||||
|
}], |
||||
|
}, |
||||
|
{ |
||||
|
"type": "actions", |
||||
|
"elements": [ |
||||
|
{ |
||||
|
"type": "button", |
||||
|
"text": { |
||||
|
"type": "plain_text", |
||||
|
"text": "Visit Uptime Kuma", |
||||
|
}, |
||||
|
"value": "Uptime-Kuma", |
||||
|
"url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", |
||||
|
}, |
||||
|
], |
||||
|
}], |
||||
|
} |
||||
|
await axios.post(notification.slackwebhookURL, data) |
||||
|
return okMsg; |
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Slack; |
@ -0,0 +1,48 @@ |
|||||
|
const nodemailer = require("nodemailer"); |
||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
|
||||
|
class SMTP extends NotificationProvider { |
||||
|
|
||||
|
name = "smtp"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
|
||||
|
const config = { |
||||
|
host: notification.smtpHost, |
||||
|
port: notification.smtpPort, |
||||
|
secure: notification.smtpSecure, |
||||
|
}; |
||||
|
|
||||
|
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
|
||||
|
if (notification.smtpUsername || notification.smtpPassword) { |
||||
|
config.auth = { |
||||
|
user: notification.smtpUsername, |
||||
|
pass: notification.smtpPassword, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
let transporter = nodemailer.createTransport(config); |
||||
|
|
||||
|
let bodyTextContent = msg; |
||||
|
if (heartbeatJSON) { |
||||
|
bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; |
||||
|
} |
||||
|
|
||||
|
// send mail with defined transport object
|
||||
|
await transporter.sendMail({ |
||||
|
from: notification.smtpFrom, |
||||
|
cc: notification.smtpCC, |
||||
|
bcc: notification.smtpBCC, |
||||
|
to: notification.smtpTo, |
||||
|
subject: msg, |
||||
|
text: bodyTextContent, |
||||
|
tls: { |
||||
|
rejectUnauthorized: notification.smtpIgnoreTLSError || false, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
return "Sent Successfully."; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = SMTP; |
@ -0,0 +1,27 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
|
||||
|
class Telegram extends NotificationProvider { |
||||
|
|
||||
|
name = "telegram"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { |
||||
|
params: { |
||||
|
chat_id: notification.telegramChatID, |
||||
|
text: msg, |
||||
|
}, |
||||
|
}) |
||||
|
return okMsg; |
||||
|
|
||||
|
} catch (error) { |
||||
|
let msg = (error.response.data.description) ? error.response.data.description : "Error without description" |
||||
|
throw new Error(msg) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Telegram; |
@ -0,0 +1,44 @@ |
|||||
|
const NotificationProvider = require("./notification-provider"); |
||||
|
const axios = require("axios"); |
||||
|
const FormData = require("form-data"); |
||||
|
|
||||
|
class Webhook extends NotificationProvider { |
||||
|
|
||||
|
name = "webhook"; |
||||
|
|
||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
||||
|
let okMsg = "Sent Successfully. "; |
||||
|
|
||||
|
try { |
||||
|
let data = { |
||||
|
heartbeat: heartbeatJSON, |
||||
|
monitor: monitorJSON, |
||||
|
msg, |
||||
|
}; |
||||
|
let finalData; |
||||
|
let config = {}; |
||||
|
|
||||
|
if (notification.webhookContentType === "form-data") { |
||||
|
finalData = new FormData(); |
||||
|
finalData.append("data", JSON.stringify(data)); |
||||
|
|
||||
|
config = { |
||||
|
headers: finalData.getHeaders(), |
||||
|
} |
||||
|
|
||||
|
} else { |
||||
|
finalData = data; |
||||
|
} |
||||
|
|
||||
|
await axios.post(notification.webhookURL, finalData, config) |
||||
|
return okMsg; |
||||
|
|
||||
|
} catch (error) { |
||||
|
this.throwGeneralAxiosError(error) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
module.exports = Webhook; |
@ -1,8 +1,20 @@ |
|||||
$primary: #5CDD8B; |
$primary: #5cdd8b; |
||||
$danger: #DC3545; |
$danger: #dc3545; |
||||
$warning: #f8a306; |
$warning: #f8a306; |
||||
$link-color: #111; |
$link-color: #111; |
||||
$border-radius: 50rem; |
$border-radius: 50rem; |
||||
|
|
||||
$highlight: #7ce8a4; |
$highlight: #7ce8a4; |
||||
$highlight-white: #e7faec; |
$highlight-white: #e7faec; |
||||
|
|
||||
|
$dark-font-color: #b1b8c0; |
||||
|
$dark-font-color2: #020b05; |
||||
|
$dark-bg: #0d1117; |
||||
|
$dark-bg2: #070a10; |
||||
|
$dark-border-color: #1d2634; |
||||
|
|
||||
|
$easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97); |
||||
|
$easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); |
||||
|
$easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86); |
||||
|
|
||||
|
$dropdown-border-radius: 0.5rem; |
||||
|
@ -0,0 +1,78 @@ |
|||||
|
<template> |
||||
|
<div class="input-group mb-3"> |
||||
|
<input |
||||
|
ref="input" |
||||
|
v-model="model" |
||||
|
:type="visibility" |
||||
|
class="form-control" |
||||
|
:placeholder="placeholder" |
||||
|
:maxlength="maxlength" |
||||
|
:autocomplete="autocomplete" |
||||
|
:required="required" |
||||
|
:readonly="readonly" |
||||
|
> |
||||
|
|
||||
|
<a v-if="visibility == 'password'" class="btn btn-outline-primary" @click="showInput()"> |
||||
|
<font-awesome-icon icon="eye" /> |
||||
|
</a> |
||||
|
<a v-if="visibility == 'text'" class="btn btn-outline-primary" @click="hideInput()"> |
||||
|
<font-awesome-icon icon="eye-slash" /> |
||||
|
</a> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props: { |
||||
|
modelValue: { |
||||
|
type: String, |
||||
|
default: "" |
||||
|
}, |
||||
|
placeholder: { |
||||
|
type: String, |
||||
|
default: "" |
||||
|
}, |
||||
|
maxlength: { |
||||
|
type: Number, |
||||
|
default: 255 |
||||
|
}, |
||||
|
autocomplete: { |
||||
|
type: String, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
required: { |
||||
|
type: Boolean |
||||
|
}, |
||||
|
readonly: { |
||||
|
type: String, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
visibility: "password", |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
model: { |
||||
|
get() { |
||||
|
return this.modelValue |
||||
|
}, |
||||
|
set(value) { |
||||
|
this.$emit("update:modelValue", value) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
showInput() { |
||||
|
this.visibility = "text"; |
||||
|
}, |
||||
|
hideInput() { |
||||
|
this.visibility = "password"; |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
@ -0,0 +1,143 @@ |
|||||
|
<template> |
||||
|
<div class="shadow-box list mb-3" :class="{ scrollbar: scrollbar }"> |
||||
|
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3"> |
||||
|
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link> |
||||
|
</div> |
||||
|
|
||||
|
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }"> |
||||
|
<div class="row"> |
||||
|
<div class="col-6 col-md-8 small-padding" :class="{ 'monitorItem': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }"> |
||||
|
<div class="info"> |
||||
|
<Uptime :monitor="item" type="24" :pill="true" /> |
||||
|
{{ item.name }} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-6 col-md-4"> |
||||
|
<HeartbeatBar size="small" :monitor-id="item.id" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row"> |
||||
|
<div class="col-12"> |
||||
|
<HeartbeatBar size="small" :monitor-id="item.id" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</router-link> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import HeartbeatBar from "../components/HeartbeatBar.vue"; |
||||
|
import Uptime from "../components/Uptime.vue"; |
||||
|
export default { |
||||
|
components: { |
||||
|
Uptime, |
||||
|
HeartbeatBar, |
||||
|
}, |
||||
|
props: { |
||||
|
scrollbar: { |
||||
|
type: Boolean, |
||||
|
}, |
||||
|
}, |
||||
|
computed: { |
||||
|
sortedMonitorList() { |
||||
|
let result = Object.values(this.$root.monitorList); |
||||
|
|
||||
|
result.sort((m1, m2) => { |
||||
|
|
||||
|
if (m1.active !== m2.active) { |
||||
|
if (m1.active === 0) { |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
if (m2.active === 0) { |
||||
|
return -1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (m1.weight !== m2.weight) { |
||||
|
if (m1.weight > m2.weight) { |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
if (m1.weight < m2.weight) { |
||||
|
return 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return m1.name.localeCompare(m2.name); |
||||
|
}) |
||||
|
|
||||
|
return result; |
||||
|
}, |
||||
|
}, |
||||
|
methods: { |
||||
|
monitorURL(id) { |
||||
|
return "/dashboard/" + id; |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
@import "../assets/vars.scss"; |
||||
|
|
||||
|
.small-padding { |
||||
|
padding-left: 5px !important; |
||||
|
padding-right: 5px !important; |
||||
|
} |
||||
|
|
||||
|
.list { |
||||
|
&.scrollbar { |
||||
|
min-height: calc(100vh - 240px); |
||||
|
max-height: calc(100vh - 30px); |
||||
|
overflow-y: auto; |
||||
|
position: sticky; |
||||
|
top: 10px; |
||||
|
} |
||||
|
|
||||
|
.item { |
||||
|
display: block; |
||||
|
text-decoration: none; |
||||
|
padding: 13px 15px 10px 15px; |
||||
|
border-radius: 10px; |
||||
|
transition: all ease-in-out 0.15s; |
||||
|
|
||||
|
&.disabled { |
||||
|
opacity: 0.3; |
||||
|
} |
||||
|
|
||||
|
.info { |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: $highlight-white; |
||||
|
} |
||||
|
|
||||
|
&.active { |
||||
|
background-color: #cdf8f4; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dark { |
||||
|
.list { |
||||
|
.item { |
||||
|
&:hover { |
||||
|
background-color: $dark-bg2; |
||||
|
} |
||||
|
|
||||
|
&.active { |
||||
|
background-color: $dark-bg2; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.monitorItem { |
||||
|
width: 100%; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,176 @@ |
|||||
|
<template> |
||||
|
<LineChart :chart-data="chartData" :options="chartOptions" /> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { BarController, BarElement, Chart, Filler, LinearScale, LineController, LineElement, PointElement, TimeScale, Tooltip } from "chart.js"; |
||||
|
import dayjs from "dayjs"; |
||||
|
import utc from "dayjs/plugin/utc"; |
||||
|
import timezone from "dayjs/plugin/timezone"; |
||||
|
import "chartjs-adapter-dayjs"; |
||||
|
import { LineChart } from "vue-chart-3"; |
||||
|
dayjs.extend(utc); |
||||
|
dayjs.extend(timezone); |
||||
|
|
||||
|
Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler); |
||||
|
|
||||
|
export default { |
||||
|
components: { LineChart }, |
||||
|
props: { |
||||
|
monitorId: { |
||||
|
type: Number, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
// Configurable filtering on top of the returned data |
||||
|
chartPeriodHrs: 6, |
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
chartOptions() { |
||||
|
return { |
||||
|
responsive: true, |
||||
|
maintainAspectRatio: false, |
||||
|
onResize: (chart) => { |
||||
|
chart.canvas.parentNode.style.position = "relative"; |
||||
|
if (screen.width < 576) { |
||||
|
chart.canvas.parentNode.style.height = "275px"; |
||||
|
} else if (screen.width < 768) { |
||||
|
chart.canvas.parentNode.style.height = "320px"; |
||||
|
} else if (screen.width < 992) { |
||||
|
chart.canvas.parentNode.style.height = "300px"; |
||||
|
} else { |
||||
|
chart.canvas.parentNode.style.height = "250px"; |
||||
|
} |
||||
|
}, |
||||
|
layout: { |
||||
|
padding: { |
||||
|
left: 10, |
||||
|
right: 30, |
||||
|
top: 30, |
||||
|
bottom: 10, |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
elements: { |
||||
|
point: { |
||||
|
// Hide points on chart unless mouse-over |
||||
|
radius: 0, |
||||
|
hitRadius: 100, |
||||
|
}, |
||||
|
}, |
||||
|
scales: { |
||||
|
x: { |
||||
|
type: "time", |
||||
|
time: { |
||||
|
minUnit: "minute", |
||||
|
round: "second", |
||||
|
tooltipFormat: "YYYY-MM-DD HH:mm:ss", |
||||
|
displayFormats: { |
||||
|
minute: "HH:mm", |
||||
|
hour: "MM-DD HH:mm", |
||||
|
} |
||||
|
}, |
||||
|
ticks: { |
||||
|
maxRotation: 0, |
||||
|
autoSkipPadding: 30, |
||||
|
}, |
||||
|
grid: { |
||||
|
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)", |
||||
|
offset: false, |
||||
|
}, |
||||
|
}, |
||||
|
y: { |
||||
|
title: { |
||||
|
display: true, |
||||
|
text: this.$t("respTime"), |
||||
|
}, |
||||
|
offset: false, |
||||
|
grid: { |
||||
|
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)", |
||||
|
}, |
||||
|
}, |
||||
|
y1: { |
||||
|
display: false, |
||||
|
position: "right", |
||||
|
grid: { |
||||
|
drawOnChartArea: false, |
||||
|
}, |
||||
|
min: 0, |
||||
|
max: 1, |
||||
|
offset: false, |
||||
|
}, |
||||
|
}, |
||||
|
bounds: "ticks", |
||||
|
plugins: { |
||||
|
tooltip: { |
||||
|
mode: "nearest", |
||||
|
intersect: false, |
||||
|
padding: 10, |
||||
|
backgroundColor: this.$root.theme === "light" ? "rgba(212,232,222,1.0)" : "rgba(32,42,38,1.0)", |
||||
|
bodyColor: this.$root.theme === "light" ? "rgba(12,12,18,1.0)" : "rgba(220,220,220,1.0)", |
||||
|
titleColor: this.$root.theme === "light" ? "rgba(12,12,18,1.0)" : "rgba(220,220,220,1.0)", |
||||
|
filter: function (tooltipItem) { |
||||
|
return tooltipItem.datasetIndex === 0; // Hide tooltip on Bar Chart |
||||
|
}, |
||||
|
callbacks: { |
||||
|
label: (context) => { |
||||
|
return ` ${new Intl.NumberFormat().format(context.parsed.y)} ms` |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
legend: { |
||||
|
display: false, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
chartData() { |
||||
|
let pingData = []; // Ping Data for Line Chart, y-axis contains ping time |
||||
|
let downData = []; // Down Data for Bar Chart, y-axis is 1 if target is down, 0 if target is up |
||||
|
if (this.monitorId in this.$root.heartbeatList) { |
||||
|
this.$root.heartbeatList[this.monitorId] |
||||
|
.filter( |
||||
|
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours"))) |
||||
|
.map((beat) => { |
||||
|
const x = this.$root.datetime(beat.time); |
||||
|
pingData.push({ |
||||
|
x, |
||||
|
y: beat.ping, |
||||
|
}); |
||||
|
downData.push({ |
||||
|
x, |
||||
|
y: beat.status === 0 ? 1 : 0, |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
return { |
||||
|
datasets: [ |
||||
|
{ |
||||
|
// Line Chart |
||||
|
data: pingData, |
||||
|
fill: "origin", |
||||
|
tension: 0.2, |
||||
|
borderColor: "#5CDD8B", |
||||
|
backgroundColor: "#5CDD8B38", |
||||
|
yAxisID: "y", |
||||
|
}, |
||||
|
{ |
||||
|
// Bar Chart |
||||
|
type: "bar", |
||||
|
data: downData, |
||||
|
borderColor: "#00000000", |
||||
|
backgroundColor: "#DC354568", |
||||
|
yAxisID: "y1", |
||||
|
barThickness: "flex", |
||||
|
barPercentage: 1, |
||||
|
categoryPercentage: 1, |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
@ -0,0 +1,75 @@ |
|||||
|
<template> |
||||
|
<div class="mb-3"> |
||||
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label> |
||||
|
<input id="hostname" v-model="$parent.notification.smtpHost" type="text" class="form-control" required> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="port" class="form-label">{{ $t("Port") }}</label> |
||||
|
<input id="port" v-model="$parent.notification.smtpPort" type="number" class="form-control" required min="0" max="65535" step="1"> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="secure" class="form-label">Secure</label> |
||||
|
<select id="secure" v-model="$parent.notification.smtpSecure" class="form-select"> |
||||
|
<option :value="false">None / STARTTLS (25, 587)</option> |
||||
|
<option :value="true">TLS (465)</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<div class="form-check"> |
||||
|
<input id="ignore-tls-error" v-model="$parent.notification.smtpIgnoreTLSError" class="form-check-input" type="checkbox" value=""> |
||||
|
<label class="form-check-label" for="ignore-tls-error"> |
||||
|
Ignore TLS Error |
||||
|
</label> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="username" class="form-label">{{ $t("Username") }}</label> |
||||
|
<input id="username" v-model="$parent.notification.smtpUsername" type="text" class="form-control" autocomplete="false"> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="password" class="form-label">{{ $t("Password") }}</label> |
||||
|
<HiddenInput id="password" v-model="$parent.notification.smtpPassword" :required="true" autocomplete="one-time-code"></HiddenInput> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="from-email" class="form-label">From Email</label> |
||||
|
<input id="from-email" v-model="$parent.notification.smtpFrom" type="text" class="form-control" required autocomplete="false" placeholder=""Uptime Kuma" <example@kuma.pet>"> |
||||
|
<div class="form-text"> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="to-email" class="form-label">To Email</label> |
||||
|
<input id="to-email" v-model="$parent.notification.smtpTo" type="text" class="form-control" required autocomplete="false" placeholder="example2@kuma.pet, example3@kuma.pet"> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="to-cc" class="form-label">CC</label> |
||||
|
<input id="to-cc" v-model="$parent.notification.smtpCC" type="text" class="form-control" autocomplete="false"> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="to-bcc" class="form-label">BCC</label> |
||||
|
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false"> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import HiddenInput from "../HiddenInput.vue"; |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
HiddenInput, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
name: "smtp", |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
</script> |
@ -0,0 +1,96 @@ |
|||||
|
<template> |
||||
|
<div class="mb-3"> |
||||
|
<label for="telegram-bot-token" class="form-label">Bot Token</label> |
||||
|
<HiddenInput id="telegram-bot-token" v-model="$parent.notification.telegramBotToken" :required="true" autocomplete="one-time-code"></HiddenInput> |
||||
|
<div class="form-text"> |
||||
|
You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mb-3"> |
||||
|
<label for="telegram-chat-id" class="form-label">Chat ID</label> |
||||
|
|
||||
|
<div class="input-group mb-3"> |
||||
|
<input id="telegram-chat-id" v-model="$parent.notification.telegramChatID" type="text" class="form-control" required> |
||||
|
<button v-if="$parent.notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID"> |
||||
|
{{ $t("Auto Get") }} |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-text"> |
||||
|
Support Direct Chat / Group / Channel's Chat ID |
||||
|
|
||||
|
<p style="margin-top: 8px;"> |
||||
|
You can get your chat id by sending message to the bot and go to this url to view the chat_id: |
||||
|
</p> |
||||
|
|
||||
|
<p style="margin-top: 8px;"> |
||||
|
<template v-if="$parent.notification.telegramBotToken"> |
||||
|
<a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else> |
||||
|
{{ telegramGetUpdatesURL }} |
||||
|
</template> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import HiddenInput from "../HiddenInput.vue"; |
||||
|
import axios from "axios"; |
||||
|
import { useToast } from "vue-toastification" |
||||
|
const toast = useToast(); |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
HiddenInput, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
name: "telegram", |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
telegramGetUpdatesURL() { |
||||
|
let token = "<YOUR BOT TOKEN HERE>" |
||||
|
|
||||
|
if (this.$parent.notification.telegramBotToken) { |
||||
|
token = this.$parent.notification.telegramBotToken; |
||||
|
} |
||||
|
|
||||
|
return `https://api.telegram.org/bot${token}/getUpdates`; |
||||
|
}, |
||||
|
}, |
||||
|
mounted() { |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
async autoGetTelegramChatID() { |
||||
|
try { |
||||
|
let res = await axios.get(this.telegramGetUpdatesURL) |
||||
|
|
||||
|
if (res.data.result.length >= 1) { |
||||
|
let update = res.data.result[res.data.result.length - 1] |
||||
|
|
||||
|
if (update.channel_post) { |
||||
|
this.notification.telegramChatID = update.channel_post.chat.id; |
||||
|
} else if (update.message) { |
||||
|
this.notification.telegramChatID = update.message.chat.id; |
||||
|
} else { |
||||
|
throw new Error("Chat ID is not found, please send a message to this bot first") |
||||
|
} |
||||
|
|
||||
|
} else { |
||||
|
throw new Error("Chat ID is not found, please send a message to this bot first") |
||||
|
} |
||||
|
|
||||
|
} catch (error) { |
||||
|
toast.error(error.message) |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
@ -1,10 +1,10 @@ |
|||||
import { library } from "@fortawesome/fontawesome-svg-core" |
import { library } from "@fortawesome/fontawesome-svg-core" |
||||
import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" |
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons" |
||||
//import { fa } from '@fortawesome/free-regular-svg-icons'
|
//import { fa } from '@fortawesome/free-regular-svg-icons'
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" |
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" |
||||
|
|
||||
// Add Free Font Awesome Icons here
|
// Add Free Font Awesome Icons here
|
||||
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
|
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
|
||||
library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList) |
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash); |
||||
|
|
||||
export { FontAwesomeIcon } |
export { FontAwesomeIcon } |
||||
|
@ -0,0 +1,18 @@ |
|||||
|
# How to translate |
||||
|
|
||||
|
1. Fork this repo. |
||||
|
2. Create a language file. (e.g. `zh-TW.js`) The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm |
||||
|
3. `npm run update-language-files --base-lang=de-DE` |
||||
|
6. Your language file should be filled in. You can translate now. |
||||
|
7. Translate `src/pages/Settings.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`). |
||||
|
8. Import your language file in `src/main.js` and add it to `languageList` constant. |
||||
|
9. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done. |
||||
|
|
||||
|
|
||||
|
|
||||
|
One of good examples: |
||||
|
https://github.com/louislam/uptime-kuma/pull/316/files |
||||
|
|
||||
|
|
||||
|
If you do not have programming skills, let me know in [Issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏 |
||||
|
|
@ -0,0 +1,131 @@ |
|||||
|
export default { |
||||
|
languageName: "Danish", |
||||
|
Settings: "Indstillinger", |
||||
|
Dashboard: "Dashboard", |
||||
|
"New Update": "Opdatering tilgængelig", |
||||
|
Language: "Sprog", |
||||
|
Appearance: "Udseende", |
||||
|
Theme: "Tema", |
||||
|
General: "Generelt", |
||||
|
Version: "Version", |
||||
|
"Check Update On GitHub": "Tjek efter opdateringer på Github", |
||||
|
List: "Liste", |
||||
|
Add: "Tilføj", |
||||
|
"Add New Monitor": "Tilføj ny Overvåger", |
||||
|
"Quick Stats": "Oversigt", |
||||
|
Up: "Aktiv", |
||||
|
Down: "Inaktiv", |
||||
|
Pending: "Afventer", |
||||
|
Unknown: "Ukendt", |
||||
|
Pause: "Pause", |
||||
|
pauseDashboardHome: "Pauset", |
||||
|
Name: "Navn", |
||||
|
Status: "Status", |
||||
|
DateTime: "Dato / Tid", |
||||
|
Message: "Beskeder", |
||||
|
"No important events": "Inden vigtige begivenheder", |
||||
|
Resume: "Fortsæt", |
||||
|
Edit: "Rediger", |
||||
|
Delete: "Slet", |
||||
|
Current: "Aktuelt", |
||||
|
Uptime: "Oppetid", |
||||
|
"Cert Exp.": "Certifikatets udløb", |
||||
|
days: "Dage", |
||||
|
day: "Dag", |
||||
|
"-day": "-Dage", |
||||
|
hour: "Timer", |
||||
|
"-hour": "-Timer", |
||||
|
checkEverySecond: "Tjek hvert {0} sekund", |
||||
|
"Avg.": "Gennemsnit", |
||||
|
Response: " Respons", |
||||
|
Ping: "Ping", |
||||
|
"Monitor Type": "Overvåger Type", |
||||
|
Keyword: "Nøgleord", |
||||
|
"Friendly Name": "Visningsnavn", |
||||
|
URL: "URL", |
||||
|
Hostname: "Hostname", |
||||
|
Port: "Port", |
||||
|
"Heartbeat Interval": "Taktinterval", |
||||
|
Retries: "Gentagelser", |
||||
|
retriesDescription: "Maksimalt antal gentagelser, før tjenesten markeres som inaktiv og sender en meddelelse.", |
||||
|
Advanced: "Avanceret", |
||||
|
ignoreTLSError: "Ignorere TLS/SSL web fejl", |
||||
|
"Upside Down Mode": "Omvendt tilstand", |
||||
|
upsideDownModeDescription: "Håndter tilstanden omvendt. Hvis tjenesten er tilgængelig, vises den som inaktiv.", |
||||
|
"Max. Redirects": "Maks. Omdirigeringer", |
||||
|
maxRedirectDescription: "Maksimalt antal omdirigeringer, der skal følges. Indstil til 0 for at deaktivere omdirigeringer.", |
||||
|
"Accepted Status Codes": "Tilladte HTTP-Statuskoder", |
||||
|
acceptedStatusCodesDescription: "Vælg de statuskoder, der stadig skal vurderes som vellykkede.", |
||||
|
Save: "Gem", |
||||
|
Notifications: "Underretninger", |
||||
|
"Not available, please setup.": "Ikke tilgængelige, opsæt venligst.", |
||||
|
"Setup Notification": "Opsæt underretninger", |
||||
|
Light: "Lys", |
||||
|
Dark: "Mørk", |
||||
|
Auto: "Auto", |
||||
|
"Theme - Heartbeat Bar": "Tema - Tidslinje", |
||||
|
Normal: "Normal", |
||||
|
Bottom: "Bunden", |
||||
|
None: "Ingen", |
||||
|
Timezone: "Tidszone", |
||||
|
"Search Engine Visibility": "Søgemaskine synlighed", |
||||
|
"Allow indexing": "Tillad indeksering", |
||||
|
"Discourage search engines from indexing site": "Frabed søgemaskiner at indeksere webstedet", |
||||
|
"Change Password": "Ændre adgangskode", |
||||
|
"Current Password": "Nuværende adgangskode", |
||||
|
"New Password": "Ny adgangskode", |
||||
|
"Repeat New Password": "Gentag den nye adgangskode", |
||||
|
passwordNotMatchMsg: "Adgangskoderne er ikke ens.", |
||||
|
"Update Password": "Opdater adgangskode", |
||||
|
"Disable Auth": "Deaktiver autentificering", |
||||
|
"Enable Auth": "Aktiver autentificering", |
||||
|
Logout: "Log ud", |
||||
|
notificationDescription: "Tildel underretninger til Overvåger(e), så denne funktion træder i kraft.", |
||||
|
Leave: "Verlassen", |
||||
|
"I understand, please disable": "Jeg er indforstået, deaktiver venligst", |
||||
|
Confirm: "Bekræft", |
||||
|
Yes: "Ja", |
||||
|
No: "Nej", |
||||
|
Username: "Brugernavn", |
||||
|
Password: "Adgangskode", |
||||
|
"Remember me": "Husk mig", |
||||
|
Login: "Log ind", |
||||
|
"No Monitors, please": "Ingen Overvågere", |
||||
|
"add one": "tilføj en", |
||||
|
"Notification Type": "Underretningstype", |
||||
|
Email: "E-Mail", |
||||
|
Test: "Test", |
||||
|
"Certificate Info": "Certifikatoplysninger", |
||||
|
keywordDescription: "Søg efter et søgeord i almindelig HTML- eller JSON -output. Bemærk, at der skelnes mellem store og små bogstaver.", |
||||
|
deleteMonitorMsg: "Er du sikker på, at du vil slette overvågeren?", |
||||
|
deleteNotificationMsg: "Er du sikker på, at du vil slette denne underretning for alle overvågere? ", |
||||
|
resoverserverDescription: "Cloudflare er standardserveren, den kan til enhver tid ændres.", |
||||
|
"Resolver Server": "Navne-server", |
||||
|
rrtypeDescription: "Vælg den type RR, du vil overvåge.", |
||||
|
"Last Result": "Seneste resultat", |
||||
|
pauseMonitorMsg: "Er du sikker på, at du vil pause Overvågeren?", |
||||
|
"Create your admin account": "Opret din administratorkonto", |
||||
|
"Repeat Password": "Gentag adgangskoden", |
||||
|
"Resource Record Type": "Resource Record Type", |
||||
|
respTime: "Resp. Time (ms)", |
||||
|
notAvailableShort: "N/A", |
||||
|
Create: "Create", |
||||
|
clearEventsMsg: "Are you sure want to delete all events for this monitor?", |
||||
|
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", |
||||
|
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", |
||||
|
"Clear Data": "Clear Data", |
||||
|
Events: "Events", |
||||
|
Heartbeats: "Heartbeats", |
||||
|
"Auto Get": "Auto Get", |
||||
|
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", |
||||
|
"Default enabled": "Default enabled", |
||||
|
"Also apply to existing monitors": "Also apply to existing monitors", |
||||
|
"Import/Export Backup": "Import/Export Backup", |
||||
|
Export: "Export", |
||||
|
Import: "Import", |
||||
|
backupDescription: "You can backup all monitors and all notifications into a JSON file.", |
||||
|
backupDescription2: "PS: History and event data is not included.", |
||||
|
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", |
||||
|
alertNoFile: "Please select a file to import.", |
||||
|
alertWrongFileType: "Please select a JSON file." |
||||
|
} |
@ -0,0 +1,132 @@ |
|||||
|
export default { |
||||
|
languageName: "German", |
||||
|
Settings: "Einstellungen", |
||||
|
Dashboard: "Dashboard", |
||||
|
"New Update": "Update Verfügbar", |
||||
|
Language: "Sprache", |
||||
|
Appearance: "Erscheinung", |
||||
|
Theme: "Thema", |
||||
|
General: "Allgemein", |
||||
|
Version: "Version", |
||||
|
"Check Update On GitHub": "Überprüfen von Updates auf Github", |
||||
|
List: "Liste", |
||||
|
Add: "Hinzufügen", |
||||
|
"Add New Monitor": "Neuer Monitor", |
||||
|
"Quick Stats": "Übersicht", |
||||
|
Up: "Aktiv", |
||||
|
Down: "Inaktiv", |
||||
|
Pending: "Ausstehend", |
||||
|
Unknown: "Unbekannt", |
||||
|
Pause: "Pausieren", |
||||
|
pauseDashboardHome: "Pausiert", |
||||
|
Name: "Name", |
||||
|
Status: "Status", |
||||
|
DateTime: "Datum / Uhrzeit", |
||||
|
Message: "Nachricht", |
||||
|
"No important events": "Keine wichtigen Ereignisse", |
||||
|
Resume: "Fortsetzen", |
||||
|
Edit: "Bearbeiten", |
||||
|
Delete: "Löschen", |
||||
|
Current: "Aktuell", |
||||
|
Uptime: "Verfügbarkeit", |
||||
|
"Cert Exp.": "Zertifikatsablauf", |
||||
|
days: "Tage", |
||||
|
day: "Tag", |
||||
|
"-day": "-Tage", |
||||
|
hour: "Stunde", |
||||
|
"-hour": "-Stunden", |
||||
|
checkEverySecond: "Überprüfe alle {0} Sekunden", |
||||
|
"Avg.": "Durchschn. ", |
||||
|
Response: " Antwortzeit", |
||||
|
Ping: "Ping", |
||||
|
"Monitor Type": "Monitor Typ", |
||||
|
Keyword: "Schlüsselwort", |
||||
|
"Friendly Name": "Anzeigename", |
||||
|
URL: "URL", |
||||
|
Hostname: "Hostname", |
||||
|
Port: "Port", |
||||
|
"Heartbeat Interval": "Taktintervall", |
||||
|
Retries: "Wiederholungen", |
||||
|
retriesDescription: "Maximale Anzahl von Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird.", |
||||
|
Advanced: "Erweitert", |
||||
|
ignoreTLSError: "Ignoriere TLS/SSL Fehler von Webseiten", |
||||
|
"Upside Down Mode": "Umgedrehter Modus", |
||||
|
upsideDownModeDescription: "Drehe den Modus um, ist der Dienst erreichbar, wird er als Inaktiv angezeigt.", |
||||
|
"Max. Redirects": "Max. Weiterleitungen", |
||||
|
maxRedirectDescription: "Maximale Anzahl von Weiterleitungen, denen gefolgt werden soll. Setzte auf 0, um Weiterleitungen zu deaktivieren.", |
||||
|
"Accepted Status Codes": "Erlaubte HTTP-Statuscodes", |
||||
|
acceptedStatusCodesDescription: "Wähle die Statuscodes aus, welche trotzdem als erfolgreich gewertet werden sollen.", |
||||
|
Save: "Speichern", |
||||
|
Notifications: "Benachrichtigungen", |
||||
|
"Not available, please setup.": "Keine verfügbar, bitte einrichten.", |
||||
|
"Setup Notification": "Benachrichtigung einrichten", |
||||
|
Light: "Hell", |
||||
|
Dark: "Dunkel", |
||||
|
Auto: "Auto", |
||||
|
"Theme - Heartbeat Bar": "Thema - Taktleiste", |
||||
|
Normal: "Normal", |
||||
|
Bottom: "Unten", |
||||
|
None: "Keine", |
||||
|
Timezone: "Zeitzone", |
||||
|
"Search Engine Visibility": "Suchmaschinensichtbarkeit", |
||||
|
"Allow indexing": "Indizierung zulassen", |
||||
|
"Discourage search engines from indexing site": "Halte Suchmaschinen von der Indexierung der Seite ab", |
||||
|
"Change Password": "Passwort ändern", |
||||
|
"Current Password": "Dezeitiges Passwort", |
||||
|
"New Password": "Neues Passwort", |
||||
|
"Repeat New Password": "Wiederhole neues Passwort", |
||||
|
passwordNotMatchMsg: "Passwörter stimmen nicht überein. ", |
||||
|
"Update Password": "Ändere Passwort", |
||||
|
"Disable Auth": "Authentifizierung deaktivieren", |
||||
|
"Enable Auth": "Authentifizierung aktivieren", |
||||
|
Logout: "Ausloggen", |
||||
|
notificationDescription: "Weise den Monitor(en) eine Benachrichtigung zu, damit diese Funktion greift.", |
||||
|
Leave: "Verlassen", |
||||
|
"I understand, please disable": "Ich verstehe, bitte deaktivieren", |
||||
|
Confirm: "Bestätige", |
||||
|
Yes: "Ja", |
||||
|
No: "Nein", |
||||
|
Username: "Benutzername", |
||||
|
Password: "Passwort", |
||||
|
"Remember me": "Passwort merken", |
||||
|
Login: "Einloggen", |
||||
|
"No Monitors, please": "Keine Monitore, bitte", |
||||
|
"add one": "hinzufügen", |
||||
|
"Notification Type": "Benachrichtigungs Dienst", |
||||
|
Email: "E-Mail", |
||||
|
Test: "Test", |
||||
|
"Certificate Info": "Zertifikatsinfo", |
||||
|
keywordDescription: "Suche nach einem Schlüsselwort in der HTML oder JSON Ausgabe. Bitte beachte, es wird in der Groß-/Kleinschreibung unterschieden.", |
||||
|
deleteMonitorMsg: "Bist du sicher das du den Monitor löschen möchtest?", |
||||
|
deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?", |
||||
|
resoverserverDescription: "Cloudflare ist als der Standardserver festgelegt, dieser kann jederzeit geändern werden.", |
||||
|
"Resolver Server": "Auflösungsserver", |
||||
|
rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.", |
||||
|
"Last Result": "Letztes Ergebnis", |
||||
|
pauseMonitorMsg: "Bist du sicher das du den Monitor pausieren möchtest?", |
||||
|
clearEventsMsg: "Bist du sicher das du alle Ereignisse für diesen Monitor löschen möchtest?", |
||||
|
clearHeartbeatsMsg: "Bist du sicher das du alle Statistiken für diesen Monitor löschen möchtest?", |
||||
|
"Clear Data": "Lösche Daten", |
||||
|
Events: "Ereignisse", |
||||
|
Heartbeats: "Statistiken", |
||||
|
confirmClearStatisticsMsg: "Bist du sicher das du ALLE Statistiken löschen möchtest?", |
||||
|
"Create your admin account": "Erstelle dein Admin Konto", |
||||
|
"Repeat Password": "Wiederhole das Passwort", |
||||
|
"Resource Record Type": "Resource Record Type", |
||||
|
"Import/Export Backup": "Import/Export Backup", |
||||
|
"Export": "Export", |
||||
|
"Import": "Import", |
||||
|
respTime: "Antw. Zeit (ms)", |
||||
|
notAvailableShort: "N/A", |
||||
|
"Default enabled": "Standardmäßig aktiviert", |
||||
|
"Also apply to existing monitors": "Auch für alle existierenden Monitore aktivieren", |
||||
|
enableDefaultNotificationDescription: "Für jeden neuen Monitor wird diese Benachrichtigung standardmäßig aktiviert. Die Benachrichtigung kann weiterhin für jeden Monitor separat deaktiviert werden.", |
||||
|
Create: "Erstellen", |
||||
|
"Auto Get": "Auto Get", |
||||
|
backupDescription: "Es können alle Monitore und Benachrichtigungen in einer JSON-Datei gesichert werden.", |
||||
|
backupDescription2: "PS: Verlaufs- und Ereignisdaten sind nicht enthalten.", |
||||
|
backupDescription3: "Sensible Daten wie Benachrichtigungstoken sind in der Exportdatei enthalten, bitte bewahre sie sorgfältig auf.", |
||||
|
alertNoFile: "Bitte wähle eine Datei zum importieren aus.", |
||||
|
alertWrongFileType: "Bitte wähle eine JSON Datei aus.", |
||||
|
"Clear all statistics": "Lösche alle Statistiken" |
||||
|
} |
@ -0,0 +1,132 @@ |
|||||
|
export default { |
||||
|
languageName: "English", |
||||
|
checkEverySecond: "Check every {0} seconds.", |
||||
|
"Avg.": "Avg. ", |
||||
|
retriesDescription: "Maximum retries before the service is marked as down and a notification is sent", |
||||
|
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites", |
||||
|
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.", |
||||
|
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.", |
||||
|
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.", |
||||
|
passwordNotMatchMsg: "The repeat password does not match.", |
||||
|
notificationDescription: "Please assign a notification to monitor(s) to get it to work.", |
||||
|
keywordDescription: "Search keyword in plain html or JSON response and it is case-sensitive", |
||||
|
pauseDashboardHome: "Pause", |
||||
|
deleteMonitorMsg: "Are you sure want to delete this monitor?", |
||||
|
deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?", |
||||
|
resoverserverDescription: "Cloudflare is the default server, you can change the resolver server anytime.", |
||||
|
rrtypeDescription: "Select the RR-Type you want to monitor", |
||||
|
pauseMonitorMsg: "Are you sure want to pause?", |
||||
|
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", |
||||
|
clearEventsMsg: "Are you sure want to delete all events for this monitor?", |
||||
|
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", |
||||
|
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", |
||||
|
Settings: "Settings", |
||||
|
Dashboard: "Dashboard", |
||||
|
"New Update": "New Update", |
||||
|
Language: "Language", |
||||
|
Appearance: "Appearance", |
||||
|
Theme: "Theme", |
||||
|
General: "General", |
||||
|
Version: "Version", |
||||
|
"Check Update On GitHub": "Check Update On GitHub", |
||||
|
List: "List", |
||||
|
Add: "Add", |
||||
|
"Add New Monitor": "Add New Monitor", |
||||
|
"Quick Stats": "Quick Stats", |
||||
|
Up: "Up", |
||||
|
Down: "Down", |
||||
|
Pending: "Pending", |
||||
|
Unknown: "Unknown", |
||||
|
Pause: "Pause", |
||||
|
Name: "Name", |
||||
|
Status: "Status", |
||||
|
DateTime: "DateTime", |
||||
|
Message: "Message", |
||||
|
"No important events": "No important events", |
||||
|
Resume: "Resume", |
||||
|
Edit: "Edit", |
||||
|
Delete: "Delete", |
||||
|
Current: "Current", |
||||
|
Uptime: "Uptime", |
||||
|
"Cert Exp.": "Cert Exp.", |
||||
|
days: "days", |
||||
|
day: "day", |
||||
|
"-day": "-day", |
||||
|
hour: "hour", |
||||
|
"-hour": "-hour", |
||||
|
Response: "Response", |
||||
|
Ping: "Ping", |
||||
|
"Monitor Type": "Monitor Type", |
||||
|
Keyword: "Keyword", |
||||
|
"Friendly Name": "Friendly Name", |
||||
|
URL: "URL", |
||||
|
Hostname: "Hostname", |
||||
|
Port: "Port", |
||||
|
"Heartbeat Interval": "Heartbeat Interval", |
||||
|
Retries: "Retries", |
||||
|
Advanced: "Advanced", |
||||
|
"Upside Down Mode": "Upside Down Mode", |
||||
|
"Max. Redirects": "Max. Redirects", |
||||
|
"Accepted Status Codes": "Accepted Status Codes", |
||||
|
Save: "Save", |
||||
|
Notifications: "Notifications", |
||||
|
"Not available, please setup.": "Not available, please setup.", |
||||
|
"Setup Notification": "Setup Notification", |
||||
|
Light: "Light", |
||||
|
Dark: "Dark", |
||||
|
Auto: "Auto", |
||||
|
"Theme - Heartbeat Bar": "Theme - Heartbeat Bar", |
||||
|
Normal: "Normal", |
||||
|
Bottom: "Bottom", |
||||
|
None: "None", |
||||
|
Timezone: "Timezone", |
||||
|
"Search Engine Visibility": "Search Engine Visibility", |
||||
|
"Allow indexing": "Allow indexing", |
||||
|
"Discourage search engines from indexing site": "Discourage search engines from indexing site", |
||||
|
"Change Password": "Change Password", |
||||
|
"Current Password": "Current Password", |
||||
|
"New Password": "New Password", |
||||
|
"Repeat New Password": "Repeat New Password", |
||||
|
"Update Password": "Update Password", |
||||
|
"Disable Auth": "Disable Auth", |
||||
|
"Enable Auth": "Enable Auth", |
||||
|
Logout: "Logout", |
||||
|
Leave: "Leave", |
||||
|
"I understand, please disable": "I understand, please disable", |
||||
|
Confirm: "Confirm", |
||||
|
Yes: "Yes", |
||||
|
No: "No", |
||||
|
Username: "Username", |
||||
|
Password: "Password", |
||||
|
"Remember me": "Remember me", |
||||
|
Login: "Login", |
||||
|
"No Monitors, please": "No Monitors, please", |
||||
|
"add one": "add one", |
||||
|
"Notification Type": "Notification Type", |
||||
|
Email: "Email", |
||||
|
Test: "Test", |
||||
|
"Certificate Info": "Certificate Info", |
||||
|
"Resolver Server": "Resolver Server", |
||||
|
"Resource Record Type": "Resource Record Type", |
||||
|
"Last Result": "Last Result", |
||||
|
"Create your admin account": "Create your admin account", |
||||
|
"Repeat Password": "Repeat Password", |
||||
|
"Import/Export Backup": "Import/Export Backup", |
||||
|
Export: "Export", |
||||
|
Import: "Import", |
||||
|
respTime: "Resp. Time (ms)", |
||||
|
notAvailableShort: "N/A", |
||||
|
"Default enabled": "Default enabled", |
||||
|
"Also apply to existing monitors": "Also apply to existing monitors", |
||||
|
Create: "Create", |
||||
|
"Clear Data": "Clear Data", |
||||
|
Events: "Events", |
||||
|
Heartbeats: "Heartbeats", |
||||
|
"Auto Get": "Auto Get", |
||||
|
backupDescription: "You can backup all monitors and all notifications into a JSON file.", |
||||
|
backupDescription2: "PS: History and event data is not included.", |
||||
|
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", |
||||
|
alertNoFile: "Please select a file to import.", |
||||
|
alertWrongFileType: "Please select a JSON file.", |
||||
|
"Clear all statistics": "Clear all Statistics" |
||||
|
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue