mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-17 00:46:56 +08:00
Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2bff1ebe0f | ||
|
ec0dbf3cbe | ||
|
210a0d414c | ||
|
49ba5fb1b2 | ||
|
d27789a8ae | ||
|
b53582a812 | ||
|
05680472a7 | ||
|
ca3b0a0f19 | ||
|
4571a9b8c1 | ||
|
362eabab8d | ||
|
a22d0f6951 | ||
|
b1168d4cdb | ||
|
6fcc2253ec | ||
|
f21937b197 | ||
|
1e7623c459 | ||
|
22047fe932 | ||
|
209e44c2e1 | ||
|
30b8d3d0ab | ||
|
64498163e1 | ||
|
4f70a70dda | ||
|
5b5a32967c | ||
|
ae8b5eea5a | ||
|
b761aaffdf | ||
|
7ffdb2eb80 | ||
|
2d36f4cd4a | ||
|
2339405f90 | ||
|
8f5e5ad944 | ||
|
575c3ee182 | ||
|
c9aa110f6c | ||
|
bb0af35d47 | ||
|
61944d642e | ||
|
21640e1bbe | ||
|
de4515ea6e | ||
|
b41799f801 | ||
|
d8bcfcaaa2 | ||
|
cf5168a4e6 | ||
|
60b0ee2959 | ||
|
f1e5e53e8f | ||
|
432388a905 | ||
|
746c1b6acc | ||
|
269ac2410b | ||
|
f72cdcc663 | ||
|
6b3fbcd1e7 | ||
|
8a48f5dd71 | ||
|
d218661f3d | ||
|
77369bd002 | ||
|
29a89df524 | ||
|
e257fa7b2d | ||
|
6980c38a6c | ||
|
8d57df7256 | ||
|
64501bf065 | ||
|
440c178403 | ||
|
c9c51e47e1 | ||
|
5e52f230b1 | ||
|
61e758d872 | ||
|
86826fb826 | ||
|
7a32e5e6ff | ||
|
610f2f9c47 | ||
|
01e9c76a6f | ||
|
5927c2703f | ||
|
316db89b9a | ||
|
eed6d3e847 | ||
|
2a62f6daae | ||
|
e09c296410 | ||
|
d7f660ec57 | ||
|
798f39acf0 | ||
|
31d5b4fd3d | ||
|
fc76c2836b | ||
|
0b30bfff87 | ||
|
72f0724b9a | ||
|
35176a614f | ||
|
8e883c9c6a | ||
|
2f89ee4937 | ||
|
233c5661af | ||
|
91d4c15b4d | ||
|
e1a38f64f8 | ||
|
45c162583b |
@@ -71,5 +71,8 @@ module.exports = {
|
|||||||
"eol-last": ["error", "always"],
|
"eol-last": ["error", "always"],
|
||||||
//'prefer-template': 'error',
|
//'prefer-template': 'error',
|
||||||
"comma-dangle": ["warn", "only-multiline"],
|
"comma-dangle": ["warn", "only-multiline"],
|
||||||
|
"no-empty": ["error", {
|
||||||
|
"allowEmptyCatch": true
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
6
.github/ISSUE_TEMPLATE/ask-for-help.md
vendored
6
.github/ISSUE_TEMPLATE/ask-for-help.md
vendored
@@ -9,3 +9,9 @@ assignees: ''
|
|||||||
**Is it a duplicate question?**
|
**Is it a duplicate question?**
|
||||||
Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q=
|
Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q=
|
||||||
|
|
||||||
|
**Info**
|
||||||
|
Uptime Kuma Version:
|
||||||
|
Using Docker?: Yes/No
|
||||||
|
OS:
|
||||||
|
Browser:
|
||||||
|
|
||||||
|
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -23,15 +23,16 @@ Steps to reproduce the behavior:
|
|||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
**Info**
|
||||||
- Uptime Kuma Version:
|
- Uptime Kuma Version:
|
||||||
- Using Docker?: Yes/No
|
- Using Docker?: Yes/No
|
||||||
- OS:
|
- OS:
|
||||||
- Browser:
|
- Browser:
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Error Log**
|
||||||
|
It is easier for us to find out the problem.
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
|
64
README.md
64
README.md
@@ -19,75 +19,45 @@ It is a self-hosted monitoring tool like "Uptime Robot".
|
|||||||
|
|
||||||
## 🔧 How to Install
|
## 🔧 How to Install
|
||||||
|
|
||||||
|
### 🚀 Installer via cli
|
||||||
|
|
||||||
|
Interactive cli installer, supports Docker or without Docker.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||||
|
```
|
||||||
|
|
||||||
### 🐳 Docker
|
### 🐳 Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create a volume
|
|
||||||
docker volume create uptime-kuma
|
docker volume create uptime-kuma
|
||||||
|
|
||||||
# Start the container
|
|
||||||
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
||||||
```
|
```
|
||||||
|
|
||||||
Browse to http://localhost:3001 after started.
|
Browse to http://localhost:3001 after started.
|
||||||
|
|
||||||
|
### ☸️ Kubernetes
|
||||||
|
|
||||||
If you want to change port and volume, or need to browse via a reserve proxy, please read: https://github.com/louislam/uptime-kuma/wiki/Installation.
|
See more [here](kubernetes/README.md)
|
||||||
|
|
||||||
### 💪🏻 Without Docker (Recommanded for x86/x64 only)
|
|
||||||
|
|
||||||
Required Tools: Node.js >= 14, git and pm2.
|
### Advanced Installation
|
||||||
|
|
||||||
```bash
|
If you need more options or need to browse via a reserve proxy, please read:
|
||||||
git clone https://github.com/louislam/uptime-kuma.git
|
|
||||||
cd uptime-kuma
|
|
||||||
npm run setup
|
|
||||||
|
|
||||||
# Option 1. Try it
|
https://github.com/louislam/uptime-kuma/wiki/%F0%9F%94%A7-How-to-Install
|
||||||
npm run start-server
|
|
||||||
|
|
||||||
# (Recommended)
|
|
||||||
# Option 2. Run in background using PM2
|
|
||||||
# Install PM2 if you don't have: npm install pm2 -g
|
|
||||||
pm2 start npm --name uptime-kuma -- run start-server
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Browse to http://localhost:3001 after started.
|
|
||||||
|
|
||||||
If you want to change port and hostname, or need to browse via a reserve proxy, please read: https://github.com/louislam/uptime-kuma/wiki/Installation.
|
|
||||||
|
|
||||||
## 🆙 How to Update
|
## 🆙 How to Update
|
||||||
|
|
||||||
### 🆙🐳 Docker
|
Please read:
|
||||||
|
|
||||||
Re-pull the latest docker image and create another container with the same volume.
|
https://github.com/louislam/uptime-kuma/wiki/%F0%9F%86%99-How-to-Update
|
||||||
|
|
||||||
For someone who used my "How-to-use" commands to install Uptime Kuma, you can update by this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker pull louislam/uptime-kuma:1
|
|
||||||
docker stop uptime-kuma
|
|
||||||
docker rm uptime-kuma
|
|
||||||
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
|
||||||
```
|
|
||||||
|
|
||||||
PS: For every new release, it takes some time to build the docker image, please be patient if it is not available yet.
|
|
||||||
|
|
||||||
### 🆙 💪🏻 Without Docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd <uptime-kuma-directory>
|
|
||||||
git fetch --all
|
|
||||||
git checkout 1.2.0 --force
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
pm2 restart uptime-kuma
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🆕 What's Next?
|
## 🆕 What's Next?
|
||||||
|
|
||||||
I will mark requests/issues to the next milestone.
|
I will mark requests/issues to the next milestone.
|
||||||
|
|
||||||
https://github.com/louislam/uptime-kuma/milestones
|
https://github.com/louislam/uptime-kuma/milestones
|
||||||
|
|
||||||
## 🖼 More Screenshots
|
## 🖼 More Screenshots
|
||||||
@@ -122,5 +92,3 @@ If you want to report a bug or request a new feature. Free feel to open a new is
|
|||||||
If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
English proofreading is needed too, because my grammar is not that great sadly. Feel free to correct my grammar in this Readme, source code or wiki.
|
English proofreading is needed too, because my grammar is not that great sadly. Feel free to correct my grammar in this Readme, source code or wiki.
|
||||||
|
|
||||||
🐻
|
|
||||||
|
14
SECURITY.md
Normal file
14
SECURITY.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 1.x.x | :white_check_mark: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
https://github.com/louislam/uptime-kuma/issues
|
@@ -1 +1,2 @@
|
|||||||
|
# Must enable File Sharing in Docker Desktop
|
||||||
docker run -it --rm -v ${pwd}:/app louislam/batsh /usr/bin/batsh bash --output ./install.sh ./extra/install.batsh
|
docker run -it --rm -v ${pwd}:/app louislam/batsh /usr/bin/batsh bash --output ./install.sh ./extra/install.batsh
|
245
extra/install.batsh
Normal file
245
extra/install.batsh
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
// install.sh is generated by ./extra/install.batsh, do not modify it directly.
|
||||||
|
// "npm run compile-install-script" to compile install.sh
|
||||||
|
// The command is working on Windows PowerShell and Docker for Windows only.
|
||||||
|
|
||||||
|
|
||||||
|
// curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||||
|
println("=====================");
|
||||||
|
println("Uptime Kuma Installer");
|
||||||
|
println("=====================");
|
||||||
|
println("Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian");
|
||||||
|
println("---------------------------------------");
|
||||||
|
println("This script is designed for Linux and basic usage.");
|
||||||
|
println("For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation");
|
||||||
|
println("---------------------------------------");
|
||||||
|
println("");
|
||||||
|
println("Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2");
|
||||||
|
println("Docker - Install Uptime Kuma Docker container");
|
||||||
|
println("");
|
||||||
|
|
||||||
|
if ("$1" != "") {
|
||||||
|
type = "$1";
|
||||||
|
} else {
|
||||||
|
call("read", "-p", "Which installation method do you prefer? [DOCKER/local]: ", "type");
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPort = "3001";
|
||||||
|
|
||||||
|
function checkNode() {
|
||||||
|
bash("nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')");
|
||||||
|
println("Node Version: " ++ nodeVersion);
|
||||||
|
|
||||||
|
if (nodeVersion < "12") {
|
||||||
|
println("Error: Required Node.js 14");
|
||||||
|
call("exit", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeVersion == "12") {
|
||||||
|
println("Warning: NodeJS " ++ nodeVersion ++ " is not tested.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deb() {
|
||||||
|
bash("nodeCheck=$(node -v)");
|
||||||
|
bash("apt --yes update");
|
||||||
|
|
||||||
|
if (nodeCheck != "") {
|
||||||
|
checkNode();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Old nodejs binary name is "nodejs"
|
||||||
|
bash("check=$(nodejs --version)");
|
||||||
|
if (check != "") {
|
||||||
|
println("Error: 'node' command is not found, but 'nodejs' command is found. Your NodeJS should be too old.");
|
||||||
|
bash("exit 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("curlCheck=$(curl --version)");
|
||||||
|
if (curlCheck == "") {
|
||||||
|
println("Installing Curl");
|
||||||
|
bash("apt --yes install curl");
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Installing Node.js 14");
|
||||||
|
bash("curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt");
|
||||||
|
bash("apt --yes install nodejs");
|
||||||
|
bash("node -v");
|
||||||
|
|
||||||
|
bash("nodeCheckAgain=$(node -v)");
|
||||||
|
|
||||||
|
if (nodeCheckAgain == "") {
|
||||||
|
println("Error during Node.js installation");
|
||||||
|
bash("exit 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("check=$(git --version)");
|
||||||
|
if (check == "") {
|
||||||
|
println("Installing Git");
|
||||||
|
bash("apt --yes install git");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "local") {
|
||||||
|
defaultInstallPath = "/opt/uptime-kuma";
|
||||||
|
|
||||||
|
if (exists("/etc/redhat-release")) {
|
||||||
|
os = call("cat", "/etc/redhat-release");
|
||||||
|
distribution = "rhel";
|
||||||
|
|
||||||
|
} else if (exists("/etc/issue")) {
|
||||||
|
bash("os=$(head -n1 /etc/issue | cut -f 1 -d ' ')");
|
||||||
|
if (os == "Ubuntu") {
|
||||||
|
distribution = "ubuntu";
|
||||||
|
}
|
||||||
|
if (os == "Debian") {
|
||||||
|
distribution = "debian";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("arch=$(uname -i)");
|
||||||
|
|
||||||
|
println("Your OS: " ++ os);
|
||||||
|
println("Distribution: " ++ distribution);
|
||||||
|
println("Arch: " ++ arch);
|
||||||
|
|
||||||
|
if ("$3" != "") {
|
||||||
|
port = "$3";
|
||||||
|
} else {
|
||||||
|
call("read", "-p", "Listening Port [$defaultPort]: ", "port");
|
||||||
|
|
||||||
|
if (port == "") {
|
||||||
|
port = defaultPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("$2" != "") {
|
||||||
|
installPath = "$2";
|
||||||
|
} else {
|
||||||
|
call("read", "-p", "Installation Path [$defaultInstallPath]: ", "installPath");
|
||||||
|
|
||||||
|
if (installPath == "") {
|
||||||
|
installPath = defaultInstallPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CentOS
|
||||||
|
if (distribution == "rhel") {
|
||||||
|
bash("nodeCheck=$(node -v)");
|
||||||
|
|
||||||
|
if (nodeCheck != "") {
|
||||||
|
checkNode();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bash("curlCheck=$(curl --version)");
|
||||||
|
if (curlCheck == "") {
|
||||||
|
println("Installing Curl");
|
||||||
|
bash("yum -y -q install curl");
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Installing Node.js 14");
|
||||||
|
bash("curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt");
|
||||||
|
bash("yum install -y -q nodejs");
|
||||||
|
bash("node -v");
|
||||||
|
|
||||||
|
bash("nodeCheckAgain=$(node -v)");
|
||||||
|
|
||||||
|
if (nodeCheckAgain == "") {
|
||||||
|
println("Error during Node.js installation");
|
||||||
|
bash("exit 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("check=$(git --version)");
|
||||||
|
if (check == "") {
|
||||||
|
println("Installing Git");
|
||||||
|
bash("yum -y -q install git");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ubuntu
|
||||||
|
} else if (distribution == "ubuntu") {
|
||||||
|
deb();
|
||||||
|
|
||||||
|
// Debian
|
||||||
|
} else if (distribution == "debian") {
|
||||||
|
deb();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Unknown distribution
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
bash("check=$(git --version)");
|
||||||
|
if (check == "") {
|
||||||
|
error = 1;
|
||||||
|
println("Error: git is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("check=$(node -v)");
|
||||||
|
if (check == "") {
|
||||||
|
error = 1;
|
||||||
|
println("Error: node is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error > 0) {
|
||||||
|
println("Please install above missing software");
|
||||||
|
bash("exit 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("check=$(pm2 --version)");
|
||||||
|
if (check == "") {
|
||||||
|
println("Installing PM2");
|
||||||
|
bash("npm install pm2 -g");
|
||||||
|
bash("pm2 startup");
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("mkdir -p $installPath");
|
||||||
|
bash("cd $installPath");
|
||||||
|
bash("git clone https://github.com/louislam/uptime-kuma.git .");
|
||||||
|
bash("npm run setup");
|
||||||
|
|
||||||
|
bash("pm2 start npm --name uptime-kuma -- run start-server -- --port=$port");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
defaultVolume = "uptime-kuma";
|
||||||
|
|
||||||
|
bash("check=$(docker -v)");
|
||||||
|
if (check == "") {
|
||||||
|
println("Error: docker is not found!");
|
||||||
|
bash("exit 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
bash("check=$(docker info)");
|
||||||
|
|
||||||
|
bash("if [[ \"$check\" == *\"Is the docker daemon running\"* ]]; then
|
||||||
|
echo \"Error: docker is not running\"
|
||||||
|
exit 1
|
||||||
|
fi");
|
||||||
|
|
||||||
|
if ("$3" != "") {
|
||||||
|
port = "$3";
|
||||||
|
} else {
|
||||||
|
call("read", "-p", "Expose Port [$defaultPort]: ", "port");
|
||||||
|
|
||||||
|
if (port == "") {
|
||||||
|
port = defaultPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("$2" != "") {
|
||||||
|
volume = "$2";
|
||||||
|
} else {
|
||||||
|
call("read", "-p", "Volume Name [$defaultVolume]: ", "volume");
|
||||||
|
|
||||||
|
if (volume == "") {
|
||||||
|
volume = defaultVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Port: $port");
|
||||||
|
println("Volume: $volume");
|
||||||
|
bash("docker volume create $volume");
|
||||||
|
bash("docker run -d --restart=always -p $port:3001 -v $volume:/app/data --name uptime-kuma louislam/uptime-kuma:1");
|
||||||
|
}
|
||||||
|
|
||||||
|
println("http://localhost:$port");
|
@@ -25,9 +25,6 @@ if (! exists) {
|
|||||||
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion);
|
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion);
|
||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||||
|
|
||||||
// Process README.md
|
|
||||||
fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion));
|
|
||||||
|
|
||||||
commit(newVersion);
|
commit(newVersion);
|
||||||
tag(newVersion);
|
tag(newVersion);
|
||||||
} else {
|
} else {
|
||||||
|
203
install.sh
Normal file
203
install.sh
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# install.sh is generated by ./extra/install.batsh, do not modify it directly.
|
||||||
|
# "npm run compile-install-script" to compile install.sh
|
||||||
|
# The command is working on Windows PowerShell and Docker for Windows only.
|
||||||
|
# curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||||
|
"echo" "-e" "====================="
|
||||||
|
"echo" "-e" "Uptime Kuma Installer"
|
||||||
|
"echo" "-e" "====================="
|
||||||
|
"echo" "-e" "Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian"
|
||||||
|
"echo" "-e" "---------------------------------------"
|
||||||
|
"echo" "-e" "This script is designed for Linux and basic usage."
|
||||||
|
"echo" "-e" "For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation"
|
||||||
|
"echo" "-e" "---------------------------------------"
|
||||||
|
"echo" "-e" ""
|
||||||
|
"echo" "-e" "Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2"
|
||||||
|
"echo" "-e" "Docker - Install Uptime Kuma Docker container"
|
||||||
|
"echo" "-e" ""
|
||||||
|
if [ "$1" != "" ]; then
|
||||||
|
type="$1"
|
||||||
|
else
|
||||||
|
"read" "-p" "Which installation method do you prefer? [DOCKER/local]: " "type"
|
||||||
|
fi
|
||||||
|
defaultPort="3001"
|
||||||
|
function checkNode {
|
||||||
|
local _0
|
||||||
|
nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')
|
||||||
|
"echo" "-e" "Node Version: ""$nodeVersion"
|
||||||
|
_0="12"
|
||||||
|
if [ $(($nodeVersion < $_0)) == 1 ]; then
|
||||||
|
"echo" "-e" "Error: Required Node.js 14"
|
||||||
|
"exit" "1"
|
||||||
|
fi
|
||||||
|
if [ "$nodeVersion" == "12" ]; then
|
||||||
|
"echo" "-e" "Warning: NodeJS ""$nodeVersion"" is not tested."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function deb {
|
||||||
|
nodeCheck=$(node -v)
|
||||||
|
apt --yes update
|
||||||
|
if [ "$nodeCheck" != "" ]; then
|
||||||
|
"checkNode"
|
||||||
|
else
|
||||||
|
# Old nodejs binary name is "nodejs"
|
||||||
|
check=$(nodejs --version)
|
||||||
|
if [ "$check" != "" ]; then
|
||||||
|
"echo" "-e" "Error: 'node' command is not found, but 'nodejs' command is found. Your NodeJS should be too old."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
curlCheck=$(curl --version)
|
||||||
|
if [ "$curlCheck" == "" ]; then
|
||||||
|
"echo" "-e" "Installing Curl"
|
||||||
|
apt --yes install curl
|
||||||
|
fi
|
||||||
|
"echo" "-e" "Installing Node.js 14"
|
||||||
|
curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt
|
||||||
|
apt --yes install nodejs
|
||||||
|
node -v
|
||||||
|
nodeCheckAgain=$(node -v)
|
||||||
|
if [ "$nodeCheckAgain" == "" ]; then
|
||||||
|
"echo" "-e" "Error during Node.js installation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
check=$(git --version)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
"echo" "-e" "Installing Git"
|
||||||
|
apt --yes install git
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
if [ "$type" == "local" ]; then
|
||||||
|
defaultInstallPath="/opt/uptime-kuma"
|
||||||
|
if [ -e "/etc/redhat-release" ]; then
|
||||||
|
os=$("cat" "/etc/redhat-release")
|
||||||
|
distribution="rhel"
|
||||||
|
else
|
||||||
|
if [ -e "/etc/issue" ]; then
|
||||||
|
os=$(head -n1 /etc/issue | cut -f 1 -d ' ')
|
||||||
|
if [ "$os" == "Ubuntu" ]; then
|
||||||
|
distribution="ubuntu"
|
||||||
|
fi
|
||||||
|
if [ "$os" == "Debian" ]; then
|
||||||
|
distribution="debian"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
arch=$(uname -i)
|
||||||
|
"echo" "-e" "Your OS: ""$os"
|
||||||
|
"echo" "-e" "Distribution: ""$distribution"
|
||||||
|
"echo" "-e" "Arch: ""$arch"
|
||||||
|
if [ "$3" != "" ]; then
|
||||||
|
port="$3"
|
||||||
|
else
|
||||||
|
"read" "-p" "Listening Port [$defaultPort]: " "port"
|
||||||
|
if [ "$port" == "" ]; then
|
||||||
|
port="$defaultPort"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$2" != "" ]; then
|
||||||
|
installPath="$2"
|
||||||
|
else
|
||||||
|
"read" "-p" "Installation Path [$defaultInstallPath]: " "installPath"
|
||||||
|
if [ "$installPath" == "" ]; then
|
||||||
|
installPath="$defaultInstallPath"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# CentOS
|
||||||
|
if [ "$distribution" == "rhel" ]; then
|
||||||
|
nodeCheck=$(node -v)
|
||||||
|
if [ "$nodeCheck" != "" ]; then
|
||||||
|
"checkNode"
|
||||||
|
else
|
||||||
|
curlCheck=$(curl --version)
|
||||||
|
if [ "$curlCheck" == "" ]; then
|
||||||
|
"echo" "-e" "Installing Curl"
|
||||||
|
yum -y -q install curl
|
||||||
|
fi
|
||||||
|
"echo" "-e" "Installing Node.js 14"
|
||||||
|
curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt
|
||||||
|
yum install -y -q nodejs
|
||||||
|
node -v
|
||||||
|
nodeCheckAgain=$(node -v)
|
||||||
|
if [ "$nodeCheckAgain" == "" ]; then
|
||||||
|
"echo" "-e" "Error during Node.js installation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
check=$(git --version)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
"echo" "-e" "Installing Git"
|
||||||
|
yum -y -q install git
|
||||||
|
fi
|
||||||
|
# Ubuntu
|
||||||
|
else
|
||||||
|
if [ "$distribution" == "ubuntu" ]; then
|
||||||
|
"deb"
|
||||||
|
# Debian
|
||||||
|
else
|
||||||
|
if [ "$distribution" == "debian" ]; then
|
||||||
|
"deb"
|
||||||
|
else
|
||||||
|
# Unknown distribution
|
||||||
|
error=$((0))
|
||||||
|
check=$(git --version)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
error=$((1))
|
||||||
|
"echo" "-e" "Error: git is missing"
|
||||||
|
fi
|
||||||
|
check=$(node -v)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
error=$((1))
|
||||||
|
"echo" "-e" "Error: node is missing"
|
||||||
|
fi
|
||||||
|
if [ $(($error > 0)) == 1 ]; then
|
||||||
|
"echo" "-e" "Please install above missing software"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
check=$(pm2 --version)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
"echo" "-e" "Installing PM2"
|
||||||
|
npm install pm2 -g
|
||||||
|
pm2 startup
|
||||||
|
fi
|
||||||
|
mkdir -p $installPath
|
||||||
|
cd $installPath
|
||||||
|
git clone https://github.com/louislam/uptime-kuma.git .
|
||||||
|
npm run setup
|
||||||
|
pm2 start npm --name uptime-kuma -- run start-server -- --port=$port
|
||||||
|
else
|
||||||
|
defaultVolume="uptime-kuma"
|
||||||
|
check=$(docker -v)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
"echo" "-e" "Error: docker is not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
check=$(docker info)
|
||||||
|
if [[ "$check" == *"Is the docker daemon running"* ]]; then
|
||||||
|
echo "Error: docker is not running"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$3" != "" ]; then
|
||||||
|
port="$3"
|
||||||
|
else
|
||||||
|
"read" "-p" "Expose Port [$defaultPort]: " "port"
|
||||||
|
if [ "$port" == "" ]; then
|
||||||
|
port="$defaultPort"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$2" != "" ]; then
|
||||||
|
volume="$2"
|
||||||
|
else
|
||||||
|
"read" "-p" "Volume Name [$defaultVolume]: " "volume"
|
||||||
|
if [ "$volume" == "" ]; then
|
||||||
|
volume="$defaultVolume"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
"echo" "-e" "Port: $port"
|
||||||
|
"echo" "-e" "Volume: $volume"
|
||||||
|
docker volume create $volume
|
||||||
|
docker run -d --restart=always -p $port:3001 -v $volume:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
||||||
|
fi
|
||||||
|
"echo" "-e" "http://localhost:$port"
|
28
kubernetes/README.md
Normal file
28
kubernetes/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Uptime-Kuma K8s Deployment
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
Kustomize is a tool which builds a complete deployment file for all config elements.
|
||||||
|
You can edit the files in the ```uptime-kuma``` folder except the ```kustomization.yml``` until you know what you're doing.
|
||||||
|
If you want to choose another namespace you can edit the ```kustomization.yml``` in the ```kubernetes```-Folder and change the ```namespace: uptime-kuma``` to something you like.
|
||||||
|
|
||||||
|
It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service
|
||||||
|
|
||||||
|
## What do i have to edit?
|
||||||
|
You have to edit the ```ingressroute.yml``` to your needs.
|
||||||
|
This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.github.io/ingress-nginx/) in combination with the [cert-manager](https://cert-manager.io/).
|
||||||
|
|
||||||
|
- host
|
||||||
|
- secrets and secret names
|
||||||
|
- (Cluster)Issuer (optional)
|
||||||
|
- the Version in the Deployment-File
|
||||||
|
- update:
|
||||||
|
- change to newer version and run the above commands, it will update the pods one after another
|
||||||
|
|
||||||
|
## How To use:
|
||||||
|
|
||||||
|
- install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/)
|
||||||
|
- Edit files mentioned above to your needs
|
||||||
|
- run ```kustomize build > apply.yml```
|
||||||
|
- run ```kubectl apply -f apply.yml```
|
||||||
|
|
||||||
|
Now you should see some k8s magic and Uptime-Kuma should be available at the specified address.
|
10
kubernetes/kustomization.yml
Normal file
10
kubernetes/kustomization.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace: uptime-kuma
|
||||||
|
namePrefix: uptime-kuma-
|
||||||
|
|
||||||
|
commonLabels:
|
||||||
|
app: uptime-kuma
|
||||||
|
|
||||||
|
bases:
|
||||||
|
- uptime-kuma
|
||||||
|
|
||||||
|
|
42
kubernetes/uptime-kuma/deployment.yml
Normal file
42
kubernetes/uptime-kuma/deployment.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: uptime-kuma
|
||||||
|
name: deployment
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
component: uptime-kuma
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
component: uptime-kuma
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: louislam/uptime-kuma:1
|
||||||
|
ports:
|
||||||
|
- containerPort: 3001
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /app/data
|
||||||
|
name: storage
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- node
|
||||||
|
- extra/healthcheck.js
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3001
|
||||||
|
scheme: HTTP
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: pvc
|
39
kubernetes/uptime-kuma/ingressroute.yml
Normal file
39
kubernetes/uptime-kuma/ingressroute.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/server-snippets: |
|
||||||
|
location / {
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
name: ingress
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- example.com
|
||||||
|
secretName: example-com-tls
|
||||||
|
rules:
|
||||||
|
- host: example.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: service
|
||||||
|
port:
|
||||||
|
number: 3001
|
5
kubernetes/uptime-kuma/kustomization.yml
Normal file
5
kubernetes/uptime-kuma/kustomization.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
resources:
|
||||||
|
- deployment.yml
|
||||||
|
- service.yml
|
||||||
|
- ingressroute.yml
|
||||||
|
- pvc.yml
|
10
kubernetes/uptime-kuma/pvc.yml
Normal file
10
kubernetes/uptime-kuma/pvc.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 4Gi
|
13
kubernetes/uptime-kuma/service.yml
Normal file
13
kubernetes/uptime-kuma/service.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
component: uptime-kuma
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 3001
|
||||||
|
targetPort: 3001
|
||||||
|
protocol: TCP
|
9231
package-lock.json
generated
9231
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.2.0",
|
"version": "1.3.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -17,14 +17,19 @@
|
|||||||
"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.2.0 --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.3.2 --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.2.0 && npm install && npm run build",
|
"setup": "git checkout 1.3.2 && npm install && npm run build",
|
||||||
"update-version": "node extra/update-version.js",
|
"update-version": "node extra/update-version.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
"compile-install-script": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./extra/compile-install-script.ps1"
|
"compile-install-script": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./extra/compile-install-script.ps1",
|
||||||
|
"test-install-script-centos7": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/centos7.dockerfile .",
|
||||||
|
"test-install-script-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
|
||||||
|
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
|
||||||
|
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
||||||
|
"test-install-script-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.dockerfile ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
@@ -40,6 +45,7 @@
|
|||||||
"chart.js": "^3.5.0",
|
"chart.js": "^3.5.0",
|
||||||
"chartjs-adapter-dayjs": "^1.0.0",
|
"chartjs-adapter-dayjs": "^1.0.0",
|
||||||
"command-exists": "^1.2.9",
|
"command-exists": "^1.2.9",
|
||||||
|
"compare-versions": "^3.6.0",
|
||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.6",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-basic-auth": "^1.2.0",
|
"express-basic-auth": "^1.2.0",
|
||||||
|
44
server/check-version.js
Normal file
44
server/check-version.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const { setSetting } = require("./util-server");
|
||||||
|
const axios = require("axios");
|
||||||
|
const { isDev } = require("../src/util");
|
||||||
|
|
||||||
|
exports.version = require("../package.json").version;
|
||||||
|
exports.latestVersion = null;
|
||||||
|
|
||||||
|
let interval;
|
||||||
|
|
||||||
|
exports.startInterval = () => {
|
||||||
|
let check = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get("https://raw.githubusercontent.com/louislam/uptime-kuma/master/package.json");
|
||||||
|
|
||||||
|
if (typeof res.data === "string") {
|
||||||
|
res.data = JSON.parse(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For debug
|
||||||
|
if (process.env.TEST_CHECK_VERSION === "1") {
|
||||||
|
res.data.version = "1000.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.latestVersion = res.data.version;
|
||||||
|
console.log("Latest Version: " + exports.latestVersion);
|
||||||
|
} catch (_) { }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
check();
|
||||||
|
interval = setInterval(check, 3600 * 1000 * 48);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.enableCheckUpdate = async (value) => {
|
||||||
|
await setSetting("checkUpdate", value);
|
||||||
|
|
||||||
|
clearInterval(interval);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
exports.startInterval();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.socket = null;
|
@@ -1,8 +1,9 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { sleep } = require("../src/util");
|
const { sleep, debug, isDev } = require("../src/util");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setSetting, setting } = require("./util-server");
|
const { setSetting, setting } = require("./util-server");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
|
const sqlite3 = require("@louislam/sqlite3");
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
|
|
||||||
@@ -10,23 +11,54 @@ class Database {
|
|||||||
static path = "./data/kuma.db";
|
static path = "./data/kuma.db";
|
||||||
static latestVersion = 6;
|
static latestVersion = 6;
|
||||||
static noReject = true;
|
static noReject = true;
|
||||||
|
static sqliteInstance = null;
|
||||||
|
|
||||||
static async connect() {
|
static async connect() {
|
||||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
|
||||||
Dialect.prototype._driver = () => require("@louislam/sqlite3");
|
|
||||||
|
|
||||||
R.setup(knex({
|
if (! this.sqliteInstance) {
|
||||||
|
this.sqliteInstance = new sqlite3.Database(Database.path);
|
||||||
|
this.sqliteInstance.run("PRAGMA journal_mode = WAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||||
|
Dialect.prototype._driver = () => sqlite3;
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
Dialect.prototype.acquireConnectionOrg = Dialect.prototype.acquireConnection;
|
||||||
|
|
||||||
|
Dialect.prototype.acquireConnection = async function () {
|
||||||
|
let a = await this.acquireConnectionOrg();
|
||||||
|
debug("acquired Connection");
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always return same connection.
|
||||||
|
Dialect.prototype.acquireRawConnection = async function () {
|
||||||
|
return Database.sqliteInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
Dialect.prototype.destroyRawConnection = async () => { }
|
||||||
|
|
||||||
|
const knexInstance = knex({
|
||||||
client: Dialect,
|
client: Dialect,
|
||||||
connection: {
|
connection: { }, // Do not remove, Leave it empty is ok
|
||||||
filename: Database.path,
|
|
||||||
},
|
|
||||||
useNullAsDefault: true,
|
useNullAsDefault: true,
|
||||||
pool: {
|
pool: {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 1,
|
max: 1,
|
||||||
idleTimeoutMillis: 30000,
|
idleTimeoutMillis: 30000,
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
console.log( knexInstance.pool)
|
||||||
|
console.log("pool size")
|
||||||
|
|
||||||
|
R.setup(knexInstance);
|
||||||
|
|
||||||
|
if (process.env.SQL_LOG === "1") {
|
||||||
|
R.debug(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Auto map the model to a bean object
|
// Auto map the model to a bean object
|
||||||
R.freeze(true)
|
R.freeze(true)
|
||||||
@@ -54,6 +86,16 @@ class Database {
|
|||||||
const backupPath = "./data/kuma.db.bak" + version;
|
const backupPath = "./data/kuma.db.bak" + version;
|
||||||
fs.copyFileSync(Database.path, backupPath);
|
fs.copyFileSync(Database.path, backupPath);
|
||||||
|
|
||||||
|
const shmPath = Database.path + "-shm";
|
||||||
|
if (fs.existsSync(shmPath)) {
|
||||||
|
fs.copyFileSync(shmPath, shmPath + ".bak" + version);
|
||||||
|
}
|
||||||
|
|
||||||
|
const walPath = Database.path + "-wal";
|
||||||
|
if (fs.existsSync(walPath)) {
|
||||||
|
fs.copyFileSync(walPath, walPath + ".bak" + version);
|
||||||
|
}
|
||||||
|
|
||||||
// Try catch anything here, if gone wrong, restore the backup
|
// Try catch anything here, if gone wrong, restore the backup
|
||||||
try {
|
try {
|
||||||
for (let i = version + 1; i <= this.latestVersion; i++) {
|
for (let i = version + 1; i <= this.latestVersion; i++) {
|
||||||
@@ -116,27 +158,10 @@ class Database {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async close() {
|
static async close() {
|
||||||
const listener = (reason, p) => {
|
if (this.sqliteInstance) {
|
||||||
Database.noReject = false;
|
this.sqliteInstance.close();
|
||||||
};
|
|
||||||
process.addListener("unhandledRejection", listener);
|
|
||||||
|
|
||||||
console.log("Closing DB")
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
Database.noReject = true;
|
|
||||||
await R.close()
|
|
||||||
await sleep(2000)
|
|
||||||
|
|
||||||
if (Database.noReject) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
console.log("Waiting to close the db")
|
|
||||||
}
|
}
|
||||||
}
|
console.log("Stopped database");
|
||||||
console.log("SQLite closed")
|
|
||||||
|
|
||||||
process.removeListener("unhandledRejection", listener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ dayjs.extend(utc)
|
|||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { debug, UP, DOWN, PENDING, flipStatus } = require("../../src/util");
|
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
||||||
const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-server");
|
const { tcping, ping, checkCertificate, checkStatusCode } = 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");
|
||||||
@@ -133,7 +133,6 @@ class Monitor extends BeanModel {
|
|||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
|
||||||
// Check certificate if https is used
|
// Check certificate if https is used
|
||||||
|
|
||||||
let certInfoStartTime = dayjs().valueOf();
|
let certInfoStartTime = dayjs().valueOf();
|
||||||
if (this.getUrl()?.protocol === "https:") {
|
if (this.getUrl()?.protocol === "https:") {
|
||||||
try {
|
try {
|
||||||
@@ -311,10 +310,10 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async sendStats(io, monitorID, userID) {
|
static async sendStats(io, monitorID, userID) {
|
||||||
Monitor.sendAvgPing(24, io, monitorID, userID);
|
await Monitor.sendAvgPing(24, io, monitorID, userID);
|
||||||
Monitor.sendUptime(24, io, monitorID, userID);
|
await Monitor.sendUptime(24, io, monitorID, userID);
|
||||||
Monitor.sendUptime(24 * 30, io, monitorID, userID);
|
await Monitor.sendUptime(24 * 30, io, monitorID, userID);
|
||||||
Monitor.sendCertInfo(io, monitorID, userID);
|
await Monitor.sendCertInfo(io, monitorID, userID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -322,6 +321,8 @@ class Monitor extends BeanModel {
|
|||||||
* @param duration : int Hours
|
* @param duration : int Hours
|
||||||
*/
|
*/
|
||||||
static async sendAvgPing(duration, io, monitorID, userID) {
|
static async sendAvgPing(duration, io, monitorID, userID) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
let avgPing = parseInt(await R.getCell(`
|
let avgPing = parseInt(await R.getCell(`
|
||||||
SELECT AVG(ping)
|
SELECT AVG(ping)
|
||||||
FROM heartbeat
|
FROM heartbeat
|
||||||
@@ -332,6 +333,8 @@ class Monitor extends BeanModel {
|
|||||||
monitorID,
|
monitorID,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
timeLogger.print(`[Monitor: ${monitorID}] avgPing`);
|
||||||
|
|
||||||
io.to(userID).emit("avgPing", monitorID, avgPing);
|
io.to(userID).emit("avgPing", monitorID, avgPing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +354,8 @@ class Monitor extends BeanModel {
|
|||||||
* @param duration : int Hours
|
* @param duration : int Hours
|
||||||
*/
|
*/
|
||||||
static async sendUptime(duration, io, monitorID, userID) {
|
static async sendUptime(duration, io, monitorID, userID) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
let sec = duration * 3600;
|
let sec = duration * 3600;
|
||||||
|
|
||||||
let heartbeatList = await R.getAll(`
|
let heartbeatList = await R.getAll(`
|
||||||
@@ -362,6 +367,8 @@ class Monitor extends BeanModel {
|
|||||||
monitorID,
|
monitorID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
timeLogger.print(`[Monitor: ${monitorID}][${duration}] sendUptime`);
|
||||||
|
|
||||||
let downtime = 0;
|
let downtime = 0;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let uptime;
|
let uptime;
|
||||||
|
@@ -197,9 +197,9 @@ class Notification {
|
|||||||
try {
|
try {
|
||||||
let config = {
|
let config = {
|
||||||
headers: {
|
headers: {
|
||||||
'api-key': notification.octopushAPIKey,
|
"api-key": notification.octopushAPIKey,
|
||||||
'api-login': notification.octopushLogin,
|
"api-login": notification.octopushLogin,
|
||||||
'cache-control': 'no-cache'
|
"cache-control": "no-cache"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let data = {
|
let data = {
|
||||||
@@ -215,7 +215,7 @@ class Notification {
|
|||||||
"sender": notification.octopushSenderName
|
"sender": notification.octopushSenderName
|
||||||
};
|
};
|
||||||
|
|
||||||
await axios.post(`https://api.octopush.com/v1/public/sms-campaign/send`, data, config)
|
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config)
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -356,11 +356,11 @@ class Notification {
|
|||||||
|
|
||||||
} else if (notification.type === "pushbullet") {
|
} else if (notification.type === "pushbullet") {
|
||||||
try {
|
try {
|
||||||
let pushbulletUrl = `https://api.pushbullet.com/v2/pushes`;
|
let pushbulletUrl = "https://api.pushbullet.com/v2/pushes";
|
||||||
let config = {
|
let config = {
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Token': notification.pushbulletAccessToken,
|
"Access-Token": notification.pushbulletAccessToken,
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (heartbeatJSON == null) {
|
if (heartbeatJSON == null) {
|
||||||
@@ -389,6 +389,53 @@ class Notification {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throwGeneralAxiosError(error)
|
throwGeneralAxiosError(error)
|
||||||
}
|
}
|
||||||
|
} else if (notification.type === "line") {
|
||||||
|
try {
|
||||||
|
let lineAPIUrl = "https://api.line.me/v2/bot/message/push";
|
||||||
|
let config = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": "Bearer " + notification.lineChannelAccessToken
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (heartbeatJSON == null) {
|
||||||
|
let testMessage = {
|
||||||
|
"to": notification.lineUserID,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text":"Test Successful!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
await axios.post(lineAPIUrl, testMessage, config)
|
||||||
|
} else if (heartbeatJSON["status"] == 0) {
|
||||||
|
let downMessage = {
|
||||||
|
"to": notification.lineUserID,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text":"UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
await axios.post(lineAPIUrl, downMessage, config)
|
||||||
|
} else if (heartbeatJSON["status"] == 1) {
|
||||||
|
let upMessage = {
|
||||||
|
"to": notification.lineUserID,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text":"UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
await axios.post(lineAPIUrl, upMessage, config)
|
||||||
|
}
|
||||||
|
return okMsg;
|
||||||
|
} catch (error) {
|
||||||
|
throwGeneralAxiosError(error)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Notification type is not supported")
|
throw new Error("Notification type is not supported")
|
||||||
}
|
}
|
||||||
@@ -432,15 +479,21 @@ class Notification {
|
|||||||
|
|
||||||
static async smtp(notification, msg) {
|
static async smtp(notification, msg) {
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport({
|
const config = {
|
||||||
host: notification.smtpHost,
|
host: notification.smtpHost,
|
||||||
port: notification.smtpPort,
|
port: notification.smtpPort,
|
||||||
secure: notification.smtpSecure,
|
secure: notification.smtpSecure,
|
||||||
auth: {
|
};
|
||||||
|
|
||||||
|
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
|
||||||
|
if (notification.smtpUsername || notification.smtpPassword) {
|
||||||
|
config.auth = {
|
||||||
user: notification.smtpUsername,
|
user: notification.smtpUsername,
|
||||||
pass: notification.smtpPassword,
|
pass: notification.smtpPassword,
|
||||||
},
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
|
let transporter = nodemailer.createTransport(config);
|
||||||
|
|
||||||
// send mail with defined transport object
|
// send mail with defined transport object
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
|
@@ -7,6 +7,7 @@ const spawn = require("child_process").spawn,
|
|||||||
WIN = /^win/.test(process.platform),
|
WIN = /^win/.test(process.platform),
|
||||||
LIN = /^linux/.test(process.platform),
|
LIN = /^linux/.test(process.platform),
|
||||||
MAC = /^darwin/.test(process.platform);
|
MAC = /^darwin/.test(process.platform);
|
||||||
|
FBSD = /^freebsd/.test(process.platform);
|
||||||
const { debug } = require("../src/util");
|
const { debug } = require("../src/util");
|
||||||
|
|
||||||
module.exports = Ping;
|
module.exports = Ping;
|
||||||
@@ -49,6 +50,18 @@ function Ping(host, options) {
|
|||||||
this._args = (options.args) ? options.args : [ "-n", "-t", "2", "-c", "1", host ];
|
this._args = (options.args) ? options.args : [ "-n", "-t", "2", "-c", "1", host ];
|
||||||
this._regmatch = /=([0-9.]+?) ms/;
|
this._regmatch = /=([0-9.]+?) ms/;
|
||||||
|
|
||||||
|
} else if (FBSD) {
|
||||||
|
this._bin = "/sbin/ping";
|
||||||
|
|
||||||
|
const defaultArgs = [ "-n", "-t", "2", "-c", "1", host ];
|
||||||
|
|
||||||
|
if (net.isIPv6(host) || options.ipv6) {
|
||||||
|
defaultArgs.unshift("-6");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._args = (options.args) ? options.args : defaultArgs;
|
||||||
|
this._regmatch = /=([0-9.]+?) ms/;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Could not detect your ping binary.");
|
throw new Error("Could not detect your ping binary.");
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
console.log("Welcome to Uptime Kuma");
|
console.log("Welcome to Uptime Kuma");
|
||||||
console.log("Node Env: " + process.env.NODE_ENV);
|
console.log("Node Env: " + process.env.NODE_ENV);
|
||||||
|
|
||||||
const { sleep, debug } = require("../src/util");
|
const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util");
|
||||||
|
|
||||||
console.log("Importing Node libraries")
|
console.log("Importing Node libraries")
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -39,16 +39,14 @@ const passwordHash = require("./password-hash");
|
|||||||
|
|
||||||
const args = require("args-parser")(process.argv);
|
const args = require("args-parser")(process.argv);
|
||||||
|
|
||||||
const version = require("../package.json").version;
|
const checkVersion = require("./check-version");
|
||||||
|
console.info("Version: " + checkVersion.version);
|
||||||
|
|
||||||
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
|
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
|
||||||
// Dual-stack support for (::)
|
// Dual-stack support for (::)
|
||||||
const hostname = process.env.HOST || args.host;
|
const hostname = process.env.HOST || args.host;
|
||||||
|
|
||||||
const port = parseInt(process.env.PORT || args.port || 3001);
|
const port = parseInt(process.env.PORT || args.port || 3001);
|
||||||
|
|
||||||
console.info("Version: " + version)
|
|
||||||
|
|
||||||
console.log("Creating express and socket.io instance")
|
console.log("Creating express and socket.io instance")
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
@@ -119,7 +117,8 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||||||
io.on("connection", async (socket) => {
|
io.on("connection", async (socket) => {
|
||||||
|
|
||||||
socket.emit("info", {
|
socket.emit("info", {
|
||||||
version,
|
version: checkVersion.version,
|
||||||
|
latestVersion: checkVersion.latestVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
totalClient++;
|
totalClient++;
|
||||||
@@ -584,6 +583,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|||||||
console.log(`Listening on ${port}`);
|
console.log(`Listening on ${port}`);
|
||||||
}
|
}
|
||||||
startMonitors();
|
startMonitors();
|
||||||
|
checkVersion.startInterval();
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@@ -644,7 +644,7 @@ async function afterLogin(socket, user) {
|
|||||||
|
|
||||||
// Delay a bit, so that it let the main page to query the data first, since SQLite can process one sql at the same time only.
|
// Delay a bit, so that it let the main page to query the data first, since SQLite can process one sql at the same time only.
|
||||||
// For example, query the edit data first.
|
// For example, query the edit data first.
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
for (let monitorID in monitorList) {
|
for (let monitorID in monitorList) {
|
||||||
sendHeartbeatList(socket, monitorID);
|
sendHeartbeatList(socket, monitorID);
|
||||||
sendImportantHeartbeatList(socket, monitorID);
|
sendImportantHeartbeatList(socket, monitorID);
|
||||||
@@ -755,15 +755,22 @@ async function startMonitors() {
|
|||||||
let list = await R.find("monitor", " active = 1 ")
|
let list = await R.find("monitor", " active = 1 ")
|
||||||
|
|
||||||
for (let monitor of list) {
|
for (let monitor of list) {
|
||||||
monitor.start(io)
|
|
||||||
monitorList[monitor.id] = monitor;
|
monitorList[monitor.id] = monitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let monitor of list) {
|
||||||
|
monitor.start(io);
|
||||||
|
// Give some delays, so all monitors won't make request at the same moment when just start the server.
|
||||||
|
await sleep(getRandomInt(300, 1000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send Heartbeat History list to socket
|
* Send Heartbeat History list to socket
|
||||||
*/
|
*/
|
||||||
async function sendHeartbeatList(socket, monitorID) {
|
async function sendHeartbeatList(socket, monitorID) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
let list = await R.find("heartbeat", `
|
let list = await R.find("heartbeat", `
|
||||||
monitor_id = ?
|
monitor_id = ?
|
||||||
ORDER BY time DESC
|
ORDER BY time DESC
|
||||||
@@ -782,6 +789,8 @@ async function sendHeartbeatList(socket, monitorID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendImportantHeartbeatList(socket, monitorID) {
|
async function sendImportantHeartbeatList(socket, monitorID) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
let list = await R.find("heartbeat", `
|
let list = await R.find("heartbeat", `
|
||||||
monitor_id = ?
|
monitor_id = ?
|
||||||
AND important = 1
|
AND important = 1
|
||||||
@@ -791,6 +800,8 @@ async function sendImportantHeartbeatList(socket, monitorID) {
|
|||||||
monitorID,
|
monitorID,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`);
|
||||||
|
|
||||||
socket.emit("importantHeartbeatList", monitorID, list)
|
socket.emit("importantHeartbeatList", monitorID, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,11 +816,10 @@ async function shutdownFunction(signal) {
|
|||||||
}
|
}
|
||||||
await sleep(2000);
|
await sleep(2000);
|
||||||
await Database.close();
|
await Database.close();
|
||||||
console.log("Stopped DB")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalFunction() {
|
function finalFunction() {
|
||||||
console.log("Graceful Shutdown Done")
|
console.log("Graceful shutdown successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
gracefulShutdown(server, {
|
gracefulShutdown(server, {
|
||||||
@@ -820,3 +830,9 @@ gracefulShutdown(server, {
|
|||||||
onShutdown: shutdownFunction, // shutdown function (async) - e.g. for cleanup DB, ...
|
onShutdown: shutdownFunction, // shutdown function (async) - e.g. for cleanup DB, ...
|
||||||
finally: finalFunction, // finally function (sync) - e.g. for logging
|
finally: finalFunction, // finally function (sync) - e.g. for logging
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Catch unexpected errors here
|
||||||
|
process.addListener("unhandledRejection", (error, promise) => {
|
||||||
|
console.trace(error);
|
||||||
|
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
});
|
||||||
|
@@ -72,7 +72,13 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover, &:active, &:focus, &.active {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dark Theme override here
|
// Dark Theme override here
|
||||||
.dark {
|
.dark {
|
||||||
@@ -99,6 +105,10 @@ h2 {
|
|||||||
.table,
|
.table,
|
||||||
.nav-link {
|
.nav-link {
|
||||||
color: $dark-font-color;
|
color: $dark-font-color;
|
||||||
|
|
||||||
|
&.btn-info {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control,
|
.form-control,
|
||||||
@@ -183,3 +193,20 @@ h2 {
|
|||||||
background-color: $dark-bg;
|
background-color: $dark-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// page-change
|
||||||
|
.slide-fade-enter-active {
|
||||||
|
transition: all 0.20s $easing-in;
|
||||||
|
}
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all 0.20s $easing-in;
|
||||||
|
}
|
||||||
|
.slide-fade-enter-from,
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
transform: translateY(50px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
@@ -12,3 +12,7 @@ $dark-font-color2: #020b05;
|
|||||||
$dark-bg: #0D1117;
|
$dark-bg: #0D1117;
|
||||||
$dark-bg2: #070A10;
|
$dark-bg2: #070A10;
|
||||||
$dark-border-color: #1d2634;
|
$dark-border-color: #1d2634;
|
||||||
|
|
||||||
|
$easing-in: cubic-bezier(0.54,0.78,0.55,0.97);
|
||||||
|
$easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
$easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86);
|
||||||
|
@@ -22,15 +22,11 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
displayText() {
|
displayText() {
|
||||||
if (this.value !== undefined && this.value !== "") {
|
|
||||||
let format = "YYYY-MM-DD HH:mm:ss";
|
|
||||||
if (this.dateOnly) {
|
if (this.dateOnly) {
|
||||||
format = "YYYY-MM-DD";
|
return this.$root.date(this.value);
|
||||||
|
} else {
|
||||||
|
return this.$root.datetime(this.value);
|
||||||
}
|
}
|
||||||
return dayjs.utc(this.value).tz(this.$root.timezone).format(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
class="beat"
|
class="beat"
|
||||||
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }"
|
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }"
|
||||||
:style="beatStyle"
|
:style="beatStyle"
|
||||||
:title="beat.msg"
|
:title="getBeatTitle(beat)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: "big",
|
default: "big",
|
||||||
},
|
},
|
||||||
monitorId: Number,
|
monitorId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -36,14 +39,15 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
beatList() {
|
beatList() {
|
||||||
if (! (this.monitorId in this.$root.heartbeatList)) {
|
|
||||||
this.$root.heartbeatList[this.monitorId] = [];
|
|
||||||
}
|
|
||||||
return this.$root.heartbeatList[this.monitorId]
|
return this.$root.heartbeatList[this.monitorId]
|
||||||
},
|
},
|
||||||
|
|
||||||
shortBeatList() {
|
shortBeatList() {
|
||||||
let placeholders = []
|
if (! this.beatList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholders = [];
|
||||||
|
|
||||||
let start = this.beatList.length - this.maxBeat;
|
let start = this.beatList.length - this.maxBeat;
|
||||||
|
|
||||||
@@ -113,6 +117,11 @@ export default {
|
|||||||
unmounted() {
|
unmounted() {
|
||||||
window.removeEventListener("resize", this.resize);
|
window.removeEventListener("resize", this.resize);
|
||||||
},
|
},
|
||||||
|
beforeMount() {
|
||||||
|
if (! (this.monitorId in this.$root.heartbeatList)) {
|
||||||
|
this.$root.heartbeatList[this.monitorId] = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.size === "small") {
|
if (this.size === "small") {
|
||||||
this.beatWidth = 5.6;
|
this.beatWidth = 5.6;
|
||||||
@@ -129,6 +138,10 @@ export default {
|
|||||||
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2))
|
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getBeatTitle(beat) {
|
||||||
|
return `${this.$root.datetime(beat.time)} - ${beat.msg}`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
133
src/components/MonitorList.vue
Normal file
133
src/components/MonitorList.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div class="shadow-box list mb-4">
|
||||||
|
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
||||||
|
No Monitors, please <router-link to="/add">add one</router-link>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 col-md-8 small-padding" :class="{ 'monitorItem': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||||
|
<div class="info">
|
||||||
|
<Uptime :monitor="item" type="24" :pill="true" />
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-6 col-md-4">
|
||||||
|
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
||||||
|
import Uptime from "../components/Uptime.vue";
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Uptime,
|
||||||
|
HeartbeatBar,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedMonitorList() {
|
||||||
|
let result = Object.values(this.$root.monitorList);
|
||||||
|
|
||||||
|
result.sort((m1, m2) => {
|
||||||
|
|
||||||
|
if (m1.active !== m2.active) {
|
||||||
|
if (m1.active === 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m2.active === 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight !== m2.weight) {
|
||||||
|
if (m1.weight > m2.weight) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight < m2.weight) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m1.name.localeCompare(m2.name);
|
||||||
|
})
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
monitorURL(id) {
|
||||||
|
return "/dashboard/" + id;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
.small-padding {
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
height: auto;
|
||||||
|
min-height: calc(100vh - 240px);
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 13px 15px 10px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all ease-in-out 0.15s;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $highlight-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #cdf8f4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
.list {
|
||||||
|
.item {
|
||||||
|
&:hover {
|
||||||
|
background-color: $dark-bg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $dark-bg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitorItem {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -26,6 +26,7 @@
|
|||||||
<option value="lunasea">LunaSea</option>
|
<option value="lunasea">LunaSea</option>
|
||||||
<option value="apprise">Apprise (Support 50+ Notification services)</option>
|
<option value="apprise">Apprise (Support 50+ Notification services)</option>
|
||||||
<option value="pushbullet">Pushbullet</option>
|
<option value="pushbullet">Pushbullet</option>
|
||||||
|
<option value="line">Line Messenger</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -383,6 +384,26 @@
|
|||||||
More info on: <a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a>
|
More info on: <a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="notification.type === 'line'">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="line-channel-access-token" class="form-label">Channel access token</label>
|
||||||
|
<input id="line-channel-access-token" v-model="notification.lineChannelAccessToken" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
Line Developers Console - <b>Basic Settings</b>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3" style="margin-top: 12px;">
|
||||||
|
<label for="line-user-id" class="form-label">User ID</label>
|
||||||
|
<input id="line-user-id" v-model="notification.lineUserID" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
Line Developers Console - <b>Messaging API</b>
|
||||||
|
</div>
|
||||||
|
<div class="form-text" style="margin-top: 8px;">
|
||||||
|
First access the <a href="https://developers.line.biz/console/" target="_blank">Line Developers Console</a>, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
|
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<LineChart :chart-data="chartData" :height="100" :options="chartOptions" />
|
<LineChart :chart-data="chartData" :options="chartOptions" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -31,6 +31,19 @@ export default {
|
|||||||
chartOptions() {
|
chartOptions() {
|
||||||
return {
|
return {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
onResize: (chart) => {
|
||||||
|
chart.canvas.parentNode.style.position = "relative";
|
||||||
|
if (screen.width < 576) {
|
||||||
|
chart.canvas.parentNode.style.height = "275px";
|
||||||
|
} else if (screen.width < 768) {
|
||||||
|
chart.canvas.parentNode.style.height = "320px";
|
||||||
|
} else if (screen.width < 992) {
|
||||||
|
chart.canvas.parentNode.style.height = "300px";
|
||||||
|
} else {
|
||||||
|
chart.canvas.parentNode.style.height = "250px";
|
||||||
|
}
|
||||||
|
},
|
||||||
layout: {
|
layout: {
|
||||||
padding: {
|
padding: {
|
||||||
left: 10,
|
left: 10,
|
||||||
@@ -52,12 +65,19 @@ export default {
|
|||||||
x: {
|
x: {
|
||||||
type: "time",
|
type: "time",
|
||||||
time: {
|
time: {
|
||||||
unit: "minute",
|
minUnit: "minute",
|
||||||
|
round: "second",
|
||||||
|
tooltipFormat: "YYYY-MM-DD HH:mm:ss",
|
||||||
|
displayFormats: {
|
||||||
|
minute: "HH:mm",
|
||||||
|
hour: "MM-DD HH:mm",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
maxRotation: 0,
|
maxRotation: 0,
|
||||||
autoSkipPadding: 10,
|
autoSkipPadding: 30,
|
||||||
},
|
},
|
||||||
|
bounds: "ticks",
|
||||||
grid: {
|
grid: {
|
||||||
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
|
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
|
||||||
},
|
},
|
||||||
@@ -65,7 +85,7 @@ export default {
|
|||||||
y: {
|
y: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: "Response Time (ms)",
|
text: "Resp. Time (ms)",
|
||||||
},
|
},
|
||||||
offset: false,
|
offset: false,
|
||||||
grid: {
|
grid: {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { library } from "@fortawesome/fontawesome-svg-core"
|
import { library } from "@fortawesome/fontawesome-svg-core"
|
||||||
import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp } from "@fortawesome/free-solid-svg-icons"
|
||||||
//import { fa } from '@fortawesome/free-regular-svg-icons'
|
//import { fa } from '@fortawesome/free-regular-svg-icons'
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
|
||||||
|
|
||||||
// Add Free Font Awesome Icons here
|
// Add Free Font Awesome Icons here
|
||||||
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
|
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
|
||||||
library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList)
|
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp);
|
||||||
|
|
||||||
export { FontAwesomeIcon }
|
export { FontAwesomeIcon }
|
||||||
|
@@ -13,6 +13,10 @@
|
|||||||
<span class="fs-4 title">Uptime Kuma</span>
|
<span class="fs-4 title">Uptime Kuma</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/uptime-kuma/releases" class="btn btn-info me-3">
|
||||||
|
<font-awesome-icon icon="arrow-alt-circle-up" /> New Update
|
||||||
|
</a>
|
||||||
|
|
||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<router-link to="/dashboard" class="nav-link">
|
<router-link to="/dashboard" class="nav-link">
|
||||||
@@ -52,22 +56,22 @@
|
|||||||
<!-- Mobile Only -->
|
<!-- Mobile Only -->
|
||||||
<div v-if="$root.isMobile" style="width: 100%;height: 60px;" />
|
<div v-if="$root.isMobile" style="width: 100%;height: 60px;" />
|
||||||
<nav v-if="$root.isMobile" class="bottom-nav">
|
<nav v-if="$root.isMobile" class="bottom-nav">
|
||||||
<router-link to="/dashboard" class="nav-link" @click="$root.cancelActiveList">
|
<router-link to="/dashboard" class="nav-link">
|
||||||
<div><font-awesome-icon icon="tachometer-alt" /></div>
|
<div><font-awesome-icon icon="tachometer-alt" /></div>
|
||||||
Dashboard
|
Dashboard
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<a href="#" :class=" { 'router-link-exact-active' : $root.showListMobile } " @click="$root.showListMobile = ! $root.showListMobile">
|
<router-link to="/list" class="nav-link">
|
||||||
<div><font-awesome-icon icon="list" /></div>
|
<div><font-awesome-icon icon="list" /></div>
|
||||||
List
|
List
|
||||||
</a>
|
</router-link>
|
||||||
|
|
||||||
<router-link to="/add" class="nav-link" @click="$root.cancelActiveList">
|
<router-link to="/add" class="nav-link">
|
||||||
<div><font-awesome-icon icon="plus" /></div>
|
<div><font-awesome-icon icon="plus" /></div>
|
||||||
Add
|
Add
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link to="/settings" class="nav-link" @click="$root.cancelActiveList">
|
<router-link to="/settings" class="nav-link">
|
||||||
<div><font-awesome-icon icon="cog" /></div>
|
<div><font-awesome-icon icon="cog" /></div>
|
||||||
Settings
|
Settings
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -77,6 +81,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Login from "../components/Login.vue";
|
import Login from "../components/Login.vue";
|
||||||
|
import compareVersions from "compare-versions";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
@@ -96,7 +101,15 @@ export default {
|
|||||||
classes[this.$root.theme] = true;
|
classes[this.$root.theme] = true;
|
||||||
classes["mobile"] = this.$root.isMobile;
|
classes["mobile"] = this.$root.isMobile;
|
||||||
return classes;
|
return classes;
|
||||||
|
},
|
||||||
|
|
||||||
|
hasNewVersion() {
|
||||||
|
if (this.$root.info.latestVersion && this.$root.info.version) {
|
||||||
|
return compareVersions(this.$root.info.latestVersion, this.$root.info.version) >= 1;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
10
src/main.js
10
src/main.js
@@ -11,12 +11,15 @@ import Layout from "./layouts/Layout.vue";
|
|||||||
import socket from "./mixins/socket";
|
import socket from "./mixins/socket";
|
||||||
import theme from "./mixins/theme";
|
import theme from "./mixins/theme";
|
||||||
import mobile from "./mixins/mobile";
|
import mobile from "./mixins/mobile";
|
||||||
|
import datetime from "./mixins/datetime";
|
||||||
import Dashboard from "./pages/Dashboard.vue";
|
import Dashboard from "./pages/Dashboard.vue";
|
||||||
import DashboardHome from "./pages/DashboardHome.vue";
|
import DashboardHome from "./pages/DashboardHome.vue";
|
||||||
import Details from "./pages/Details.vue";
|
import Details from "./pages/Details.vue";
|
||||||
import EditMonitor from "./pages/EditMonitor.vue";
|
import EditMonitor from "./pages/EditMonitor.vue";
|
||||||
import Settings from "./pages/Settings.vue";
|
import Settings from "./pages/Settings.vue";
|
||||||
import Setup from "./pages/Setup.vue";
|
import Setup from "./pages/Setup.vue";
|
||||||
|
import List from "./pages/List.vue";
|
||||||
|
|
||||||
import { appName } from "./util.ts";
|
import { appName } from "./util.ts";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@@ -52,6 +55,10 @@ const routes = [
|
|||||||
path: "/add",
|
path: "/add",
|
||||||
component: EditMonitor,
|
component: EditMonitor,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/list",
|
||||||
|
component: List,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,7 +87,8 @@ const app = createApp({
|
|||||||
mixins: [
|
mixins: [
|
||||||
socket,
|
socket,
|
||||||
theme,
|
theme,
|
||||||
mobile
|
mobile,
|
||||||
|
datetime
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
57
src/mixins/datetime.js
Normal file
57
src/mixins/datetime.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTime Mixin
|
||||||
|
* Handled timezone and format
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userTimezone: localStorage.timezone || "auto",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
datetime(value) {
|
||||||
|
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss");
|
||||||
|
},
|
||||||
|
|
||||||
|
date(value) {
|
||||||
|
return this.datetimeFormat(value, "YYYY-MM-DD");
|
||||||
|
},
|
||||||
|
|
||||||
|
time(value, second = true) {
|
||||||
|
let secondString;
|
||||||
|
if (second) {
|
||||||
|
secondString = ":ss";
|
||||||
|
} else {
|
||||||
|
secondString = "";
|
||||||
|
}
|
||||||
|
return this.datetimeFormat(value, "HH:mm" + secondString);
|
||||||
|
},
|
||||||
|
|
||||||
|
datetimeFormat(value, format) {
|
||||||
|
if (value !== undefined && value !== "") {
|
||||||
|
return dayjs.utc(value).tz(this.timezone).format(format);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
timezone() {
|
||||||
|
if (this.userTimezone === "auto") {
|
||||||
|
return dayjs.tz.guess()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.userTimezone
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,4 +1,3 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -17,7 +16,6 @@ export default {
|
|||||||
connectCount: 0,
|
connectCount: 0,
|
||||||
},
|
},
|
||||||
remember: (localStorage.remember !== "0"),
|
remember: (localStorage.remember !== "0"),
|
||||||
userTimezone: localStorage.timezone || "auto",
|
|
||||||
allowLoginDialog: false, // Allowed to show login dialog, but "loggedIn" have to be true too. This exists because prevent the login dialog show 0.1s in first before the socket server auth-ed.
|
allowLoginDialog: false, // Allowed to show login dialog, but "loggedIn" have to be true too. This exists because prevent the login dialog show 0.1s in first before the socket server auth-ed.
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
monitorList: { },
|
monitorList: { },
|
||||||
@@ -27,7 +25,6 @@ export default {
|
|||||||
uptimeList: { },
|
uptimeList: { },
|
||||||
certInfoList: {},
|
certInfoList: {},
|
||||||
notificationList: [],
|
notificationList: [],
|
||||||
showListMobile: false,
|
|
||||||
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
|
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -188,10 +185,6 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
cancelActiveList() {
|
|
||||||
this.$root.showListMobile = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
storage() {
|
storage() {
|
||||||
return (this.remember) ? localStorage : sessionStorage;
|
return (this.remember) ? localStorage : sessionStorage;
|
||||||
},
|
},
|
||||||
@@ -265,15 +258,6 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
timezone() {
|
|
||||||
|
|
||||||
if (this.userTimezone === "auto") {
|
|
||||||
return dayjs.tz.guess()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.userTimezone
|
|
||||||
},
|
|
||||||
|
|
||||||
lastHeartbeatList() {
|
lastHeartbeatList() {
|
||||||
let result = {}
|
let result = {}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
|
system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
|
||||||
userTheme: localStorage.theme,
|
userTheme: localStorage.theme,
|
||||||
|
userHeartbeatBar: localStorage.heartbeatBarTheme,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -13,6 +14,11 @@ export default {
|
|||||||
this.userTheme = "light";
|
this.userTheme = "light";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default Heartbeat Bar
|
||||||
|
if (!this.userHeartbeatBar) {
|
||||||
|
this.userHeartbeatBar = "normal";
|
||||||
|
}
|
||||||
|
|
||||||
document.body.classList.add(this.theme);
|
document.body.classList.add(this.theme);
|
||||||
this.updateThemeColorMeta();
|
this.updateThemeColorMeta();
|
||||||
},
|
},
|
||||||
@@ -35,6 +41,15 @@ export default {
|
|||||||
document.body.classList.remove(from);
|
document.body.classList.remove(from);
|
||||||
document.body.classList.add(this.theme);
|
document.body.classList.add(this.theme);
|
||||||
this.updateThemeColorMeta();
|
this.updateThemeColorMeta();
|
||||||
|
},
|
||||||
|
|
||||||
|
userHeartbeatBar(to, from) {
|
||||||
|
localStorage.heartbeatBarTheme = to;
|
||||||
|
},
|
||||||
|
|
||||||
|
heartbeatBarTheme(to, from) {
|
||||||
|
document.body.classList.remove(from);
|
||||||
|
document.body.classList.add(this.heartbeatBarTheme);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -1,31 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-5 col-xl-4">
|
<div v-if="! $root.isMobile" class="col-12 col-md-5 col-xl-4">
|
||||||
<div v-if="! $root.isMobile">
|
<div>
|
||||||
<router-link to="/add" class="btn btn-primary mb-3"><font-awesome-icon icon="plus" /> Add New Monitor</router-link>
|
<router-link to="/add" class="btn btn-primary mb-3"><font-awesome-icon icon="plus" /> Add New Monitor</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<MonitorList />
|
||||||
<div v-if="showList" class="shadow-box list mb-4">
|
|
||||||
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
|
||||||
No Monitors, please <router-link to="/add">add one</router-link>.
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" @click="$root.cancelActiveList">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6 col-md-8 small-padding">
|
|
||||||
<div class="info">
|
|
||||||
<Uptime :monitor="item" type="24" :pill="true" />
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 col-md-4">
|
|
||||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-7 col-xl-8">
|
<div class="col-12 col-md-7 col-xl-8">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
@@ -35,115 +17,21 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
import MonitorList from "../components/MonitorList.vue";
|
||||||
import Uptime from "../components/Uptime.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Uptime,
|
MonitorList,
|
||||||
HeartbeatBar,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
sortedMonitorList() {
|
|
||||||
let result = Object.values(this.$root.monitorList);
|
|
||||||
|
|
||||||
result.sort((m1, m2) => {
|
|
||||||
|
|
||||||
if (m1.active !== m2.active) {
|
|
||||||
if (m1.active === 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m2.active === 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m1.weight !== m2.weight) {
|
|
||||||
if (m1.weight > m2.weight) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m1.weight < m2.weight) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m1.name.localeCompare(m2.name);
|
|
||||||
})
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
showList() {
|
|
||||||
return ! this.$root.isMobile || this.$root.showListMobile;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
monitorURL(id) {
|
|
||||||
return "/dashboard/" + id;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../assets/vars.scss";
|
|
||||||
|
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
width: 98%
|
width: 98%
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
|
||||||
height: auto;
|
|
||||||
min-height: calc(100vh - 240px);
|
|
||||||
|
|
||||||
.item {
|
|
||||||
display: block;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 15px 15px 12px 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: all ease-in-out 0.15s;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $highlight-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #cdf8f4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-padding {
|
|
||||||
padding-left: 5px !important;
|
|
||||||
padding-right: 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
.list {
|
|
||||||
.item {
|
|
||||||
&:hover {
|
|
||||||
background-color: $dark-bg2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: $dark-bg2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
<div v-if="$route.name === 'DashboardHome'">
|
<div v-if="$route.name === 'DashboardHome'">
|
||||||
<h1 class="mb-3">
|
<h1 class="mb-3">
|
||||||
Quick Stats
|
Quick Stats
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shadow-box" style="margin-top: 25px;">
|
<div class="shadow-box" style="margin-top: 25px;overflow-x: scroll">
|
||||||
<table class="table table-borderless table-hover">
|
<table class="table table-borderless table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
<router-view ref="child" />
|
<router-view ref="child" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
|
<div>
|
||||||
<h1> {{ monitor.name }}</h1>
|
<h1> {{ monitor.name }}</h1>
|
||||||
<p class="url">
|
<p class="url">
|
||||||
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a>
|
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a>
|
||||||
@@ -74,14 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showPingChartBox" class="shadow-box big-padding text-center">
|
<transition name="slide-fade" appear>
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<PingChart :monitor-id="monitor.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="showCertInfoBox" class="shadow-box big-padding text-center">
|
<div v-if="showCertInfoBox" class="shadow-box big-padding text-center">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@@ -123,6 +118,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<div v-if="showPingChartBox" class="shadow-box big-padding text-center ping-chart-wrapper">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<PingChart :monitor-id="monitor.id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="shadow-box">
|
<div class="shadow-box">
|
||||||
<table class="table table-borderless table-hover">
|
<table class="table table-borderless table-hover">
|
||||||
@@ -164,6 +168,8 @@
|
|||||||
<Confirm ref="confirmDelete" btn-style="btn-danger" @yes="deleteMonitor">
|
<Confirm ref="confirmDelete" btn-style="btn-danger" @yes="deleteMonitor">
|
||||||
Are you sure want to delete this monitor?
|
Are you sure want to delete this monitor?
|
||||||
</Confirm>
|
</Confirm>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -322,6 +328,12 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../assets/vars.scss";
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.badge {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 550px) {
|
@media (max-width: 550px) {
|
||||||
.functions {
|
.functions {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -331,6 +343,10 @@ export default {
|
|||||||
margin-left: 10px !important;
|
margin-left: 10px !important;
|
||||||
margin-right: 10px !important;
|
margin-right: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ping-chart-wrapper {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
@media (max-width: 400px) {
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
|
<div>
|
||||||
<h1 class="mb-3">{{ pageName }}</h1>
|
<h1 class="mb-3">{{ pageName }}</h1>
|
||||||
<form @submit.prevent="submit">
|
<form @submit.prevent="submit">
|
||||||
<div class="shadow-box">
|
<div class="shadow-box">
|
||||||
@@ -145,6 +147,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<NotificationDialog ref="notificationDialog" />
|
<NotificationDialog ref="notificationDialog" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
14
src/pages/List.vue
Normal file
14
src/pages/List.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<MonitorList />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MonitorList from "../components/MonitorList.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MonitorList,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 class="mb-3">
|
<transition name="slide-fade" appear>
|
||||||
|
<div>
|
||||||
|
<h1 v-show="show" class="mb-3">
|
||||||
Settings
|
Settings
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -26,6 +28,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Theme - Heartbeat Bar</label>
|
||||||
|
<div>
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
|
||||||
|
<input id="btncheck4" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="normal">
|
||||||
|
<label class="btn btn-outline-primary" for="btncheck4">Normal</label>
|
||||||
|
|
||||||
|
<input id="btncheck5" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="bottom">
|
||||||
|
<label class="btn btn-outline-primary" for="btncheck5">Bottom</label>
|
||||||
|
|
||||||
|
<input id="btncheck6" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="none">
|
||||||
|
<label class="btn btn-outline-primary" for="btncheck6">None</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="timezone" class="form-label">Timezone</label>
|
<label for="timezone" class="form-label">Timezone</label>
|
||||||
<select id="timezone" v-model="$root.userTimezone" class="form-select">
|
<select id="timezone" v-model="$root.userTimezone" class="form-select">
|
||||||
@@ -134,6 +152,8 @@
|
|||||||
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
|
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
|
||||||
<p>Please use it carefully.</p>
|
<p>Please use it carefully.</p>
|
||||||
</Confirm>
|
</Confirm>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -158,7 +178,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
timezoneList: timezoneList(),
|
timezoneList: timezoneList(),
|
||||||
guessTimezone: dayjs.tz.guess(),
|
guessTimezone: dayjs.tz.guess(),
|
||||||
|
show: true,
|
||||||
invalidPassword: false,
|
invalidPassword: false,
|
||||||
password: {
|
password: {
|
||||||
currentPassword: "",
|
currentPassword: "",
|
||||||
|
28
src/util.js
28
src/util.js
@@ -1,6 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.PENDING = exports.UP = exports.DOWN = exports.appName = void 0;
|
exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
|
||||||
|
const _dayjs = require("dayjs");
|
||||||
|
const dayjs = _dayjs;
|
||||||
|
exports.isDev = process.env.NODE_ENV === "development";
|
||||||
exports.appName = "Uptime Kuma";
|
exports.appName = "Uptime Kuma";
|
||||||
exports.DOWN = 0;
|
exports.DOWN = 0;
|
||||||
exports.UP = 1;
|
exports.UP = 1;
|
||||||
@@ -28,7 +31,7 @@ function ucfirst(str) {
|
|||||||
}
|
}
|
||||||
exports.ucfirst = ucfirst;
|
exports.ucfirst = ucfirst;
|
||||||
function debug(msg) {
|
function debug(msg) {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (exports.isDev) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,3 +47,24 @@ function polyfill() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.polyfill = polyfill;
|
exports.polyfill = polyfill;
|
||||||
|
class TimeLogger {
|
||||||
|
constructor() {
|
||||||
|
this.startTime = dayjs().valueOf();
|
||||||
|
}
|
||||||
|
print(name) {
|
||||||
|
if (exports.isDev) {
|
||||||
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.TimeLogger = TimeLogger;
|
||||||
|
function getRandomArbitrary(min, max) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
exports.getRandomArbitrary = getRandomArbitrary;
|
||||||
|
function getRandomInt(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
exports.getRandomInt = getRandomInt;
|
||||||
|
40
src/util.ts
40
src/util.ts
@@ -4,6 +4,10 @@
|
|||||||
// Frontend uses util.ts
|
// Frontend uses util.ts
|
||||||
// Need to run "tsc" to compile if there are any changes.
|
// Need to run "tsc" to compile if there are any changes.
|
||||||
|
|
||||||
|
import * as _dayjs from "dayjs";
|
||||||
|
const dayjs = _dayjs;
|
||||||
|
|
||||||
|
export const isDev = process.env.NODE_ENV === "development";
|
||||||
export const appName = "Uptime Kuma";
|
export const appName = "Uptime Kuma";
|
||||||
export const DOWN = 0;
|
export const DOWN = 0;
|
||||||
export const UP = 1;
|
export const UP = 1;
|
||||||
@@ -39,7 +43,7 @@ export function ucfirst(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function debug(msg) {
|
export function debug(msg) {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (isDev) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,3 +69,37 @@ export function polyfill() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TimeLogger {
|
||||||
|
constructor() {
|
||||||
|
this.startTime = dayjs().valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
print(name) {
|
||||||
|
if (isDev) {
|
||||||
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a random number between min (inclusive) and max (exclusive)
|
||||||
|
*/
|
||||||
|
export function getRandomArbitrary(min, max) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
|
||||||
|
*
|
||||||
|
* Returns a random integer between min (inclusive) and max (inclusive).
|
||||||
|
* The value is no lower than min (or the next integer greater than min
|
||||||
|
* if min isn't an integer) and no greater than max (or the next integer
|
||||||
|
* lower than max if max isn't an integer).
|
||||||
|
* Using Math.round() will give you a non-uniform distribution!
|
||||||
|
*/
|
||||||
|
export function getRandomInt(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
4
test/test_install_script/alpine3.dockerfile
Normal file
4
test/test_install_script/alpine3.dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM alpine:3
|
||||||
|
RUN apk add --update nodejs npm git
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN /bin/sh install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
4
test/test_install_script/centos7.dockerfile
Normal file
4
test/test_install_script/centos7.dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM centos:7
|
||||||
|
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
4
test/test_install_script/centos8.dockerfile
Normal file
4
test/test_install_script/centos8.dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM centos:8
|
||||||
|
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
10
test/test_install_script/debian.dockerfile
Normal file
10
test/test_install_script/debian.dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM debian
|
||||||
|
|
||||||
|
# Test invalid node version, these commands install nodejs 10
|
||||||
|
# RUN apt-get update
|
||||||
|
# RUN apt --yes install nodejs
|
||||||
|
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
|
# RUN node -v
|
||||||
|
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
10
test/test_install_script/ubuntu.dockerfile
Normal file
10
test/test_install_script/ubuntu.dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM ubuntu
|
||||||
|
|
||||||
|
# Test invalid node version, these commands install nodejs 10
|
||||||
|
# RUN apt-get update
|
||||||
|
# RUN apt --yes install nodejs
|
||||||
|
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
|
# RUN node -v
|
||||||
|
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
10
test/test_install_script/ubuntu1604.dockerfile
Normal file
10
test/test_install_script/ubuntu1604.dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM ubuntu:16.04
|
||||||
|
|
||||||
|
# Test invalid node version, these commands install nodejs 10
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt --yes install nodejs
|
||||||
|
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
|
# RUN node -v
|
||||||
|
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
Reference in New Issue
Block a user