Compare commits
No commits in common. "master" and "1.14.0-beta.1" have entirely different histories.
master
...
1.14.0-bet
@ -1,7 +1,6 @@
|
|||||||
/.idea
|
/.idea
|
||||||
/node_modules
|
/node_modules
|
||||||
/data
|
/data
|
||||||
/cypress
|
|
||||||
/out
|
/out
|
||||||
/test
|
/test
|
||||||
/kubernetes
|
/kubernetes
|
||||||
@ -29,11 +28,6 @@ SECURITY.md
|
|||||||
tsconfig.json
|
tsconfig.json
|
||||||
.env
|
.env
|
||||||
/tmp
|
/tmp
|
||||||
/babel.config.js
|
|
||||||
/ecosystem.config.js
|
|
||||||
/extra/healthcheck.exe
|
|
||||||
/extra/healthcheck
|
|
||||||
|
|
||||||
|
|
||||||
### .gitignore content (commented rules are duplicated)
|
### .gitignore content (commented rules are duplicated)
|
||||||
|
|
||||||
@ -48,6 +42,4 @@ dist-ssr
|
|||||||
#!/data/.gitkeep
|
#!/data/.gitkeep
|
||||||
#.vscode
|
#.vscode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### End of .gitignore content
|
### End of .gitignore content
|
||||||
|
@ -19,6 +19,3 @@ indent_size = 2
|
|||||||
|
|
||||||
[*.vue]
|
[*.vue]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.go]
|
|
||||||
indent_style = tab
|
|
||||||
|
60
.eslintrc.js
60
.eslintrc.js
@ -1,9 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: [
|
|
||||||
"test/*",
|
|
||||||
"server/modules/apicache/*",
|
|
||||||
"src/util.js"
|
|
||||||
],
|
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
@ -22,48 +17,39 @@ module.exports = {
|
|||||||
requireConfigFile: false,
|
requireConfigFile: false,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"yoda": "error",
|
"linebreak-style": ["error", "unix"],
|
||||||
eqeqeq: [ "warn", "smart" ],
|
"camelcase": ["warn", {
|
||||||
"linebreak-style": [ "error", "unix" ],
|
|
||||||
"camelcase": [ "warn", {
|
|
||||||
"properties": "never",
|
"properties": "never",
|
||||||
"ignoreImports": true
|
"ignoreImports": true
|
||||||
}],
|
}],
|
||||||
"no-unused-vars": [ "warn", {
|
// override/add rules settings here, such as:
|
||||||
"args": "none"
|
// 'vue/no-unused-vars': 'error'
|
||||||
}],
|
"no-unused-vars": "warn",
|
||||||
indent: [
|
indent: [
|
||||||
"error",
|
"error",
|
||||||
4,
|
4,
|
||||||
{
|
{
|
||||||
ignoredNodes: [ "TemplateLiteral" ],
|
ignoredNodes: ["TemplateLiteral"],
|
||||||
SwitchCase: 1,
|
SwitchCase: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
quotes: [ "error", "double" ],
|
quotes: ["warn", "double"],
|
||||||
semi: "error",
|
semi: "warn",
|
||||||
"vue/html-indent": [ "error", 4 ], // default: 2
|
"vue/html-indent": ["warn", 4], // default: 2
|
||||||
"vue/max-attributes-per-line": "off",
|
"vue/max-attributes-per-line": "off",
|
||||||
"vue/singleline-html-element-content-newline": "off",
|
"vue/singleline-html-element-content-newline": "off",
|
||||||
"vue/html-self-closing": "off",
|
"vue/html-self-closing": "off",
|
||||||
"vue/require-component-is": "off", // not allow is="style" https://github.com/vuejs/eslint-plugin-vue/issues/462#issuecomment-430234675
|
|
||||||
"vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly
|
"vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly
|
||||||
"vue/multi-word-component-names": "off",
|
"no-multi-spaces": ["error", {
|
||||||
"no-multi-spaces": [ "error", {
|
|
||||||
ignoreEOLComments: true,
|
ignoreEOLComments: true,
|
||||||
}],
|
}],
|
||||||
"array-bracket-spacing": [ "warn", "always", {
|
"space-before-function-paren": ["error", {
|
||||||
"singleValue": true,
|
|
||||||
"objectsInArrays": false,
|
|
||||||
"arraysInArrays": false
|
|
||||||
}],
|
|
||||||
"space-before-function-paren": [ "error", {
|
|
||||||
"anonymous": "always",
|
"anonymous": "always",
|
||||||
"named": "never",
|
"named": "never",
|
||||||
"asyncArrow": "always"
|
"asyncArrow": "always"
|
||||||
}],
|
}],
|
||||||
"curly": "error",
|
"curly": "error",
|
||||||
"object-curly-spacing": [ "error", "always" ],
|
"object-curly-spacing": ["error", "always"],
|
||||||
"object-curly-newline": "off",
|
"object-curly-newline": "off",
|
||||||
"object-property-newline": "error",
|
"object-property-newline": "error",
|
||||||
"comma-spacing": "error",
|
"comma-spacing": "error",
|
||||||
@ -73,37 +59,37 @@ module.exports = {
|
|||||||
"keyword-spacing": "warn",
|
"keyword-spacing": "warn",
|
||||||
"space-infix-ops": "warn",
|
"space-infix-ops": "warn",
|
||||||
"arrow-spacing": "warn",
|
"arrow-spacing": "warn",
|
||||||
"no-trailing-spaces": "error",
|
"no-trailing-spaces": "warn",
|
||||||
"no-constant-condition": [ "error", {
|
"no-constant-condition": ["error", {
|
||||||
"checkLoops": false,
|
"checkLoops": false,
|
||||||
}],
|
}],
|
||||||
"space-before-blocks": "warn",
|
"space-before-blocks": "warn",
|
||||||
//'no-console': 'warn',
|
//'no-console': 'warn',
|
||||||
"no-extra-boolean-cast": "off",
|
"no-extra-boolean-cast": "off",
|
||||||
"no-multiple-empty-lines": [ "warn", {
|
"no-multiple-empty-lines": ["warn", {
|
||||||
"max": 1,
|
"max": 1,
|
||||||
"maxBOF": 0,
|
"maxBOF": 0,
|
||||||
}],
|
}],
|
||||||
"lines-between-class-members": [ "warn", "always", {
|
"lines-between-class-members": ["warn", "always", {
|
||||||
exceptAfterSingleLine: true,
|
exceptAfterSingleLine: true,
|
||||||
}],
|
}],
|
||||||
"no-unneeded-ternary": "error",
|
"no-unneeded-ternary": "error",
|
||||||
"array-bracket-newline": [ "error", "consistent" ],
|
"array-bracket-newline": ["error", "consistent"],
|
||||||
"eol-last": [ "error", "always" ],
|
"eol-last": ["error", "always"],
|
||||||
//'prefer-template': 'error',
|
//'prefer-template': 'error',
|
||||||
"comma-dangle": [ "warn", "only-multiline" ],
|
"comma-dangle": ["warn", "only-multiline"],
|
||||||
"no-empty": [ "error", {
|
"no-empty": ["error", {
|
||||||
"allowEmptyCatch": true
|
"allowEmptyCatch": true
|
||||||
}],
|
}],
|
||||||
"no-control-regex": "off",
|
"no-control-regex": "off",
|
||||||
"one-var": [ "error", "never" ],
|
"one-var": ["error", "never"],
|
||||||
"max-statements-per-line": [ "error", { "max": 1 }]
|
"max-statements-per-line": ["error", { "max": 1 }]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": [ "src/languages/*.js", "src/icon.js" ],
|
"files": [ "src/languages/*.js", "src/icon.js" ],
|
||||||
"rules": {
|
"rules": {
|
||||||
"comma-dangle": [ "error", "always-multiline" ],
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,9 +1,3 @@
|
|||||||
⚠️⚠️⚠️ Since we do not accept all types of pull requests and do not want to waste your time. Please be sure that you have read pull request rules:
|
|
||||||
https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma
|
|
||||||
|
|
||||||
Tick the checkbox if you understand [x]:
|
|
||||||
- [ ] I have read and understand the pull request rules.
|
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
Fixes #(issue)
|
Fixes #(issue)
|
||||||
@ -26,7 +20,6 @@ Please delete any options that are not relevant.
|
|||||||
- [ ] I ran ESLint and other linters for modified files
|
- [ ] I ran ESLint and other linters for modified files
|
||||||
- [ ] I have performed a self-review of my own code and tested it
|
- [ ] I have performed a self-review of my own code and tested it
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||||
(including JSDoc for methods)
|
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
- [ ] My code needed automated testing. I have added them (this is optional task)
|
- [ ] My code needed automated testing. I have added them (this is optional task)
|
||||||
|
|
||||||
|
61
.github/workflows/auto-test.yml
vendored
61
.github/workflows/auto-test.yml
vendored
@ -11,74 +11,25 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-test:
|
auto-test:
|
||||||
needs: [ check-linters ]
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
node: [ 14, 16, 18, 19 ]
|
node-version: [14.x, 16.x, 17.x]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- run: npm install
|
- run: npm run install-legacy
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm test
|
- run: npm test
|
||||||
env:
|
env:
|
||||||
HEADLESS_TEST: 1
|
HEADLESS_TEST: 1
|
||||||
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
||||||
check-linters:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Use Node.js 14
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 14
|
|
||||||
cache: 'npm'
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run lint
|
|
||||||
|
|
||||||
e2e-tests:
|
|
||||||
needs: [ check-linters ]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Use Node.js 14
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 14
|
|
||||||
cache: 'npm'
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build
|
|
||||||
- run: npm run cy:test
|
|
||||||
|
|
||||||
frontend-unit-tests:
|
|
||||||
needs: [ check-linters ]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Use Node.js 14
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 14
|
|
||||||
cache: 'npm'
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build
|
|
||||||
- run: npm run cy:run:unit
|
|
||||||
|
22
.github/workflows/stale-bot.yml
vendored
22
.github/workflows/stale-bot.yml
vendored
@ -1,22 +0,0 @@
|
|||||||
name: 'Automatically close stale issues and PRs'
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 */6 * * *'
|
|
||||||
#Run every 6 hours
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v5
|
|
||||||
with:
|
|
||||||
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
|
||||||
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.'
|
|
||||||
days-before-stale: 90
|
|
||||||
days-before-close: 2
|
|
||||||
days-before-pr-stale: 999999999
|
|
||||||
days-before-pr-close: 1
|
|
||||||
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request'
|
|
||||||
exempt-issue-assignees: 'louislam'
|
|
||||||
operations-per-run: 200
|
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -13,10 +13,3 @@ dist-ssr
|
|||||||
/out
|
/out
|
||||||
/tmp
|
/tmp
|
||||||
.env
|
.env
|
||||||
|
|
||||||
cypress/videos
|
|
||||||
cypress/screenshots
|
|
||||||
|
|
||||||
/extra/healthcheck.exe
|
|
||||||
/extra/healthcheck
|
|
||||||
/extra/healthcheck-armv7
|
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "stylelint-config-standard",
|
"extends": "stylelint-config-standard",
|
||||||
"customSyntax": "postcss-html",
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"indentation": 4,
|
"indentation": 4,
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
"selector-list-comma-newline-after": null,
|
"selector-list-comma-newline-after": null,
|
||||||
"declaration-empty-line-before": null,
|
"declaration-empty-line-before": null
|
||||||
"alpha-value-notation": "number",
|
|
||||||
"color-function-notation": "legacy",
|
|
||||||
"shorthand-property-no-redundant-values": null,
|
|
||||||
"color-hex-length": null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
107
CONTRIBUTING.md
107
CONTRIBUTING.md
@ -1,6 +1,6 @@
|
|||||||
# Project Info
|
# Project Info
|
||||||
|
|
||||||
First of all, I want to thank everyone who made pull requests for Uptime Kuma. I never thought the GitHub Community would be so nice! Because of this, I also never thought that other people would actually read and edit my code. It is not very well structured or commented, sorry about that.
|
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 structured and commented so well, lol. Sorry about that.
|
||||||
|
|
||||||
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
|
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
|
||||||
|
|
||||||
@ -27,43 +27,23 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
|||||||
|
|
||||||
## Can I create a pull request for Uptime Kuma?
|
## Can I create a pull request for Uptime Kuma?
|
||||||
|
|
||||||
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**. Especially for a large pull request or you don't know it will be merged or not.
|
⚠️ 2022-03-02 Update:
|
||||||
|
|
||||||
Here are some references:
|
Since I found that merging pull requests is a pretty heavy task for me, I try to rearrange it.
|
||||||
|
|
||||||
✅ Usually Accept:
|
✅ Accept:
|
||||||
- Bug/Security fix
|
- Bug/Security fix
|
||||||
- Translations
|
- Translations
|
||||||
- Adding notification providers
|
- Adding notification providers
|
||||||
|
|
||||||
⚠️ Discussion First
|
❌ Avoid:
|
||||||
- Large pull requests
|
- Large pull requests
|
||||||
- New features
|
- New big features
|
||||||
|
|
||||||
❌ Won't Merge
|
|
||||||
- Do not pass auto test
|
|
||||||
- Any breaking changes
|
|
||||||
- Duplicated pull request
|
|
||||||
- Buggy
|
|
||||||
- UI/UX is not close to Uptime Kuma
|
|
||||||
- Existing logic is completely modified or deleted for no reason
|
|
||||||
- A function that is completely out of scope
|
|
||||||
- Convert existing code into other programming languages
|
|
||||||
- Unnecessary large code changes (Hard to review, causes code conflicts to other pull requests)
|
|
||||||
|
|
||||||
The above cases cannot cover all situations.
|
|
||||||
|
|
||||||
I (@louislam) have the final say. If your pull request does not meet my expectations, I will reject it, no matter how much time you spend on it. Therefore, it is essential to have a discussion beforehand.
|
|
||||||
|
|
||||||
I will mark your pull request in the [milestones](https://github.com/louislam/uptime-kuma/milestones), if I am plan to review and merge it.
|
|
||||||
|
|
||||||
Also, please don't rush or ask for ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
|
|
||||||
|
|
||||||
|
My long story here: https://www.reddit.com/r/UptimeKuma/comments/t1t6or/comment/hynyijx/
|
||||||
|
|
||||||
### Recommended Pull Request Guideline
|
### Recommended Pull Request Guideline
|
||||||
|
|
||||||
Before deep into coding, discussion first is preferred. Creating an empty pull request for discussion would be recommended.
|
|
||||||
|
|
||||||
1. Fork the project
|
1. Fork the project
|
||||||
1. Clone your fork repo to local
|
1. Clone your fork repo to local
|
||||||
1. Create a new branch
|
1. Create a new branch
|
||||||
@ -73,38 +53,42 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
|
|||||||
1. Create a pull request: https://github.com/louislam/uptime-kuma/compare
|
1. Create a pull request: https://github.com/louislam/uptime-kuma/compare
|
||||||
1. Write a proper description
|
1. Write a proper description
|
||||||
1. Click "Change to draft"
|
1. Click "Change to draft"
|
||||||
1. Discussion
|
|
||||||
|
#### ❌ Won't Merge
|
||||||
|
|
||||||
|
- Any breaking changes
|
||||||
|
- Duplicated pull request
|
||||||
|
- Buggy
|
||||||
|
- Existing logic is completely modified or deleted
|
||||||
|
- A function that is completely out of scope
|
||||||
|
|
||||||
## Project Styles
|
## Project Styles
|
||||||
|
|
||||||
I personally do not like it when something requires so much learning and configuration before you can finally start the app.
|
I personally do not like something need to learn so much and need to config so much before you can finally start the app.
|
||||||
|
|
||||||
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort required to get it running
|
- 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-compose file. Just map the volume and expose the port, then good to go
|
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
||||||
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`.
|
- Settings should be configurable in the frontend. Env var is not encouraged.
|
||||||
- Easy to use
|
- Easy to use
|
||||||
- The web UI styling should be consistent and nice.
|
|
||||||
|
|
||||||
## Coding Styles
|
## Coding Styles
|
||||||
|
|
||||||
- 4 spaces indentation
|
- 4 spaces indentation
|
||||||
- Follow `.editorconfig`
|
- Follow `.editorconfig`
|
||||||
- Follow ESLint
|
- Follow ESLint
|
||||||
- Methods and functions should be documented with JSDoc
|
|
||||||
|
|
||||||
## Name convention
|
## Name convention
|
||||||
|
|
||||||
- Javascript/Typescript: camelCaseType
|
- Javascript/Typescript: camelCaseType
|
||||||
- SQLite: snake_case (Underscore)
|
- SQLite: underscore_type
|
||||||
- CSS/SCSS: kebab-case (Dash)
|
- CSS/SCSS: dash-type
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
- Node.js >= 14
|
- Node.js >= 14
|
||||||
- NPM >= 8.5
|
|
||||||
- Git
|
- Git
|
||||||
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
||||||
- A SQLite GUI tool (SQLite Expert Personal is suggested)
|
- A SQLite tool (SQLite Expert Personal is suggested)
|
||||||
|
|
||||||
## Install dependencies
|
## Install dependencies
|
||||||
|
|
||||||
@ -112,45 +96,39 @@ I personally do not like it when something requires so much learning and configu
|
|||||||
npm ci
|
npm ci
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dev Server
|
## How to start the Backend Dev Server
|
||||||
|
|
||||||
(2022-04-26 Update)
|
(2021-09-23 Update)
|
||||||
|
|
||||||
We can start the frontend dev server and the backend dev server in one command.
|
|
||||||
|
|
||||||
Port `3000` and port `3001` will be used.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run start-server-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Backend Server
|
|
||||||
|
|
||||||
It binds to `0.0.0.0:3001` by default.
|
It binds to `0.0.0.0:3001` by default.
|
||||||
|
|
||||||
|
### Backend Details
|
||||||
|
|
||||||
It is mainly a socket.io app + express.js.
|
It is mainly a socket.io app + express.js.
|
||||||
|
|
||||||
express.js is used for:
|
express.js is just used for serving the frontend built files (index.html, .js and .css etc.)
|
||||||
- entry point such as redirecting to a status page or the dashboard
|
|
||||||
- serving the frontend built files (index.html, .js and .css etc.)
|
|
||||||
- serving internal APIs of status page
|
|
||||||
|
|
||||||
|
|
||||||
### Structure in /server/
|
|
||||||
|
|
||||||
- model/ (Object model, auto mapping to the database table name)
|
- model/ (Object model, auto mapping to the database table name)
|
||||||
- modules/ (Modified 3rd-party modules)
|
- modules/ (Modified 3rd-party modules)
|
||||||
- notification-providers/ (individual notification logic)
|
- notification-providers/ (individual notification logic)
|
||||||
- routers/ (Express Routers)
|
- routers/ (Express Routers)
|
||||||
- socket-handler (Socket.io Handlers)
|
- socket-handler (Socket.io Handlers)
|
||||||
- server.js (Server entry point and main logic)
|
- server.js (Server main logic)
|
||||||
|
|
||||||
## Frontend Dev Server
|
## How to start the Frontend Dev Server
|
||||||
|
|
||||||
It binds to `0.0.0.0:3000` by default. Frontend dev server is used for development only.
|
1. Set the env var `NODE_ENV` to "development".
|
||||||
|
2. Start the frontend dev server by the following command.
|
||||||
|
|
||||||
For production, it is not used. It will be compiled to `dist` directory instead.
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
It binds to `0.0.0.0:3000` by default.
|
||||||
|
|
||||||
You can use Vue.js devtools Chrome extension for debugging.
|
You can use Vue.js devtools Chrome extension for debugging.
|
||||||
|
|
||||||
@ -177,23 +155,16 @@ The data and socket logic are in `src/mixins/socket.js`.
|
|||||||
|
|
||||||
## Unit Test
|
## Unit Test
|
||||||
|
|
||||||
|
It is an end-to-end testing. It is using Jest and Puppeteer.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
By default, the Chromium window will be shown up during the test. Specifying `HEADLESS_TEST=1` for terminal environments.
|
||||||
|
|
||||||
Both frontend and backend share the same package.json. However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into dist files. So:
|
## Update Dependencies
|
||||||
|
|
||||||
- Frontend dependencies = "devDependencies"
|
|
||||||
- Examples: vue, chart.js
|
|
||||||
- Backend dependencies = "dependencies"
|
|
||||||
- Examples: socket.io, sqlite3
|
|
||||||
- Development dependencies = "devDependencies"
|
|
||||||
- Examples: eslint, sass
|
|
||||||
|
|
||||||
### Update Dependencies
|
|
||||||
|
|
||||||
Install `ncu`
|
Install `ncu`
|
||||||
https://github.com/raineorshine/npm-check-updates
|
https://github.com/raineorshine/npm-check-updates
|
||||||
|
74
README.md
74
README.md
@ -7,51 +7,47 @@
|
|||||||
<img src="./public/icon.svg" width="128" alt="" />
|
<img src="./public/icon.svg" width="128" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Uptime Kuma is an easy-to-use self-hosted monitoring tool.
|
It is a self-hosted monitoring tool like "Uptime Robot".
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/1336778/212262296-e6205815-ad62-488c-83ec-a5b0d0689f7c.jpg" width="700" alt="" />
|
<img src="https://uptime.kuma.pet/img/dark.jpg" width="700" alt="" />
|
||||||
|
|
||||||
## 🥔 Live Demo
|
## 🥔 Live Demo
|
||||||
|
|
||||||
Try it!
|
Try it!
|
||||||
|
|
||||||
- Tokyo Demo Server: https://demo.uptime.kuma.pet (Sponsored by [Uptime Kuma Sponsors](https://github.com/louislam/uptime-kuma#%EF%B8%8F-sponsors))
|
https://demo.uptime.kuma.pet
|
||||||
- Europe Demo Server: https://demo.uptime-kuma.karimi.dev:27000 (Provided by [@mhkarimi1383](https://github.com/mhkarimi1383))
|
|
||||||
|
|
||||||
It is a temporary live demo, all data will be deleted after 10 minutes. Use the one that is closer to you, but I suggest that you should install and try it out for the best demo experience.
|
It is a temporary live demo, all data will be deleted after 10 minutes. The server is located in Tokyo, so if you live far from there, it may affect your experience. I suggest that you should install and try it out for the best demo experience.
|
||||||
|
|
||||||
|
VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much!
|
||||||
|
|
||||||
## ⭐ Features
|
## ⭐ Features
|
||||||
|
|
||||||
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server / Docker Containers
|
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server.
|
||||||
* Fancy, Reactive, Fast UI/UX
|
* Fancy, Reactive, Fast UI/UX.
|
||||||
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications)
|
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications).
|
||||||
* 20 second intervals
|
* 20 second intervals.
|
||||||
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
|
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
|
||||||
* Multiple status pages
|
* Simple Status Page
|
||||||
* Map status pages to specific domains
|
* Ping Chart
|
||||||
* Ping chart
|
* Certificate Info
|
||||||
* Certificate info
|
|
||||||
* Proxy support
|
|
||||||
* 2FA support
|
|
||||||
|
|
||||||
## 🔧 How to Install
|
## 🔧 How to Install
|
||||||
|
|
||||||
### 🐳 Docker
|
### 🐳 Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
docker volume create uptime-kuma
|
||||||
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️ Please use a **local volume** only. Other types such as NFS are not supported.
|
⚠️ Please use a **local volume** only. Other types such as NFS are not supported.
|
||||||
|
|
||||||
Uptime Kuma is now running on http://localhost:3001
|
Browse to http://localhost:3001 after starting.
|
||||||
|
|
||||||
### 💪🏻 Non-Docker
|
### 💪🏻 Non-Docker
|
||||||
|
|
||||||
Required Tools:
|
Required Tools: Node.js >= 14, git and pm2.
|
||||||
- [Node.js](https://nodejs.org/en/download/) >= 14
|
|
||||||
- [Git](https://git-scm.com/downloads)
|
|
||||||
- [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Update your npm to the latest version
|
# Update your npm to the latest version
|
||||||
@ -71,20 +67,12 @@ npm install pm2 -g && pm2 install pm2-logrotate
|
|||||||
# Start Server
|
# Start Server
|
||||||
pm2 start server/server.js --name uptime-kuma
|
pm2 start server/server.js --name uptime-kuma
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
Uptime Kuma is now running on http://localhost:3001
|
|
||||||
|
|
||||||
More useful PM2 Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# If you want to see the current console output
|
# If you want to see the current console output
|
||||||
pm2 monit
|
pm2 monit
|
||||||
|
|
||||||
# If you want to add it to startup
|
|
||||||
pm2 save && pm2 startup
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Browse to http://localhost:3001 after starting.
|
||||||
|
|
||||||
### Advanced Installation
|
### Advanced Installation
|
||||||
|
|
||||||
If you need more options or need to browse via a reverse proxy, please read:
|
If you need more options or need to browse via a reverse proxy, please read:
|
||||||
@ -105,7 +93,7 @@ https://github.com/louislam/uptime-kuma/milestones
|
|||||||
|
|
||||||
Project Plan:
|
Project Plan:
|
||||||
|
|
||||||
https://github.com/users/louislam/projects/4/views/1
|
https://github.com/louislam/uptime-kuma/projects/1
|
||||||
|
|
||||||
## ❤️ Sponsors
|
## ❤️ Sponsors
|
||||||
|
|
||||||
@ -150,30 +138,16 @@ You can discuss or ask for help in [issues](https://github.com/louislam/uptime-k
|
|||||||
|
|
||||||
### Subreddit
|
### Subreddit
|
||||||
|
|
||||||
My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).
|
My Reddit account: louislamlam
|
||||||
You can mention me if you ask a question on Reddit.
|
You can mention me if you ask a question on Reddit.
|
||||||
[r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
|
https://www.reddit.com/r/UptimeKuma/
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
### Test Pull Requests
|
If you want to report a bug or request a new feature. Free feel to open a [new issue](https://github.com/louislam/uptime-kuma/issues).
|
||||||
|
|
||||||
There are a lot of pull requests right now, but I don't have time to test them all.
|
|
||||||
|
|
||||||
If you want to help, you can check this:
|
|
||||||
https://github.com/louislam/uptime-kuma/wiki/Test-Pull-Requests
|
|
||||||
|
|
||||||
### Test Beta Version
|
|
||||||
|
|
||||||
Check out the latest beta release here: https://github.com/louislam/uptime-kuma/releases
|
|
||||||
|
|
||||||
### Bug Reports / Feature Requests
|
|
||||||
If you want to report a bug or request a new feature, feel free to open a [new issue](https://github.com/louislam/uptime-kuma/issues).
|
|
||||||
|
|
||||||
### Translations
|
|
||||||
If you want to translate Uptime Kuma into your language, please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
If you want to translate Uptime Kuma into your language, please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
||||||
|
|
||||||
Feel free to correct my grammar in this README, source code, or wiki, as my mother language is not English and my grammar is not that great.
|
If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
### Create Pull Requests
|
Unfortunately, English proofreading is needed too because my grammar is not that great. Feel free to correct my grammar in this README, source code, or wiki.
|
||||||
If you want to modify Uptime Kuma, please read this guide and follow the rules here: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
|
||||||
|
16
SECURITY.md
16
SECURITY.md
@ -2,15 +2,21 @@
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report security issues to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
Please report security issues to uptime@kuma.pet.
|
||||||
|
|
||||||
Do not use the public issue tracker or discuss it in the public as it will cause more damage.
|
Do not use the issue tracker or discuss it in the public as it will cause more damage.
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
### Uptime Kuma Versions
|
### Uptime Kuma Versions
|
||||||
|
|
||||||
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` versions are upgradable to the lastest version.
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 1.9.X | :white_check_mark: |
|
||||||
|
| <= 1.8.X | ❌ |
|
||||||
|
|
||||||
### Upgradable Docker Tags
|
### Upgradable Docker Tags
|
||||||
|
|
||||||
@ -18,8 +24,8 @@ You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` vers
|
|||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 1 | :white_check_mark: |
|
| 1 | :white_check_mark: |
|
||||||
| 1-debian | :white_check_mark: |
|
| 1-debian | :white_check_mark: |
|
||||||
|
| 1-alpine | :white_check_mark: |
|
||||||
| latest | :white_check_mark: |
|
| latest | :white_check_mark: |
|
||||||
| debian | :white_check_mark: |
|
| debian | :white_check_mark: |
|
||||||
| 1-alpine | ⚠️ Deprecated |
|
| alpine | :white_check_mark: |
|
||||||
| alpine | ⚠️ Deprecated |
|
|
||||||
| All other tags | ❌ |
|
| All other tags | ❌ |
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
const config = {};
|
const config = {};
|
||||||
|
|
||||||
if (process.env.TEST_FRONTEND) {
|
if (process.env.TEST_FRONTEND) {
|
||||||
config.presets = [ "@babel/preset-env" ];
|
config.presets = ["@babel/preset-env"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.TEST_BACKEND) {
|
if (process.env.TEST_BACKEND) {
|
||||||
config.plugins = [ "babel-plugin-rewire" ];
|
config.plugins = ["babel-plugin-rewire"];
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
const { defineConfig } = require("cypress");
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
|
||||||
projectId: "vyjuem",
|
|
||||||
e2e: {
|
|
||||||
experimentalStudio: true,
|
|
||||||
setupNodeEvents(on, config) {
|
|
||||||
|
|
||||||
},
|
|
||||||
fixturesFolder: "test/cypress/fixtures",
|
|
||||||
screenshotsFolder: "test/cypress/screenshots",
|
|
||||||
videosFolder: "test/cypress/videos",
|
|
||||||
downloadsFolder: "test/cypress/downloads",
|
|
||||||
supportFile: "test/cypress/support/e2e.js",
|
|
||||||
baseUrl: "http://localhost:3002",
|
|
||||||
defaultCommandTimeout: 10000,
|
|
||||||
pageLoadTimeout: 60000,
|
|
||||||
viewportWidth: 1920,
|
|
||||||
viewportHeight: 1080,
|
|
||||||
specPattern: [
|
|
||||||
"test/cypress/e2e/setup.cy.js",
|
|
||||||
"test/cypress/e2e/**/*.js"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
baseUrl: "http://localhost:3002",
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
const { defineConfig } = require("cypress");
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
|
||||||
e2e: {
|
|
||||||
supportFile: false,
|
|
||||||
specPattern: [
|
|
||||||
"test/cypress/unit/**/*.js"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
});
|
|
33
config/jest-debug-env.js
Normal file
33
config/jest-debug-env.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const PuppeteerEnvironment = require("jest-environment-puppeteer");
|
||||||
|
const util = require("util");
|
||||||
|
|
||||||
|
class DebugEnv extends PuppeteerEnvironment {
|
||||||
|
async handleTestEvent(event, state) {
|
||||||
|
const ignoredEvents = [
|
||||||
|
"setup",
|
||||||
|
"add_hook",
|
||||||
|
"start_describe_definition",
|
||||||
|
"add_test",
|
||||||
|
"finish_describe_definition",
|
||||||
|
"run_start",
|
||||||
|
"run_describe_start",
|
||||||
|
"test_start",
|
||||||
|
"hook_start",
|
||||||
|
"hook_success",
|
||||||
|
"test_fn_start",
|
||||||
|
"test_fn_success",
|
||||||
|
"test_done",
|
||||||
|
"run_describe_finish",
|
||||||
|
"run_finish",
|
||||||
|
"teardown",
|
||||||
|
"test_fn_failure",
|
||||||
|
];
|
||||||
|
if (!ignoredEvents.includes(event.name)) {
|
||||||
|
console.log(
|
||||||
|
new Date().toString() + ` Unhandled event [${event.name}] ` + util.inspect(event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DebugEnv;
|
5
config/jest-frontend.config.js
Normal file
5
config/jest-frontend.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
"rootDir": "..",
|
||||||
|
"testRegex": "./test/frontend.spec.js",
|
||||||
|
};
|
||||||
|
|
20
config/jest-puppeteer.config.js
Normal file
20
config/jest-puppeteer.config.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
"launch": {
|
||||||
|
"dumpio": true,
|
||||||
|
"slowMo": 500,
|
||||||
|
"headless": process.env.HEADLESS_TEST || false,
|
||||||
|
"userDataDir": "./data/test-chrome-profile",
|
||||||
|
args: [
|
||||||
|
"--disable-setuid-sandbox",
|
||||||
|
"--disable-gpu",
|
||||||
|
"--disable-dev-shm-usage",
|
||||||
|
"--no-default-browser-check",
|
||||||
|
"--no-experiments",
|
||||||
|
"--no-first-run",
|
||||||
|
"--no-pings",
|
||||||
|
"--no-sandbox",
|
||||||
|
"--no-zygote",
|
||||||
|
"--single-process",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
};
|
12
config/jest.config.js
Normal file
12
config/jest.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module.exports = {
|
||||||
|
"verbose": true,
|
||||||
|
"preset": "jest-puppeteer",
|
||||||
|
"globals": {
|
||||||
|
"__DEV__": true
|
||||||
|
},
|
||||||
|
"testRegex": "./test/e2e.spec.js",
|
||||||
|
"testEnvironment": "./config/jest-debug-env.js",
|
||||||
|
"rootDir": "..",
|
||||||
|
"testTimeout": 30000,
|
||||||
|
};
|
||||||
|
|
@ -1,53 +1,24 @@
|
|||||||
import legacy from "@vitejs/plugin-legacy";
|
import legacy from "@vitejs/plugin-legacy";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import visualizer from "rollup-plugin-visualizer";
|
|
||||||
import viteCompression from "vite-plugin-compression";
|
|
||||||
|
|
||||||
const postCssScss = require("postcss-scss");
|
const postCssScss = require("postcss-scss");
|
||||||
const postcssRTLCSS = require("postcss-rtlcss");
|
const postcssRTLCSS = require("postcss-rtlcss");
|
||||||
|
|
||||||
const viteCompressionFilter = /\.(js|mjs|json|css|html|svg)$/i;
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
},
|
|
||||||
define: {
|
|
||||||
"FRONTEND_VERSION": JSON.stringify(process.env.npm_package_version),
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
legacy({
|
legacy({
|
||||||
targets: [ "since 2015" ],
|
targets: ["ie > 11"],
|
||||||
}),
|
additionalLegacyPolyfills: ["regenerator-runtime/runtime"]
|
||||||
visualizer({
|
})
|
||||||
filename: "tmp/dist-stats.html"
|
|
||||||
}),
|
|
||||||
viteCompression({
|
|
||||||
algorithm: "gzip",
|
|
||||||
filter: viteCompressionFilter,
|
|
||||||
}),
|
|
||||||
viteCompression({
|
|
||||||
algorithm: "brotliCompress",
|
|
||||||
filter: viteCompressionFilter,
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
css: {
|
css: {
|
||||||
postcss: {
|
postcss: {
|
||||||
"parser": postCssScss,
|
"parser": postCssScss,
|
||||||
"map": false,
|
"map": false,
|
||||||
"plugins": [ postcssRTLCSS ]
|
"plugins": [postcssRTLCSS]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
manualChunks(id, { getModuleInfo, getModuleIds }) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
ALTER TABLE monitor_group
|
|
||||||
ADD send_url BOOLEAN DEFAULT 0 NOT NULL;
|
|
||||||
COMMIT;
|
|
@ -1,18 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
CREATE TABLE docker_host (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
docker_daemon VARCHAR(255),
|
|
||||||
docker_type VARCHAR(255),
|
|
||||||
name VARCHAR(255)
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD docker_host INTEGER REFERENCES docker_host(id);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD docker_container VARCHAR(255);
|
|
||||||
|
|
||||||
COMMIT;
|
|
@ -1,18 +0,0 @@
|
|||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD auth_method VARCHAR(250);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD auth_domain TEXT;
|
|
||||||
ALTER TABLE monitor
|
|
||||||
|
|
||||||
ADD auth_workstation TEXT;
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
UPDATE monitor
|
|
||||||
SET auth_method = 'basic'
|
|
||||||
WHERE basic_auth_user is not null;
|
|
||||||
COMMIT;
|
|
@ -1,18 +0,0 @@
|
|||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD radius_username VARCHAR(255);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD radius_password VARCHAR(255);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD radius_calling_station_id VARCHAR(50);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD radius_called_station_id VARCHAR(50);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD radius_secret VARCHAR(255);
|
|
||||||
|
|
||||||
COMMIT
|
|
@ -1,10 +0,0 @@
|
|||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD database_connection_string VARCHAR(2000);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD database_query TEXT;
|
|
||||||
|
|
||||||
|
|
||||||
COMMIT
|
|
@ -1,16 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD mqtt_topic TEXT;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD mqtt_success_message VARCHAR(255);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD mqtt_username VARCHAR(255);
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD mqtt_password VARCHAR(255);
|
|
||||||
|
|
||||||
COMMIT;
|
|
@ -1,25 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_url VARCHAR(255) default null;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_protobuf TEXT default null;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_body TEXT default null;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_metadata TEXT default null;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_method VARCHAR(255) default null;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_service_name VARCHAR(255) default null;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD grpc_enable_tls BOOLEAN default 0 not null;
|
|
||||||
|
|
||||||
COMMIT;
|
|
@ -1,83 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
-- Just for someone who tested maintenance before (patch-maintenance-table.sql)
|
|
||||||
DROP TABLE IF EXISTS maintenance_status_page;
|
|
||||||
DROP TABLE IF EXISTS monitor_maintenance;
|
|
||||||
DROP TABLE IF EXISTS maintenance;
|
|
||||||
DROP TABLE IF EXISTS maintenance_timeslot;
|
|
||||||
|
|
||||||
-- maintenance
|
|
||||||
CREATE TABLE [maintenance] (
|
|
||||||
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
[title] VARCHAR(150) NOT NULL,
|
|
||||||
[description] TEXT NOT NULL,
|
|
||||||
[user_id] INTEGER REFERENCES [user]([id]) ON DELETE SET NULL ON UPDATE CASCADE,
|
|
||||||
[active] BOOLEAN NOT NULL DEFAULT 1,
|
|
||||||
[strategy] VARCHAR(50) NOT NULL DEFAULT 'single',
|
|
||||||
[start_date] DATETIME,
|
|
||||||
[end_date] DATETIME,
|
|
||||||
[start_time] TIME,
|
|
||||||
[end_time] TIME,
|
|
||||||
[weekdays] VARCHAR2(250) DEFAULT '[]',
|
|
||||||
[days_of_month] TEXT DEFAULT '[]',
|
|
||||||
[interval_day] INTEGER
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX [manual_active] ON [maintenance] (
|
|
||||||
[strategy],
|
|
||||||
[active]
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX [active] ON [maintenance] ([active]);
|
|
||||||
|
|
||||||
CREATE INDEX [maintenance_user_id] ON [maintenance] ([user_id]);
|
|
||||||
|
|
||||||
-- maintenance_status_page
|
|
||||||
CREATE TABLE maintenance_status_page (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
status_page_id INTEGER NOT NULL,
|
|
||||||
maintenance_id INTEGER NOT NULL,
|
|
||||||
CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
CONSTRAINT FK_status_page FOREIGN KEY (status_page_id) REFERENCES status_page (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX [status_page_id_index]
|
|
||||||
ON [maintenance_status_page]([status_page_id]);
|
|
||||||
|
|
||||||
CREATE INDEX [maintenance_id_index]
|
|
||||||
ON [maintenance_status_page]([maintenance_id]);
|
|
||||||
|
|
||||||
-- maintenance_timeslot
|
|
||||||
CREATE TABLE [maintenance_timeslot] (
|
|
||||||
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
[maintenance_id] INTEGER NOT NULL CONSTRAINT [FK_maintenance] REFERENCES [maintenance]([id]) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
[start_date] DATETIME NOT NULL,
|
|
||||||
[end_date] DATETIME,
|
|
||||||
[generated_next] BOOLEAN DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX [maintenance_id] ON [maintenance_timeslot] ([maintenance_id] DESC);
|
|
||||||
|
|
||||||
CREATE INDEX [active_timeslot_index] ON [maintenance_timeslot] (
|
|
||||||
[maintenance_id] DESC,
|
|
||||||
[start_date] DESC,
|
|
||||||
[end_date] DESC
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX [generated_next_index] ON [maintenance_timeslot] ([generated_next]);
|
|
||||||
|
|
||||||
-- monitor_maintenance
|
|
||||||
CREATE TABLE monitor_maintenance (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
monitor_id INTEGER NOT NULL,
|
|
||||||
maintenance_id INTEGER NOT NULL,
|
|
||||||
CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX [maintenance_id_index2] ON [monitor_maintenance]([maintenance_id]);
|
|
||||||
|
|
||||||
CREATE INDEX [monitor_id_index] ON [monitor_maintenance]([monitor_id]);
|
|
||||||
|
|
||||||
COMMIT;
|
|
@ -1,10 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD resend_interval INTEGER default 0 not null;
|
|
||||||
|
|
||||||
ALTER TABLE heartbeat
|
|
||||||
ADD down_count INTEGER default 0 not null;
|
|
||||||
|
|
||||||
COMMIT;
|
|
@ -1,7 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
|
|
||||||
ALTER TABLE monitor
|
|
||||||
ADD expiry_notification BOOLEAN default 1;
|
|
||||||
|
|
||||||
COMMIT;
|
|
@ -1,6 +0,0 @@
|
|||||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
ALTER TABLE status_page ADD footer_text TEXT;
|
|
||||||
ALTER TABLE status_page ADD custom_css TEXT;
|
|
||||||
ALTER TABLE status_page ADD show_powered_by BOOLEAN NOT NULL DEFAULT 1;
|
|
||||||
COMMIT;
|
|
@ -4,5 +4,5 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Install apprise, iputils for non-root ping, setpriv
|
# Install apprise, iputils for non-root ping, setpriv
|
||||||
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
|
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
|
||||||
pip3 --no-cache-dir install apprise==1.2.1 && \
|
pip3 --no-cache-dir install apprise==0.9.7 && \
|
||||||
rm -rf /root/.cache
|
rm -rf /root/.cache
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
############################################
|
|
||||||
# Build in Golang
|
|
||||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
|
||||||
############################################
|
|
||||||
FROM golang:1.19.4-buster
|
|
||||||
WORKDIR /app
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
COPY ./extra/ ./extra/
|
|
||||||
|
|
||||||
# Compile healthcheck.go
|
|
||||||
RUN apt update && \
|
|
||||||
apt --yes --no-install-recommends install curl && \
|
|
||||||
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
|
|
||||||
apt --yes --no-install-recommends install nodejs && \
|
|
||||||
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
|
|
||||||
apt --yes remove nodejs
|
|
@ -11,9 +11,8 @@ WORKDIR /app
|
|||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||||
sqlite3 iputils-ping util-linux dumb-init && \
|
sqlite3 iputils-ping util-linux dumb-init && \
|
||||||
pip3 --no-cache-dir install apprise==1.2.1 && \
|
pip3 --no-cache-dir install apprise==0.9.7 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/*
|
||||||
apt --yes autoremove
|
|
||||||
|
|
||||||
# Install cloudflared
|
# Install cloudflared
|
||||||
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
|
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
|
||||||
@ -23,6 +22,5 @@ RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
|
|||||||
apt update && \
|
apt update && \
|
||||||
apt --yes --no-install-recommends install ./cloudflared.deb && \
|
apt --yes --no-install-recommends install ./cloudflared.deb && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
rm -f cloudflared.deb && \
|
rm -f cloudflared.deb
|
||||||
apt --yes autoremove
|
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
# Simple docker-compose.yml
|
# Simple docker-composer.yml
|
||||||
# You can change your port or volume location
|
# You can change your port or volume location
|
||||||
|
|
||||||
version: '3.3'
|
version: '3.3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
uptime-kuma:
|
uptime-kuma:
|
||||||
image: louislam/uptime-kuma:1
|
image: louislam/uptime-kuma
|
||||||
container_name: uptime-kuma
|
container_name: uptime-kuma
|
||||||
volumes:
|
volumes:
|
||||||
- ./uptime-kuma-data:/app/data
|
- ./uptime-kuma:/app/data
|
||||||
ports:
|
ports:
|
||||||
- 3001:3001 # <Host Port>:<Container Port>
|
- 3001:3001
|
||||||
restart: always
|
|
||||||
|
@ -1,82 +1,31 @@
|
|||||||
############################################
|
|
||||||
# Build in Golang
|
|
||||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
|
||||||
# Check file: builder-go.dockerfile
|
|
||||||
############################################
|
|
||||||
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Build in Node.js
|
|
||||||
############################################
|
|
||||||
FROM louislam/uptime-kuma:base-debian AS build
|
FROM louislam/uptime-kuma:base-debian AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||||
COPY .npmrc .npmrc
|
|
||||||
COPY package.json package.json
|
|
||||||
COPY package-lock.json package-lock.json
|
|
||||||
RUN npm ci --omit=dev
|
|
||||||
COPY . .
|
|
||||||
COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
|
|
||||||
RUN chmod +x /app/extra/entrypoint.sh
|
|
||||||
|
|
||||||
############################################
|
COPY . .
|
||||||
# ⭐ Main Image
|
RUN npm ci --production && \
|
||||||
############################################
|
chmod +x /app/extra/entrypoint.sh
|
||||||
|
|
||||||
|
|
||||||
FROM louislam/uptime-kuma:base-debian AS release
|
FROM louislam/uptime-kuma:base-debian AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy app files from build layer
|
# Copy app files from build layer
|
||||||
COPY --from=build /app /app
|
COPY --from=build /app /app
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
VOLUME ["/app/data"]
|
VOLUME ["/app/data"]
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD extra/healthcheck
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
||||||
CMD ["node", "server/server.js"]
|
CMD ["node", "server/server.js"]
|
||||||
|
|
||||||
############################################
|
|
||||||
# Mark as Nightly
|
|
||||||
############################################
|
|
||||||
FROM release AS nightly
|
FROM release AS nightly
|
||||||
RUN npm run mark-as-nightly
|
RUN npm run mark-as-nightly
|
||||||
|
|
||||||
############################################
|
|
||||||
# Build an image for testing pr
|
|
||||||
############################################
|
|
||||||
FROM louislam/uptime-kuma:base-debian AS pr-test
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
|
||||||
|
|
||||||
## Install Git
|
|
||||||
RUN apt update \
|
|
||||||
&& apt --yes --no-install-recommends install curl \
|
|
||||||
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
||||||
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
||||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
|
||||||
&& apt update \
|
|
||||||
&& apt --yes --no-install-recommends install git
|
|
||||||
|
|
||||||
## Empty the directory, because we have to clone the Git repo.
|
|
||||||
RUN rm -rf ./* && chown node /app
|
|
||||||
|
|
||||||
USER node
|
|
||||||
RUN git config --global user.email "no-reply@no-reply.com"
|
|
||||||
RUN git config --global user.name "PR Tester"
|
|
||||||
RUN git clone https://github.com/louislam/uptime-kuma.git .
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
EXPOSE 3000 3001
|
|
||||||
VOLUME ["/app/data"]
|
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
|
||||||
CMD ["npm", "run", "start-pr-test"]
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Upload the artifact to Github
|
# Upload the artifact to Github
|
||||||
############################################
|
|
||||||
FROM louislam/uptime-kuma:base-debian AS upload-artifact
|
FROM louislam/uptime-kuma:base-debian AS upload-artifact
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
|
@ -3,12 +3,10 @@ WORKDIR /app
|
|||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||||
|
|
||||||
COPY .npmrc .npmrc
|
|
||||||
COPY package.json package.json
|
|
||||||
COPY package-lock.json package-lock.json
|
|
||||||
RUN npm ci --omit=dev
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN chmod +x /app/extra/entrypoint.sh
|
RUN npm ci --production && \
|
||||||
|
chmod +x /app/extra/entrypoint.sh
|
||||||
|
|
||||||
|
|
||||||
FROM louislam/uptime-kuma:base-alpine AS release
|
FROM louislam/uptime-kuma:base-alpine AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
apps: [{
|
apps: [{
|
||||||
name: "uptime-kuma",
|
name: "uptime-kuma",
|
||||||
script: "./server/server.js",
|
script: "./server/server.js",
|
||||||
}]
|
}]
|
||||||
};
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
const pkg = require("../../package.json");
|
const pkg = require("../../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const childProcess = require("child_process");
|
const child_process = require("child_process");
|
||||||
const util = require("../../src/util");
|
const util = require("../../src/util");
|
||||||
|
|
||||||
util.polyfill();
|
util.polyfill();
|
||||||
|
|
||||||
|
const oldVersion = pkg.version;
|
||||||
const version = process.env.VERSION;
|
const version = process.env.VERSION;
|
||||||
|
|
||||||
console.log("Beta Version: " + version);
|
console.log("Beta Version: " + version);
|
||||||
@ -20,10 +21,6 @@ if (! exists) {
|
|||||||
// Process package.json
|
// Process package.json
|
||||||
pkg.version = version;
|
pkg.version = version;
|
||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||||
|
|
||||||
// Also update package-lock.json
|
|
||||||
childProcess.spawnSync("npm", [ "install" ]);
|
|
||||||
|
|
||||||
commit(version);
|
commit(version);
|
||||||
tag(version);
|
tag(version);
|
||||||
|
|
||||||
@ -32,14 +29,10 @@ if (! exists) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit updated files
|
|
||||||
* @param {string} version Version to update to
|
|
||||||
*/
|
|
||||||
function commit(version) {
|
function commit(version) {
|
||||||
let msg = "Update to " + version;
|
let msg = "Update to " + version;
|
||||||
|
|
||||||
let res = childProcess.spawnSync("git", [ "commit", "-m", msg, "-a" ]);
|
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
|
||||||
let stdout = res.stdout.toString().trim();
|
let stdout = res.stdout.toString().trim();
|
||||||
console.log(stdout);
|
console.log(stdout);
|
||||||
|
|
||||||
@ -47,33 +40,32 @@ function commit(version) {
|
|||||||
throw new Error("commit error");
|
throw new Error("commit error");
|
||||||
}
|
}
|
||||||
|
|
||||||
res = childProcess.spawnSync("git", [ "push", "origin", "master" ]);
|
res = child_process.spawnSync("git", ["push", "origin", "master"]);
|
||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a tag with the specified version
|
|
||||||
* @param {string} version Tag to create
|
|
||||||
*/
|
|
||||||
function tag(version) {
|
function tag(version) {
|
||||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
let res = child_process.spawnSync("git", ["tag", version]);
|
||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
|
|
||||||
res = childProcess.spawnSync("git", [ "push", "origin", version ]);
|
res = child_process.spawnSync("git", ["push", "origin", version]);
|
||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a tag exists for the specified version
|
|
||||||
* @param {string} version Version to check
|
|
||||||
* @returns {boolean} Does the tag already exist
|
|
||||||
*/
|
|
||||||
function tagExists(version) {
|
function tagExists(version) {
|
||||||
if (! version) {
|
if (! version) {
|
||||||
throw new Error("invalid version");
|
throw new Error("invalid version");
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = childProcess.spawnSync("git", [ "tag", "-l", version ]);
|
let res = child_process.spawnSync("git", ["tag", "-l", version]);
|
||||||
|
|
||||||
return res.stdout.toString().trim() === version;
|
return res.stdout.toString().trim() === version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function safeDelete(dir) {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
fs.rmdirSync(dir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
const childProcess = require("child_process");
|
|
||||||
const fs = require("fs");
|
|
||||||
const platform = process.argv[2];
|
|
||||||
|
|
||||||
if (!platform) {
|
|
||||||
console.error("No platform??");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform === "linux/arm/v7") {
|
|
||||||
console.log("Arch: armv7");
|
|
||||||
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
|
||||||
fs.renameSync("./extra/healthcheck-armv7", "./extra/healthcheck");
|
|
||||||
console.log("Already built in the host, skip.");
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
console.log("prebuilt not found, it will be slow! You should execute `npm run build-healthcheck-armv7` before build.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
|
||||||
fs.rmSync("./extra/healthcheck-armv7");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = childProcess.execSync("go build -x -o ./extra/healthcheck ./extra/healthcheck.go").toString("utf8");
|
|
||||||
console.log(output);
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
|||||||
const childProcess = require("child_process");
|
|
||||||
|
|
||||||
if (!process.env.UPTIME_KUMA_GH_REPO) {
|
|
||||||
console.error("Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let inputArray = process.env.UPTIME_KUMA_GH_REPO.split(":");
|
|
||||||
|
|
||||||
if (inputArray.length !== 2) {
|
|
||||||
console.error("Invalid format. Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)");
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = inputArray[0];
|
|
||||||
let branch = inputArray[1];
|
|
||||||
|
|
||||||
console.log("Checkout pr");
|
|
||||||
|
|
||||||
// Checkout the pr
|
|
||||||
let result = childProcess.spawnSync("git", [ "remote", "add", name, `https://github.com/${name}/uptime-kuma` ]);
|
|
||||||
|
|
||||||
console.log(result.stdout.toString());
|
|
||||||
console.error(result.stderr.toString());
|
|
||||||
|
|
||||||
result = childProcess.spawnSync("git", [ "fetch", name, branch ]);
|
|
||||||
|
|
||||||
console.log(result.stdout.toString());
|
|
||||||
console.error(result.stderr.toString());
|
|
||||||
|
|
||||||
result = childProcess.spawnSync("git", [ "checkout", `${name}/${branch}`, "--force" ]);
|
|
||||||
|
|
||||||
console.log(result.stdout.toString());
|
|
||||||
console.error(result.stderr.toString());
|
|
@ -29,7 +29,7 @@ const github = require("@actions/github");
|
|||||||
owner: issue.owner,
|
owner: issue.owner,
|
||||||
repo: issue.repo,
|
repo: issue.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
labels: [ "invalid-format" ]
|
labels: ["invalid-format"]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add the issue closing comment
|
// Add the issue closing comment
|
||||||
|
@ -25,10 +25,6 @@ if (platform === "linux/amd64") {
|
|||||||
const file = fs.createWriteStream("cloudflared.deb");
|
const file = fs.createWriteStream("cloudflared.deb");
|
||||||
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
|
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
|
||||||
|
|
||||||
/**
|
|
||||||
* Download specified file
|
|
||||||
* @param {string} url URL to request
|
|
||||||
*/
|
|
||||||
function get(url) {
|
function get(url) {
|
||||||
http.get(url, function (res) {
|
http.get(url, function (res) {
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||||
|
@ -12,12 +12,6 @@ const filename = "dist.tar.gz";
|
|||||||
const url = `https://github.com/louislam/uptime-kuma/releases/download/${version}/${filename}`;
|
const url = `https://github.com/louislam/uptime-kuma/releases/download/${version}/${filename}`;
|
||||||
download(url);
|
download(url);
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads the latest version of the dist from a GitHub release.
|
|
||||||
* @param {string} url The URL to download from.
|
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
|
||||||
function download(url) {
|
function download(url) {
|
||||||
console.log(url);
|
console.log(url);
|
||||||
|
|
||||||
|
@ -4,10 +4,7 @@ const fs = require("fs");
|
|||||||
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
|
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
|
||||||
* or the `recursive` property removing completely in the future Node.js version.
|
* or the `recursive` property removing completely in the future Node.js version.
|
||||||
* See the link below.
|
* See the link below.
|
||||||
*
|
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true-
|
||||||
* @todo Once we drop the support for Node.js v14 (or at least versions before v14.14.0), we can safely replace this function with `fs.rmSync`, since `fs.rmSync` was add in Node.js v14.14.0 and currently we supports all the Node.js v14 versions that include the versions before the v14.14.0, and this function have almost the same signature with `fs.rmSync`.
|
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true- the deprecation infomation of `fs.rmdirSync`
|
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/fs.html#fsrmsyncpath-options the document of `fs.rmSync`
|
|
||||||
* @param {fs.PathLike} path Valid types for path values in "fs".
|
* @param {fs.PathLike} path Valid types for path values in "fs".
|
||||||
* @param {fs.RmDirOptions} [options] options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
|
* @param {fs.RmDirOptions} [options] options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
|
||||||
*/
|
*/
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* If changed, have to run `npm run build-docker-builder-go`.
|
|
||||||
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
|
||||||
*/
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
isFreeBSD := runtime.GOOS == "freebsd"
|
|
||||||
|
|
||||||
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
client := http.Client{
|
|
||||||
Timeout: 28 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
sslKey := os.Getenv("UPTIME_KUMA_SSL_KEY")
|
|
||||||
if len(sslKey) == 0 {
|
|
||||||
sslKey = os.Getenv("SSL_KEY")
|
|
||||||
}
|
|
||||||
|
|
||||||
sslCert := os.Getenv("UPTIME_KUMA_SSL_CERT")
|
|
||||||
if len(sslCert) == 0 {
|
|
||||||
sslCert = os.Getenv("SSL_CERT")
|
|
||||||
}
|
|
||||||
|
|
||||||
hostname := os.Getenv("UPTIME_KUMA_HOST")
|
|
||||||
if len(hostname) == 0 && !isFreeBSD {
|
|
||||||
hostname = os.Getenv("HOST")
|
|
||||||
}
|
|
||||||
if len(hostname) == 0 {
|
|
||||||
hostname = "127.0.0.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
port := os.Getenv("UPTIME_KUMA_PORT")
|
|
||||||
if len(port) == 0 {
|
|
||||||
port = os.Getenv("PORT")
|
|
||||||
}
|
|
||||||
if len(port) == 0 {
|
|
||||||
port = "3001"
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol := ""
|
|
||||||
if len(sslKey) != 0 && len(sslCert) != 0 {
|
|
||||||
protocol = "https"
|
|
||||||
} else {
|
|
||||||
protocol = "http"
|
|
||||||
}
|
|
||||||
|
|
||||||
url := protocol + "://" + hostname + ":" + port
|
|
||||||
|
|
||||||
log.Println("Checking " + url)
|
|
||||||
resp, err := client.Get(url)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
_, err = ioutil.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Health Check OK [Res Code: %d]\n", resp.StatusCode)
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* ⚠️ Deprecated: Changed to healthcheck.go, it will be deleted in the future.
|
|
||||||
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
||||||
*/
|
*/
|
||||||
const { FBSD } = require("../server/util-server");
|
const { FBSD } = require("../server/util-server");
|
||||||
|
@ -4,21 +4,21 @@ const util = require("../src/util");
|
|||||||
|
|
||||||
util.polyfill();
|
util.polyfill();
|
||||||
|
|
||||||
const oldVersion = pkg.version;
|
const oldVersion = pkg.version
|
||||||
const newVersion = oldVersion + "-nightly-" + util.genSecret(8);
|
const newVersion = oldVersion + "-nightly"
|
||||||
|
|
||||||
console.log("Old Version: " + oldVersion);
|
console.log("Old Version: " + oldVersion)
|
||||||
console.log("New Version: " + newVersion);
|
console.log("New Version: " + newVersion)
|
||||||
|
|
||||||
if (newVersion) {
|
if (newVersion) {
|
||||||
// Process package.json
|
// Process package.json
|
||||||
pkg.version = newVersion;
|
pkg.version = newVersion
|
||||||
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, 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"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion)
|
||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n")
|
||||||
|
|
||||||
// Process README.md
|
// Process README.md
|
||||||
if (fs.existsSync("README.md")) {
|
if (fs.existsSync("README.md")) {
|
||||||
fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion));
|
fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,6 @@ const main = async () => {
|
|||||||
console.log("Finished.");
|
console.log("Finished.");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask question of user
|
|
||||||
* @param {string} question Question to ask
|
|
||||||
* @returns {Promise<string>} Users response
|
|
||||||
*/
|
|
||||||
function question(question) {
|
function question(question) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
rl.question(question, (answer) => {
|
rl.question(question, (answer) => {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
console.log("== Uptime Kuma Reset Password Tool ==");
|
console.log("== Uptime Kuma Reset Password Tool ==");
|
||||||
|
|
||||||
|
console.log("Loading the database");
|
||||||
|
|
||||||
const Database = require("../server/database");
|
const Database = require("../server/database");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const readline = require("readline");
|
const readline = require("readline");
|
||||||
const { initJWTSecret } = require("../server/util-server");
|
const { initJWTSecret } = require("../server/util-server");
|
||||||
const User = require("../server/model/user");
|
|
||||||
const args = require("args-parser")(process.argv);
|
const args = require("args-parser")(process.argv);
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
@ -12,9 +13,8 @@ const rl = readline.createInterface({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
console.log("Connecting the database");
|
|
||||||
Database.init(args);
|
Database.init(args);
|
||||||
await Database.connect(false, false, true);
|
await Database.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
|
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
|
||||||
@ -31,7 +31,7 @@ const main = async () => {
|
|||||||
let confirmPassword = await question("Confirm New Password: ");
|
let confirmPassword = await question("Confirm New Password: ");
|
||||||
|
|
||||||
if (password === confirmPassword) {
|
if (password === confirmPassword) {
|
||||||
await User.resetPassword(user.id, password);
|
await user.resetPassword(password);
|
||||||
|
|
||||||
// Reset all sessions by reset jwt secret
|
// Reset all sessions by reset jwt secret
|
||||||
await initJWTSecret();
|
await initJWTSecret();
|
||||||
@ -53,11 +53,6 @@ const main = async () => {
|
|||||||
console.log("Finished.");
|
console.log("Finished.");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask question of user
|
|
||||||
* @param {string} question Question to ask
|
|
||||||
* @returns {Promise<string>} Users response
|
|
||||||
*/
|
|
||||||
function question(question) {
|
function question(question) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
rl.question(question, (answer) => {
|
rl.question(question, (answer) => {
|
||||||
|
@ -26,7 +26,7 @@ server.on("request", (request, send, rinfo) => {
|
|||||||
ttl: 300,
|
ttl: 300,
|
||||||
address: "1.2.3.4"
|
address: "1.2.3.4"
|
||||||
});
|
});
|
||||||
} else if (question.type === Packet.TYPE.AAAA) {
|
} if (question.type === Packet.TYPE.AAAA) {
|
||||||
response.answers.push({
|
response.answers.push({
|
||||||
name: question.name,
|
name: question.name,
|
||||||
type: question.type,
|
type: question.type,
|
||||||
@ -135,11 +135,6 @@ server.listen({
|
|||||||
udp: 5300
|
udp: 5300
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Get human readable request type from request code
|
|
||||||
* @param {number} code Request code to translate
|
|
||||||
* @returns {string} Human readable request type
|
|
||||||
*/
|
|
||||||
function type(code) {
|
function type(code) {
|
||||||
for (let name in Packet.TYPE) {
|
for (let name in Packet.TYPE) {
|
||||||
if (Packet.TYPE[name] === code) {
|
if (Packet.TYPE[name] === code) {
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
const { log } = require("../src/util");
|
|
||||||
|
|
||||||
const mqttUsername = "louis1";
|
|
||||||
const mqttPassword = "!@#$LLam";
|
|
||||||
|
|
||||||
class SimpleMqttServer {
|
|
||||||
aedes = require("aedes")();
|
|
||||||
server = require("net").createServer(this.aedes.handle);
|
|
||||||
|
|
||||||
constructor(port) {
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Start the MQTT server */
|
|
||||||
start() {
|
|
||||||
this.server.listen(this.port, () => {
|
|
||||||
console.log("server started and listening on port ", this.port);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let server1 = new SimpleMqttServer(10000);
|
|
||||||
|
|
||||||
server1.aedes.authenticate = function (client, username, password, callback) {
|
|
||||||
if (username && password) {
|
|
||||||
console.log(password.toString("utf-8"));
|
|
||||||
callback(null, username === mqttUsername && password.toString("utf-8") === mqttPassword);
|
|
||||||
} else {
|
|
||||||
callback(null, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
server1.aedes.on("subscribe", (subscriptions, client) => {
|
|
||||||
console.log(subscriptions);
|
|
||||||
|
|
||||||
for (let s of subscriptions) {
|
|
||||||
if (s.topic === "test") {
|
|
||||||
server1.aedes.publish({
|
|
||||||
topic: "test",
|
|
||||||
payload: Buffer.from("ok"),
|
|
||||||
}, (error) => {
|
|
||||||
if (error) {
|
|
||||||
log.error("mqtt_server", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
server1.start();
|
|
@ -1,45 +1,51 @@
|
|||||||
// Need to use ES6 to read language files
|
// Need to use ES6 to read language files
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
import util from "util";
|
import util from "util";
|
||||||
import rmSync from "../fs-rmSync.js";
|
import rmSync from "../fs-rmSync.js";
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
|
||||||
/**
|
/**
|
||||||
* Copy across the required language files
|
* Look ma, it's cp -R.
|
||||||
* Creates a local directory (./languages) and copies the required files
|
* @param {string} src The path to the thing to copy.
|
||||||
* into it.
|
* @param {string} dest The path to the new copy.
|
||||||
* @param {string} langCode Code of language to update. A file will be
|
|
||||||
* created with this code if one does not already exist
|
|
||||||
* @param {string} baseLang The second base language file to copy. This
|
|
||||||
* will be ignored if set to "en" as en.js is copied by default
|
|
||||||
*/
|
*/
|
||||||
function copyFiles(langCode, baseLang) {
|
const copyRecursiveSync = function (src, dest) {
|
||||||
if (fs.existsSync("./languages")) {
|
let exists = fs.existsSync(src);
|
||||||
rmSync("./languages", { recursive: true });
|
let stats = exists && fs.statSync(src);
|
||||||
}
|
let isDirectory = exists && stats.isDirectory();
|
||||||
fs.mkdirSync("./languages");
|
|
||||||
|
|
||||||
if (!fs.existsSync(`../../src/languages/${langCode}.js`)) {
|
if (isDirectory) {
|
||||||
fs.closeSync(fs.openSync(`./languages/${langCode}.js`, "a"));
|
fs.mkdirSync(dest);
|
||||||
|
fs.readdirSync(src).forEach(function (childItemName) {
|
||||||
|
copyRecursiveSync(path.join(src, childItemName),
|
||||||
|
path.join(dest, childItemName));
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
fs.copyFileSync(`../../src/languages/${langCode}.js`, `./languages/${langCode}.js`);
|
fs.copyFileSync(src, dest);
|
||||||
}
|
|
||||||
fs.copyFileSync("../../src/languages/en.js", "./languages/en.js");
|
|
||||||
if (baseLang !== "en") {
|
|
||||||
fs.copyFileSync(`../../src/languages/${baseLang}.js`, `./languages/${baseLang}.js`);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Arguments:", process.argv);
|
||||||
|
const baseLangCode = process.argv[2] || "en";
|
||||||
|
console.log("Base Lang: " + baseLangCode);
|
||||||
|
if (fs.existsSync("./languages")) {
|
||||||
|
rmSync("./languages", { recursive: true });
|
||||||
}
|
}
|
||||||
|
copyRecursiveSync("../../src/languages", "./languages");
|
||||||
|
|
||||||
/**
|
const en = (await import("./languages/en.js")).default;
|
||||||
* Update the specified language file
|
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
|
||||||
* @param {string} langCode Language code to update
|
const files = fs.readdirSync("./languages");
|
||||||
* @param {string} baseLang Second language to copy keys from
|
console.log("Files:", files);
|
||||||
*/
|
|
||||||
async function updateLanguage(langCode, baseLangCode) {
|
for (const file of files) {
|
||||||
const en = (await import("./languages/en.js")).default;
|
if (! file.endsWith(".js")) {
|
||||||
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
|
console.log("Skipping " + file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let file = langCode + ".js";
|
|
||||||
console.log("Processing " + file);
|
console.log("Processing " + file);
|
||||||
const lang = await import("./languages/" + file);
|
const lang = await import("./languages/" + file);
|
||||||
|
|
||||||
@ -77,20 +83,5 @@ async function updateLanguage(langCode, baseLangCode) {
|
|||||||
fs.writeFileSync(`../../src/languages/${file}`, code);
|
fs.writeFileSync(`../../src/languages/${file}`, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get command line arguments
|
|
||||||
const baseLangCode = process.env.npm_config_baselang || "en";
|
|
||||||
const langCode = process.env.npm_config_language;
|
|
||||||
|
|
||||||
// We need the file to edit
|
|
||||||
if (langCode == null) {
|
|
||||||
throw new Error("Argument --language=<code> must be provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Base Lang: " + baseLangCode);
|
|
||||||
console.log("Updating: " + langCode);
|
|
||||||
|
|
||||||
copyFiles(langCode, baseLangCode);
|
|
||||||
await updateLanguage(langCode, baseLangCode);
|
|
||||||
rmSync("./languages", { recursive: true });
|
rmSync("./languages", { recursive: true });
|
||||||
|
|
||||||
console.log("Done. Fixing formatting by ESLint...");
|
console.log("Done. Fixing formatting by ESLint...");
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const pkg = require("../package.json");
|
const pkg = require("../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const childProcess = require("child_process");
|
const rmSync = require("./fs-rmSync.js");
|
||||||
|
const child_process = require("child_process");
|
||||||
const util = require("../src/util");
|
const util = require("../src/util");
|
||||||
|
|
||||||
util.polyfill();
|
util.polyfill();
|
||||||
@ -25,9 +26,6 @@ if (! exists) {
|
|||||||
pkg.scripts.setup = pkg.scripts.setup.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
|
pkg.scripts.setup = pkg.scripts.setup.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
|
||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||||
|
|
||||||
// Also update package-lock.json
|
|
||||||
childProcess.spawnSync("npm", [ "install" ]);
|
|
||||||
|
|
||||||
commit(newVersion);
|
commit(newVersion);
|
||||||
tag(newVersion);
|
tag(newVersion);
|
||||||
|
|
||||||
@ -35,14 +33,10 @@ if (! exists) {
|
|||||||
console.log("version exists");
|
console.log("version exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit updated files
|
|
||||||
* @param {string} version Version to update to
|
|
||||||
*/
|
|
||||||
function commit(version) {
|
function commit(version) {
|
||||||
let msg = "Update to " + version;
|
let msg = "Update to " + version;
|
||||||
|
|
||||||
let res = childProcess.spawnSync("git", [ "commit", "-m", msg, "-a" ]);
|
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
|
||||||
let stdout = res.stdout.toString().trim();
|
let stdout = res.stdout.toString().trim();
|
||||||
console.log(stdout);
|
console.log(stdout);
|
||||||
|
|
||||||
@ -51,26 +45,17 @@ function commit(version) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a tag with the specified version
|
|
||||||
* @param {string} version Tag to create
|
|
||||||
*/
|
|
||||||
function tag(version) {
|
function tag(version) {
|
||||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
let res = child_process.spawnSync("git", ["tag", version]);
|
||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a tag exists for the specified version
|
|
||||||
* @param {string} version Version to check
|
|
||||||
* @returns {boolean} Does the tag already exist
|
|
||||||
*/
|
|
||||||
function tagExists(version) {
|
function tagExists(version) {
|
||||||
if (! version) {
|
if (! version) {
|
||||||
throw new Error("invalid version");
|
throw new Error("invalid version");
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = childProcess.spawnSync("git", [ "tag", "-l", version ]);
|
let res = child_process.spawnSync("git", ["tag", "-l", version]);
|
||||||
|
|
||||||
return res.stdout.toString().trim() === version;
|
return res.stdout.toString().trim() === version;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const childProcess = require("child_process");
|
const child_process = require("child_process");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
const newVersion = process.env.VERSION;
|
const newVersion = process.env.VERSION;
|
||||||
@ -10,46 +10,38 @@ if (!newVersion) {
|
|||||||
|
|
||||||
updateWiki(newVersion);
|
updateWiki(newVersion);
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the wiki with new version number
|
|
||||||
* @param {string} newVersion Version to update to
|
|
||||||
*/
|
|
||||||
function updateWiki(newVersion) {
|
function updateWiki(newVersion) {
|
||||||
const wikiDir = "./tmp/wiki";
|
const wikiDir = "./tmp/wiki";
|
||||||
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
|
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
|
||||||
|
|
||||||
safeDelete(wikiDir);
|
safeDelete(wikiDir);
|
||||||
|
|
||||||
childProcess.spawnSync("git", [ "clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir ]);
|
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
|
||||||
let content = fs.readFileSync(howToUpdateFilename).toString();
|
let content = fs.readFileSync(howToUpdateFilename).toString();
|
||||||
|
|
||||||
// Replace the version: https://regex101.com/r/hmj2Bc/1
|
// Replace the version: https://regex101.com/r/hmj2Bc/1
|
||||||
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
|
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
|
||||||
fs.writeFileSync(howToUpdateFilename, content);
|
fs.writeFileSync(howToUpdateFilename, content);
|
||||||
|
|
||||||
childProcess.spawnSync("git", [ "add", "-A" ], {
|
child_process.spawnSync("git", ["add", "-A"], {
|
||||||
cwd: wikiDir,
|
cwd: wikiDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
childProcess.spawnSync("git", [ "commit", "-m", `Update to ${newVersion}` ], {
|
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], {
|
||||||
cwd: wikiDir,
|
cwd: wikiDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Pushing to Github");
|
console.log("Pushing to Github");
|
||||||
childProcess.spawnSync("git", [ "push" ], {
|
child_process.spawnSync("git", ["push"], {
|
||||||
cwd: wikiDir,
|
cwd: wikiDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
safeDelete(wikiDir);
|
safeDelete(wikiDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a directory exists and then delete it
|
|
||||||
* @param {string} dir Directory to delete
|
|
||||||
*/
|
|
||||||
function safeDelete(dir) {
|
function safeDelete(dir) {
|
||||||
if (fs.existsSync(dir)) {
|
if (fs.existsSync(dir)) {
|
||||||
fs.rm(dir, {
|
fs.rmdirSync(dir, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
22977
package-lock.json
generated
22977
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
186
package.json
186
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.19.6",
|
"version": "1.14.0-beta.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -10,36 +10,33 @@
|
|||||||
"node": "14.* || >=16.*"
|
"node": "14.* || >=16.*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install-legacy": "npm install",
|
"install-legacy": "npm install --legacy-peer-deps",
|
||||||
"update-legacy": "npm update",
|
"update-legacy": "npm update --legacy-peer-deps",
|
||||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
||||||
"lint-fix:js": "eslint --ext \".js,.vue\" --fix --ignore-path .gitignore .",
|
|
||||||
"lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore",
|
"lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore",
|
||||||
"lint-fix:style": "stylelint \"**/*.{vue,css,scss}\" --fix --ignore-path .gitignore",
|
|
||||||
"lint": "npm run lint:js && npm run lint:style",
|
"lint": "npm run lint:js && npm run lint:style",
|
||||||
"dev": "concurrently -k -r \"wait-on tcp:3000 && npm run start-server-dev \" \"npm run start-frontend-dev\"",
|
"dev": "vite --host --config ./config/vite.config.js",
|
||||||
"start-frontend-dev": "cross-env NODE_ENV=development vite --host --config ./config/vite.config.js",
|
|
||||||
"start": "npm run start-server",
|
"start": "npm run start-server",
|
||||||
"start-server": "node server/server.js",
|
"start-server": "node server/server.js",
|
||||||
"start-server-dev": "cross-env NODE_ENV=development node server/server.js",
|
"start-server-dev": "cross-env NODE_ENV=development node server/server.js",
|
||||||
"build": "vite build --config ./config/vite.config.js",
|
"build": "vite build --config ./config/vite.config.js",
|
||||||
"test": "node test/prepare-test-server.js && npm run jest-backend",
|
"test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test",
|
||||||
"test-with-build": "npm run build && npm test",
|
"test-with-build": "npm run build && npm test",
|
||||||
"jest-backend": "cross-env TEST_BACKEND=1 jest --runInBand --detectOpenHandles --forceExit --config=./config/jest-backend.config.js",
|
"jest": "node test/prepare-jest.js && npm run jest-frontend && npm run jest-backend",
|
||||||
|
"jest-frontend": "cross-env TEST_FRONTEND=1 jest --config=./config/jest-frontend.config.js",
|
||||||
|
"jest-backend": "cross-env TEST_BACKEND=1 jest --config=./config/jest-backend.config.js",
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
||||||
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
|
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
|
||||||
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
|
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
|
||||||
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
|
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
|
||||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
|
||||||
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
|
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
|
||||||
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
|
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
|
||||||
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||||
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
|
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
|
||||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
|
||||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||||
"setup": "git checkout 1.19.6 && npm ci --production && npm run download-dist",
|
"setup": "git checkout 1.13.1 && npm ci --production && npm run download-dist",
|
||||||
"download-dist": "node extra/download-dist.js",
|
"download-dist": "node extra/download-dist.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
@ -51,130 +48,95 @@
|
|||||||
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
||||||
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
|
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
|
||||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||||
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
"update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix",
|
||||||
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix",
|
||||||
"ncu-patch": "npm-check-updates -u -t patch",
|
"ncu-patch": "npm-check-updates -u -t patch",
|
||||||
"release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
"release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||||
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
||||||
"git-remove-tag": "git tag -d",
|
"git-remove-tag": "git tag -d"
|
||||||
"build-dist-and-restart": "npm run build && npm run start-server-dev",
|
|
||||||
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
|
|
||||||
"cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e",
|
|
||||||
"cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js",
|
|
||||||
"cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
|
|
||||||
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
|
||||||
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.7.3",
|
|
||||||
"@louislam/ping": "~0.4.2-mod.1",
|
|
||||||
"@louislam/sqlite3": "15.1.2",
|
|
||||||
"args-parser": "~1.3.0",
|
|
||||||
"axios": "~0.27.0",
|
|
||||||
"axios-ntlm": "1.3.0",
|
|
||||||
"badge-maker": "~3.3.1",
|
|
||||||
"bcryptjs": "~2.4.3",
|
|
||||||
"bree": "~7.1.5",
|
|
||||||
"cacheable-lookup": "~6.0.4",
|
|
||||||
"chardet": "~1.4.0",
|
|
||||||
"check-password-strength": "^2.0.5",
|
|
||||||
"cheerio": "~1.0.0-rc.12",
|
|
||||||
"chroma-js": "~2.4.2",
|
|
||||||
"command-exists": "~1.2.9",
|
|
||||||
"compare-versions": "~3.6.0",
|
|
||||||
"compression": "~1.7.4",
|
|
||||||
"dayjs": "~1.11.5",
|
|
||||||
"express": "~4.17.3",
|
|
||||||
"express-basic-auth": "~1.2.1",
|
|
||||||
"express-static-gzip": "~2.1.7",
|
|
||||||
"form-data": "~4.0.0",
|
|
||||||
"http-graceful-shutdown": "~3.1.7",
|
|
||||||
"http-proxy-agent": "~5.0.0",
|
|
||||||
"https-proxy-agent": "~5.0.1",
|
|
||||||
"iconv-lite": "~0.6.3",
|
|
||||||
"jsesc": "~3.0.2",
|
|
||||||
"jsonwebtoken": "~9.0.0",
|
|
||||||
"jwt-decode": "~3.1.2",
|
|
||||||
"limiter": "~2.1.0",
|
|
||||||
"mongodb": "~4.13.0",
|
|
||||||
"mqtt": "~4.3.7",
|
|
||||||
"mssql": "~8.1.4",
|
|
||||||
"mysql2": "~2.3.3",
|
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
|
||||||
"node-radius-client": "~1.0.0",
|
|
||||||
"nodemailer": "~6.6.5",
|
|
||||||
"notp": "~2.0.3",
|
|
||||||
"password-hash": "~1.2.2",
|
|
||||||
"pg": "~8.8.0",
|
|
||||||
"pg-connection-string": "~2.5.0",
|
|
||||||
"prom-client": "~13.2.0",
|
|
||||||
"prometheus-api-metrics": "~3.2.1",
|
|
||||||
"protobufjs": "~7.1.1",
|
|
||||||
"redbean-node": "~0.2.0",
|
|
||||||
"redis": "~4.5.1",
|
|
||||||
"socket.io": "~4.5.3",
|
|
||||||
"socket.io-client": "~4.5.3",
|
|
||||||
"socks-proxy-agent": "6.1.1",
|
|
||||||
"tar": "~6.1.11",
|
|
||||||
"tcp-ping": "~0.1.1",
|
|
||||||
"thirty-two": "~1.0.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@actions/github": "~5.0.1",
|
|
||||||
"@babel/eslint-parser": "~7.17.0",
|
|
||||||
"@babel/preset-env": "^7.15.8",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
||||||
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
||||||
|
"@louislam/sqlite3": "~6.0.1",
|
||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
"@types/bootstrap": "~5.1.9",
|
"args-parser": "~1.3.0",
|
||||||
"@vitejs/plugin-legacy": "~2.1.0",
|
"axios": "~0.26.1",
|
||||||
"@vitejs/plugin-vue": "~3.1.0",
|
"bcryptjs": "~2.4.3",
|
||||||
"@vue/compiler-sfc": "~3.2.36",
|
|
||||||
"@vuepic/vue-datepicker": "~3.4.8",
|
|
||||||
"aedes": "^0.46.3",
|
|
||||||
"babel-plugin-rewire": "~1.2.0",
|
|
||||||
"bootstrap": "5.1.3",
|
"bootstrap": "5.1.3",
|
||||||
|
"bree": "~7.1.5",
|
||||||
|
"chardet": "^1.3.0",
|
||||||
"chart.js": "~3.6.2",
|
"chart.js": "~3.6.2",
|
||||||
"chartjs-adapter-dayjs": "~1.0.0",
|
"chartjs-adapter-dayjs": "~1.0.0",
|
||||||
"concurrently": "^7.1.0",
|
"check-password-strength": "^2.0.5",
|
||||||
"core-js": "~3.26.1",
|
"command-exists": "~1.2.9",
|
||||||
"cross-env": "~7.0.3",
|
"compare-versions": "~3.6.0",
|
||||||
"cypress": "^10.1.0",
|
"dayjs": "~1.10.8",
|
||||||
"delay": "^5.0.0",
|
"express": "~4.17.3",
|
||||||
"dns2": "~2.0.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
"eslint": "~8.14.0",
|
"favico.js": "^0.3.10",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"form-data": "~4.0.0",
|
||||||
"favico.js": "~0.3.10",
|
"http-graceful-shutdown": "~3.1.7",
|
||||||
"jest": "~27.2.5",
|
"http-proxy-agent": "^5.0.0",
|
||||||
"postcss-html": "~1.5.0",
|
"https-proxy-agent": "^5.0.0",
|
||||||
"postcss-rtlcss": "~3.7.2",
|
"iconv-lite": "^0.6.3",
|
||||||
"postcss-scss": "~4.0.4",
|
"jsonwebtoken": "~8.5.1",
|
||||||
"prismjs": "~1.29.0",
|
"jwt-decode": "^3.1.2",
|
||||||
|
"limiter": "^2.1.0",
|
||||||
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
|
"nodemailer": "~6.6.5",
|
||||||
|
"notp": "~2.0.3",
|
||||||
|
"password-hash": "~1.2.2",
|
||||||
|
"postcss-rtlcss": "~3.4.1",
|
||||||
|
"postcss-scss": "~4.0.3",
|
||||||
|
"prom-client": "~13.2.0",
|
||||||
|
"prometheus-api-metrics": "~3.2.1",
|
||||||
"qrcode": "~1.5.0",
|
"qrcode": "~1.5.0",
|
||||||
"rollup-plugin-visualizer": "^5.6.0",
|
"redbean-node": "0.1.3",
|
||||||
"sass": "~1.42.1",
|
"socket.io": "~4.4.1",
|
||||||
"stylelint": "~14.7.1",
|
"socket.io-client": "~4.4.1",
|
||||||
"stylelint-config-standard": "~25.0.0",
|
"socks-proxy-agent": "^6.1.1",
|
||||||
"terser": "~5.15.0",
|
"tar": "^6.1.11",
|
||||||
|
"tcp-ping": "~0.1.1",
|
||||||
|
"thirty-two": "~1.0.2",
|
||||||
"timezones-list": "~3.0.1",
|
"timezones-list": "~3.0.1",
|
||||||
"typescript": "~4.4.4",
|
|
||||||
"v-pagination-3": "~0.1.7",
|
"v-pagination-3": "~0.1.7",
|
||||||
"vite": "~3.1.0",
|
|
||||||
"vite-plugin-compression": "^0.5.1",
|
|
||||||
"vue": "next",
|
"vue": "next",
|
||||||
"vue-chart-3": "3.0.9",
|
"vue-chart-3": "3.0.9",
|
||||||
"vue-confirm-dialog": "~1.0.2",
|
"vue-confirm-dialog": "~1.0.2",
|
||||||
"vue-contenteditable": "~3.0.4",
|
"vue-contenteditable": "~3.0.4",
|
||||||
"vue-i18n": "~9.2.2",
|
"vue-i18n": "~9.1.9",
|
||||||
"vue-image-crop-upload": "~3.0.3",
|
"vue-image-crop-upload": "~3.0.3",
|
||||||
"vue-multiselect": "~3.0.0-alpha.2",
|
"vue-multiselect": "~3.0.0-alpha.2",
|
||||||
"vue-prism-editor": "~2.0.0-alpha.2",
|
|
||||||
"vue-qrcode": "~1.0.0",
|
"vue-qrcode": "~1.0.0",
|
||||||
"vue-router": "~4.0.14",
|
"vue-router": "~4.0.14",
|
||||||
"vue-toastification": "~2.0.0-rc.5",
|
"vue-toastification": "~2.0.0-rc.5",
|
||||||
"vuedraggable": "~4.1.0",
|
"vuedraggable": "~4.1.0"
|
||||||
"wait-on": "^6.0.1"
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@actions/github": "~5.0.1",
|
||||||
|
"@babel/eslint-parser": "~7.15.8",
|
||||||
|
"@babel/preset-env": "^7.15.8",
|
||||||
|
"@types/bootstrap": "~5.1.9",
|
||||||
|
"@vitejs/plugin-legacy": "~1.6.4",
|
||||||
|
"@vitejs/plugin-vue": "~1.9.4",
|
||||||
|
"@vue/compiler-sfc": "~3.2.31",
|
||||||
|
"babel-plugin-rewire": "~1.2.0",
|
||||||
|
"core-js": "~3.18.3",
|
||||||
|
"cross-env": "~7.0.3",
|
||||||
|
"dns2": "~2.0.1",
|
||||||
|
"eslint": "~7.32.0",
|
||||||
|
"eslint-plugin-vue": "~7.18.0",
|
||||||
|
"jest": "~27.2.5",
|
||||||
|
"jest-puppeteer": "~6.0.3",
|
||||||
|
"npm-check-updates": "^12.5.5",
|
||||||
|
"puppeteer": "~13.1.3",
|
||||||
|
"sass": "~1.42.1",
|
||||||
|
"stylelint": "~14.2.0",
|
||||||
|
"stylelint-config-standard": "~24.0.0",
|
||||||
|
"typescript": "~4.4.4",
|
||||||
|
"vite": "~2.6.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 6.4 KiB |
@ -1,12 +1,8 @@
|
|||||||
|
const { checkLogin } = require("./util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
|
||||||
class TwoFA {
|
class TwoFA {
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable 2FA for specified user
|
|
||||||
* @param {number} userID ID of user to disable
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
static async disable2FA(userID) {
|
static async disable2FA(userID) {
|
||||||
return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
|
return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
|
||||||
userID,
|
userID,
|
||||||
|
@ -2,13 +2,14 @@ const basicAuth = require("express-basic-auth");
|
|||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setting } = require("./util-server");
|
const { setting } = require("./util-server");
|
||||||
|
const { debug } = require("../src/util");
|
||||||
const { loginRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter } = require("./rate-limiter");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login to web app
|
*
|
||||||
* @param {string} username
|
* @param username : string
|
||||||
* @param {string} password
|
* @param password : string
|
||||||
* @returns {Promise<(Bean|null)>}
|
* @returns {Promise<Bean|null>}
|
||||||
*/
|
*/
|
||||||
exports.login = async function (username, password) {
|
exports.login = async function (username, password) {
|
||||||
if (typeof username !== "string" || typeof password !== "string") {
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
@ -33,19 +34,6 @@ exports.login = async function (username, password) {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for myAuthorizer
|
|
||||||
* @callback myAuthorizerCB
|
|
||||||
* @param {any} err Any error encountered
|
|
||||||
* @param {boolean} authorized Is the client authorized?
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom authorizer for express-basic-auth
|
|
||||||
* @param {string} username
|
|
||||||
* @param {string} password
|
|
||||||
* @param {myAuthorizerCB} callback
|
|
||||||
*/
|
|
||||||
function myAuthorizer(username, password, callback) {
|
function myAuthorizer(username, password, callback) {
|
||||||
// Login Rate Limit
|
// Login Rate Limit
|
||||||
loginRateLimiter.pass(null, 0).then((pass) => {
|
loginRateLimiter.pass(null, 0).then((pass) => {
|
||||||
@ -63,12 +51,6 @@ function myAuthorizer(username, password, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Use basic auth if auth is not disabled
|
|
||||||
* @param {express.Request} req Express request object
|
|
||||||
* @param {express.Response} res Express response object
|
|
||||||
* @param {express.NextFunction} next
|
|
||||||
*/
|
|
||||||
exports.basicAuth = async function (req, res, next) {
|
exports.basicAuth = async function (req, res, next) {
|
||||||
const middleware = basicAuth({
|
const middleware = basicAuth({
|
||||||
authorizer: myAuthorizer,
|
authorizer: myAuthorizer,
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
const https = require("https");
|
|
||||||
const http = require("http");
|
|
||||||
const CacheableLookup = require("cacheable-lookup");
|
|
||||||
const { Settings } = require("./settings");
|
|
||||||
const { log } = require("../src/util");
|
|
||||||
|
|
||||||
class CacheableDnsHttpAgent {
|
|
||||||
|
|
||||||
static cacheable = new CacheableLookup();
|
|
||||||
|
|
||||||
static httpAgentList = {};
|
|
||||||
static httpsAgentList = {};
|
|
||||||
|
|
||||||
static enable = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register/Disable cacheable to global agents
|
|
||||||
*/
|
|
||||||
static async update() {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "update");
|
|
||||||
let isEnable = await Settings.get("dnsCache");
|
|
||||||
|
|
||||||
if (isEnable !== this.enable) {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "value changed");
|
|
||||||
|
|
||||||
if (isEnable) {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "enable");
|
|
||||||
this.cacheable.install(http.globalAgent);
|
|
||||||
this.cacheable.install(https.globalAgent);
|
|
||||||
} else {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "disable");
|
|
||||||
this.cacheable.uninstall(http.globalAgent);
|
|
||||||
this.cacheable.uninstall(https.globalAgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.enable = isEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach cacheable to HTTP agent
|
|
||||||
* @param {http.Agent} agent Agent to install
|
|
||||||
*/
|
|
||||||
static install(agent) {
|
|
||||||
this.cacheable.install(agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var {https.AgentOptions} agentOptions
|
|
||||||
* @return {https.Agent}
|
|
||||||
*/
|
|
||||||
static getHttpsAgent(agentOptions) {
|
|
||||||
if (!this.enable) {
|
|
||||||
return new https.Agent(agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = JSON.stringify(agentOptions);
|
|
||||||
if (!(key in this.httpsAgentList)) {
|
|
||||||
this.httpsAgentList[key] = new https.Agent(agentOptions);
|
|
||||||
this.cacheable.install(this.httpsAgentList[key]);
|
|
||||||
}
|
|
||||||
return this.httpsAgentList[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var {http.AgentOptions} agentOptions
|
|
||||||
* @return {https.Agents}
|
|
||||||
*/
|
|
||||||
static getHttpAgent(agentOptions) {
|
|
||||||
if (!this.enable) {
|
|
||||||
return new http.Agent(agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = JSON.stringify(agentOptions);
|
|
||||||
if (!(key in this.httpAgentList)) {
|
|
||||||
this.httpAgentList[key] = new http.Agent(agentOptions);
|
|
||||||
this.cacheable.install(this.httpAgentList[key]);
|
|
||||||
}
|
|
||||||
return this.httpAgentList[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
CacheableDnsHttpAgent,
|
|
||||||
};
|
|
@ -7,7 +7,6 @@ exports.latestVersion = null;
|
|||||||
|
|
||||||
let interval;
|
let interval;
|
||||||
|
|
||||||
/** Start 48 hour check interval */
|
|
||||||
exports.startInterval = () => {
|
exports.startInterval = () => {
|
||||||
let check = async () => {
|
let check = async () => {
|
||||||
try {
|
try {
|
||||||
@ -18,14 +17,14 @@ exports.startInterval = () => {
|
|||||||
res.data.slow = "1000.0.0";
|
res.data.slow = "1000.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await setting("checkUpdate") === false) {
|
if (!await setting("checkUpdate")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkBeta = await setting("checkBeta");
|
let checkBeta = await setting("checkBeta");
|
||||||
|
|
||||||
if (checkBeta && res.data.beta) {
|
if (checkBeta && res.data.beta) {
|
||||||
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
if (compareVersions.compare(res.data.beta, res.data.beta, ">")) {
|
||||||
exports.latestVersion = res.data.beta;
|
exports.latestVersion = res.data.beta;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,11 +42,6 @@ exports.startInterval = () => {
|
|||||||
interval = setInterval(check, 3600 * 1000 * 48);
|
interval = setInterval(check, 3600 * 1000 * 48);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable the check update feature
|
|
||||||
* @param {boolean} value Should the check update feature be enabled?
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
exports.enableCheckUpdate = async (value) => {
|
exports.enableCheckUpdate = async (value) => {
|
||||||
await setSetting("checkUpdate", value);
|
await setSetting("checkUpdate", value);
|
||||||
|
|
||||||
|
@ -3,17 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
const { TimeLogger } = require("../src/util");
|
const { TimeLogger } = require("../src/util");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
const { io } = require("./server");
|
||||||
const server = UptimeKumaServer.getInstance();
|
|
||||||
const io = server.io;
|
|
||||||
const { setting } = require("./util-server");
|
const { setting } = require("./util-server");
|
||||||
const checkVersion = require("./check-version");
|
const checkVersion = require("./check-version");
|
||||||
|
|
||||||
/**
|
|
||||||
* Send list of notification providers to client
|
|
||||||
* @param {Socket} socket Socket.io socket instance
|
|
||||||
* @returns {Promise<Bean[]>}
|
|
||||||
*/
|
|
||||||
async function sendNotificationList(socket) {
|
async function sendNotificationList(socket) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
@ -23,10 +16,7 @@ async function sendNotificationList(socket) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
for (let bean of list) {
|
for (let bean of list) {
|
||||||
let notificationObject = bean.export();
|
result.push(bean.export());
|
||||||
notificationObject.isDefault = (notificationObject.isDefault === 1);
|
|
||||||
notificationObject.active = (notificationObject.active === 1);
|
|
||||||
result.push(notificationObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
io.to(socket.userID).emit("notificationList", result);
|
io.to(socket.userID).emit("notificationList", result);
|
||||||
@ -38,11 +28,8 @@ async function sendNotificationList(socket) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send Heartbeat History list to socket
|
* Send Heartbeat History list to socket
|
||||||
* @param {Socket} socket Socket.io instance
|
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
|
||||||
* @param {number} monitorID ID of monitor to send heartbeat history
|
* @param overwrite Overwrite client-side's heartbeat list
|
||||||
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
|
|
||||||
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
@ -68,12 +55,11 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Important Heart beat list (aka event list)
|
* Important Heart beat list (aka event list)
|
||||||
* @param {Socket} socket Socket.io instance
|
* @param socket
|
||||||
* @param {number} monitorID ID of monitor to send heartbeat history
|
* @param monitorID
|
||||||
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
|
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
|
||||||
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
|
* @param overwrite Overwrite client-side's heartbeat list
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
@ -98,14 +84,15 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit proxy list to client
|
* Delivers proxy list
|
||||||
* @param {Socket} socket Socket.io socket instance
|
*
|
||||||
|
* @param socket
|
||||||
* @return {Promise<Bean[]>}
|
* @return {Promise<Bean[]>}
|
||||||
*/
|
*/
|
||||||
async function sendProxyList(socket) {
|
async function sendProxyList(socket) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
const list = await R.find("proxy", " user_id = ? ", [ socket.userID ]);
|
const list = await R.find("proxy", " user_id = ? ", [socket.userID]);
|
||||||
io.to(socket.userID).emit("proxyList", list.map(bean => bean.export()));
|
io.to(socket.userID).emit("proxyList", list.map(bean => bean.export()));
|
||||||
|
|
||||||
timeLogger.print("Send Proxy List");
|
timeLogger.print("Send Proxy List");
|
||||||
@ -113,50 +100,18 @@ async function sendProxyList(socket) {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits the version information to the client.
|
|
||||||
* @param {Socket} socket Socket.io socket instance
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async function sendInfo(socket) {
|
async function sendInfo(socket) {
|
||||||
socket.emit("info", {
|
socket.emit("info", {
|
||||||
version: checkVersion.version,
|
version: checkVersion.version,
|
||||||
latestVersion: checkVersion.latestVersion,
|
latestVersion: checkVersion.latestVersion,
|
||||||
primaryBaseURL: await setting("primaryBaseURL"),
|
primaryBaseURL: await setting("primaryBaseURL")
|
||||||
serverTimezone: await server.getTimezone(),
|
|
||||||
serverTimezoneOffset: server.getTimezoneOffset(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send list of docker hosts to client
|
|
||||||
* @param {Socket} socket Socket.io socket instance
|
|
||||||
* @returns {Promise<Bean[]>}
|
|
||||||
*/
|
|
||||||
async function sendDockerHostList(socket) {
|
|
||||||
const timeLogger = new TimeLogger();
|
|
||||||
|
|
||||||
let result = [];
|
|
||||||
let list = await R.find("docker_host", " user_id = ? ", [
|
|
||||||
socket.userID,
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let bean of list) {
|
|
||||||
result.push(bean.toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
io.to(socket.userID).emit("dockerHostList", result);
|
|
||||||
|
|
||||||
timeLogger.print("Send Docker Host List");
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sendNotificationList,
|
sendNotificationList,
|
||||||
sendImportantHeartbeatList,
|
sendImportantHeartbeatList,
|
||||||
sendHeartbeatList,
|
sendHeartbeatList,
|
||||||
sendProxyList,
|
sendProxyList,
|
||||||
sendInfo,
|
sendInfo,
|
||||||
sendDockerHostList
|
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,7 @@
|
|||||||
const args = require("args-parser")(process.argv);
|
const args = require("args-parser")(process.argv);
|
||||||
const demoMode = args["demo"] || false;
|
const demoMode = args["demo"] || false;
|
||||||
|
|
||||||
const badgeConstants = {
|
|
||||||
naColor: "#999",
|
|
||||||
defaultUpColor: "#66c20a",
|
|
||||||
defaultDownColor: "#c2290a",
|
|
||||||
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
|
||||||
defaultStyle: "flat",
|
|
||||||
defaultPingValueSuffix: "ms",
|
|
||||||
defaultPingLabelSuffix: "h",
|
|
||||||
defaultUptimeValueSuffix: "%",
|
|
||||||
defaultUptimeLabelSuffix: "h",
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
args,
|
args,
|
||||||
demoMode,
|
demoMode
|
||||||
badgeConstants,
|
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setSetting, setting } = require("./util-server");
|
const { setSetting, setting } = require("./util-server");
|
||||||
const { log, sleep } = require("../src/util");
|
const { debug, sleep } = require("../src/util");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
|
|
||||||
@ -53,20 +53,9 @@ class Database {
|
|||||||
"patch-2fa-invalidate-used-token.sql": true,
|
"patch-2fa-invalidate-used-token.sql": true,
|
||||||
"patch-notification_sent_history.sql": true,
|
"patch-notification_sent_history.sql": true,
|
||||||
"patch-monitor-basic-auth.sql": true,
|
"patch-monitor-basic-auth.sql": true,
|
||||||
"patch-add-docker-columns.sql": true,
|
|
||||||
"patch-status-page.sql": true,
|
"patch-status-page.sql": true,
|
||||||
"patch-proxy.sql": true,
|
"patch-proxy.sql": true,
|
||||||
"patch-monitor-expiry-notification.sql": true,
|
}
|
||||||
"patch-status-page-footer-css.sql": true,
|
|
||||||
"patch-added-mqtt-monitor.sql": true,
|
|
||||||
"patch-add-clickable-status-page-link.sql": true,
|
|
||||||
"patch-add-sqlserver-monitor.sql": true,
|
|
||||||
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
|
|
||||||
"patch-grpc-monitor.sql": true,
|
|
||||||
"patch-add-radius-monitor.sql": true,
|
|
||||||
"patch-monitor-add-resend-interval.sql": true,
|
|
||||||
"patch-maintenance-table2.sql": true,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The final version should be 10 after merged tag feature
|
* The final version should be 10 after merged tag feature
|
||||||
@ -76,10 +65,6 @@ class Database {
|
|||||||
|
|
||||||
static noReject = true;
|
static noReject = true;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the database
|
|
||||||
* @param {Object} args Arguments to initialize DB with
|
|
||||||
*/
|
|
||||||
static init(args) {
|
static init(args) {
|
||||||
// Data Directory (must be end with "/")
|
// Data Directory (must be end with "/")
|
||||||
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||||
@ -94,19 +79,10 @@ class Database {
|
|||||||
fs.mkdirSync(Database.uploadDir, { recursive: true });
|
fs.mkdirSync(Database.uploadDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("db", `Data Dir: ${Database.dataDir}`);
|
console.log(`Data Dir: ${Database.dataDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static async connect(testMode = false) {
|
||||||
* Connect to the database
|
|
||||||
* @param {boolean} [testMode=false] Should the connection be
|
|
||||||
* started in test mode?
|
|
||||||
* @param {boolean} [autoloadModels=true] Should models be
|
|
||||||
* automatically loaded?
|
|
||||||
* @param {boolean} [noLog=false] Should logs not be output?
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
|
||||||
const acquireConnectionTimeout = 120 * 1000;
|
const acquireConnectionTimeout = 120 * 1000;
|
||||||
|
|
||||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||||
@ -136,10 +112,7 @@ class Database {
|
|||||||
|
|
||||||
// Auto map the model to a bean object
|
// Auto map the model to a bean object
|
||||||
R.freeze(true);
|
R.freeze(true);
|
||||||
|
await R.autoloadModels("./server/model");
|
||||||
if (autoloadModels) {
|
|
||||||
await R.autoloadModels("./server/model");
|
|
||||||
}
|
|
||||||
|
|
||||||
await R.exec("PRAGMA foreign_keys = ON");
|
await R.exec("PRAGMA foreign_keys = ON");
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
@ -152,20 +125,12 @@ class Database {
|
|||||||
await R.exec("PRAGMA cache_size = -12000");
|
await R.exec("PRAGMA cache_size = -12000");
|
||||||
await R.exec("PRAGMA auto_vacuum = FULL");
|
await R.exec("PRAGMA auto_vacuum = FULL");
|
||||||
|
|
||||||
// This ensures that an operating system crash or power failure will not corrupt the database.
|
console.log("SQLite config:");
|
||||||
// FULL synchronous is very safe, but it is also slower.
|
console.log(await R.getAll("PRAGMA journal_mode"));
|
||||||
// Read more: https://sqlite.org/pragma.html#pragma_synchronous
|
console.log(await R.getAll("PRAGMA cache_size"));
|
||||||
await R.exec("PRAGMA synchronous = FULL");
|
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
|
||||||
|
|
||||||
if (!noLog) {
|
|
||||||
log.info("db", "SQLite config:");
|
|
||||||
log.info("db", await R.getAll("PRAGMA journal_mode"));
|
|
||||||
log.info("db", await R.getAll("PRAGMA cache_size"));
|
|
||||||
log.info("db", "SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Patch the database */
|
|
||||||
static async patch() {
|
static async patch() {
|
||||||
let version = parseInt(await setting("database_version"));
|
let version = parseInt(await setting("database_version"));
|
||||||
|
|
||||||
@ -173,39 +138,33 @@ class Database {
|
|||||||
version = 0;
|
version = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("db", "Your database version: " + version);
|
console.info("Your database version: " + version);
|
||||||
log.info("db", "Latest database version: " + this.latestVersion);
|
console.info("Latest database version: " + this.latestVersion);
|
||||||
|
|
||||||
if (version === this.latestVersion) {
|
if (version === this.latestVersion) {
|
||||||
log.info("db", "Database patch not needed");
|
console.info("Database patch not needed");
|
||||||
} else if (version > this.latestVersion) {
|
} else if (version > this.latestVersion) {
|
||||||
log.info("db", "Warning: Database version is newer than expected");
|
console.info("Warning: Database version is newer than expected");
|
||||||
} else {
|
} else {
|
||||||
log.info("db", "Database patch is needed");
|
console.info("Database patch is needed");
|
||||||
|
|
||||||
try {
|
this.backup(version);
|
||||||
this.backup(version);
|
|
||||||
} catch (e) {
|
|
||||||
log.error("db", e);
|
|
||||||
log.error("db", "Unable to create a backup before patching the database. Please make sure you have enough space and permission.");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try catch anything here, if gone wrong, restore the backup
|
// Try catch anything here, if gone wrong, restore the backup
|
||||||
try {
|
try {
|
||||||
for (let i = version + 1; i <= this.latestVersion; i++) {
|
for (let i = version + 1; i <= this.latestVersion; i++) {
|
||||||
const sqlFile = `./db/patch${i}.sql`;
|
const sqlFile = `./db/patch${i}.sql`;
|
||||||
log.info("db", `Patching ${sqlFile}`);
|
console.info(`Patching ${sqlFile}`);
|
||||||
await Database.importSQLFile(sqlFile);
|
await Database.importSQLFile(sqlFile);
|
||||||
log.info("db", `Patched ${sqlFile}`);
|
console.info(`Patched ${sqlFile}`);
|
||||||
await setSetting("database_version", i);
|
await setSetting("database_version", i);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await Database.close();
|
await Database.close();
|
||||||
|
|
||||||
log.error("db", ex);
|
console.error(ex);
|
||||||
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
console.error("Start Uptime-Kuma failed due to issue patching the database");
|
||||||
log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
this.restore();
|
this.restore();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@ -217,21 +176,19 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patch DB using new process
|
|
||||||
* Call it from patch() only
|
* Call it from patch() only
|
||||||
* @private
|
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async patch2() {
|
static async patch2() {
|
||||||
log.info("db", "Database Patch 2.0 Process");
|
console.log("Database Patch 2.0 Process");
|
||||||
let databasePatchedFiles = await setting("databasePatchedFiles");
|
let databasePatchedFiles = await setting("databasePatchedFiles");
|
||||||
|
|
||||||
if (! databasePatchedFiles) {
|
if (! databasePatchedFiles) {
|
||||||
databasePatchedFiles = {};
|
databasePatchedFiles = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("db", "Patched files:");
|
debug("Patched files:");
|
||||||
log.debug("db", databasePatchedFiles);
|
debug(databasePatchedFiles);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let sqlFilename in this.patchList) {
|
for (let sqlFilename in this.patchList) {
|
||||||
@ -239,15 +196,15 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.patched) {
|
if (this.patched) {
|
||||||
log.info("db", "Database Patched Successfully");
|
console.log("Database Patched Successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await Database.close();
|
await Database.close();
|
||||||
|
|
||||||
log.error("db", ex);
|
console.error(ex);
|
||||||
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
console.error("Start Uptime-Kuma failed due to issue patching the database");
|
||||||
log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
this.restore();
|
this.restore();
|
||||||
|
|
||||||
@ -326,27 +283,24 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patch database using new patching process
|
|
||||||
* Used it patch2() only
|
* Used it patch2() only
|
||||||
* @private
|
|
||||||
* @param sqlFilename
|
* @param sqlFilename
|
||||||
* @param databasePatchedFiles
|
* @param databasePatchedFiles
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
||||||
let value = this.patchList[sqlFilename];
|
let value = this.patchList[sqlFilename];
|
||||||
|
|
||||||
if (! value) {
|
if (! value) {
|
||||||
log.info("db", sqlFilename + " skip");
|
console.log(sqlFilename + " skip");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if patched
|
// Check if patched
|
||||||
if (! databasePatchedFiles[sqlFilename]) {
|
if (! databasePatchedFiles[sqlFilename]) {
|
||||||
log.info("db", sqlFilename + " is not patched");
|
console.log(sqlFilename + " is not patched");
|
||||||
|
|
||||||
if (value.parents) {
|
if (value.parents) {
|
||||||
log.info("db", sqlFilename + " need parents");
|
console.log(sqlFilename + " need parents");
|
||||||
for (let parentSQLFilename of value.parents) {
|
for (let parentSQLFilename of value.parents) {
|
||||||
await this.patch2Recursion(parentSQLFilename, databasePatchedFiles);
|
await this.patch2Recursion(parentSQLFilename, databasePatchedFiles);
|
||||||
}
|
}
|
||||||
@ -354,24 +308,24 @@ class Database {
|
|||||||
|
|
||||||
this.backup(dayjs().format("YYYYMMDDHHmmss"));
|
this.backup(dayjs().format("YYYYMMDDHHmmss"));
|
||||||
|
|
||||||
log.info("db", sqlFilename + " is patching");
|
console.log(sqlFilename + " is patching");
|
||||||
this.patched = true;
|
this.patched = true;
|
||||||
await this.importSQLFile("./db/" + sqlFilename);
|
await this.importSQLFile("./db/" + sqlFilename);
|
||||||
databasePatchedFiles[sqlFilename] = true;
|
databasePatchedFiles[sqlFilename] = true;
|
||||||
log.info("db", sqlFilename + " was patched successfully");
|
console.log(sqlFilename + " was patched successfully");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.debug("db", sqlFilename + " is already patched, skip");
|
debug(sqlFilename + " is already patched, skip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an SQL file and execute it
|
* Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
|
||||||
* @param filename Filename of SQL file to import
|
* @param filename
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async importSQLFile(filename) {
|
static async importSQLFile(filename) {
|
||||||
// Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
|
|
||||||
await R.getCell("SELECT 1");
|
await R.getCell("SELECT 1");
|
||||||
|
|
||||||
let text = fs.readFileSync(filename).toString();
|
let text = fs.readFileSync(filename).toString();
|
||||||
@ -399,10 +353,6 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Aquire a direct connection to database
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
static getBetterSQLite3Database() {
|
static getBetterSQLite3Database() {
|
||||||
return R.knex.client.acquireConnection();
|
return R.knex.client.acquireConnection();
|
||||||
}
|
}
|
||||||
@ -417,7 +367,7 @@ class Database {
|
|||||||
};
|
};
|
||||||
process.addListener("unhandledRejection", listener);
|
process.addListener("unhandledRejection", listener);
|
||||||
|
|
||||||
log.info("db", "Closing the database");
|
console.log("Closing the database");
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Database.noReject = true;
|
Database.noReject = true;
|
||||||
@ -427,10 +377,10 @@ class Database {
|
|||||||
if (Database.noReject) {
|
if (Database.noReject) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
log.info("db", "Waiting to close the database");
|
console.log("Waiting to close the database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("db", "SQLite closed");
|
console.log("SQLite closed");
|
||||||
|
|
||||||
process.removeListener("unhandledRejection", listener);
|
process.removeListener("unhandledRejection", listener);
|
||||||
}
|
}
|
||||||
@ -438,11 +388,11 @@ class Database {
|
|||||||
/**
|
/**
|
||||||
* One backup one time in this process.
|
* One backup one time in this process.
|
||||||
* Reset this.backupPath if you want to backup again
|
* Reset this.backupPath if you want to backup again
|
||||||
* @param {string} version Version code of backup
|
* @param version
|
||||||
*/
|
*/
|
||||||
static backup(version) {
|
static backup(version) {
|
||||||
if (! this.backupPath) {
|
if (! this.backupPath) {
|
||||||
log.info("db", "Backing up the database");
|
console.info("Backing up the database");
|
||||||
this.backupPath = this.dataDir + "kuma.db.bak" + version;
|
this.backupPath = this.dataDir + "kuma.db.bak" + version;
|
||||||
fs.copyFileSync(Database.path, this.backupPath);
|
fs.copyFileSync(Database.path, this.backupPath);
|
||||||
|
|
||||||
@ -457,30 +407,15 @@ class Database {
|
|||||||
this.backupWalPath = walPath + ".bak" + version;
|
this.backupWalPath = walPath + ".bak" + version;
|
||||||
fs.copyFileSync(walPath, this.backupWalPath);
|
fs.copyFileSync(walPath, this.backupWalPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double confirm if all files actually backup
|
|
||||||
if (!fs.existsSync(this.backupPath)) {
|
|
||||||
throw new Error("Backup failed! " + this.backupPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(shmPath)) {
|
|
||||||
if (!fs.existsSync(this.backupShmPath)) {
|
|
||||||
throw new Error("Backup failed! " + this.backupShmPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(walPath)) {
|
|
||||||
if (!fs.existsSync(this.backupWalPath)) {
|
|
||||||
throw new Error("Backup failed! " + this.backupWalPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Restore from most recent backup */
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
static restore() {
|
static restore() {
|
||||||
if (this.backupPath) {
|
if (this.backupPath) {
|
||||||
log.error("db", "Patching the database failed!!! Restoring the backup");
|
console.error("Patching the database failed!!! Restoring the backup");
|
||||||
|
|
||||||
const shmPath = Database.path + "-shm";
|
const shmPath = Database.path + "-shm";
|
||||||
const walPath = Database.path + "-wal";
|
const walPath = Database.path + "-wal";
|
||||||
@ -499,7 +434,7 @@ class Database {
|
|||||||
fs.unlinkSync(walPath);
|
fs.unlinkSync(walPath);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("db", "Restore failed; you may need to restore the backup manually");
|
console.log("Restore failed; you may need to restore the backup manually");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,22 +450,17 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.info("db", "Nothing to restore");
|
console.log("Nothing to restore");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the size of the database */
|
|
||||||
static getSize() {
|
static getSize() {
|
||||||
log.debug("db", "Database.getSize()");
|
debug("Database.getSize()");
|
||||||
let stats = fs.statSync(Database.path);
|
let stats = fs.statSync(Database.path);
|
||||||
log.debug("db", stats);
|
debug(stats);
|
||||||
return stats.size;
|
return stats.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shrink the database
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
static async shrink() {
|
static async shrink() {
|
||||||
await R.exec("VACUUM");
|
await R.exec("VACUUM");
|
||||||
}
|
}
|
||||||
|
118
server/docker.js
118
server/docker.js
@ -1,118 +0,0 @@
|
|||||||
const axios = require("axios");
|
|
||||||
const { R } = require("redbean-node");
|
|
||||||
const version = require("../package.json").version;
|
|
||||||
const https = require("https");
|
|
||||||
|
|
||||||
class DockerHost {
|
|
||||||
/**
|
|
||||||
* Save a docker host
|
|
||||||
* @param {Object} dockerHost Docker host to save
|
|
||||||
* @param {?number} dockerHostID ID of the docker host to update
|
|
||||||
* @param {number} userID ID of the user who adds the docker host
|
|
||||||
* @returns {Promise<Bean>}
|
|
||||||
*/
|
|
||||||
static async save(dockerHost, dockerHostID, userID) {
|
|
||||||
let bean;
|
|
||||||
|
|
||||||
if (dockerHostID) {
|
|
||||||
bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]);
|
|
||||||
|
|
||||||
if (!bean) {
|
|
||||||
throw new Error("docker host not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
bean = R.dispense("docker_host");
|
|
||||||
}
|
|
||||||
|
|
||||||
bean.user_id = userID;
|
|
||||||
bean.docker_daemon = dockerHost.dockerDaemon;
|
|
||||||
bean.docker_type = dockerHost.dockerType;
|
|
||||||
bean.name = dockerHost.name;
|
|
||||||
|
|
||||||
await R.store(bean);
|
|
||||||
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a Docker host
|
|
||||||
* @param {number} dockerHostID ID of the Docker host to delete
|
|
||||||
* @param {number} userID ID of the user who created the Docker host
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
static async delete(dockerHostID, userID) {
|
|
||||||
let bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]);
|
|
||||||
|
|
||||||
if (!bean) {
|
|
||||||
throw new Error("docker host not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removed proxy from monitors if exists
|
|
||||||
await R.exec("UPDATE monitor SET docker_host = null WHERE docker_host = ?", [ dockerHostID ]);
|
|
||||||
|
|
||||||
await R.trash(bean);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the amount of containers on the Docker host
|
|
||||||
* @param {Object} dockerHost Docker host to check for
|
|
||||||
* @returns {number} Total amount of containers on the host
|
|
||||||
*/
|
|
||||||
static async testDockerHost(dockerHost) {
|
|
||||||
const options = {
|
|
||||||
url: "/containers/json?all=true",
|
|
||||||
headers: {
|
|
||||||
"Accept": "*/*",
|
|
||||||
"User-Agent": "Uptime-Kuma/" + version
|
|
||||||
},
|
|
||||||
httpsAgent: new https.Agent({
|
|
||||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dockerHost.dockerType === "socket") {
|
|
||||||
options.socketPath = dockerHost.dockerDaemon;
|
|
||||||
} else if (dockerHost.dockerType === "tcp") {
|
|
||||||
options.baseURL = DockerHost.patchDockerURL(dockerHost.dockerDaemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = await axios.request(options);
|
|
||||||
|
|
||||||
if (Array.isArray(res.data)) {
|
|
||||||
|
|
||||||
if (res.data.length > 1) {
|
|
||||||
|
|
||||||
if ("ImageID" in res.data[0]) {
|
|
||||||
return res.data.length;
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid Docker response, is it Docker really a daemon?");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return res.data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid Docker response, is it Docker really a daemon?");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since axios 0.27.X, it does not accept `tcp://` protocol.
|
|
||||||
* Change it to `http://` on the fly in order to fix it. (https://github.com/louislam/uptime-kuma/issues/2165)
|
|
||||||
*/
|
|
||||||
static patchDockerURL(url) {
|
|
||||||
if (typeof url === "string") {
|
|
||||||
// Replace the first occurrence only with g
|
|
||||||
return url.replace(/tcp:\/\//g, "http://");
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
DockerHost,
|
|
||||||
};
|
|
@ -3,21 +3,12 @@
|
|||||||
Modified with 0 dependencies
|
Modified with 0 dependencies
|
||||||
*/
|
*/
|
||||||
let fs = require("fs");
|
let fs = require("fs");
|
||||||
const { log } = require("../src/util");
|
|
||||||
|
|
||||||
let ImageDataURI = (() => {
|
let ImageDataURI = (() => {
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the data:image/ URI
|
|
||||||
* @param {string} dataURI data:image/ URI to decode
|
|
||||||
* @returns {?Object} An object with properties "imageType" and "dataBase64".
|
|
||||||
* The former is the image type, e.g., "png", and the latter is a base64
|
|
||||||
* encoded string of the image's binary data. If it fails to parse, returns
|
|
||||||
* null instead of an object.
|
|
||||||
*/
|
|
||||||
function decode(dataURI) {
|
function decode(dataURI) {
|
||||||
if (!/data:image\//.test(dataURI)) {
|
if (!/data:image\//.test(dataURI)) {
|
||||||
log.error("image-data-uri", "It seems that it is not an Image Data URI. Couldn't match \"data:image/\"");
|
console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\"");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,16 +20,9 @@ let ImageDataURI = (() => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Endcode an image into data:image/ URI
|
|
||||||
* @param {(Buffer|string)} data Data to encode
|
|
||||||
* @param {string} mediaType Media type of data
|
|
||||||
* @returns {(string|null)} A string representing the base64-encoded
|
|
||||||
* version of the given Buffer object or null if an error occurred.
|
|
||||||
*/
|
|
||||||
function encode(data, mediaType) {
|
function encode(data, mediaType) {
|
||||||
if (!data || !mediaType) {
|
if (!data || !mediaType) {
|
||||||
log.error("image-data-uri", "Missing some of the required params: data, mediaType");
|
console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType ");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,12 +33,6 @@ let ImageDataURI = (() => {
|
|||||||
return dataImgBase64;
|
return dataImgBase64;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write data URI to file
|
|
||||||
* @param {string} dataURI data:image/ URI
|
|
||||||
* @param {string} [filePath] Path to write file to
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
function outputFile(dataURI, filePath) {
|
function outputFile(dataURI, filePath) {
|
||||||
filePath = filePath || "./";
|
filePath = filePath || "./";
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const Bree = require("bree");
|
const Bree = require("bree");
|
||||||
const { SHARE_ENV } = require("worker_threads");
|
const { SHARE_ENV } = require("worker_threads");
|
||||||
const { log } = require("../src/util");
|
|
||||||
let bree;
|
|
||||||
const jobs = [
|
const jobs = [
|
||||||
{
|
{
|
||||||
name: "clear-old-data",
|
name: "clear-old-data",
|
||||||
@ -10,13 +9,8 @@ const jobs = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize background jobs
|
|
||||||
* @param {Object} args Arguments to pass to workers
|
|
||||||
* @returns {Bree}
|
|
||||||
*/
|
|
||||||
const initBackgroundJobs = function (args) {
|
const initBackgroundJobs = function (args) {
|
||||||
bree = new Bree({
|
const bree = new Bree({
|
||||||
root: path.resolve("server", "jobs"),
|
root: path.resolve("server", "jobs"),
|
||||||
jobs,
|
jobs,
|
||||||
worker: {
|
worker: {
|
||||||
@ -24,7 +18,7 @@ const initBackgroundJobs = function (args) {
|
|||||||
workerData: args,
|
workerData: args,
|
||||||
},
|
},
|
||||||
workerMessageHandler: (message) => {
|
workerMessageHandler: (message) => {
|
||||||
log.info("jobs", message);
|
console.log("[Background Job]:", message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,14 +26,6 @@ const initBackgroundJobs = function (args) {
|
|||||||
return bree;
|
return bree;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Stop all background jobs if running */
|
|
||||||
const stopBackgroundJobs = function () {
|
|
||||||
if (bree) {
|
|
||||||
bree.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initBackgroundJobs,
|
initBackgroundJobs
|
||||||
stopBackgroundJobs
|
|
||||||
};
|
};
|
||||||
|
@ -25,20 +25,15 @@ const DEFAULT_KEEP_PERIOD = 180;
|
|||||||
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedPeriod < 1) {
|
log(`Clearing Data older than ${parsedPeriod} days...`);
|
||||||
log(`Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
log(`Clearing Data older than ${parsedPeriod} days...`);
|
try {
|
||||||
|
await R.exec(
|
||||||
try {
|
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
|
||||||
await R.exec(
|
[parsedPeriod]
|
||||||
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
|
);
|
||||||
[ parsedPeriod ]
|
} catch (e) {
|
||||||
);
|
log(`Failed to clear old data: ${e.message}`);
|
||||||
} catch (e) {
|
|
||||||
log(`Failed to clear old data: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exit();
|
exit();
|
||||||
|
@ -2,24 +2,14 @@ const { parentPort, workerData } = require("worker_threads");
|
|||||||
const Database = require("../database");
|
const Database = require("../database");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
/**
|
|
||||||
* Send message to parent process for logging
|
|
||||||
* since worker_thread does not have access to stdout, this is used
|
|
||||||
* instead of console.log()
|
|
||||||
* @param {any} any The message to log
|
|
||||||
*/
|
|
||||||
const log = function (any) {
|
const log = function (any) {
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
parentPort.postMessage(any);
|
parentPort.postMessage(any);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Exit the worker process
|
|
||||||
* @param {number} error The status code to exit
|
|
||||||
*/
|
|
||||||
const exit = function (error) {
|
const exit = function (error) {
|
||||||
if (error && error !== 0) {
|
if (error && error != 0) {
|
||||||
process.exit(error);
|
process.exit(error);
|
||||||
} else {
|
} else {
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
@ -30,7 +20,6 @@ const exit = function (error) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Connects to the database */
|
|
||||||
const connectDb = async function () {
|
const connectDb = async function () {
|
||||||
const dbPath = path.join(
|
const dbPath = path.join(
|
||||||
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
|
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
|
||||||
|
|
||||||
class DockerHost extends BeanModel {
|
|
||||||
/**
|
|
||||||
* Returns an object that ready to parse to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
userID: this.user_id,
|
|
||||||
dockerDaemon: this.docker_daemon,
|
|
||||||
dockerType: this.docker_type,
|
|
||||||
name: this.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DockerHost;
|
|
@ -3,12 +3,6 @@ const { R } = require("redbean-node");
|
|||||||
|
|
||||||
class Group extends BeanModel {
|
class Group extends BeanModel {
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON for public
|
|
||||||
* Only show necessary data to public
|
|
||||||
* @param {boolean} [showTags=false] Should the JSON include monitor tags
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toPublicJSON(showTags = false) {
|
async toPublicJSON(showTags = false) {
|
||||||
let monitorBeanList = await this.getMonitorList();
|
let monitorBeanList = await this.getMonitorList();
|
||||||
let monitorList = [];
|
let monitorList = [];
|
||||||
@ -25,13 +19,9 @@ class Group extends BeanModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all monitors
|
|
||||||
* @returns {Bean[]}
|
|
||||||
*/
|
|
||||||
async getMonitorList() {
|
async getMonitorList() {
|
||||||
return R.convertToBeans("monitor", await R.getAll(`
|
return R.convertToBeans("monitor", await R.getAll(`
|
||||||
SELECT monitor.*, monitor_group.send_url FROM monitor, monitor_group
|
SELECT monitor.* FROM monitor, monitor_group
|
||||||
WHERE monitor.id = monitor_group.monitor_id
|
WHERE monitor.id = monitor_group.monitor_id
|
||||||
AND group_id = ?
|
AND group_id = ?
|
||||||
ORDER BY monitor_group.weight
|
ORDER BY monitor_group.weight
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
const dayjs = require("dayjs");
|
||||||
|
const utc = require("dayjs/plugin/utc");
|
||||||
|
let timezone = require("dayjs/plugin/timezone");
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -5,15 +10,9 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
* 1 = UP
|
* 1 = UP
|
||||||
* 2 = PENDING
|
* 2 = PENDING
|
||||||
* 3 = MAINTENANCE
|
|
||||||
*/
|
*/
|
||||||
class Heartbeat extends BeanModel {
|
class Heartbeat extends BeanModel {
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON for public
|
|
||||||
* Only show necessary data to public
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toPublicJSON() {
|
toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
status: this.status,
|
status: this.status,
|
||||||
@ -23,10 +22,6 @@ class Heartbeat extends BeanModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
monitorID: this.monitor_id,
|
monitorID: this.monitor_id,
|
||||||
|
@ -2,11 +2,6 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||||||
|
|
||||||
class Incident extends BeanModel {
|
class Incident extends BeanModel {
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON for public
|
|
||||||
* Only show necessary data to public
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toPublicJSON() {
|
toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -1,240 +0,0 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
|
||||||
const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util");
|
|
||||||
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server");
|
|
||||||
const { R } = require("redbean-node");
|
|
||||||
const dayjs = require("dayjs");
|
|
||||||
|
|
||||||
class Maintenance extends BeanModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON for public
|
|
||||||
* Only show necessary data to public
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toPublicJSON() {
|
|
||||||
|
|
||||||
let dateRange = [];
|
|
||||||
if (this.start_date) {
|
|
||||||
dateRange.push(utcToLocal(this.start_date));
|
|
||||||
if (this.end_date) {
|
|
||||||
dateRange.push(utcToLocal(this.end_date));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeRange = [];
|
|
||||||
let startTime = timeObjectToLocal(parseTimeObject(this.start_time));
|
|
||||||
timeRange.push(startTime);
|
|
||||||
let endTime = timeObjectToLocal(parseTimeObject(this.end_time));
|
|
||||||
timeRange.push(endTime);
|
|
||||||
|
|
||||||
let obj = {
|
|
||||||
id: this.id,
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
strategy: this.strategy,
|
|
||||||
intervalDay: this.interval_day,
|
|
||||||
active: !!this.active,
|
|
||||||
dateRange: dateRange,
|
|
||||||
timeRange: timeRange,
|
|
||||||
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [],
|
|
||||||
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [],
|
|
||||||
timeslotList: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeslotList = await this.getTimeslotList();
|
|
||||||
|
|
||||||
for (let timeslot of timeslotList) {
|
|
||||||
obj.timeslotList.push(await timeslot.toPublicJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(obj.weekdays)) {
|
|
||||||
obj.weekdays = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(obj.daysOfMonth)) {
|
|
||||||
obj.daysOfMonth = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maintenance Status
|
|
||||||
if (!obj.active) {
|
|
||||||
obj.status = "inactive";
|
|
||||||
} else if (obj.strategy === "manual") {
|
|
||||||
obj.status = "under-maintenance";
|
|
||||||
} else if (obj.timeslotList.length > 0) {
|
|
||||||
let currentTimestamp = dayjs().unix();
|
|
||||||
|
|
||||||
for (let timeslot of obj.timeslotList) {
|
|
||||||
if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) {
|
|
||||||
log.debug("timeslot", "Timeslot ID: " + timeslot.id);
|
|
||||||
log.debug("timeslot", "currentTimestamp:" + currentTimestamp);
|
|
||||||
log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix());
|
|
||||||
log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix());
|
|
||||||
|
|
||||||
obj.status = "under-maintenance";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!obj.status) {
|
|
||||||
obj.status = "scheduled";
|
|
||||||
}
|
|
||||||
} else if (obj.timeslotList.length === 0) {
|
|
||||||
obj.status = "ended";
|
|
||||||
} else {
|
|
||||||
obj.status = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only get future or current timeslots only
|
|
||||||
* @returns {Promise<[]>}
|
|
||||||
*/
|
|
||||||
async getTimeslotList() {
|
|
||||||
return R.convertToBeans("maintenance_timeslot", await R.getAll(`
|
|
||||||
SELECT maintenance_timeslot.*
|
|
||||||
FROM maintenance_timeslot, maintenance
|
|
||||||
WHERE maintenance_timeslot.maintenance_id = maintenance.id
|
|
||||||
AND maintenance.id = ?
|
|
||||||
AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()}
|
|
||||||
`, [
|
|
||||||
this.id
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON
|
|
||||||
* @param {string} timezone If not specified, the timeRange will be in UTC
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toJSON(timezone = null) {
|
|
||||||
return this.toPublicJSON(timezone);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of weekdays that the maintenance is active for
|
|
||||||
* Monday=1, Tuesday=2 etc.
|
|
||||||
* @returns {number[]} Array of active weekdays
|
|
||||||
*/
|
|
||||||
getDayOfWeekList() {
|
|
||||||
log.debug("timeslot", "List: " + this.weekdays);
|
|
||||||
return JSON.parse(this.weekdays).sort(function (a, b) {
|
|
||||||
return a - b;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of days in month that maintenance is active for
|
|
||||||
* @returns {number[]} Array of active days in month
|
|
||||||
*/
|
|
||||||
getDayOfMonthList() {
|
|
||||||
return JSON.parse(this.days_of_month).sort(function (a, b) {
|
|
||||||
return a - b;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the start date and time for maintenance
|
|
||||||
* @returns {dayjs.Dayjs} Start date and time
|
|
||||||
*/
|
|
||||||
getStartDateTime() {
|
|
||||||
let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
|
|
||||||
log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
|
|
||||||
|
|
||||||
// Start Time
|
|
||||||
let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
|
|
||||||
log.debug("timeslot", "startTime: " + startTimeSecond);
|
|
||||||
|
|
||||||
// Bake StartDate + StartTime = Start DateTime
|
|
||||||
return dayjs.utc(this.start_date).add(startTimeSecond, "second");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the duraction of maintenance in seconds
|
|
||||||
* @returns {number} Duration of maintenance
|
|
||||||
*/
|
|
||||||
getDuration() {
|
|
||||||
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
|
|
||||||
// Add 24hours if it is across day
|
|
||||||
if (duration < 0) {
|
|
||||||
duration += 24 * 3600;
|
|
||||||
}
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert data from socket to bean
|
|
||||||
* @param {Bean} bean Bean to fill in
|
|
||||||
* @param {Object} obj Data to fill bean with
|
|
||||||
* @returns {Bean} Filled bean
|
|
||||||
*/
|
|
||||||
static jsonToBean(bean, obj) {
|
|
||||||
if (obj.id) {
|
|
||||||
bean.id = obj.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply timezone offset to timeRange, as it cannot apply automatically.
|
|
||||||
if (obj.timeRange[0]) {
|
|
||||||
timeObjectToUTC(obj.timeRange[0]);
|
|
||||||
if (obj.timeRange[1]) {
|
|
||||||
timeObjectToUTC(obj.timeRange[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bean.title = obj.title;
|
|
||||||
bean.description = obj.description;
|
|
||||||
bean.strategy = obj.strategy;
|
|
||||||
bean.interval_day = obj.intervalDay;
|
|
||||||
bean.active = obj.active;
|
|
||||||
|
|
||||||
if (obj.dateRange[0]) {
|
|
||||||
bean.start_date = localToUTC(obj.dateRange[0]);
|
|
||||||
|
|
||||||
if (obj.dateRange[1]) {
|
|
||||||
bean.end_date = localToUTC(obj.dateRange[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
|
||||||
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
|
||||||
|
|
||||||
bean.weekdays = JSON.stringify(obj.weekdays);
|
|
||||||
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
|
||||||
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL conditions for active maintenance
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static getActiveMaintenanceSQLCondition() {
|
|
||||||
return `
|
|
||||||
(
|
|
||||||
(maintenance_timeslot.start_date <= DATETIME('now')
|
|
||||||
AND maintenance_timeslot.end_date >= DATETIME('now')
|
|
||||||
AND maintenance.active = 1)
|
|
||||||
OR
|
|
||||||
(maintenance.strategy = 'manual' AND active = 1)
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL conditions for active and future maintenance
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static getActiveAndFutureMaintenanceSQLCondition() {
|
|
||||||
return `
|
|
||||||
(
|
|
||||||
((maintenance_timeslot.end_date >= DATETIME('now')
|
|
||||||
AND maintenance.active = 1)
|
|
||||||
OR
|
|
||||||
(maintenance.strategy = 'manual' AND active = 1))
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Maintenance;
|
|
@ -1,198 +0,0 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
|
||||||
const { R } = require("redbean-node");
|
|
||||||
const dayjs = require("dayjs");
|
|
||||||
const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util");
|
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
|
||||||
|
|
||||||
class MaintenanceTimeslot extends BeanModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON for public
|
|
||||||
* Only show necessary data to public
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toPublicJSON() {
|
|
||||||
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
id: this.id,
|
|
||||||
startDate: this.start_date,
|
|
||||||
endDate: this.end_date,
|
|
||||||
startDateServerTimezone: utcToLocal(this.start_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
|
||||||
endDateServerTimezone: utcToLocal(this.end_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
|
||||||
serverTimezoneOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toJSON() {
|
|
||||||
return await this.toPublicJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Maintenance} maintenance
|
|
||||||
* @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date.
|
|
||||||
* @param {boolean} removeExist Remove existing timeslot before create
|
|
||||||
* @returns {Promise<MaintenanceTimeslot>}
|
|
||||||
*/
|
|
||||||
static async generateTimeslot(maintenance, minDate = null, removeExist = false) {
|
|
||||||
if (removeExist) {
|
|
||||||
await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [
|
|
||||||
maintenance.id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maintenance.strategy === "manual") {
|
|
||||||
log.debug("maintenance", "No need to generate timeslot for manual type");
|
|
||||||
|
|
||||||
} else if (maintenance.strategy === "single") {
|
|
||||||
let bean = R.dispense("maintenance_timeslot");
|
|
||||||
bean.maintenance_id = maintenance.id;
|
|
||||||
bean.start_date = maintenance.start_date;
|
|
||||||
bean.end_date = maintenance.end_date;
|
|
||||||
bean.generated_next = true;
|
|
||||||
return await R.store(bean);
|
|
||||||
|
|
||||||
} else if (maintenance.strategy === "recurring-interval") {
|
|
||||||
// Prevent dead loop, in case interval_day is not set
|
|
||||||
if (!maintenance.interval_day || maintenance.interval_day <= 0) {
|
|
||||||
maintenance.interval_day = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
|
||||||
return startDateTime.add(maintenance.interval_day, "day");
|
|
||||||
}, () => {
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (maintenance.strategy === "recurring-weekday") {
|
|
||||||
let dayOfWeekList = maintenance.getDayOfWeekList();
|
|
||||||
log.debug("timeslot", dayOfWeekList);
|
|
||||||
|
|
||||||
if (dayOfWeekList.length <= 0) {
|
|
||||||
log.debug("timeslot", "No weekdays selected?");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = (startDateTime) => {
|
|
||||||
log.debug("timeslot", "nextDateTime: " + startDateTime);
|
|
||||||
|
|
||||||
let day = startDateTime.local().day();
|
|
||||||
log.debug("timeslot", "nextDateTime.day(): " + day);
|
|
||||||
|
|
||||||
return dayOfWeekList.includes(day);
|
|
||||||
};
|
|
||||||
|
|
||||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
|
||||||
while (true) {
|
|
||||||
startDateTime = startDateTime.add(1, "day");
|
|
||||||
|
|
||||||
if (isValid(startDateTime)) {
|
|
||||||
return startDateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, isValid);
|
|
||||||
|
|
||||||
} else if (maintenance.strategy === "recurring-day-of-month") {
|
|
||||||
let dayOfMonthList = maintenance.getDayOfMonthList();
|
|
||||||
if (dayOfMonthList.length <= 0) {
|
|
||||||
log.debug("timeslot", "No day selected?");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = (startDateTime) => {
|
|
||||||
let day = parseInt(startDateTime.local().format("D"));
|
|
||||||
|
|
||||||
log.debug("timeslot", "day: " + day);
|
|
||||||
|
|
||||||
// Check 1-31
|
|
||||||
if (dayOfMonthList.includes(day)) {
|
|
||||||
return startDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check "lastDay1","lastDay2"...
|
|
||||||
let daysInMonth = startDateTime.daysInMonth();
|
|
||||||
let lastDayList = [];
|
|
||||||
|
|
||||||
// Small first, e.g. 28 > 29 > 30 > 31
|
|
||||||
for (let i = 4; i >= 1; i--) {
|
|
||||||
if (dayOfMonthList.includes("lastDay" + i)) {
|
|
||||||
lastDayList.push(daysInMonth - i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug("timeslot", lastDayList);
|
|
||||||
return lastDayList.includes(day);
|
|
||||||
};
|
|
||||||
|
|
||||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
|
||||||
while (true) {
|
|
||||||
startDateTime = startDateTime.add(1, "day");
|
|
||||||
if (isValid(startDateTime)) {
|
|
||||||
return startDateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, isValid);
|
|
||||||
} else {
|
|
||||||
throw new Error("Unknown maintenance strategy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a next timeslot for all recurring types
|
|
||||||
* @param maintenance
|
|
||||||
* @param minDate
|
|
||||||
* @param {function} nextDayCallback The logic how to get the next possible day
|
|
||||||
* @param {function} isValidCallback Check the day whether is matched the current strategy
|
|
||||||
* @returns {Promise<null|MaintenanceTimeslot>}
|
|
||||||
*/
|
|
||||||
static async handleRecurringType(maintenance, minDate, nextDayCallback, isValidCallback) {
|
|
||||||
let bean = R.dispense("maintenance_timeslot");
|
|
||||||
|
|
||||||
let duration = maintenance.getDuration();
|
|
||||||
let startDateTime = maintenance.getStartDateTime();
|
|
||||||
let endDateTime;
|
|
||||||
|
|
||||||
// Keep generating from the first possible date, until it is ok
|
|
||||||
while (true) {
|
|
||||||
log.debug("timeslot", "startDateTime: " + startDateTime.format());
|
|
||||||
|
|
||||||
// Handling out of effective date range
|
|
||||||
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
|
||||||
log.debug("timeslot", "Out of effective date range");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
endDateTime = startDateTime.add(duration, "second");
|
|
||||||
|
|
||||||
// If endDateTime is out of effective date range, use the end datetime from effective date range
|
|
||||||
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
|
||||||
endDateTime = dayjs.utc(maintenance.end_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If minDate is set, the endDateTime must be bigger than it.
|
|
||||||
// And the endDateTime must be bigger current time
|
|
||||||
// Is valid under current recurring strategy
|
|
||||||
if (
|
|
||||||
(!minDate || endDateTime.diff(minDate) > 0) &&
|
|
||||||
endDateTime.diff(dayjs()) > 0 &&
|
|
||||||
isValidCallback(startDateTime)
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
startDateTime = nextDayCallback(startDateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
bean.maintenance_id = maintenance.id;
|
|
||||||
bean.start_date = localToUTC(startDateTime);
|
|
||||||
bean.end_date = localToUTC(endDateTime);
|
|
||||||
bean.generated_next = false;
|
|
||||||
return await R.store(bean);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MaintenanceTimeslot;
|
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,6 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
|
||||||
class Proxy extends BeanModel {
|
class Proxy extends BeanModel {
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
id: this._id,
|
id: this._id,
|
||||||
|
@ -1,139 +1,8 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const cheerio = require("cheerio");
|
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
|
||||||
const jsesc = require("jsesc");
|
|
||||||
const Maintenance = require("./maintenance");
|
|
||||||
|
|
||||||
class StatusPage extends BeanModel {
|
class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
|
||||||
* Like this: { "test-uptime.kuma.pet": "default" }
|
|
||||||
* @type {{}}
|
|
||||||
*/
|
|
||||||
static domainMappingList = { };
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Response} response
|
|
||||||
* @param {string} indexHTML
|
|
||||||
* @param {string} slug
|
|
||||||
*/
|
|
||||||
static async handleStatusPageResponse(response, indexHTML, slug) {
|
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
|
||||||
slug
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (statusPage) {
|
|
||||||
response.send(await StatusPage.renderHTML(indexHTML, statusPage));
|
|
||||||
} else {
|
|
||||||
response.status(404).send(UptimeKumaServer.getInstance().indexHTML);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSR for status pages
|
|
||||||
* @param {string} indexHTML
|
|
||||||
* @param {StatusPage} statusPage
|
|
||||||
*/
|
|
||||||
static async renderHTML(indexHTML, statusPage) {
|
|
||||||
const $ = cheerio.load(indexHTML);
|
|
||||||
const description155 = statusPage.description?.substring(0, 155) ?? "";
|
|
||||||
|
|
||||||
$("title").text(statusPage.title);
|
|
||||||
$("meta[name=description]").attr("content", description155);
|
|
||||||
|
|
||||||
if (statusPage.icon) {
|
|
||||||
$("link[rel=icon]")
|
|
||||||
.attr("href", statusPage.icon)
|
|
||||||
.removeAttr("type");
|
|
||||||
|
|
||||||
$("link[rel=apple-touch-icon]").remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const head = $("head");
|
|
||||||
|
|
||||||
// OG Meta Tags
|
|
||||||
head.append(`<meta property="og:title" content="${statusPage.title}" />`);
|
|
||||||
head.append(`<meta property="og:description" content="${description155}" />`);
|
|
||||||
|
|
||||||
// Preload data
|
|
||||||
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
|
|
||||||
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
|
|
||||||
"isScriptContext": true
|
|
||||||
});
|
|
||||||
|
|
||||||
const script = $(`
|
|
||||||
<script id="preload-data" data-json="{}">
|
|
||||||
window.preloadData = ${escapedJSONObject};
|
|
||||||
</script>
|
|
||||||
`);
|
|
||||||
|
|
||||||
head.append(script);
|
|
||||||
|
|
||||||
// manifest.json
|
|
||||||
$("link[rel=manifest]").attr("href", `/api/status-page/${statusPage.slug}/manifest.json`);
|
|
||||||
|
|
||||||
return $.root().html();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all status page data in one call
|
|
||||||
* @param {StatusPage} statusPage
|
|
||||||
*/
|
|
||||||
static async getStatusPageData(statusPage) {
|
|
||||||
// Incident
|
|
||||||
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
|
|
||||||
statusPage.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (incident) {
|
|
||||||
incident = incident.toPublicJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
let maintenanceList = await StatusPage.getMaintenanceList(statusPage.id);
|
|
||||||
|
|
||||||
// Public Group List
|
|
||||||
const publicGroupList = [];
|
|
||||||
const showTags = !!statusPage.show_tags;
|
|
||||||
|
|
||||||
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
|
||||||
statusPage.id
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let groupBean of list) {
|
|
||||||
let monitorGroup = await groupBean.toPublicJSON(showTags);
|
|
||||||
publicGroupList.push(monitorGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response
|
|
||||||
return {
|
|
||||||
config: await statusPage.toPublicJSON(),
|
|
||||||
incident,
|
|
||||||
publicGroupList,
|
|
||||||
maintenanceList,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads domain mapping from DB
|
|
||||||
* Return object like this: { "test-uptime.kuma.pet": "default" }
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
static async loadDomainMappingList() {
|
|
||||||
StatusPage.domainMappingList = await R.getAssoc(`
|
|
||||||
SELECT domain, slug
|
|
||||||
FROM status_page, status_page_cname
|
|
||||||
WHERE status_page.id = status_page_cname.status_page_id
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send status page list to client
|
|
||||||
* @param {Server} io io Socket server instance
|
|
||||||
* @param {Socket} socket Socket.io instance
|
|
||||||
* @returns {Promise<Bean[]>}
|
|
||||||
*/
|
|
||||||
static async sendStatusPageList(io, socket) {
|
static async sendStatusPageList(io, socket) {
|
||||||
let result = {};
|
let result = {};
|
||||||
|
|
||||||
@ -147,70 +16,6 @@ class StatusPage extends BeanModel {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update list of domain names
|
|
||||||
* @param {string[]} domainNameList
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async updateDomainNameList(domainNameList) {
|
|
||||||
|
|
||||||
if (!Array.isArray(domainNameList)) {
|
|
||||||
throw new Error("Invalid array");
|
|
||||||
}
|
|
||||||
|
|
||||||
let trx = await R.begin();
|
|
||||||
|
|
||||||
await trx.exec("DELETE FROM status_page_cname WHERE status_page_id = ?", [
|
|
||||||
this.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (let domain of domainNameList) {
|
|
||||||
if (typeof domain !== "string") {
|
|
||||||
throw new Error("Invalid domain");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domain.trim() === "") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the domain name is used in another status page, delete it
|
|
||||||
await trx.exec("DELETE FROM status_page_cname WHERE domain = ?", [
|
|
||||||
domain,
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mapping = trx.dispense("status_page_cname");
|
|
||||||
mapping.status_page_id = this.id;
|
|
||||||
mapping.domain = domain;
|
|
||||||
await trx.store(mapping);
|
|
||||||
}
|
|
||||||
await trx.commit();
|
|
||||||
} catch (error) {
|
|
||||||
await trx.rollback();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get list of domain names
|
|
||||||
* @returns {Object[]}
|
|
||||||
*/
|
|
||||||
getDomainNameList() {
|
|
||||||
let domainList = [];
|
|
||||||
for (let domain in StatusPage.domainMappingList) {
|
|
||||||
let s = StatusPage.domainMappingList[domain];
|
|
||||||
|
|
||||||
if (this.slug === s) {
|
|
||||||
domainList.push(domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return domainList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toJSON() {
|
async toJSON() {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -221,18 +26,9 @@ class StatusPage extends BeanModel {
|
|||||||
theme: this.theme,
|
theme: this.theme,
|
||||||
published: !!this.published,
|
published: !!this.published,
|
||||||
showTags: !!this.show_tags,
|
showTags: !!this.show_tags,
|
||||||
domainNameList: this.getDomainNameList(),
|
|
||||||
customCSS: this.custom_css,
|
|
||||||
footerText: this.footer_text,
|
|
||||||
showPoweredBy: !!this.show_powered_by,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON for public
|
|
||||||
* Only show necessary data to public
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
async toPublicJSON() {
|
async toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
slug: this.slug,
|
slug: this.slug,
|
||||||
@ -242,26 +38,15 @@ class StatusPage extends BeanModel {
|
|||||||
theme: this.theme,
|
theme: this.theme,
|
||||||
published: !!this.published,
|
published: !!this.published,
|
||||||
showTags: !!this.show_tags,
|
showTags: !!this.show_tags,
|
||||||
customCSS: this.custom_css,
|
|
||||||
footerText: this.footer_text,
|
|
||||||
showPoweredBy: !!this.show_powered_by,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert slug to status page ID
|
|
||||||
* @param {string} slug
|
|
||||||
*/
|
|
||||||
static async slugToID(slug) {
|
static async slugToID(slug) {
|
||||||
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
||||||
slug
|
slug
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get path to the icon for the page
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getIcon() {
|
getIcon() {
|
||||||
if (!this.icon) {
|
if (!this.icon) {
|
||||||
return "/icon.svg";
|
return "/icon.svg";
|
||||||
@ -270,38 +55,6 @@ class StatusPage extends BeanModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get list of maintenances
|
|
||||||
* @param {number} statusPageId ID of status page to get maintenance for
|
|
||||||
* @returns {Object} Object representing maintenances sanitized for public
|
|
||||||
*/
|
|
||||||
static async getMaintenanceList(statusPageId) {
|
|
||||||
try {
|
|
||||||
const publicMaintenanceList = [];
|
|
||||||
|
|
||||||
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
|
|
||||||
let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(`
|
|
||||||
SELECT DISTINCT maintenance.*
|
|
||||||
FROM maintenance
|
|
||||||
JOIN maintenance_status_page
|
|
||||||
ON maintenance_status_page.maintenance_id = maintenance.id
|
|
||||||
AND maintenance_status_page.status_page_id = ?
|
|
||||||
LEFT JOIN maintenance_timeslot
|
|
||||||
ON maintenance_timeslot.maintenance_id = maintenance.id
|
|
||||||
WHERE ${activeCondition}
|
|
||||||
ORDER BY maintenance.end_date
|
|
||||||
`, [ statusPageId ]));
|
|
||||||
|
|
||||||
for (const bean of maintenanceBeanList) {
|
|
||||||
publicMaintenanceList.push(await bean.toPublicJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
return publicMaintenanceList;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = StatusPage;
|
module.exports = StatusPage;
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
|
||||||
class Tag extends BeanModel {
|
class Tag extends BeanModel {
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that ready to parse to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
id: this._id,
|
id: this._id,
|
||||||
|
@ -3,30 +3,19 @@ const passwordHash = require("../password-hash");
|
|||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
|
||||||
class User extends BeanModel {
|
class User extends BeanModel {
|
||||||
/**
|
|
||||||
* Reset user password
|
|
||||||
* Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead.
|
|
||||||
* @param {number} userID ID of user to update
|
|
||||||
* @param {string} newPassword
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
static async resetPassword(userID, newPassword) {
|
|
||||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
|
||||||
passwordHash.generate(newPassword),
|
|
||||||
userID
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset this users password
|
* Direct execute, no need R.store()
|
||||||
* @param {string} newPassword
|
* @param newPassword
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async resetPassword(newPassword) {
|
async resetPassword(newPassword) {
|
||||||
await User.resetPassword(this.id, newPassword);
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||||
|
passwordHash.generate(newPassword),
|
||||||
|
this.id
|
||||||
|
]);
|
||||||
this.password = newPassword;
|
this.password = newPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = User;
|
module.exports = User;
|
||||||
|
@ -13,49 +13,27 @@ let t = {
|
|||||||
|
|
||||||
let instances = [];
|
let instances = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Does a === b
|
|
||||||
* @param {any} a
|
|
||||||
* @returns {function(any): boolean}
|
|
||||||
*/
|
|
||||||
let matches = function (a) {
|
let matches = function (a) {
|
||||||
return function (b) {
|
return function (b) {
|
||||||
return a === b;
|
return a === b;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Does a!==b
|
|
||||||
* @param {any} a
|
|
||||||
* @returns {function(any): boolean}
|
|
||||||
*/
|
|
||||||
let doesntMatch = function (a) {
|
let doesntMatch = function (a) {
|
||||||
return function (b) {
|
return function (b) {
|
||||||
return !matches(a)(b);
|
return !matches(a)(b);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get log duration
|
|
||||||
* @param {number} d Time in ms
|
|
||||||
* @param {string} prefix Prefix for log
|
|
||||||
* @returns {string} Coloured log string
|
|
||||||
*/
|
|
||||||
let logDuration = function (d, prefix) {
|
let logDuration = function (d, prefix) {
|
||||||
let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms";
|
let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms";
|
||||||
return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m";
|
return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m";
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get safe headers
|
|
||||||
* @param {Object} res Express response object
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
function getSafeHeaders(res) {
|
function getSafeHeaders(res) {
|
||||||
return res.getHeaders ? res.getHeaders() : res._headers;
|
return res.getHeaders ? res.getHeaders() : res._headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor for ApiCache instance */
|
|
||||||
function ApiCache() {
|
function ApiCache() {
|
||||||
let memCache = new MemoryCache();
|
let memCache = new MemoryCache();
|
||||||
|
|
||||||
@ -90,15 +68,6 @@ function ApiCache() {
|
|||||||
instances.push(this);
|
instances.push(this);
|
||||||
this.id = instances.length;
|
this.id = instances.length;
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs a message to the console if the `DEBUG` environment variable is set.
|
|
||||||
* @param {string} a The first argument to log.
|
|
||||||
* @param {string} b The second argument to log.
|
|
||||||
* @param {string} c The third argument to log.
|
|
||||||
* @param {string} d The fourth argument to log, and so on... (optional)
|
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
|
||||||
function debug(a, b, c, d) {
|
function debug(a, b, c, d) {
|
||||||
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
|
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
|
||||||
return arg !== undefined;
|
return arg !== undefined;
|
||||||
@ -108,13 +77,6 @@ function ApiCache() {
|
|||||||
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
|
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given request and response should be logged.
|
|
||||||
* @param {Object} request The HTTP request object.
|
|
||||||
* @param {Object} response The HTTP response object.
|
|
||||||
* @param {function(Object, Object):boolean} toggle
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function shouldCacheResponse(request, response, toggle) {
|
function shouldCacheResponse(request, response, toggle) {
|
||||||
let opt = globalOptions;
|
let opt = globalOptions;
|
||||||
let codes = opt.statusCodes;
|
let codes = opt.statusCodes;
|
||||||
@ -137,11 +99,6 @@ function ApiCache() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add key to index array
|
|
||||||
* @param {string} key Key to add
|
|
||||||
* @param {Object} req Express request object
|
|
||||||
*/
|
|
||||||
function addIndexEntries(key, req) {
|
function addIndexEntries(key, req) {
|
||||||
let groupName = req.apicacheGroup;
|
let groupName = req.apicacheGroup;
|
||||||
|
|
||||||
@ -154,16 +111,6 @@ function ApiCache() {
|
|||||||
index.all.unshift(key);
|
index.all.unshift(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new object containing only the whitelisted headers.
|
|
||||||
* @param {Object} headers The original object of header names and
|
|
||||||
* values.
|
|
||||||
* @param {string[]} globalOptions.headerWhitelist An array of
|
|
||||||
* strings representing the whitelisted header names to keep in the
|
|
||||||
* output object.
|
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
|
||||||
function filterBlacklistedHeaders(headers) {
|
function filterBlacklistedHeaders(headers) {
|
||||||
return Object.keys(headers)
|
return Object.keys(headers)
|
||||||
.filter(function (key) {
|
.filter(function (key) {
|
||||||
@ -175,14 +122,6 @@ function ApiCache() {
|
|||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cache object
|
|
||||||
* @param {Object} headers The response headers to filter.
|
|
||||||
* @returns {Object} A new object containing only the whitelisted
|
|
||||||
* response headers.
|
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
|
||||||
function createCacheObject(status, headers, data, encoding) {
|
function createCacheObject(status, headers, data, encoding) {
|
||||||
return {
|
return {
|
||||||
status: status,
|
status: status,
|
||||||
@ -193,15 +132,6 @@ function ApiCache() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a cache value for the given key.
|
|
||||||
* @param {string} key The cache key to set.
|
|
||||||
* @param {any} value The cache value to set.
|
|
||||||
* @param {number} duration How long in milliseconds the cached
|
|
||||||
* response should be valid for (defaults to 1 hour).
|
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
|
||||||
function cacheResponse(key, value, duration) {
|
function cacheResponse(key, value, duration) {
|
||||||
let redis = globalOptions.redisClient;
|
let redis = globalOptions.redisClient;
|
||||||
let expireCallback = globalOptions.events.expire;
|
let expireCallback = globalOptions.events.expire;
|
||||||
@ -224,13 +154,6 @@ function ApiCache() {
|
|||||||
}, Math.min(duration, 2147483647));
|
}, Math.min(duration, 2147483647));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends content to the response.
|
|
||||||
* @param {Object} res Express response object
|
|
||||||
* @param {(string|Buffer)} content The content to append.
|
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
|
||||||
function accumulateContent(res, content) {
|
function accumulateContent(res, content) {
|
||||||
if (content) {
|
if (content) {
|
||||||
if (typeof content == "string") {
|
if (typeof content == "string") {
|
||||||
@ -256,17 +179,6 @@ function ApiCache() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Monkeypatches the response object to add cache control headers
|
|
||||||
* and create a cache object.
|
|
||||||
* @param {Object} req Express request object
|
|
||||||
* @param {Object} res Express response object
|
|
||||||
* @param {function} next Function to call next
|
|
||||||
* @param {string} key Key to add response as
|
|
||||||
* @param {number} duration Time to cache response for
|
|
||||||
* @param {string} strDuration Duration in string form
|
|
||||||
* @param {function(Object, Object):boolean} toggle
|
|
||||||
*/
|
|
||||||
function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) {
|
function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) {
|
||||||
// monkeypatch res.end to create cache object
|
// monkeypatch res.end to create cache object
|
||||||
res._apicache = {
|
res._apicache = {
|
||||||
@ -333,17 +245,6 @@ function ApiCache() {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a cached response to client
|
|
||||||
* @param {Request} request Express request object
|
|
||||||
* @param {Response} response Express response object
|
|
||||||
* @param {object} cacheObject Cache object to send
|
|
||||||
* @param {function(Object, Object):boolean} toggle
|
|
||||||
* @param {function} next Function to call next
|
|
||||||
* @param {number} duration Not used
|
|
||||||
* @returns {boolean|undefined} true if the request should be
|
|
||||||
* cached, false otherwise. If undefined, defaults to true.
|
|
||||||
*/
|
|
||||||
function sendCachedResponse(request, response, cacheObject, toggle, next, duration) {
|
function sendCachedResponse(request, response, cacheObject, toggle, next, duration) {
|
||||||
if (toggle && !toggle(request, response)) {
|
if (toggle && !toggle(request, response)) {
|
||||||
return next();
|
return next();
|
||||||
@ -384,19 +285,12 @@ function ApiCache() {
|
|||||||
return response.end(data, cacheObject.encoding);
|
return response.end(data, cacheObject.encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sync caching options */
|
|
||||||
function syncOptions() {
|
function syncOptions() {
|
||||||
for (let i in middlewareOptions) {
|
for (let i in middlewareOptions) {
|
||||||
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions);
|
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear key from cache
|
|
||||||
* @param {string} target Key to clear
|
|
||||||
* @param {boolean} isAutomatic Is the key being cleared automatically
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
this.clear = function (target, isAutomatic) {
|
this.clear = function (target, isAutomatic) {
|
||||||
let group = index.groups[target];
|
let group = index.groups[target];
|
||||||
let redis = globalOptions.redisClient;
|
let redis = globalOptions.redisClient;
|
||||||
@ -471,14 +365,6 @@ function ApiCache() {
|
|||||||
return this.getIndex();
|
return this.getIndex();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a duration string to an integer number of milliseconds.
|
|
||||||
* @param {(string|number)} duration The string to convert.
|
|
||||||
* @param {number} defaultDuration The default duration to return if
|
|
||||||
* can't parse duration
|
|
||||||
* @returns {number} The converted value in milliseconds, or the
|
|
||||||
* defaultDuration if it can't be parsed.
|
|
||||||
*/
|
|
||||||
function parseDuration(duration, defaultDuration) {
|
function parseDuration(duration, defaultDuration) {
|
||||||
if (typeof duration === "number") {
|
if (typeof duration === "number") {
|
||||||
return duration;
|
return duration;
|
||||||
@ -501,24 +387,17 @@ function ApiCache() {
|
|||||||
return defaultDuration;
|
return defaultDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse duration
|
|
||||||
* @param {(number|string)} duration
|
|
||||||
* @returns {number} Duration parsed to a number
|
|
||||||
*/
|
|
||||||
this.getDuration = function (duration) {
|
this.getDuration = function (duration) {
|
||||||
return parseDuration(duration, globalOptions.defaultDuration);
|
return parseDuration(duration, globalOptions.defaultDuration);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return cache performance statistics (hit rate). Suitable for
|
* Return cache performance statistics (hit rate). Suitable for putting into a route:
|
||||||
* putting into a route:
|
|
||||||
* <code>
|
* <code>
|
||||||
* app.get('/api/cache/performance', (req, res) => {
|
* app.get('/api/cache/performance', (req, res) => {
|
||||||
* res.json(apicache.getPerformance())
|
* res.json(apicache.getPerformance())
|
||||||
* })
|
* })
|
||||||
* </code>
|
* </code>
|
||||||
* @returns {any[]}
|
|
||||||
*/
|
*/
|
||||||
this.getPerformance = function () {
|
this.getPerformance = function () {
|
||||||
return performanceArray.map(function (p) {
|
return performanceArray.map(function (p) {
|
||||||
@ -526,11 +405,6 @@ function ApiCache() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get index of a group
|
|
||||||
* @param {string} group
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
this.getIndex = function (group) {
|
this.getIndex = function (group) {
|
||||||
if (group) {
|
if (group) {
|
||||||
return index.groups[group];
|
return index.groups[group];
|
||||||
@ -539,14 +413,6 @@ function ApiCache() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Express middleware
|
|
||||||
* @param {(string|number)} strDuration Duration to cache responses
|
|
||||||
* for.
|
|
||||||
* @param {function(Object, Object):boolean} middlewareToggle
|
|
||||||
* @param {Object} localOptions Options for APICache
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
|
this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
|
||||||
let duration = instance.getDuration(strDuration);
|
let duration = instance.getDuration(strDuration);
|
||||||
let opt = {};
|
let opt = {};
|
||||||
@ -570,72 +436,63 @@ function ApiCache() {
|
|||||||
options(localOptions);
|
options(localOptions);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Function for non tracking performance
|
* A Function for non tracking performance
|
||||||
*/
|
*/
|
||||||
function NOOPCachePerformance() {
|
function NOOPCachePerformance() {
|
||||||
this.report = this.hit = this.miss = function () {}; // noop;
|
this.report = this.hit = this.miss = function () {}; // noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function for tracking and reporting hit rate. These
|
* A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above.
|
||||||
* statistics are returned by the getPerformance() call above.
|
*/
|
||||||
*/
|
|
||||||
function CachePerformance() {
|
function CachePerformance() {
|
||||||
/**
|
/**
|
||||||
* Tracks the hit rate for the last 100 requests. If there
|
* Tracks the hit rate for the last 100 requests.
|
||||||
* have been fewer than 100 requests, the hit rate just
|
* If there have been fewer than 100 requests, the hit rate just considers the requests that have happened.
|
||||||
* considers the requests that have happened.
|
*/
|
||||||
*/
|
|
||||||
this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits
|
this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the hit rate for the last 1000 requests. If there
|
* Tracks the hit rate for the last 1000 requests.
|
||||||
* have been fewer than 1000 requests, the hit rate just
|
* If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened.
|
||||||
* considers the requests that have happened.
|
*/
|
||||||
*/
|
|
||||||
this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits
|
this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the hit rate for the last 10000 requests. If there
|
* Tracks the hit rate for the last 10000 requests.
|
||||||
* have been fewer than 10000 requests, the hit rate just
|
* If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened.
|
||||||
* considers the requests that have happened.
|
*/
|
||||||
*/
|
|
||||||
this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits
|
this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the hit rate for the last 100000 requests. If
|
* Tracks the hit rate for the last 100000 requests.
|
||||||
* there have been fewer than 100000 requests, the hit rate
|
* If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened.
|
||||||
* just considers the requests that have happened.
|
*/
|
||||||
*/
|
|
||||||
this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits
|
this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of calls that have passed through the
|
* The number of calls that have passed through the middleware since the server started.
|
||||||
* middleware since the server started.
|
*/
|
||||||
*/
|
|
||||||
this.callCount = 0;
|
this.callCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The total number of hits since the server started
|
* The total number of hits since the server started
|
||||||
*/
|
*/
|
||||||
this.hitCount = 0;
|
this.hitCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key from the last cache hit. This is useful in
|
* The key from the last cache hit. This is useful in identifying which route these statistics apply to.
|
||||||
* identifying which route these statistics apply to.
|
*/
|
||||||
*/
|
|
||||||
this.lastCacheHit = null;
|
this.lastCacheHit = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key from the last cache miss. This is useful in
|
* The key from the last cache miss. This is useful in identifying which route these statistics apply to.
|
||||||
* identifying which route these statistics apply to.
|
*/
|
||||||
*/
|
|
||||||
this.lastCacheMiss = null;
|
this.lastCacheMiss = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return performance statistics
|
* Return performance statistics
|
||||||
* @returns {Object}
|
*/
|
||||||
*/
|
|
||||||
this.report = function () {
|
this.report = function () {
|
||||||
return {
|
return {
|
||||||
lastCacheHit: this.lastCacheHit,
|
lastCacheHit: this.lastCacheHit,
|
||||||
@ -652,13 +509,10 @@ function ApiCache() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes a cache hit rate from an array of hits and
|
* Computes a cache hit rate from an array of hits and misses.
|
||||||
* misses.
|
* @param {Uint8Array} array An array representing hits and misses.
|
||||||
* @param {Uint8Array} array An array representing hits and
|
* @returns a number between 0 and 1, or null if the array has no hits or misses
|
||||||
* misses.
|
*/
|
||||||
* @returns {?number} a number between 0 and 1, or null if
|
|
||||||
* the array has no hits or misses
|
|
||||||
*/
|
|
||||||
this.hitRate = function (array) {
|
this.hitRate = function (array) {
|
||||||
let hits = 0;
|
let hits = 0;
|
||||||
let misses = 0;
|
let misses = 0;
|
||||||
@ -684,17 +538,16 @@ function ApiCache() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a hit or miss in the given array. It will be
|
* Record a hit or miss in the given array. It will be recorded at a position determined
|
||||||
* recorded at a position determined by the current value of
|
* by the current value of the callCount variable.
|
||||||
* the callCount variable.
|
* @param {Uint8Array} array An array representing hits and misses.
|
||||||
* @param {Uint8Array} array An array representing hits and
|
* @param {boolean} hit true for a hit, false for a miss
|
||||||
* misses.
|
* Each element in the array is 8 bits, and encodes 4 hit/miss records.
|
||||||
* @param {boolean} hit true for a hit, false for a miss
|
* Each hit or miss is encoded as to bits as follows:
|
||||||
* Each element in the array is 8 bits, and encodes 4
|
* 00 means no hit or miss has been recorded in these bits
|
||||||
* hit/miss records. Each hit or miss is encoded as to bits
|
* 01 encodes a hit
|
||||||
* as follows: 00 means no hit or miss has been recorded in
|
* 10 encodes a miss
|
||||||
* these bits 01 encodes a hit 10 encodes a miss
|
*/
|
||||||
*/
|
|
||||||
this.recordHitInArray = function (array, hit) {
|
this.recordHitInArray = function (array, hit) {
|
||||||
let arrayIndex = ~~(this.callCount / 4) % array.length;
|
let arrayIndex = ~~(this.callCount / 4) % array.length;
|
||||||
let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element
|
let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element
|
||||||
@ -704,11 +557,9 @@ function ApiCache() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records the hit or miss in the tracking arrays and
|
* Records the hit or miss in the tracking arrays and increments the call count.
|
||||||
* increments the call count.
|
* @param {boolean} hit true records a hit, false records a miss
|
||||||
* @param {boolean} hit true records a hit, false records a
|
*/
|
||||||
* miss
|
|
||||||
*/
|
|
||||||
this.recordHit = function (hit) {
|
this.recordHit = function (hit) {
|
||||||
this.recordHitInArray(this.hitsLast100, hit);
|
this.recordHitInArray(this.hitsLast100, hit);
|
||||||
this.recordHitInArray(this.hitsLast1000, hit);
|
this.recordHitInArray(this.hitsLast1000, hit);
|
||||||
@ -721,18 +572,18 @@ function ApiCache() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records a hit event, setting lastCacheMiss to the given key
|
* Records a hit event, setting lastCacheMiss to the given key
|
||||||
* @param {string} key The key that had the cache hit
|
* @param {string} key The key that had the cache hit
|
||||||
*/
|
*/
|
||||||
this.hit = function (key) {
|
this.hit = function (key) {
|
||||||
this.recordHit(true);
|
this.recordHit(true);
|
||||||
this.lastCacheHit = key;
|
this.lastCacheHit = key;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records a miss event, setting lastCacheMiss to the given key
|
* Records a miss event, setting lastCacheMiss to the given key
|
||||||
* @param {string} key The key that had the cache miss
|
* @param {string} key The key that had the cache miss
|
||||||
*/
|
*/
|
||||||
this.miss = function (key) {
|
this.miss = function (key) {
|
||||||
this.recordHit(false);
|
this.recordHit(false);
|
||||||
this.lastCacheMiss = key;
|
this.lastCacheMiss = key;
|
||||||
@ -743,13 +594,6 @@ function ApiCache() {
|
|||||||
|
|
||||||
performanceArray.push(perf);
|
performanceArray.push(perf);
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache a request
|
|
||||||
* @param {Object} req Express request object
|
|
||||||
* @param {Object} res Express response object
|
|
||||||
* @param {function} next Function to call next
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
let cache = function (req, res, next) {
|
let cache = function (req, res, next) {
|
||||||
function bypass() {
|
function bypass() {
|
||||||
debug("bypass detected, skipping cache.");
|
debug("bypass detected, skipping cache.");
|
||||||
@ -857,11 +701,6 @@ function ApiCache() {
|
|||||||
return cache;
|
return cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Process options
|
|
||||||
* @param {Object} options
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
this.options = function (options) {
|
this.options = function (options) {
|
||||||
if (options) {
|
if (options) {
|
||||||
Object.assign(globalOptions, options);
|
Object.assign(globalOptions, options);
|
||||||
@ -882,7 +721,6 @@ function ApiCache() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Reset the index */
|
|
||||||
this.resetIndex = function () {
|
this.resetIndex = function () {
|
||||||
index = {
|
index = {
|
||||||
all: [],
|
all: [],
|
||||||
@ -890,11 +728,6 @@ function ApiCache() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new instance of ApiCache
|
|
||||||
* @param {Object} config Config to pass
|
|
||||||
* @returns {ApiCache}
|
|
||||||
*/
|
|
||||||
this.newInstance = function (config) {
|
this.newInstance = function (config) {
|
||||||
let instance = new ApiCache();
|
let instance = new ApiCache();
|
||||||
|
|
||||||
@ -905,7 +738,6 @@ function ApiCache() {
|
|||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Clone this instance */
|
|
||||||
this.clone = function () {
|
this.clone = function () {
|
||||||
return this.newInstance(this.options());
|
return this.newInstance(this.options());
|
||||||
};
|
};
|
||||||
|
@ -3,15 +3,6 @@ function MemoryCache() {
|
|||||||
this.size = 0;
|
this.size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} key Key to store cache as
|
|
||||||
* @param {any} value Value to store
|
|
||||||
* @param {number} time Time to store for
|
|
||||||
* @param {function(any, string)} timeoutCallback Callback to call in
|
|
||||||
* case of timeout
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
||||||
let old = this.cache[key];
|
let old = this.cache[key];
|
||||||
let instance = this;
|
let instance = this;
|
||||||
@ -31,11 +22,6 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
|||||||
return entry;
|
return entry;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a cache entry
|
|
||||||
* @param {string} key Key to delete
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
MemoryCache.prototype.delete = function (key) {
|
MemoryCache.prototype.delete = function (key) {
|
||||||
let entry = this.cache[key];
|
let entry = this.cache[key];
|
||||||
|
|
||||||
@ -50,32 +36,18 @@ MemoryCache.prototype.delete = function (key) {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of key
|
|
||||||
* @param {string} key
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
MemoryCache.prototype.get = function (key) {
|
MemoryCache.prototype.get = function (key) {
|
||||||
let entry = this.cache[key];
|
let entry = this.cache[key];
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of cache entry
|
|
||||||
* @param {string} key
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
MemoryCache.prototype.getValue = function (key) {
|
MemoryCache.prototype.getValue = function (key) {
|
||||||
let entry = this.get(key);
|
let entry = this.get(key);
|
||||||
|
|
||||||
return entry && entry.value;
|
return entry && entry.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear cache
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
MemoryCache.prototype.clear = function () {
|
MemoryCache.prototype.clear = function () {
|
||||||
Object.keys(this.cache).forEach(function (key) {
|
Object.keys(this.cache).forEach(function (key) {
|
||||||
this.delete(key);
|
this.delete(key);
|
||||||
|
20
server/modules/dayjs/plugin/timezone.d.ts
vendored
20
server/modules/dayjs/plugin/timezone.d.ts
vendored
@ -1,20 +0,0 @@
|
|||||||
import { PluginFunc, ConfigType } from 'dayjs'
|
|
||||||
|
|
||||||
declare const plugin: PluginFunc
|
|
||||||
export = plugin
|
|
||||||
|
|
||||||
declare module 'dayjs' {
|
|
||||||
interface Dayjs {
|
|
||||||
tz(timezone?: string, keepLocalTime?: boolean): Dayjs
|
|
||||||
offsetName(type?: 'short' | 'long'): string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DayjsTimezone {
|
|
||||||
(date: ConfigType, timezone?: string): Dayjs
|
|
||||||
(date: ConfigType, format: string, timezone?: string): Dayjs
|
|
||||||
guess(): string
|
|
||||||
setDefault(timezone?: string): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const tz: DayjsTimezone
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copy from node_modules/dayjs/plugin/timezone.js
|
|
||||||
* Try to fix https://github.com/louislam/uptime-kuma/issues/2318
|
|
||||||
* Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
!function (t, e) {
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
typeof exports == "object" && typeof module != "undefined" ? module.exports = e() : typeof define == "function" && define.amd ? define(e) : (t = typeof globalThis != "undefined" ? globalThis : t || self).dayjs_plugin_timezone = e();
|
|
||||||
}(this, (function () {
|
|
||||||
"use strict";
|
|
||||||
let t = {
|
|
||||||
year: 0,
|
|
||||||
month: 1,
|
|
||||||
day: 2,
|
|
||||||
hour: 3,
|
|
||||||
minute: 4,
|
|
||||||
second: 5
|
|
||||||
};
|
|
||||||
let e = {};
|
|
||||||
return function (n, i, o) {
|
|
||||||
let r;
|
|
||||||
let a = function (t, n, i) {
|
|
||||||
void 0 === i && (i = {});
|
|
||||||
let o = new Date(t);
|
|
||||||
let r = function (t, n) {
|
|
||||||
void 0 === n && (n = {});
|
|
||||||
let i = n.timeZoneName || "short";
|
|
||||||
let o = t + "|" + i;
|
|
||||||
let r = e[o];
|
|
||||||
return r || (r = new Intl.DateTimeFormat("en-US", {
|
|
||||||
hour12: !1,
|
|
||||||
timeZone: t,
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
timeZoneName: i
|
|
||||||
}), e[o] = r), r;
|
|
||||||
}(n, i);
|
|
||||||
return r.formatToParts(o);
|
|
||||||
};
|
|
||||||
let u = function (e, n) {
|
|
||||||
let i = a(e, n);
|
|
||||||
let r = [];
|
|
||||||
let u = 0;
|
|
||||||
for (; u < i.length; u += 1) {
|
|
||||||
let f = i[u];
|
|
||||||
let s = f.type;
|
|
||||||
let m = f.value;
|
|
||||||
let c = t[s];
|
|
||||||
c >= 0 && (r[c] = parseInt(m, 10));
|
|
||||||
}
|
|
||||||
let d = r[3];
|
|
||||||
let l = d === 24 ? 0 : d;
|
|
||||||
let v = r[0] + "-" + r[1] + "-" + r[2] + " " + l + ":" + r[4] + ":" + r[5] + ":000";
|
|
||||||
let h = +e;
|
|
||||||
return (o.utc(v).valueOf() - (h -= h % 1e3)) / 6e4;
|
|
||||||
};
|
|
||||||
let f = i.prototype;
|
|
||||||
f.tz = function (t, e) {
|
|
||||||
void 0 === t && (t = r);
|
|
||||||
let n = this.utcOffset();
|
|
||||||
let i = this.toDate();
|
|
||||||
let a = i.toLocaleString("en-US", { timeZone: t }).replace("\u202f", " ");
|
|
||||||
let u = Math.round((i - new Date(a)) / 1e3 / 60);
|
|
||||||
let f = o(a).$set("millisecond", this.$ms).utcOffset(15 * -Math.round(i.getTimezoneOffset() / 15) - u, !0);
|
|
||||||
if (e) {
|
|
||||||
let s = f.utcOffset();
|
|
||||||
f = f.add(n - s, "minute");
|
|
||||||
}
|
|
||||||
return f.$x.$timezone = t, f;
|
|
||||||
}, f.offsetName = function (t) {
|
|
||||||
let e = this.$x.$timezone || o.tz.guess();
|
|
||||||
let n = a(this.valueOf(), e, { timeZoneName: t }).find((function (t) {
|
|
||||||
return t.type.toLowerCase() === "timezonename";
|
|
||||||
}));
|
|
||||||
return n && n.value;
|
|
||||||
};
|
|
||||||
let s = f.startOf;
|
|
||||||
f.startOf = function (t, e) {
|
|
||||||
if (!this.$x || !this.$x.$timezone) {
|
|
||||||
return s.call(this, t, e);
|
|
||||||
}
|
|
||||||
let n = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));
|
|
||||||
return s.call(n, t, e).tz(this.$x.$timezone, !0);
|
|
||||||
}, o.tz = function (t, e, n) {
|
|
||||||
let i = n && e;
|
|
||||||
let a = n || e || r;
|
|
||||||
let f = u(+o(), a);
|
|
||||||
if (typeof t != "string") {
|
|
||||||
return o(t).tz(a);
|
|
||||||
}
|
|
||||||
let s = function (t, e, n) {
|
|
||||||
let i = t - 60 * e * 1e3;
|
|
||||||
let o = u(i, n);
|
|
||||||
if (e === o) {
|
|
||||||
return [ i, e ];
|
|
||||||
}
|
|
||||||
let r = u(i -= 60 * (o - e) * 1e3, n);
|
|
||||||
return o === r ? [ i, o ] : [ t - 60 * Math.min(o, r) * 1e3, Math.max(o, r) ];
|
|
||||||
}(o.utc(t, i).valueOf(), f, a);
|
|
||||||
let m = s[0];
|
|
||||||
let c = s[1];
|
|
||||||
let d = o(m).utcOffset(c);
|
|
||||||
return d.$x.$timezone = a, d;
|
|
||||||
}, o.tz.guess = function () {
|
|
||||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
||||||
}, o.tz.setDefault = function (t) {
|
|
||||||
r = t;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}));
|
|
@ -14,7 +14,7 @@ class Alerta extends NotificationProvider {
|
|||||||
let config = {
|
let config = {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json;charset=UTF-8",
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
"Authorization": "Key " + notification.alertaApiKey,
|
"Authorization": "Key " + notification.alertaapiKey,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let data = {
|
let data = {
|
||||||
@ -40,17 +40,17 @@ class Alerta extends NotificationProvider {
|
|||||||
await axios.post(alertaUrl, postData, config);
|
await axios.post(alertaUrl, postData, config);
|
||||||
} else {
|
} else {
|
||||||
let datadup = Object.assign( {
|
let datadup = Object.assign( {
|
||||||
correlate: [ "service_up", "service_down" ],
|
correlate: ["service_up", "service_down"],
|
||||||
event: monitorJSON["type"],
|
event: monitorJSON["type"],
|
||||||
group: "uptimekuma-" + monitorJSON["type"],
|
group: "uptimekuma-" + monitorJSON["type"],
|
||||||
resource: monitorJSON["name"],
|
resource: monitorJSON["name"],
|
||||||
}, data );
|
}, data );
|
||||||
|
|
||||||
if (heartbeatJSON["status"] === DOWN) {
|
if (heartbeatJSON["status"] == DOWN) {
|
||||||
datadup.severity = notification.alertaAlertState; // critical
|
datadup.severity = notification.alertaAlertState; // critical
|
||||||
datadup.text = "Service " + monitorJSON["type"] + " is down.";
|
datadup.text = "Service " + monitorJSON["type"] + " is down.";
|
||||||
await axios.post(alertaUrl, datadup, config);
|
await axios.post(alertaUrl, datadup, config);
|
||||||
} else if (heartbeatJSON["status"] === UP) {
|
} else if (heartbeatJSON["status"] == UP) {
|
||||||
datadup.severity = notification.alertaRecoverState; // cleaned
|
datadup.severity = notification.alertaRecoverState; // cleaned
|
||||||
datadup.text = "Service " + monitorJSON["type"] + " is up.";
|
datadup.text = "Service " + monitorJSON["type"] + " is up.";
|
||||||
await axios.post(alertaUrl, datadup, config);
|
await axios.post(alertaUrl, datadup, config);
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
const NotificationProvider = require("./notification-provider");
|
|
||||||
const axios = require("axios");
|
|
||||||
const { setting } = require("../util-server");
|
|
||||||
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
|
|
||||||
|
|
||||||
class AlertNow extends NotificationProvider {
|
|
||||||
|
|
||||||
name = "AlertNow";
|
|
||||||
|
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
|
||||||
let okMsg = "Sent Successfully.";
|
|
||||||
try {
|
|
||||||
let textMsg = "";
|
|
||||||
let status = "open";
|
|
||||||
let eventType = "ERROR";
|
|
||||||
let eventId = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
||||||
|
|
||||||
if (heartbeatJSON && heartbeatJSON.status === UP) {
|
|
||||||
textMsg = `[${heartbeatJSON.name}] ✅ Application is back online`;
|
|
||||||
status = "close";
|
|
||||||
eventType = "INFO";
|
|
||||||
eventId += `_${heartbeatJSON.name.replace(/\s/g, "")}`;
|
|
||||||
} else if (heartbeatJSON && heartbeatJSON.status === DOWN) {
|
|
||||||
textMsg = `[${heartbeatJSON.name}] 🔴 Application went down`;
|
|
||||||
}
|
|
||||||
|
|
||||||
textMsg += ` - ${msg}`;
|
|
||||||
|
|
||||||
const baseURL = await setting("primaryBaseURL");
|
|
||||||
if (baseURL && monitorJSON) {
|
|
||||||
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
"summary": textMsg,
|
|
||||||
"status": status,
|
|
||||||
"event_type": eventType,
|
|
||||||
"event_id": eventId,
|
|
||||||
};
|
|
||||||
|
|
||||||
await axios.post(notification.alertNowWebhookURL, data);
|
|
||||||
return okMsg;
|
|
||||||
} catch (error) {
|
|
||||||
this.throwGeneralAxiosError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = AlertNow;
|
|
@ -37,12 +37,6 @@ class AliyunSMS extends NotificationProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the SMS notification
|
|
||||||
* @param {BeanModel} notification Notification details
|
|
||||||
* @param {string} msgbody Message template
|
|
||||||
* @returns {boolean} True if successful else false
|
|
||||||
*/
|
|
||||||
async sendSms(notification, msgbody) {
|
async sendSms(notification, msgbody) {
|
||||||
let params = {
|
let params = {
|
||||||
PhoneNumbers: notification.phonenumber,
|
PhoneNumbers: notification.phonenumber,
|
||||||
@ -70,18 +64,13 @@ class AliyunSMS extends NotificationProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let result = await axios(config);
|
let result = await axios(config);
|
||||||
if (result.data.Message === "OK") {
|
if (result.data.Message == "OK") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Aliyun request sign */
|
||||||
* Aliyun request sign
|
|
||||||
* @param {Object} param Parameters object to sign
|
|
||||||
* @param {string} AccessKeySecret Secret key to sign parameters with
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
sign(param, AccessKeySecret) {
|
sign(param, AccessKeySecret) {
|
||||||
let param2 = {};
|
let param2 = {};
|
||||||
let data = [];
|
let data = [];
|
||||||
@ -93,23 +82,8 @@ class AliyunSMS extends NotificationProvider {
|
|||||||
param2[key] = param[key];
|
param2[key] = param[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape more characters than encodeURIComponent does.
|
|
||||||
// For generating Aliyun signature, all characters except A-Za-z0-9~-._ are encoded.
|
|
||||||
// See https://help.aliyun.com/document_detail/315526.html
|
|
||||||
// This encoding methods as known as RFC 3986 (https://tools.ietf.org/html/rfc3986)
|
|
||||||
let moreEscapesTable = function (m) {
|
|
||||||
return {
|
|
||||||
"!": "%21",
|
|
||||||
"*": "%2A",
|
|
||||||
"'": "%27",
|
|
||||||
"(": "%28",
|
|
||||||
")": "%29"
|
|
||||||
}[m];
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let key in param2) {
|
for (let key in param2) {
|
||||||
let value = encodeURIComponent(param2[key]).replace(/[!*'()]/g, moreEscapesTable);
|
data.push(`${encodeURIComponent(key)}=${encodeURIComponent(param2[key])}`);
|
||||||
data.push(`${encodeURIComponent(key)}=${value}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`;
|
let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`;
|
||||||
@ -119,11 +93,6 @@ class AliyunSMS extends NotificationProvider {
|
|||||||
.digest("base64");
|
.digest("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert status constant to string
|
|
||||||
* @param {const} status The status constant
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
statusToString(status) {
|
statusToString(status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DOWN:
|
case DOWN:
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const childProcess = require("child_process");
|
const child_process = require("child_process");
|
||||||
|
|
||||||
class Apprise extends NotificationProvider {
|
class Apprise extends NotificationProvider {
|
||||||
|
|
||||||
name = "apprise";
|
name = "apprise";
|
||||||
|
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
const args = [ "-vv", "-b", msg, notification.appriseURL ];
|
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
|
||||||
if (notification.title) {
|
|
||||||
args.push("-t");
|
|
||||||
args.push(notification.title);
|
|
||||||
}
|
|
||||||
const s = childProcess.spawnSync("apprise", args);
|
|
||||||
|
|
||||||
const output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
|
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
|
||||||
|
|
||||||
if (output) {
|
if (output) {
|
||||||
|
|
||||||
@ -21,7 +16,7 @@ class Apprise extends NotificationProvider {
|
|||||||
return "Sent Successfully";
|
return "Sent Successfully";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(output);
|
throw new Error(output)
|
||||||
} else {
|
} else {
|
||||||
return "No output from apprise";
|
return "No output from apprise";
|
||||||
}
|
}
|
||||||
|
@ -12,67 +12,55 @@ const { default: axios } = require("axios");
|
|||||||
|
|
||||||
// bark is an APN bridge that sends notifications to Apple devices.
|
// bark is an APN bridge that sends notifications to Apple devices.
|
||||||
|
|
||||||
|
const barkNotificationGroup = "UptimeKuma";
|
||||||
const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||||
|
const barkNotificationSound = "telegraph";
|
||||||
const successMessage = "Successes!";
|
const successMessage = "Successes!";
|
||||||
|
|
||||||
class Bark extends NotificationProvider {
|
class Bark extends NotificationProvider {
|
||||||
name = "Bark";
|
name = "Bark";
|
||||||
|
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
let barkEndpoint = notification.barkEndpoint;
|
try {
|
||||||
|
var barkEndpoint = notification.barkEndpoint;
|
||||||
|
|
||||||
// check if the endpoint has a "/" suffix, if so, delete it first
|
// check if the endpoint has a "/" suffix, if so, delete it first
|
||||||
if (barkEndpoint.endsWith("/")) {
|
if (barkEndpoint.endsWith("/")) {
|
||||||
barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1);
|
barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) {
|
||||||
let title = "UptimeKuma Monitor Up";
|
let title = "UptimeKuma Monitor Up";
|
||||||
return await this.postNotification(notification, title, msg, barkEndpoint);
|
return await this.postNotification(title, msg, barkEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) {
|
||||||
let title = "UptimeKuma Monitor Down";
|
let title = "UptimeKuma Monitor Down";
|
||||||
return await this.postNotification(notification, title, msg, barkEndpoint);
|
return await this.postNotification(title, msg, barkEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
let title = "UptimeKuma Message";
|
let title = "UptimeKuma Message";
|
||||||
return await this.postNotification(notification, title, msg, barkEndpoint);
|
return await this.postNotification(title, msg, barkEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// add additional parameter for better on device styles (iOS 15 optimized)
|
||||||
* Add additional parameter for better on device styles (iOS 15
|
appendAdditionalParameters(postUrl) {
|
||||||
* optimized)
|
|
||||||
* @param {string} postUrl URL to append parameters to
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
appendAdditionalParameters(notification, postUrl) {
|
|
||||||
// set icon to uptime kuma icon, 11kb should be fine
|
|
||||||
postUrl += "?icon=" + barkNotificationAvatar;
|
|
||||||
// grouping all our notifications
|
// grouping all our notifications
|
||||||
if (notification.barkGroup != null) {
|
postUrl += "?group=" + barkNotificationGroup;
|
||||||
postUrl += "&group=" + notification.barkGroup;
|
// set icon to uptime kuma icon, 11kb should be fine
|
||||||
} else {
|
postUrl += "&icon=" + barkNotificationAvatar;
|
||||||
// default name
|
|
||||||
postUrl += "&group=" + "UptimeKuma";
|
|
||||||
}
|
|
||||||
// picked a sound, this should follow system's mute status when arrival
|
// picked a sound, this should follow system's mute status when arrival
|
||||||
if (notification.barkSound != null) {
|
postUrl += "&sound=" + barkNotificationSound;
|
||||||
postUrl += "&sound=" + notification.barkSound;
|
|
||||||
} else {
|
|
||||||
// default sound
|
|
||||||
postUrl += "&sound=" + "telegraph";
|
|
||||||
}
|
|
||||||
return postUrl;
|
return postUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// thrown if failed to check result, result code should be in range 2xx
|
||||||
* Check if result is successful
|
|
||||||
* @param {Object} result Axios response object
|
|
||||||
* @throws {Error} The status code is not in range 2xx
|
|
||||||
*/
|
|
||||||
checkResult(result) {
|
checkResult(result) {
|
||||||
if (result.status == null) {
|
if (result.status == null) {
|
||||||
throw new Error("Bark notification failed with invalid response!");
|
throw new Error("Bark notification failed with invalid response!");
|
||||||
@ -82,19 +70,12 @@ class Bark extends NotificationProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async postNotification(title, subtitle, endpoint) {
|
||||||
* Send the message
|
|
||||||
* @param {string} title Message title
|
|
||||||
* @param {string} subtitle Message
|
|
||||||
* @param {string} endpoint Endpoint to send request to
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
async postNotification(notification, title, subtitle, endpoint) {
|
|
||||||
// url encode title and subtitle
|
// url encode title and subtitle
|
||||||
title = encodeURIComponent(title);
|
title = encodeURIComponent(title);
|
||||||
subtitle = encodeURIComponent(subtitle);
|
subtitle = encodeURIComponent(subtitle);
|
||||||
let postUrl = endpoint + "/" + title + "/" + subtitle;
|
let postUrl = endpoint + "/" + title + "/" + subtitle;
|
||||||
postUrl = this.appendAdditionalParameters(notification, postUrl);
|
postUrl = this.appendAdditionalParameters(postUrl);
|
||||||
let result = await axios.get(postUrl);
|
let result = await axios.get(postUrl);
|
||||||
this.checkResult(result);
|
this.checkResult(result);
|
||||||
if (result.statusText != null) {
|
if (result.statusText != null) {
|
||||||
|
@ -12,7 +12,7 @@ class ClickSendSMS extends NotificationProvider {
|
|||||||
let config = {
|
let config = {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString("base64"),
|
"Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString('base64'),
|
||||||
"Accept": "text/json",
|
"Accept": "text/json",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -37,12 +37,6 @@ class DingDing extends NotificationProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send message to DingDing
|
|
||||||
* @param {BeanModel} notification
|
|
||||||
* @param {Object} params Parameters of message
|
|
||||||
* @returns {boolean} True if successful else false
|
|
||||||
*/
|
|
||||||
async sendToDingDing(notification, params) {
|
async sendToDingDing(notification, params) {
|
||||||
let timestamp = Date.now();
|
let timestamp = Date.now();
|
||||||
|
|
||||||
@ -56,18 +50,13 @@ class DingDing extends NotificationProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let result = await axios(config);
|
let result = await axios(config);
|
||||||
if (result.data.errmsg === "ok") {
|
if (result.data.errmsg == "ok") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** DingDing sign */
|
||||||
* DingDing sign
|
|
||||||
* @param {Date} timestamp Timestamp of message
|
|
||||||
* @param {string} secretKey Secret key to sign data with
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
sign(timestamp, secretKey) {
|
sign(timestamp, secretKey) {
|
||||||
return Crypto
|
return Crypto
|
||||||
.createHmac("sha256", Buffer.from(secretKey, "utf8"))
|
.createHmac("sha256", Buffer.from(secretKey, "utf8"))
|
||||||
@ -75,13 +64,7 @@ class DingDing extends NotificationProvider {
|
|||||||
.digest("base64");
|
.digest("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert status constant to string
|
|
||||||
* @param {const} status The status constant
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
statusToString(status) {
|
statusToString(status) {
|
||||||
// TODO: Move to notification-provider.js to avoid repetition in classes
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DOWN:
|
case DOWN:
|
||||||
return "DOWN";
|
return "DOWN";
|
||||||
|
@ -17,32 +17,25 @@ class Discord extends NotificationProvider {
|
|||||||
let discordtestdata = {
|
let discordtestdata = {
|
||||||
username: discordDisplayName,
|
username: discordDisplayName,
|
||||||
content: msg,
|
content: msg,
|
||||||
};
|
}
|
||||||
await axios.post(notification.discordWebhookUrl, discordtestdata);
|
await axios.post(notification.discordWebhookUrl, discordtestdata)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
let address;
|
let url;
|
||||||
|
|
||||||
switch (monitorJSON["type"]) {
|
if (monitorJSON["type"] === "port") {
|
||||||
case "ping":
|
url = monitorJSON["hostname"];
|
||||||
address = monitorJSON["hostname"];
|
if (monitorJSON["port"]) {
|
||||||
break;
|
url += ":" + monitorJSON["port"];
|
||||||
case "port":
|
}
|
||||||
case "dns":
|
|
||||||
case "steam":
|
} else {
|
||||||
address = monitorJSON["hostname"];
|
url = monitorJSON["url"];
|
||||||
if (monitorJSON["port"]) {
|
|
||||||
address += ":" + monitorJSON["port"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
address = monitorJSON["url"];
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||||
if (heartbeatJSON["status"] === DOWN) {
|
if (heartbeatJSON["status"] == DOWN) {
|
||||||
let discorddowndata = {
|
let discorddowndata = {
|
||||||
username: discordDisplayName,
|
username: discordDisplayName,
|
||||||
embeds: [{
|
embeds: [{
|
||||||
@ -55,8 +48,8 @@ class Discord extends NotificationProvider {
|
|||||||
value: monitorJSON["name"],
|
value: monitorJSON["name"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: "Service URL",
|
||||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
value: url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Time (UTC)",
|
name: "Time (UTC)",
|
||||||
@ -64,20 +57,20 @@ class Discord extends NotificationProvider {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Error",
|
name: "Error",
|
||||||
value: heartbeatJSON["msg"] == null ? "N/A" : heartbeatJSON["msg"],
|
value: heartbeatJSON["msg"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
};
|
}
|
||||||
|
|
||||||
if (notification.discordPrefixMessage) {
|
if (notification.discordPrefixMessage) {
|
||||||
discorddowndata.content = notification.discordPrefixMessage;
|
discorddowndata.content = notification.discordPrefixMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.post(notification.discordWebhookUrl, discorddowndata);
|
await axios.post(notification.discordWebhookUrl, discorddowndata)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
|
|
||||||
} else if (heartbeatJSON["status"] === UP) {
|
} else if (heartbeatJSON["status"] == UP) {
|
||||||
let discordupdata = {
|
let discordupdata = {
|
||||||
username: discordDisplayName,
|
username: discordDisplayName,
|
||||||
embeds: [{
|
embeds: [{
|
||||||
@ -90,8 +83,8 @@ class Discord extends NotificationProvider {
|
|||||||
value: monitorJSON["name"],
|
value: monitorJSON["name"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: "Service URL",
|
||||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Time (UTC)",
|
name: "Time (UTC)",
|
||||||
@ -99,21 +92,21 @@ class Discord extends NotificationProvider {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ping",
|
name: "Ping",
|
||||||
value: heartbeatJSON["ping"] == null ? "N/A" : heartbeatJSON["ping"] + " ms",
|
value: heartbeatJSON["ping"] + "ms",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
};
|
}
|
||||||
|
|
||||||
if (notification.discordPrefixMessage) {
|
if (notification.discordPrefixMessage) {
|
||||||
discordupdata.content = notification.discordPrefixMessage;
|
discordupdata.content = notification.discordPrefixMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.post(notification.discordWebhookUrl, discordupdata);
|
await axios.post(notification.discordWebhookUrl, discordupdata)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throwGeneralAxiosError(error);
|
this.throwGeneralAxiosError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class Feishu extends NotificationProvider {
|
|||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heartbeatJSON["status"] === DOWN) {
|
if (heartbeatJSON["status"] == DOWN) {
|
||||||
let downdata = {
|
let downdata = {
|
||||||
msg_type: "post",
|
msg_type: "post",
|
||||||
content: {
|
content: {
|
||||||
@ -48,7 +48,7 @@ class Feishu extends NotificationProvider {
|
|||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heartbeatJSON["status"] === UP) {
|
if (heartbeatJSON["status"] == UP) {
|
||||||
let updata = {
|
let updata = {
|
||||||
msg_type: "post",
|
msg_type: "post",
|
||||||
content: {
|
content: {
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
const NotificationProvider = require("./notification-provider");
|
|
||||||
const axios = require("axios");
|
|
||||||
|
|
||||||
class FreeMobile extends NotificationProvider {
|
|
||||||
|
|
||||||
name = "FreeMobile";
|
|
||||||
|
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
|
||||||
let okMsg = "Sent Successfully.";
|
|
||||||
try {
|
|
||||||
await axios.post(`https://smsapi.free-mobile.fr/sendmsg?msg=${encodeURIComponent(msg.replace("🔴", "⛔️"))}`, {
|
|
||||||
"user": notification.freemobileUser,
|
|
||||||
"pass": notification.freemobilePass,
|
|
||||||
});
|
|
||||||
|
|
||||||
return okMsg;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.throwGeneralAxiosError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = FreeMobile;
|
|
@ -1,35 +0,0 @@
|
|||||||
const NotificationProvider = require("./notification-provider");
|
|
||||||
const axios = require("axios");
|
|
||||||
const { UP } = require("../../src/util");
|
|
||||||
|
|
||||||
class GoAlert extends NotificationProvider {
|
|
||||||
|
|
||||||
name = "GoAlert";
|
|
||||||
|
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
|
||||||
let okMsg = "Sent Successfully.";
|
|
||||||
try {
|
|
||||||
let closeAction = "close";
|
|
||||||
let data = {
|
|
||||||
summary: msg,
|
|
||||||
};
|
|
||||||
if (heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
|
||||||
data["action"] = closeAction;
|
|
||||||
}
|
|
||||||
let headers = {
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
};
|
|
||||||
let config = {
|
|
||||||
headers: headers
|
|
||||||
};
|
|
||||||
await axios.post(`${notification.goAlertBaseURL}/api/v2/generic/incoming?token=${notification.goAlertToken}`, data, config);
|
|
||||||
return okMsg;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
let msg = (error.response.data) ? error.response.data : "Error without response";
|
|
||||||
throw new Error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = GoAlert;
|
|
@ -13,11 +13,11 @@ class GoogleChat extends NotificationProvider {
|
|||||||
try {
|
try {
|
||||||
// Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
|
// Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
|
||||||
|
|
||||||
let textMsg = "";
|
let textMsg = ''
|
||||||
if (heartbeatJSON && heartbeatJSON.status === UP) {
|
if (heartbeatJSON && heartbeatJSON.status === UP) {
|
||||||
textMsg = "✅ Application is back online\n";
|
textMsg = `✅ Application is back online\n`;
|
||||||
} else if (heartbeatJSON && heartbeatJSON.status === DOWN) {
|
} else if (heartbeatJSON && heartbeatJSON.status === DOWN) {
|
||||||
textMsg = "🔴 Application went down\n";
|
textMsg = `🔴 Application went down\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitorJSON && monitorJSON.name) {
|
if (monitorJSON && monitorJSON.name) {
|
||||||
|
@ -18,7 +18,7 @@ class Gorush extends NotificationProvider {
|
|||||||
let data = {
|
let data = {
|
||||||
"notifications": [
|
"notifications": [
|
||||||
{
|
{
|
||||||
"tokens": [ notification.gorushDeviceToken ],
|
"tokens": [notification.gorushDeviceToken],
|
||||||
"platform": platformMapping[notification.gorushPlatform],
|
"platform": platformMapping[notification.gorushPlatform],
|
||||||
"message": msg,
|
"message": msg,
|
||||||
// Optional
|
// Optional
|
||||||
|
@ -15,7 +15,7 @@ class Gotify extends NotificationProvider {
|
|||||||
"message": msg,
|
"message": msg,
|
||||||
"priority": notification.gotifyPriority || 8,
|
"priority": notification.gotifyPriority || 8,
|
||||||
"title": "Uptime-Kuma",
|
"title": "Uptime-Kuma",
|
||||||
});
|
})
|
||||||
|
|
||||||
return okMsg;
|
return okMsg;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user