mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 13:46:13 +08:00 
			
		
		
		
	Merge branch 'master' into eslint_stylelint
This commit is contained in:
		@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user