Browse Source
# Conflicts: # package-lock.json # package.jsonphilippdormann/feature/release-management
61 changed files with 10213 additions and 14870 deletions
@ -1,4 +1,37 @@ |
|||||
/.idea |
/.idea |
||||
/dist |
/dist |
||||
/node_modules |
/node_modules |
||||
/data/kuma.db |
/data |
||||
|
/.do |
||||
|
**/.dockerignore |
||||
|
**/.git |
||||
|
**/.gitignore |
||||
|
**/docker-compose* |
||||
|
**/[Dd]ockerfile* |
||||
|
LICENSE |
||||
|
README.md |
||||
|
.editorconfig |
||||
|
.vscode |
||||
|
.eslint* |
||||
|
.stylelint* |
||||
|
/.github |
||||
|
package-lock.json |
||||
|
yarn.lock |
||||
|
app.json |
||||
|
CODE_OF_CONDUCT.md |
||||
|
CONTRIBUTING.md |
||||
|
|
||||
|
### .gitignore content (commented rules are duplicated) |
||||
|
|
||||
|
#node_modules |
||||
|
.DS_Store |
||||
|
#dist |
||||
|
dist-ssr |
||||
|
*.local |
||||
|
#.idea |
||||
|
|
||||
|
#/data |
||||
|
#!/data/.gitkeep |
||||
|
#.vscode |
||||
|
|
||||
|
### End of .gitignore content |
||||
|
@ -0,0 +1,73 @@ |
|||||
|
module.exports = { |
||||
|
env: { |
||||
|
browser: true, |
||||
|
commonjs: true, |
||||
|
es2020: true, |
||||
|
node: true, |
||||
|
}, |
||||
|
extends: [ |
||||
|
"eslint:recommended", |
||||
|
"plugin:vue/vue3-recommended", |
||||
|
], |
||||
|
parser: "vue-eslint-parser", |
||||
|
parserOptions: { |
||||
|
parser: "@babel/eslint-parser", |
||||
|
sourceType: "module", |
||||
|
requireConfigFile: false, |
||||
|
}, |
||||
|
rules: { |
||||
|
// override/add rules settings here, such as:
|
||||
|
// 'vue/no-unused-vars': 'error'
|
||||
|
"no-unused-vars": "warn", |
||||
|
indent: [ |
||||
|
"error", |
||||
|
4, |
||||
|
{ |
||||
|
ignoredNodes: ["TemplateLiteral"], |
||||
|
SwitchCase: 1, |
||||
|
}, |
||||
|
], |
||||
|
quotes: ["warn", "double"], |
||||
|
//semi: ['off', 'never'],
|
||||
|
"vue/html-indent": ["warn", 4], // default: 2
|
||||
|
"vue/max-attributes-per-line": "off", |
||||
|
"vue/singleline-html-element-content-newline": "off", |
||||
|
"vue/html-self-closing": "off", |
||||
|
"no-multi-spaces": ["error", { |
||||
|
ignoreEOLComments: true, |
||||
|
}], |
||||
|
"curly": "error", |
||||
|
"object-curly-spacing": ["error", "always"], |
||||
|
"object-curly-newline": "off", |
||||
|
"object-property-newline": "error", |
||||
|
"comma-spacing": "error", |
||||
|
"brace-style": "error", |
||||
|
"no-var": "error", |
||||
|
"key-spacing": "warn", |
||||
|
"keyword-spacing": "warn", |
||||
|
"space-infix-ops": "warn", |
||||
|
"arrow-spacing": "warn", |
||||
|
"no-trailing-spaces": "warn", |
||||
|
"no-constant-condition": ["error", { |
||||
|
"checkLoops": false, |
||||
|
}], |
||||
|
"space-before-blocks": "warn", |
||||
|
//'no-console': 'warn',
|
||||
|
"no-extra-boolean-cast": "off", |
||||
|
"no-multiple-empty-lines": ["warn", { |
||||
|
"max": 1, |
||||
|
"maxBOF": 0, |
||||
|
}], |
||||
|
"lines-between-class-members": ["warn", "always", { |
||||
|
exceptAfterSingleLine: true, |
||||
|
}], |
||||
|
"no-unneeded-ternary": "error", |
||||
|
"no-else-return": ["error", { |
||||
|
"allowElseIf": false, |
||||
|
}], |
||||
|
"array-bracket-newline": ["error", "consistent"], |
||||
|
"eol-last": ["error", "always"], |
||||
|
//'prefer-template': 'error',
|
||||
|
"comma-dangle": ["warn", "only-multiline"], |
||||
|
}, |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
--- |
||||
|
name: Ask for help |
||||
|
about: You can ask any question related to Uptime Kuma. |
||||
|
title: '' |
||||
|
labels: help |
||||
|
assignees: '' |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
|
@ -0,0 +1,34 @@ |
|||||
|
--- |
||||
|
name: Bug report |
||||
|
about: Create a report to help us improve |
||||
|
title: '' |
||||
|
labels: bug |
||||
|
assignees: '' |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Describe the bug** |
||||
|
A clear and concise description of what the bug is. |
||||
|
|
||||
|
**To Reproduce** |
||||
|
Steps to reproduce the behavior: |
||||
|
1. Go to '...' |
||||
|
2. Click on '....' |
||||
|
3. Scroll down to '....' |
||||
|
4. See error |
||||
|
|
||||
|
**Expected behavior** |
||||
|
A clear and concise description of what you expected to happen. |
||||
|
|
||||
|
**Screenshots** |
||||
|
If applicable, add screenshots to help explain your problem. |
||||
|
|
||||
|
**Desktop (please complete the following information):** |
||||
|
- Uptime Kuma Version: |
||||
|
- Using Docker?: Yes/No |
||||
|
- OS: |
||||
|
- Browser: |
||||
|
|
||||
|
|
||||
|
**Additional context** |
||||
|
Add any other context about the problem here. |
@ -0,0 +1,20 @@ |
|||||
|
--- |
||||
|
name: Feature request |
||||
|
about: Suggest an idea for this project |
||||
|
title: '' |
||||
|
labels: enhancement |
||||
|
assignees: '' |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Is your feature request related to a problem? Please describe.** |
||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] |
||||
|
|
||||
|
**Describe the solution you'd like** |
||||
|
A clear and concise description of what you want to happen. |
||||
|
|
||||
|
**Describe alternatives you've considered** |
||||
|
A clear and concise description of any alternative solutions or features you've considered. |
||||
|
|
||||
|
**Additional context** |
||||
|
Add any other context or screenshots about the feature request here. |
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"extends": "stylelint-config-recommended", |
||||
|
} |
@ -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,104 @@ |
|||||
|
# 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. |
||||
|
|
||||
|
Your IDE should follow the config in ".editorconfig". The most special thing is I set it to 4 spaces indentation. I know 2 spaces indentation became a kind of standard nowadays for js, but my eyes is not so comfortable for this. In my opinion, there is no callback-hell nowadays, it is good to go back 4 spaces world again. |
||||
|
|
||||
|
# 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 |
||||
|
|
||||
|
# Tools |
||||
|
- Node.js >= 14 |
||||
|
- Git |
||||
|
- IDE that supports .editorconfig (I am using Intellji Idea) |
||||
|
- A SQLite tool (I am using SQLite Expert Personal) |
||||
|
|
||||
|
# Prepare the dev |
||||
|
|
||||
|
```bash |
||||
|
npm install |
||||
|
``` |
||||
|
|
||||
|
# 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 |
||||
|
|
||||
|
TODO |
||||
|
|
||||
|
# 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,7 @@ |
|||||
|
{ |
||||
|
"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,37 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
-- Change Monitor.created_date from "TIMESTAMP" to "DATETIME" |
||||
|
-- SQL Generated by Intellij Idea |
||||
|
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, |
||||
|
keyword VARCHAR(255) |
||||
|
); |
||||
|
|
||||
|
insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword 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,9 @@ |
|||||
|
BEGIN TRANSACTION; |
||||
|
|
||||
|
CREATE TABLE monitor_tls_info ( |
||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
||||
|
monitor_id INTEGER NOT NULL, |
||||
|
info_json TEXT |
||||
|
); |
||||
|
|
||||
|
COMMIT; |
@ -0,0 +1,37 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
-- Add maxretries column to monitor |
||||
|
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, |
||||
|
keyword VARCHAR(255), |
||||
|
maxretries INTEGER NOT NULL DEFAULT 0 |
||||
|
); |
||||
|
|
||||
|
insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword 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,40 @@ |
|||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
||||
|
-- OK.... serious wrong, missing maxretries column |
||||
|
-- Developers should patch it manually if you have missing the maxretries column |
||||
|
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, |
||||
|
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, created_date, keyword, maxretries) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword, maxretries 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; |
@ -1,18 +1,31 @@ |
|||||
FROM node:14-alpine3.14 |
# DON'T UPDATE TO alpine3.13, 1.14, see #41. |
||||
|
FROM node:14-alpine3.12 AS release |
||||
|
WORKDIR /app |
||||
|
|
||||
# sqlite have to build on arm |
# split the sqlite install here, so that it can caches the arm prebuilt |
||||
# TODO: use prebuilt sqlite for arm, because it is very very slow. |
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \ |
||||
RUN apk add --no-cache make g++ python3 |
ln -s /usr/bin/python3 /usr/bin/python && \ |
||||
RUN ln -s /usr/bin/python3 /usr/bin/python |
npm install sqlite3@5.0.2 bcrypt@5.0.1 && \ |
||||
|
apk del .build-deps |
||||
|
|
||||
WORKDIR /app |
# Touching above code may causes sqlite3 re-compile again, painful slow. |
||||
COPY . . |
|
||||
RUN npm install |
# Install apprise |
||||
RUN npm run build |
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib |
||||
|
RUN pip3 --no-cache-dir install apprise && \ |
||||
|
rm -rf /root/.cache |
||||
|
|
||||
# Remove built tools |
# New things add here |
||||
RUN apk del make g++ |
|
||||
|
COPY . . |
||||
|
RUN npm install && \ |
||||
|
npm run build && \ |
||||
|
npm prune |
||||
|
|
||||
EXPOSE 3001 |
EXPOSE 3001 |
||||
VOLUME ["/app/data"] |
VOLUME ["/app/data"] |
||||
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js |
||||
CMD ["npm", "run", "start-server"] |
CMD ["npm", "run", "start-server"] |
||||
|
|
||||
|
FROM release AS nightly |
||||
|
RUN npm run mark-as-nightly |
||||
|
@ -0,0 +1,19 @@ |
|||||
|
var http = require("http"); |
||||
|
var options = { |
||||
|
host: "localhost", |
||||
|
port: "3001", |
||||
|
timeout: 2000, |
||||
|
}; |
||||
|
var request = http.request(options, (res) => { |
||||
|
console.log(`STATUS: ${res.statusCode}`); |
||||
|
if (res.statusCode == 200) { |
||||
|
process.exit(0); |
||||
|
} else { |
||||
|
process.exit(1); |
||||
|
} |
||||
|
}); |
||||
|
request.on("error", function (err) { |
||||
|
console.log("ERROR"); |
||||
|
process.exit(1); |
||||
|
}); |
||||
|
request.end(); |
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* 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 = oldVersion + "-nightly" |
||||
|
|
||||
|
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
|
||||
|
if (fs.existsSync("README.md")) { |
||||
|
fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion)) |
||||
|
} |
||||
|
} |
File diff suppressed because it is too large
@ -1,66 +1,74 @@ |
|||||
{ |
{ |
||||
"name": "uptime-kuma", |
"name": "uptime-kuma", |
||||
"version": "1.0.4", |
"version": "1.0.8", |
||||
|
"license": "MIT", |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "https://github.com/louislam/uptime-kuma.git" |
||||
|
}, |
||||
|
"engines": { |
||||
|
"node": "14.*" |
||||
|
}, |
||||
"scripts": { |
"scripts": { |
||||
"dev": "vite --host", |
"dev": "vite --host", |
||||
"release": "release-it", |
"start": "npm run start-server", |
||||
"start-server": "node server/server.js", |
"start-server": "node server/server.js", |
||||
"update": "", |
"update": "", |
||||
"build": "vite build", |
"build": "vite build", |
||||
"vite-preview-dist": "vite preview --host", |
"vite-preview-dist": "vite preview --host", |
||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.3 . --push", |
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.8 --target release . --push", |
||||
"build-docker-nightly": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly . --push", |
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", |
||||
"setup": "git checkout 1.0.3 && npm install && npm run build", |
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push", |
||||
"version-global-replace": "node extra/version-global-replace.js" |
"setup": "git checkout 1.0.8 && npm install && npm run build", |
||||
|
"version-global-replace": "node extra/version-global-replace.js", |
||||
|
"mark-as-nightly": "node extra/mark-as-nightly.js" |
||||
}, |
}, |
||||
"dependencies": { |
"dependencies": { |
||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.35", |
||||
|
"@fortawesome/free-regular-svg-icons": "^5.15.3", |
||||
|
"@fortawesome/free-solid-svg-icons": "^5.15.3", |
||||
|
"@fortawesome/vue-fontawesome": "^3.0.0-4", |
||||
"@popperjs/core": "^2.9.2", |
"@popperjs/core": "^2.9.2", |
||||
"args-parser": "^1.3.0", |
"args-parser": "^1.3.0", |
||||
"axios": "^0.21.1", |
"axios": "^0.21.1", |
||||
"bcrypt": "^5.0.1", |
"bcrypt": "^5.0.1", |
||||
"bootstrap": "^5.0.0", |
"bootstrap": "^5.0.2", |
||||
"dayjs": "^1.10.4", |
"command-exists": "^1.2.9", |
||||
|
"dayjs": "^1.10.6", |
||||
"express": "^4.17.1", |
"express": "^4.17.1", |
||||
|
"express-basic-auth": "^1.2.0", |
||||
"form-data": "^4.0.0", |
"form-data": "^4.0.0", |
||||
|
"http-graceful-shutdown": "^3.1.2", |
||||
"jsonwebtoken": "^8.5.1", |
"jsonwebtoken": "^8.5.1", |
||||
"nodemailer": "^6.6.2", |
"nodemailer": "^6.6.3", |
||||
"password-hash": "^1.2.2", |
"password-hash": "^1.2.2", |
||||
|
"prom-client": "^13.1.0", |
||||
|
"prometheus-api-metrics": "^3.2.0", |
||||
"redbean-node": "0.0.20", |
"redbean-node": "0.0.20", |
||||
"socket.io": "^4.0.2", |
"socket.io": "^4.1.3", |
||||
"socket.io-client": "^4.1.2", |
"socket.io-client": "^4.1.3", |
||||
"sqlite3": "^5.0.0", |
"sqlite3": "^5.0.2", |
||||
"tcp-ping": "^0.1.1", |
"tcp-ping": "^0.1.1", |
||||
|
"v-pagination-3": "^0.1.6", |
||||
"vue": "^3.0.5", |
"vue": "^3.0.5", |
||||
"vue-confirm-dialog": "^1.0.2", |
"vue-confirm-dialog": "^1.0.2", |
||||
"vue-router": "^4.0.10", |
"vue-router": "^4.0.10", |
||||
"vue-toastification": "^2.0.0-rc.1" |
"vue-toastification": "^2.0.0-rc.1" |
||||
}, |
}, |
||||
"devDependencies": { |
"devDependencies": { |
||||
"@vitejs/plugin-legacy": "^1.4.3", |
"@babel/eslint-parser": "^7.13.10", |
||||
"@vitejs/plugin-vue": "^1.2.3", |
"@types/bootstrap": "^5.0.17", |
||||
"@vue/compiler-sfc": "^3.0.5", |
"@vitejs/plugin-legacy": "^1.5.0", |
||||
|
"@vitejs/plugin-vue": "^1.3.0", |
||||
|
"@vue/compiler-sfc": "^3.1.5", |
||||
"core-js": "^3.15.2", |
"core-js": "^3.15.2", |
||||
"sass": "^1.35.1", |
"eslint": "^7.31.0", |
||||
"vite": "^2.3.7", |
"eslint-plugin-vue": "^7.14.0", |
||||
"auto-changelog": "2.3.0", |
"sass": "^1.36.0", |
||||
"release-it": "14.10.0" |
"stylelint": "^13.13.1", |
||||
}, |
"stylelint-config-recommended": "^5.0.0", |
||||
"release-it": { |
"stylelint-config-standard": "^22.0.0", |
||||
"git": { |
"typescript": "^4.3.5", |
||||
"commit": true, |
"vite": "^2.4.4" |
||||
"requireUpstream": false, |
|
||||
"requireCleanWorkingDir": false, |
|
||||
"commitMessage": "🚀RELEASE v${version}", |
|
||||
"push": false, |
|
||||
"tag": true, |
|
||||
"tagName": "v${version}", |
|
||||
"tagAnnotation": "v${version}" |
|
||||
}, |
|
||||
"npm": { |
|
||||
"publish": false |
|
||||
}, |
|
||||
"hooks": { |
|
||||
"after:bump": "auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md" |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,3 @@ |
|||||
|
# https://www.robotstxt.org/robotstxt.html |
||||
|
User-agent: * |
||||
|
Disallow: |
@ -0,0 +1,51 @@ |
|||||
|
const basicAuth = require("express-basic-auth") |
||||
|
const passwordHash = require("./password-hash"); |
||||
|
const { R } = require("redbean-node"); |
||||
|
const { setting } = require("./util-server"); |
||||
|
const { debug } = require("../src/util"); |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param username : string |
||||
|
* @param password : string |
||||
|
* @returns {Promise<Bean|null>} |
||||
|
*/ |
||||
|
exports.login = async function (username, password) { |
||||
|
let user = await R.findOne("user", " username = ? AND active = 1 ", [ |
||||
|
username, |
||||
|
]) |
||||
|
|
||||
|
if (user && passwordHash.verify(password, user.password)) { |
||||
|
// Upgrade the hash to bcrypt
|
||||
|
if (passwordHash.needRehash(user.password)) { |
||||
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ |
||||
|
passwordHash.generate(password), |
||||
|
user.id, |
||||
|
]); |
||||
|
} |
||||
|
return user; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
function myAuthorizer(username, password, callback) { |
||||
|
|
||||
|
setting("disableAuth").then((result) => { |
||||
|
|
||||
|
if (result) { |
||||
|
callback(null, true) |
||||
|
} else { |
||||
|
exports.login(username, password).then((user) => { |
||||
|
callback(null, user != null) |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
exports.basicAuth = basicAuth({ |
||||
|
authorizer: myAuthorizer, |
||||
|
authorizeAsync: true, |
||||
|
challenge: true, |
||||
|
}); |
@ -0,0 +1,120 @@ |
|||||
|
const fs = require("fs"); |
||||
|
const { sleep } = require("../src/util"); |
||||
|
const { R } = require("redbean-node"); |
||||
|
const { |
||||
|
setSetting, setting, |
||||
|
} = require("./util-server"); |
||||
|
|
||||
|
class Database { |
||||
|
|
||||
|
static templatePath = "./db/kuma.db" |
||||
|
static path = "./data/kuma.db"; |
||||
|
static latestVersion = 4; |
||||
|
static noReject = true; |
||||
|
|
||||
|
static async patch() { |
||||
|
let version = parseInt(await setting("database_version")); |
||||
|
|
||||
|
if (! version) { |
||||
|
version = 0; |
||||
|
} |
||||
|
|
||||
|
console.info("Your database version: " + version); |
||||
|
console.info("Latest database version: " + this.latestVersion); |
||||
|
|
||||
|
if (version === this.latestVersion) { |
||||
|
console.info("Database no need to patch"); |
||||
|
} else { |
||||
|
console.info("Database patch is needed") |
||||
|
|
||||
|
console.info("Backup the db") |
||||
|
const backupPath = "./data/kuma.db.bak" + version; |
||||
|
fs.copyFileSync(Database.path, backupPath); |
||||
|
|
||||
|
// Try catch anything here, if gone wrong, restore the backup
|
||||
|
try { |
||||
|
for (let i = version + 1; i <= this.latestVersion; i++) { |
||||
|
const sqlFile = `./db/patch${i}.sql`; |
||||
|
console.info(`Patching ${sqlFile}`); |
||||
|
await Database.importSQLFile(sqlFile); |
||||
|
console.info(`Patched ${sqlFile}`); |
||||
|
await setSetting("database_version", i); |
||||
|
} |
||||
|
console.log("Database Patched Successfully"); |
||||
|
} catch (ex) { |
||||
|
await Database.close(); |
||||
|
console.error("Patch db failed!!! Restoring the backup") |
||||
|
fs.copyFileSync(backupPath, Database.path); |
||||
|
console.error(ex) |
||||
|
|
||||
|
console.error("Start Uptime-Kuma failed due to patch db failed") |
||||
|
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues") |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself |
||||
|
* @param filename |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
static async importSQLFile(filename) { |
||||
|
|
||||
|
await R.getCell("SELECT 1"); |
||||
|
|
||||
|
let text = fs.readFileSync(filename).toString(); |
||||
|
|
||||
|
// Remove all comments (--)
|
||||
|
let lines = text.split("\n"); |
||||
|
lines = lines.filter((line) => { |
||||
|
return ! line.startsWith("--") |
||||
|
}); |
||||
|
|
||||
|
// Split statements by semicolon
|
||||
|
// Filter out empty line
|
||||
|
text = lines.join("\n") |
||||
|
|
||||
|
let statements = text.split(";") |
||||
|
.map((statement) => { |
||||
|
return statement.trim(); |
||||
|
}) |
||||
|
.filter((statement) => { |
||||
|
return statement !== ""; |
||||
|
}) |
||||
|
|
||||
|
for (let statement of statements) { |
||||
|
await R.exec(statement); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Special handle, because tarn.js throw a promise reject that cannot be caught |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
static async close() { |
||||
|
const listener = (reason, p) => { |
||||
|
Database.noReject = false; |
||||
|
}; |
||||
|
process.addListener("unhandledRejection", listener); |
||||
|
|
||||
|
console.log("Closing DB") |
||||
|
|
||||
|
while (true) { |
||||
|
Database.noReject = true; |
||||
|
await R.close() |
||||
|
await sleep(2000) |
||||
|
|
||||
|
if (Database.noReject) { |
||||
|
break; |
||||
|
} else { |
||||
|
console.log("Waiting to close the db") |
||||
|
} |
||||
|
} |
||||
|
console.log("SQLite closed") |
||||
|
|
||||
|
process.removeListener("unhandledRejection", listener); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = Database; |
@ -0,0 +1,59 @@ |
|||||
|
const PrometheusClient = require('prom-client'); |
||||
|
|
||||
|
const commonLabels = [ |
||||
|
'monitor_name', |
||||
|
'monitor_type', |
||||
|
'monitor_url', |
||||
|
'monitor_hostname', |
||||
|
'monitor_port', |
||||
|
] |
||||
|
|
||||
|
const monitor_response_time = new PrometheusClient.Gauge({ |
||||
|
name: 'monitor_response_time', |
||||
|
help: 'Monitor Response Time (ms)', |
||||
|
labelNames: commonLabels |
||||
|
}); |
||||
|
|
||||
|
const monitor_status = new PrometheusClient.Gauge({ |
||||
|
name: 'monitor_status', |
||||
|
help: 'Monitor Status (1 = UP, 0= DOWN)', |
||||
|
labelNames: commonLabels |
||||
|
}); |
||||
|
|
||||
|
class Prometheus { |
||||
|
monitorLabelValues = {} |
||||
|
|
||||
|
constructor(monitor) { |
||||
|
this.monitorLabelValues = { |
||||
|
monitor_name: monitor.name, |
||||
|
monitor_type: monitor.type, |
||||
|
monitor_url: monitor.url, |
||||
|
monitor_hostname: monitor.hostname, |
||||
|
monitor_port: monitor.port |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
update(heartbeat) { |
||||
|
try { |
||||
|
monitor_status.set(this.monitorLabelValues, heartbeat.status) |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
if (typeof heartbeat.ping === 'number') { |
||||
|
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping) |
||||
|
} else { |
||||
|
// Is it good?
|
||||
|
monitor_response_time.set(this.monitorLabelValues, -1) |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
Prometheus |
||||
|
} |
@ -1,20 +0,0 @@ |
|||||
/* |
|
||||
* Common functions - can be used in frontend or backend |
|
||||
*/ |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
export function sleep(ms) { |
|
||||
return new Promise(resolve => setTimeout(resolve, ms)); |
|
||||
} |
|
||||
|
|
||||
export function ucfirst(str) { |
|
||||
if (! str) { |
|
||||
return str; |
|
||||
} |
|
||||
|
|
||||
const firstLetter = str.substr(0, 1); |
|
||||
return firstLetter.toUpperCase() + str.substr(1); |
|
||||
} |
|
||||
|
|
@ -0,0 +1,10 @@ |
|||||
|
import { library } from "@fortawesome/fontawesome-svg-core" |
||||
|
import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" |
||||
|
//import { fa } from '@fortawesome/free-regular-svg-icons'
|
||||
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" |
||||
|
|
||||
|
// Add Free Font Awesome Icons here
|
||||
|
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
|
||||
|
library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList) |
||||
|
|
||||
|
export { FontAwesomeIcon } |
@ -0,0 +1,34 @@ |
|||||
|
"use strict"; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.PENDING = exports.UP = exports.DOWN = void 0; |
||||
|
exports.DOWN = 0; |
||||
|
exports.UP = 1; |
||||
|
exports.PENDING = 2; |
||||
|
function flipStatus(s) { |
||||
|
if (s === exports.UP) { |
||||
|
return exports.DOWN; |
||||
|
} |
||||
|
if (s === exports.DOWN) { |
||||
|
return exports.UP; |
||||
|
} |
||||
|
return s; |
||||
|
} |
||||
|
exports.flipStatus = flipStatus; |
||||
|
function sleep(ms) { |
||||
|
return new Promise(resolve => setTimeout(resolve, ms)); |
||||
|
} |
||||
|
exports.sleep = sleep; |
||||
|
function ucfirst(str) { |
||||
|
if (!str) { |
||||
|
return str; |
||||
|
} |
||||
|
const firstLetter = str.substr(0, 1); |
||||
|
return firstLetter.toUpperCase() + str.substr(1); |
||||
|
} |
||||
|
exports.ucfirst = ucfirst; |
||||
|
function debug(msg) { |
||||
|
if (process.env.NODE_ENV === "development") { |
||||
|
console.log(msg); |
||||
|
} |
||||
|
} |
||||
|
exports.debug = debug; |
@ -0,0 +1,43 @@ |
|||||
|
// Common Util for frontend and backend
|
||||
|
// Backend uses the compiled file util.js
|
||||
|
// Frontend uses util.ts
|
||||
|
// Need to run "tsc" to compile if there are any changes.
|
||||
|
|
||||
|
export const DOWN = 0; |
||||
|
export const UP = 1; |
||||
|
export const PENDING = 2; |
||||
|
|
||||
|
export function flipStatus(s) { |
||||
|
if (s === UP) { |
||||
|
return DOWN; |
||||
|
} |
||||
|
|
||||
|
if (s === DOWN) { |
||||
|
return UP; |
||||
|
} |
||||
|
|
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
export function sleep(ms) { |
||||
|
return new Promise(resolve => setTimeout(resolve, ms)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* PHP's ucfirst |
||||
|
* @param str |
||||
|
*/ |
||||
|
export function ucfirst(str) { |
||||
|
if (! str) { |
||||
|
return str; |
||||
|
} |
||||
|
|
||||
|
const firstLetter = str.substr(0, 1); |
||||
|
return firstLetter.toUpperCase() + str.substr(1); |
||||
|
} |
||||
|
|
||||
|
export function debug(msg) { |
||||
|
if (process.env.NODE_ENV === "development") { |
||||
|
console.log(msg) |
||||
|
} |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"compileOnSave": true, |
||||
|
"compilerOptions": { |
||||
|
"target": "ES2018", |
||||
|
"module": "commonjs", |
||||
|
"removeComments": true, |
||||
|
"preserveConstEnums": true, |
||||
|
"sourceMap": false, |
||||
|
"files.insertFinalNewline": true |
||||
|
}, |
||||
|
"files": [ |
||||
|
"./server/util.ts" |
||||
|
] |
||||
|
} |
Loading…
Reference in new issue