mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-28 01:04:05 +00:00
Merge branch 'master' into eslint_stylelint
This commit is contained in:
commit
8331e795e7
22 changed files with 733 additions and 57 deletions
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
name: ⚠ Please go to "Discussions" Tab if you want to ask or share something
|
|
||||||
about: BUG REPORT ONLY HERE
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
BUG REPORT ONLY HERE
|
|
10
.github/ISSUE_TEMPLATE/ask-for-help.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/ask-for-help.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: Ask for help
|
||||||
|
about: You can ask any question related to Uptime Kuma.
|
||||||
|
title: ''
|
||||||
|
labels: help
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '35 5 * * 2'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
|
@ -15,7 +15,7 @@ It is a self-hosted monitoring tool like "Uptime Robot".
|
||||||
|
|
||||||
* Monitoring uptime for HTTP(s) / TCP / Ping.
|
* Monitoring uptime for HTTP(s) / TCP / Ping.
|
||||||
* Fancy, Reactive, Fast UI/UX.
|
* Fancy, Reactive, Fast UI/UX.
|
||||||
* Notifications via Webhook, Telegram, Discord and email (SMTP).
|
* Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise.
|
||||||
* 20 seconds interval.
|
* 20 seconds interval.
|
||||||
|
|
||||||
# How to Use
|
# How to Use
|
||||||
|
@ -80,7 +80,7 @@ PS: For every new release, it takes some time to build the docker image, please
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git fetch --all
|
git fetch --all
|
||||||
git checkout 1.0.6 --force
|
git checkout 1.0.7 --force
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
pm2 restart uptime-kuma
|
pm2 restart uptime-kuma
|
||||||
|
|
9
db/patch2.sql
Normal file
9
db/patch2.sql
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
CREATE TABLE monitor_tls_info (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
monitor_id INTEGER NOT NULL,
|
||||||
|
info_json TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
230
package-lock.json
generated
230
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -29,21 +29,96 @@
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "0.2.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz",
|
||||||
|
"integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw=="
|
||||||
|
},
|
||||||
|
"@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "1.2.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz",
|
||||||
|
"integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.35"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/free-regular-svg-icons": {
|
||||||
|
"version": "5.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz",
|
||||||
|
"integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.35"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "5.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz",
|
||||||
|
"integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.35"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/vue-fontawesome": {
|
||||||
|
"version": "3.0.0-4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-4.tgz",
|
||||||
|
"integrity": "sha512-dQVhhMRcUPCb0aqk5ohm0KGk5OJ7wFZ9aYapLzJB3Z+xs7LhkRWLTb87reelUAG5PFDjutDAXuloT9hi6cz72A=="
|
||||||
|
},
|
||||||
"@popperjs/core": {
|
"@popperjs/core": {
|
||||||
"version": "2.9.2",
|
"version": "2.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
|
||||||
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
|
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
|
||||||
},
|
},
|
||||||
|
"@types/accepts": {
|
||||||
|
"version": "1.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||||
|
"integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/body-parser": {
|
||||||
|
"version": "1.19.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz",
|
||||||
|
"integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/connect": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/component-emitter": {
|
"@types/component-emitter": {
|
||||||
"version": "1.2.10",
|
"version": "1.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
|
||||||
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
||||||
},
|
},
|
||||||
|
"@types/connect": {
|
||||||
|
"version": "3.4.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||||
|
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/content-disposition": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ=="
|
||||||
|
},
|
||||||
"@types/cookie": {
|
"@types/cookie": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||||
},
|
},
|
||||||
|
"@types/cookies": {
|
||||||
|
"version": "0.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz",
|
||||||
|
"integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/connect": "*",
|
||||||
|
"@types/express": "*",
|
||||||
|
"@types/keygrip": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/cors": {
|
"@types/cors": {
|
||||||
"version": "2.8.12",
|
"version": "2.8.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||||
|
@ -55,11 +130,94 @@
|
||||||
"integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
|
"integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/express": {
|
||||||
|
"version": "4.17.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
|
||||||
|
"integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/body-parser": "*",
|
||||||
|
"@types/express-serve-static-core": "^4.17.18",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/serve-static": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/express-serve-static-core": {
|
||||||
|
"version": "4.17.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz",
|
||||||
|
"integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/range-parser": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/http-assert": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ=="
|
||||||
|
},
|
||||||
|
"@types/http-errors": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q=="
|
||||||
|
},
|
||||||
|
"@types/keygrip": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw=="
|
||||||
|
},
|
||||||
|
"@types/koa": {
|
||||||
|
"version": "2.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.4.tgz",
|
||||||
|
"integrity": "sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/accepts": "*",
|
||||||
|
"@types/content-disposition": "*",
|
||||||
|
"@types/cookies": "*",
|
||||||
|
"@types/http-assert": "*",
|
||||||
|
"@types/http-errors": "*",
|
||||||
|
"@types/keygrip": "*",
|
||||||
|
"@types/koa-compose": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/koa-compose": {
|
||||||
|
"version": "3.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz",
|
||||||
|
"integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/koa": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/mime": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
|
||||||
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "16.3.3",
|
"version": "16.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.3.tgz",
|
||||||
"integrity": "sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ=="
|
"integrity": "sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ=="
|
||||||
},
|
},
|
||||||
|
"@types/qs": {
|
||||||
|
"version": "6.9.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||||
|
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
|
||||||
|
},
|
||||||
|
"@types/range-parser": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
|
||||||
|
},
|
||||||
|
"@types/serve-static": {
|
||||||
|
"version": "1.13.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||||
|
"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/mime": "^1",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@vitejs/plugin-legacy": {
|
"@vitejs/plugin-legacy": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.4.tgz",
|
||||||
|
@ -467,6 +625,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||||
},
|
},
|
||||||
|
"basic-auth": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"bcrypt": {
|
"bcrypt": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
||||||
|
@ -557,6 +723,11 @@
|
||||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"bintrees": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ="
|
||||||
|
},
|
||||||
"block-stream": {
|
"block-stream": {
|
||||||
"version": "0.0.9",
|
"version": "0.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
|
||||||
|
@ -1131,6 +1302,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"express-basic-auth": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-iJ0h1Gk6fZRrFmO7tP9nIbxwNgCUJASfNj5fb0Hy15lGtbqqsxpt7609+wq+0XlByZjXmC/rslWQtnuSTVRIcg==",
|
||||||
|
"requires": {
|
||||||
|
"basic-auth": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"extend": {
|
"extend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
@ -2004,6 +2183,11 @@
|
||||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
|
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.get": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
|
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||||
|
},
|
||||||
"lodash.includes": {
|
"lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
@ -2757,6 +2941,11 @@
|
||||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"pkginfo": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz",
|
||||||
|
"integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8="
|
||||||
|
},
|
||||||
"posix-character-classes": {
|
"posix-character-classes": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
|
||||||
|
@ -2845,6 +3034,37 @@
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
},
|
},
|
||||||
|
"prom-client": {
|
||||||
|
"version": "13.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz",
|
||||||
|
"integrity": "sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng==",
|
||||||
|
"requires": {
|
||||||
|
"tdigest": "^0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prometheus-api-metrics": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prometheus-api-metrics/-/prometheus-api-metrics-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-JekPhtIBLGX8HxD2EndvBsLU6ZQ1JVVqyHWVfm5CposUOqgBHXnUVFW6x5Ux2gykpdej/5LLM3dU9V8Ma7GfkA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/express": "^4.17.8",
|
||||||
|
"@types/express-serve-static-core": "^4.17.12",
|
||||||
|
"@types/koa": "^2.11.4",
|
||||||
|
"debug": "^3.2.6",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"pkginfo": "^0.4.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "3.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"proxy-addr": {
|
"proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
@ -3529,6 +3749,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/tcp-ping/-/tcp-ping-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tcp-ping/-/tcp-ping-0.1.1.tgz",
|
||||||
"integrity": "sha1-At1/QrW/fXy3jVt6rO+hVf2PfAw="
|
"integrity": "sha1-At1/QrW/fXy3jVt6rO+hVf2PfAw="
|
||||||
},
|
},
|
||||||
|
"tdigest": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=",
|
||||||
|
"requires": {
|
||||||
|
"bintrees": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tildify": {
|
"tildify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
|
||||||
|
|
13
package.json
13
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -12,14 +12,18 @@
|
||||||
"update": "",
|
"update": "",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"vite-preview-dist": "vite preview --host",
|
"vite-preview-dist": "vite preview --host",
|
||||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.6 --target release . --push",
|
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.7 --target release . --push",
|
||||||
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||||
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
|
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
|
||||||
"setup": "git checkout 1.0.6 && npm install && npm run build",
|
"setup": "git checkout 1.0.7 && npm install && npm run build",
|
||||||
"version-global-replace": "node extra/version-global-replace.js",
|
"version-global-replace": "node extra/version-global-replace.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
|
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
||||||
"@popperjs/core": "^2.9.2",
|
"@popperjs/core": "^2.9.2",
|
||||||
"args-parser": "^1.3.0",
|
"args-parser": "^1.3.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
@ -28,11 +32,14 @@
|
||||||
"command-exists": "^1.2.9",
|
"command-exists": "^1.2.9",
|
||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.6",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"express-basic-auth": "^1.2.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"http-graceful-shutdown": "^3.1.2",
|
"http-graceful-shutdown": "^3.1.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"nodemailer": "^6.6.3",
|
"nodemailer": "^6.6.3",
|
||||||
"password-hash": "^1.2.2",
|
"password-hash": "^1.2.2",
|
||||||
|
"prom-client": "^13.1.0",
|
||||||
|
"prometheus-api-metrics": "^3.2.0",
|
||||||
"redbean-node": "0.0.20",
|
"redbean-node": "0.0.20",
|
||||||
"socket.io": "^4.1.3",
|
"socket.io": "^4.1.3",
|
||||||
"socket.io-client": "^4.1.3",
|
"socket.io-client": "^4.1.3",
|
||||||
|
|
40
server/auth.js
Normal file
40
server/auth.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
const basicAuth = require('express-basic-auth')
|
||||||
|
const passwordHash = require('./password-hash');
|
||||||
|
const {R} = require("redbean-node");
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param username : string
|
||||||
|
* @param password : string
|
||||||
|
* @returns {Promise<Bean|null>}
|
||||||
|
*/
|
||||||
|
exports.login = async function (username, password) {
|
||||||
|
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
||||||
|
username
|
||||||
|
])
|
||||||
|
|
||||||
|
if (user && passwordHash.verify(password, user.password)) {
|
||||||
|
// Upgrade the hash to bcrypt
|
||||||
|
if (passwordHash.needRehash(user.password)) {
|
||||||
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||||
|
passwordHash.generate(password),
|
||||||
|
user.id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function myAuthorizer(username, password, callback) {
|
||||||
|
exports.login(username, password).then((user) => {
|
||||||
|
callback(null, user != null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.basicAuth = basicAuth({
|
||||||
|
authorizer: myAuthorizer,
|
||||||
|
authorizeAsync: true,
|
||||||
|
challenge: true
|
||||||
|
});
|
|
@ -8,7 +8,7 @@ class Database {
|
||||||
|
|
||||||
static templatePath = "./db/kuma.db"
|
static templatePath = "./db/kuma.db"
|
||||||
static path = './data/kuma.db';
|
static path = './data/kuma.db';
|
||||||
static latestVersion = 1;
|
static latestVersion = 3;
|
||||||
static noReject = true;
|
static noReject = true;
|
||||||
|
|
||||||
static async patch() {
|
static async patch() {
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
|
const https = require('https');
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
var timezone = require('dayjs/plugin/timezone')
|
var timezone = require('dayjs/plugin/timezone')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const {UP, DOWN, PENDING} = require("../util");
|
const {Prometheus} = require("../prometheus");
|
||||||
const {tcping, ping} = require("../util-server");
|
const {debug, UP, DOWN, PENDING} = require("../util");
|
||||||
|
const {tcping, ping, checkCertificate} = require("../util-server");
|
||||||
const {R} = require("redbean-node");
|
const {R} = require("redbean-node");
|
||||||
const {BeanModel} = require("redbean-node/dist/bean-model");
|
const {BeanModel} = require("redbean-node/dist/bean-model");
|
||||||
const {Notification} = require("../notification")
|
const {Notification} = require("../notification")
|
||||||
|
|
||||||
|
// Use Custom agent to disable session reuse
|
||||||
|
// https://github.com/nodejs/node/issues/3940
|
||||||
|
const customAgent = new https.Agent({
|
||||||
|
maxCachedSessions: 0
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status:
|
* status:
|
||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
|
@ -49,6 +56,8 @@ class Monitor extends BeanModel {
|
||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
|
let prometheus = new Prometheus(this);
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
|
|
||||||
if (! previousBeat) {
|
if (! previousBeat) {
|
||||||
|
@ -75,11 +84,25 @@ class Monitor extends BeanModel {
|
||||||
if (this.type === "http" || this.type === "keyword") {
|
if (this.type === "http" || this.type === "keyword") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
let res = await axios.get(this.url, {
|
let res = await axios.get(this.url, {
|
||||||
headers: { 'User-Agent':'Uptime-Kuma' }
|
headers: { "User-Agent": "Uptime-Kuma" },
|
||||||
})
|
httpsAgent: customAgent,
|
||||||
|
});
|
||||||
bean.msg = `${res.status} - ${res.statusText}`
|
bean.msg = `${res.status} - ${res.statusText}`
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
|
||||||
|
// Check certificate if https is used
|
||||||
|
|
||||||
|
let certInfoStartTime = dayjs().valueOf();
|
||||||
|
if (this.getUrl()?.protocol === "https:") {
|
||||||
|
try {
|
||||||
|
await this.updateTlsInfo(checkCertificate(res));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms")
|
||||||
|
|
||||||
if (this.type === "http") {
|
if (this.type === "http") {
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
} else {
|
} else {
|
||||||
|
@ -170,6 +193,7 @@ class Monitor extends BeanModel {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bean.status === UP) {
|
if (bean.status === UP) {
|
||||||
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
|
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
|
||||||
} else if (bean.status === PENDING) {
|
} else if (bean.status === PENDING) {
|
||||||
|
@ -178,6 +202,8 @@ class Monitor extends BeanModel {
|
||||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prometheus.update(bean)
|
||||||
|
|
||||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
|
|
||||||
await R.store(bean)
|
await R.store(bean)
|
||||||
|
@ -194,10 +220,35 @@ class Monitor extends BeanModel {
|
||||||
clearInterval(this.heartbeatInterval)
|
clearInterval(this.heartbeatInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper Method:
|
||||||
|
// returns URL object for further usage
|
||||||
|
// returns null if url is invalid
|
||||||
|
getUrl() {
|
||||||
|
try {
|
||||||
|
return new URL(this.url);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store TLS info to database
|
||||||
|
async updateTlsInfo(checkCertificateResult) {
|
||||||
|
let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||||
|
this.id
|
||||||
|
]);
|
||||||
|
if (tls_info_bean == null) {
|
||||||
|
tls_info_bean = R.dispense("monitor_tls_info");
|
||||||
|
tls_info_bean.monitor_id = this.id;
|
||||||
|
}
|
||||||
|
tls_info_bean.info_json = JSON.stringify(checkCertificateResult);
|
||||||
|
await R.store(tls_info_bean);
|
||||||
|
}
|
||||||
|
|
||||||
static async sendStats(io, monitorID, userID) {
|
static async sendStats(io, monitorID, userID) {
|
||||||
Monitor.sendAvgPing(24, io, monitorID, userID);
|
Monitor.sendAvgPing(24, io, monitorID, userID);
|
||||||
Monitor.sendUptime(24, io, monitorID, userID);
|
Monitor.sendUptime(24, io, monitorID, userID);
|
||||||
Monitor.sendUptime(24 * 30, io, monitorID, userID);
|
Monitor.sendUptime(24 * 30, io, monitorID, userID);
|
||||||
|
Monitor.sendCertInfo(io, monitorID, userID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,6 +269,15 @@ class Monitor extends BeanModel {
|
||||||
io.to(userID).emit("avgPing", monitorID, avgPing);
|
io.to(userID).emit("avgPing", monitorID, avgPing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async sendCertInfo(io, monitorID, userID) {
|
||||||
|
let tls_info = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||||
|
monitorID
|
||||||
|
]);
|
||||||
|
if (tls_info != null) {
|
||||||
|
io.to(userID).emit("certInfo", monitorID, tls_info.info_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uptime with calculation
|
* Uptime with calculation
|
||||||
* Calculation based on:
|
* Calculation based on:
|
||||||
|
|
59
server/prometheus.js
Normal file
59
server/prometheus.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
const PrometheusClient = require('prom-client');
|
||||||
|
|
||||||
|
const commonLabels = [
|
||||||
|
'monitor_name',
|
||||||
|
'monitor_type',
|
||||||
|
'monitor_url',
|
||||||
|
'monitor_hostname',
|
||||||
|
'monitor_port',
|
||||||
|
]
|
||||||
|
|
||||||
|
const monitor_response_time = new PrometheusClient.Gauge({
|
||||||
|
name: 'monitor_response_time',
|
||||||
|
help: 'Monitor Response Time (ms)',
|
||||||
|
labelNames: commonLabels
|
||||||
|
});
|
||||||
|
|
||||||
|
const monitor_status = new PrometheusClient.Gauge({
|
||||||
|
name: 'monitor_status',
|
||||||
|
help: 'Monitor Status (1 = UP, 0= DOWN)',
|
||||||
|
labelNames: commonLabels
|
||||||
|
});
|
||||||
|
|
||||||
|
class Prometheus {
|
||||||
|
monitorLabelValues = {}
|
||||||
|
|
||||||
|
constructor(monitor) {
|
||||||
|
this.monitorLabelValues = {
|
||||||
|
monitor_name: monitor.name,
|
||||||
|
monitor_type: monitor.type,
|
||||||
|
monitor_url: monitor.url,
|
||||||
|
monitor_hostname: monitor.hostname,
|
||||||
|
monitor_port: monitor.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(heartbeat) {
|
||||||
|
try {
|
||||||
|
monitor_status.set(this.monitorLabelValues, heartbeat.status)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof heartbeat.ping === 'number') {
|
||||||
|
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping)
|
||||||
|
} else {
|
||||||
|
// Is it good?
|
||||||
|
monitor_response_time.set(this.monitorLabelValues, -1)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Prometheus
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ const http = require('http');
|
||||||
const { Server } = require("socket.io");
|
const { Server } = require("socket.io");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const {R} = require("redbean-node");
|
const {R} = require("redbean-node");
|
||||||
const passwordHash = require('./password-hash');
|
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const Monitor = require("./model/monitor");
|
const Monitor = require("./model/monitor");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
@ -15,7 +14,9 @@ const gracefulShutdown = require('http-graceful-shutdown');
|
||||||
const Database = require("./database");
|
const Database = require("./database");
|
||||||
const {sleep} = require("./util");
|
const {sleep} = require("./util");
|
||||||
const args = require('args-parser')(process.argv);
|
const args = require('args-parser')(process.argv);
|
||||||
|
const prometheusAPIMetrics = require('prometheus-api-metrics');
|
||||||
|
const { basicAuth } = require("./auth");
|
||||||
|
const {login} = require("./auth");
|
||||||
const version = require('../package.json').version;
|
const version = require('../package.json').version;
|
||||||
const hostname = args.host || "0.0.0.0"
|
const hostname = args.host || "0.0.0.0"
|
||||||
const port = args.port || 3001
|
const port = args.port || 3001
|
||||||
|
@ -27,6 +28,9 @@ const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const io = new Server(server);
|
const io = new Server(server);
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
const basicAuthRouter = express.Router();
|
||||||
|
basicAuthRouter.use(basicAuth)
|
||||||
|
app.use(basicAuthRouter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total WebSocket client connected to server currently, no actual use
|
* Total WebSocket client connected to server currently, no actual use
|
||||||
|
@ -56,13 +60,27 @@ let needSetup = false;
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
|
|
||||||
console.log("Adding route")
|
console.log("Adding route")
|
||||||
|
|
||||||
|
// Normal Router here
|
||||||
|
|
||||||
app.use('/', express.static("dist"));
|
app.use('/', express.static("dist"));
|
||||||
|
|
||||||
|
// Basic Auth Router here
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
basicAuthRouter.get('/test-auth', (req, res) => {
|
||||||
|
res.end("OK")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prometheus API metrics /metrics
|
||||||
|
// With Basic Auth using the first user's username/password
|
||||||
|
basicAuthRouter.use(prometheusAPIMetrics())
|
||||||
|
|
||||||
|
// Universal Route Handler, must be at the end
|
||||||
app.get('*', function(request, response, next) {
|
app.get('*', function(request, response, next) {
|
||||||
response.sendFile(process.cwd() + '/dist/index.html');
|
response.sendFile(process.cwd() + '/dist/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
console.log("Adding socket handler")
|
console.log("Adding socket handler")
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
|
|
||||||
|
@ -118,20 +136,9 @@ let needSetup = false;
|
||||||
socket.on("login", async (data, callback) => {
|
socket.on("login", async (data, callback) => {
|
||||||
console.log("Login")
|
console.log("Login")
|
||||||
|
|
||||||
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
let user = await login(data.username, data.password)
|
||||||
data.username
|
|
||||||
])
|
|
||||||
|
|
||||||
if (user && passwordHash.verify(data.password, user.password)) {
|
|
||||||
|
|
||||||
// Upgrade the hash to bcrypt
|
|
||||||
if (passwordHash.needRehash(user.password)) {
|
|
||||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
|
||||||
passwordHash.generate(data.password),
|
|
||||||
user.id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (user) {
|
||||||
await afterLogin(socket, user)
|
await afterLogin(socket, user)
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
|
@ -543,12 +550,12 @@ async function afterLogin(socket, user) {
|
||||||
let monitorList = await sendMonitorList(socket)
|
let monitorList = await sendMonitorList(socket)
|
||||||
|
|
||||||
for (let monitorID in monitorList) {
|
for (let monitorID in monitorList) {
|
||||||
await sendHeartbeatList(socket, monitorID);
|
sendHeartbeatList(socket, monitorID);
|
||||||
await sendImportantHeartbeatList(socket, monitorID);
|
sendImportantHeartbeatList(socket, monitorID);
|
||||||
await Monitor.sendStats(io, monitorID, user.id)
|
Monitor.sendStats(io, monitorID, user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendNotificationList(socket)
|
sendNotificationList(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMonitorJSONList(userID) {
|
async function getMonitorJSONList(userID) {
|
||||||
|
|
|
@ -70,3 +70,52 @@ exports.getSettings = async function (type) {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ssl-checker by @dyaa
|
||||||
|
// param: res - response object from axios
|
||||||
|
// return an object containing the certificate information
|
||||||
|
|
||||||
|
const getDaysBetween = (validFrom, validTo) =>
|
||||||
|
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
|
||||||
|
|
||||||
|
const getDaysRemaining = (validFrom, validTo) => {
|
||||||
|
const daysRemaining = getDaysBetween(validFrom, validTo);
|
||||||
|
if (new Date(validTo).getTime() < new Date().getTime()) {
|
||||||
|
return -daysRemaining;
|
||||||
|
}
|
||||||
|
return daysRemaining;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkCertificate = function (res) {
|
||||||
|
const {
|
||||||
|
valid_from,
|
||||||
|
valid_to,
|
||||||
|
subjectaltname,
|
||||||
|
issuer,
|
||||||
|
fingerprint,
|
||||||
|
} = res.request.res.socket.getPeerCertificate(false);
|
||||||
|
|
||||||
|
if (!valid_from || !valid_to || !subjectaltname) {
|
||||||
|
throw { message: 'No TLS certificate in response' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = res.request.res.socket.authorized || false;
|
||||||
|
|
||||||
|
const validTo = new Date(valid_to);
|
||||||
|
|
||||||
|
const validFor = subjectaltname
|
||||||
|
.replace(/DNS:|IP Address:/g, "")
|
||||||
|
.split(", ");
|
||||||
|
|
||||||
|
const daysRemaining = getDaysRemaining(new Date(), validTo);
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid,
|
||||||
|
validFor,
|
||||||
|
validTo,
|
||||||
|
daysRemaining,
|
||||||
|
issuer,
|
||||||
|
fingerprint,
|
||||||
|
};
|
||||||
|
}
|
|
@ -18,3 +18,9 @@ exports.ucfirst = function (str) {
|
||||||
return firstLetter.toUpperCase() + str.substr(1);
|
return firstLetter.toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.debug = (msg) => {
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
console.log(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,23 @@ dayjs.extend(relativeTime)
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
value: String,
|
value: String,
|
||||||
|
dateOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
displayText() {
|
displayText() {
|
||||||
|
if (this.value !== undefined && this.value !== "") {
|
||||||
let format = "YYYY-MM-DD HH:mm:ss";
|
let format = "YYYY-MM-DD HH:mm:ss";
|
||||||
return dayjs.utc(this.value).tz(this.$root.timezone).format(format)
|
if (this.dateOnly) {
|
||||||
|
format = "YYYY-MM-DD";
|
||||||
|
}
|
||||||
|
return dayjs.utc(this.value).tz(this.$root.timezone).format(format);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/icon.js
Normal file
12
src/icon.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
//import { fa } from '@fortawesome/free-regular-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
import { faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
// Add Free Font Awesome Icons here
|
||||||
|
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
|
||||||
|
library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList)
|
||||||
|
|
||||||
|
export {
|
||||||
|
FontAwesomeIcon
|
||||||
|
}
|
|
@ -14,8 +14,8 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<ul class="nav nav-pills" >
|
<ul class="nav nav-pills" >
|
||||||
<li class="nav-item"><router-link to="/dashboard" class="nav-link">📊 Dashboard</router-link></li>
|
<li class="nav-item"><router-link to="/dashboard" class="nav-link"><font-awesome-icon icon="tachometer-alt" /> Dashboard</router-link></li>
|
||||||
<li class="nav-item"><router-link to="/settings" class="nav-link">🔧 Settings</router-link></li>
|
<li class="nav-item"><router-link to="/settings" class="nav-link"><font-awesome-icon icon="cog" /> Settings</router-link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -44,10 +44,27 @@
|
||||||
<!-- Mobile Only -->
|
<!-- Mobile Only -->
|
||||||
<div style="width: 100%;height: 60px;" v-if="$root.isMobile"></div>
|
<div style="width: 100%;height: 60px;" v-if="$root.isMobile"></div>
|
||||||
<nav class="bottom-nav" v-if="$root.isMobile">
|
<nav class="bottom-nav" v-if="$root.isMobile">
|
||||||
<router-link to="/dashboard" class="nav-link" @click="$root.cancelActiveList"><div>📊</div>Dashboard</router-link>
|
|
||||||
<a href="#" :class=" { 'router-link-exact-active' : $root.showListMobile } " @click="$root.showListMobile = ! $root.showListMobile"><div>📃</div>List</a>
|
<router-link to="/dashboard" class="nav-link" @click="$root.cancelActiveList">
|
||||||
<router-link to="/add" class="nav-link" @click="$root.cancelActiveList"><div>➕</div>Add</router-link>
|
<div><font-awesome-icon icon="tachometer-alt" /></div>
|
||||||
<router-link to="/settings" class="nav-link" @click="$root.cancelActiveList"><div>🔧</div>Settings</router-link>
|
Dashboard
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<a href="#" :class=" { 'router-link-exact-active' : $root.showListMobile } " @click="$root.showListMobile = ! $root.showListMobile">
|
||||||
|
<div><font-awesome-icon icon="list" /></div>
|
||||||
|
List
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<router-link to="/add" class="nav-link" @click="$root.cancelActiveList">
|
||||||
|
<div><font-awesome-icon icon="plus" /></div>
|
||||||
|
Add
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link to="/settings" class="nav-link" @click="$root.cancelActiveList">
|
||||||
|
<div><font-awesome-icon icon="cog" /></div>
|
||||||
|
Settings
|
||||||
|
</router-link>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -99,7 +116,7 @@ export default {
|
||||||
box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05);
|
box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0 35px;
|
padding: 0 10px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -144,6 +161,7 @@ main {
|
||||||
footer {
|
footer {
|
||||||
color: #AAA;
|
color: #AAA;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
margin-top: 10px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import Toast from "vue-toastification";
|
||||||
import "vue-toastification/dist/index.css";
|
import "vue-toastification/dist/index.css";
|
||||||
import "bootstrap"
|
import "bootstrap"
|
||||||
import Setup from "./pages/Setup.vue";
|
import Setup from "./pages/Setup.vue";
|
||||||
|
import {FontAwesomeIcon} from "./icon.js"
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
@ -88,5 +89,7 @@ const options = {
|
||||||
|
|
||||||
app.use(Toast, options);
|
app.use(Toast, options);
|
||||||
|
|
||||||
|
app.component('font-awesome-icon', FontAwesomeIcon)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ export default {
|
||||||
importantHeartbeatList: { },
|
importantHeartbeatList: { },
|
||||||
avgPingList: { },
|
avgPingList: { },
|
||||||
uptimeList: { },
|
uptimeList: { },
|
||||||
|
certInfoList: {},
|
||||||
notificationList: [],
|
notificationList: [],
|
||||||
windowWidth: window.innerWidth,
|
windowWidth: window.innerWidth,
|
||||||
showListMobile: false,
|
showListMobile: false,
|
||||||
|
@ -58,7 +59,17 @@ export default {
|
||||||
this.$router.push("/setup")
|
this.$router.push("/setup")
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('monitorList', (data) => {
|
socket.on("monitorList", (data) => {
|
||||||
|
// Add Helper function
|
||||||
|
Object.entries(data).forEach(([monitorID, monitor]) => {
|
||||||
|
monitor.getUrl = () => {
|
||||||
|
try {
|
||||||
|
return new URL(monitor.url);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
this.monitorList = data;
|
this.monitorList = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,6 +125,10 @@ export default {
|
||||||
this.uptimeList[`${monitorID}_${type}`] = data
|
this.uptimeList[`${monitorID}_${type}`] = data
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('certInfo', (monitorID, data) => {
|
||||||
|
this.certInfoList[monitorID] = JSON.parse(data)
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('importantHeartbeatList', (monitorID, data) => {
|
socket.on('importantHeartbeatList', (monitorID, data) => {
|
||||||
if (! (monitorID in this.importantHeartbeatList)) {
|
if (! (monitorID in this.importantHeartbeatList)) {
|
||||||
this.importantHeartbeatList[monitorID] = data;
|
this.importantHeartbeatList[monitorID] = data;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-5 col-xl-4">
|
<div class="col-12 col-md-5 col-xl-4">
|
||||||
<div v-if="! $root.isMobile">
|
<div v-if="! $root.isMobile">
|
||||||
<router-link to="/add" class="btn btn-primary">Add New Monitor</router-link>
|
<router-link to="/add" class="btn btn-primary"><font-awesome-icon icon="plus" /> Add New Monitor</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shadow-box list mb-4" v-if="showList">
|
<div class="shadow-box list mb-4" v-if="showList">
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="functions">
|
<div class="functions">
|
||||||
<button class="btn btn-light" @click="pauseDialog" v-if="monitor.active">Pause</button>
|
<button class="btn btn-light" @click="pauseDialog" v-if="monitor.active"><font-awesome-icon icon="pause" /> Pause</button>
|
||||||
<button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active">Resume</button>
|
<button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active"><font-awesome-icon icon="pause" /> Resume</button>
|
||||||
<router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary">Edit</router-link>
|
<router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary"><font-awesome-icon icon="edit" /> Edit</router-link>
|
||||||
<button class="btn btn-danger" @click="deleteDialog">Delete</button>
|
<button class="btn btn-danger" @click="deleteDialog"><font-awesome-icon icon="trash" /> Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shadow-box">
|
<div class="shadow-box">
|
||||||
|
@ -51,6 +51,46 @@
|
||||||
<p>(30-day)</p>
|
<p>(30-day)</p>
|
||||||
<span class="num"><Uptime :monitor="monitor" type="720" /></span>
|
<span class="num"><Uptime :monitor="monitor" type="720" /></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col" v-if="certInfo">
|
||||||
|
<h4>CertExp.</h4>
|
||||||
|
<p>(<Datetime :value="certInfo.validTo" date-only />)</p>
|
||||||
|
<span class="num" >
|
||||||
|
<a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{certInfo.daysRemaining}} days</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow-box big-padding text-center" v-if="showCertInfoBox">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Certificate Info</h4>
|
||||||
|
<table class="text-start">
|
||||||
|
<tbody>
|
||||||
|
<tr class="my-3">
|
||||||
|
<td class="px-3">Valid: </td>
|
||||||
|
<td>{{ certInfo.valid }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="my-3">
|
||||||
|
<td class="px-3">Valid To: </td>
|
||||||
|
<td><Datetime :value="certInfo.validTo" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="my-3">
|
||||||
|
<td class="px-3">Days Remaining: </td>
|
||||||
|
<td>{{ certInfo.daysRemaining }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="my-3">
|
||||||
|
<td class="px-3">Issuer: </td>
|
||||||
|
<td>{{ certInfo.issuer }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="my-3">
|
||||||
|
<td class="px-3">Fingerprint: </td>
|
||||||
|
<td>{{ certInfo.fingerprint }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -122,6 +162,7 @@ export default {
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 25,
|
perPage: 25,
|
||||||
heartBeatList: [],
|
heartBeatList: [],
|
||||||
|
toggleCertInfoBox: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -180,6 +221,18 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
certInfo() {
|
||||||
|
if (this.$root.certInfoList[this.monitor.id]) {
|
||||||
|
return this.$root.certInfoList[this.monitor.id]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showCertInfoBox() {
|
||||||
|
return this.certInfo != null && this.toggleCertInfoBox;
|
||||||
|
},
|
||||||
|
|
||||||
displayedRecords() {
|
displayedRecords() {
|
||||||
const startIndex = this.perPage * (this.page - 1);
|
const startIndex = this.perPage * (this.page - 1);
|
||||||
const endIndex = startIndex + this.perPage;
|
const endIndex = startIndex + this.perPage;
|
||||||
|
@ -268,4 +321,12 @@ table {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #AAA;
|
color: #AAA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.col {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue