mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-10-01 01:25:45 -04:00
Merge remote-tracking branch 'origin/master' into status-page-expiry
# Conflicts: # src/lang/en.json
This commit is contained in:
commit
7f68e4a987
4
.github/workflows/auto-test.yml
vendored
4
.github/workflows/auto-test.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
||||||
node: [ 14, 18 ]
|
node: [ 14, 20 ]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -50,7 +50,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ARMv7 ]
|
os: [ ARMv7 ]
|
||||||
node: [ 14.21.3, 18.16.1 ]
|
node: [ 14.21.3, 20.5.0 ]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"color-function-notation": "legacy",
|
"color-function-notation": "legacy",
|
||||||
"shorthand-property-no-redundant-values": null,
|
"shorthand-property-no-redundant-values": null,
|
||||||
"color-hex-length": null,
|
"color-hex-length": null,
|
||||||
"declaration-block-no-redundant-longhand-properties": null
|
"declaration-block-no-redundant-longhand-properties": null,
|
||||||
|
"at-rule-no-unknown": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,29 @@ ARG TARGETPLATFORM
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install Curl
|
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
||||||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
|
# python3* = apprise's dependencies
|
||||||
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
# sqlite3 = for debugging
|
||||||
|
# iputils-ping = for ping
|
||||||
|
# util-linux = for setpriv (Should be dropped in 2.0.0?)
|
||||||
|
# dumb-init = avoid zombie processes (#480)
|
||||||
|
# curl = for debugging
|
||||||
|
# ca-certificates = keep the cert up-to-date
|
||||||
|
# sudo = for start service nscd with non-root user
|
||||||
|
# nscd = for better DNS caching
|
||||||
|
# (pip) apprise = for notifications
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
apt-get --yes --no-install-recommends install \
|
||||||
sqlite3 iputils-ping util-linux dumb-init git curl ca-certificates && \
|
python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||||
pip3 --no-cache-dir install apprise==1.4.0 && \
|
sqlite3 \
|
||||||
|
iputils-ping \
|
||||||
|
util-linux \
|
||||||
|
dumb-init \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
sudo \
|
||||||
|
nscd && \
|
||||||
|
pip3 --no-cache-dir install apprise==1.4.5 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt --yes autoremove
|
apt --yes autoremove
|
||||||
|
|
||||||
@ -26,3 +42,7 @@ RUN set -eux && \
|
|||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt --yes autoremove
|
apt --yes autoremove
|
||||||
|
|
||||||
|
# For nscd
|
||||||
|
COPY ./docker/etc/nscd.conf /etc/nscd.conf
|
||||||
|
COPY ./docker/etc/sudoers /etc/sudoers
|
||||||
|
|
||||||
|
90
docker/etc/nscd.conf
Normal file
90
docker/etc/nscd.conf
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#
|
||||||
|
# /etc/nscd.conf
|
||||||
|
#
|
||||||
|
# An example Name Service Cache config file. This file is needed by nscd.
|
||||||
|
#
|
||||||
|
# Legal entries are:
|
||||||
|
#
|
||||||
|
# logfile <file>
|
||||||
|
# debug-level <level>
|
||||||
|
# threads <initial #threads to use>
|
||||||
|
# max-threads <maximum #threads to use>
|
||||||
|
# server-user <user to run server as instead of root>
|
||||||
|
# server-user is ignored if nscd is started with -S parameters
|
||||||
|
# stat-user <user who is allowed to request statistics>
|
||||||
|
# reload-count unlimited|<number>
|
||||||
|
# paranoia <yes|no>
|
||||||
|
# restart-interval <time in seconds>
|
||||||
|
#
|
||||||
|
# enable-cache <service> <yes|no>
|
||||||
|
# positive-time-to-live <service> <time in seconds>
|
||||||
|
# negative-time-to-live <service> <time in seconds>
|
||||||
|
# suggested-size <service> <prime number>
|
||||||
|
# check-files <service> <yes|no>
|
||||||
|
# persistent <service> <yes|no>
|
||||||
|
# shared <service> <yes|no>
|
||||||
|
# max-db-size <service> <number bytes>
|
||||||
|
# auto-propagate <service> <yes|no>
|
||||||
|
#
|
||||||
|
# Currently supported cache names (services): passwd, group, hosts, services
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# logfile /var/log/nscd.log
|
||||||
|
# threads 4
|
||||||
|
# max-threads 32
|
||||||
|
# server-user node
|
||||||
|
# stat-user somebody
|
||||||
|
debug-level 0
|
||||||
|
# reload-count 5
|
||||||
|
paranoia no
|
||||||
|
# restart-interval 3600
|
||||||
|
|
||||||
|
enable-cache passwd no
|
||||||
|
positive-time-to-live passwd 600
|
||||||
|
negative-time-to-live passwd 20
|
||||||
|
suggested-size passwd 211
|
||||||
|
check-files passwd yes
|
||||||
|
persistent passwd yes
|
||||||
|
shared passwd yes
|
||||||
|
max-db-size passwd 33554432
|
||||||
|
auto-propagate passwd yes
|
||||||
|
|
||||||
|
enable-cache group no
|
||||||
|
positive-time-to-live group 3600
|
||||||
|
negative-time-to-live group 60
|
||||||
|
suggested-size group 211
|
||||||
|
check-files group yes
|
||||||
|
persistent group yes
|
||||||
|
shared group yes
|
||||||
|
max-db-size group 33554432
|
||||||
|
auto-propagate group yes
|
||||||
|
|
||||||
|
enable-cache hosts yes
|
||||||
|
positive-time-to-live hosts 3600
|
||||||
|
negative-time-to-live hosts 20
|
||||||
|
suggested-size hosts 211
|
||||||
|
check-files hosts yes
|
||||||
|
persistent hosts yes
|
||||||
|
# Set shared to "no" to display stats in `nscd -g`
|
||||||
|
# Read more: https://stackoverflow.com/questions/40429245/nscdcentos7curl-0-dns-cache-hit-rate
|
||||||
|
shared hosts no
|
||||||
|
max-db-size hosts 33554432
|
||||||
|
|
||||||
|
enable-cache services no
|
||||||
|
positive-time-to-live services 28800
|
||||||
|
negative-time-to-live services 20
|
||||||
|
suggested-size services 211
|
||||||
|
check-files services yes
|
||||||
|
persistent services yes
|
||||||
|
shared services yes
|
||||||
|
max-db-size services 33554432
|
||||||
|
|
||||||
|
enable-cache netgroup no
|
||||||
|
positive-time-to-live netgroup 28800
|
||||||
|
negative-time-to-live netgroup 20
|
||||||
|
suggested-size netgroup 211
|
||||||
|
check-files netgroup yes
|
||||||
|
persistent netgroup yes
|
||||||
|
shared netgroup yes
|
||||||
|
max-db-size netgroup 33554432
|
31
docker/etc/sudoers
Normal file
31
docker/etc/sudoers
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# This file MUST be edited with the 'visudo' command as root.
|
||||||
|
#
|
||||||
|
# Please consider adding local content in /etc/sudoers.d/ instead of
|
||||||
|
# directly modifying this file.
|
||||||
|
#
|
||||||
|
# See the man page for details on how to write a sudoers file.
|
||||||
|
#
|
||||||
|
Defaults env_reset
|
||||||
|
Defaults mail_badpass
|
||||||
|
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
|
# Host alias specification
|
||||||
|
|
||||||
|
# User alias specification
|
||||||
|
|
||||||
|
# Cmnd alias specification
|
||||||
|
|
||||||
|
# User privilege specification
|
||||||
|
root ALL=(ALL:ALL) ALL
|
||||||
|
|
||||||
|
# Allow members of group sudo to execute any command
|
||||||
|
%sudo ALL=(ALL:ALL) ALL
|
||||||
|
|
||||||
|
# See sudoers(5) for more information on "#include" directives:
|
||||||
|
|
||||||
|
#includedir /etc/sudoers.d
|
||||||
|
|
||||||
|
# Allow `node` to control service (mainly for nscd)
|
||||||
|
node ALL=(root) NOPASSWD: /usr/sbin/nscdservice
|
||||||
|
node ALL=(root) NOPASSWD: /usr/sbin/service
|
@ -5,15 +5,15 @@
|
|||||||
|
|
||||||
// curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
// curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||||
println("=====================");
|
println("=====================");
|
||||||
println("Uptime Kuma Installer");
|
println("Uptime Kuma Install Script");
|
||||||
println("=====================");
|
println("=====================");
|
||||||
println("Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian");
|
println("Supported OS: Ubuntu >= 16.04, Debian and CentOS/RHEL 7/8");
|
||||||
println("---------------------------------------");
|
println("---------------------------------------");
|
||||||
println("This script is designed for Linux and basic usage.");
|
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("For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation");
|
||||||
println("---------------------------------------");
|
println("---------------------------------------");
|
||||||
println("");
|
println("");
|
||||||
println("Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2");
|
println("Local - Install Uptime Kuma on your current machine with git, Node.js and pm2");
|
||||||
println("Docker - Install Uptime Kuma Docker container");
|
println("Docker - Install Uptime Kuma Docker container");
|
||||||
println("");
|
println("");
|
||||||
|
|
||||||
@ -29,14 +29,10 @@ function checkNode() {
|
|||||||
bash("nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')");
|
bash("nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')");
|
||||||
println("Node Version: " ++ nodeVersion);
|
println("Node Version: " ++ nodeVersion);
|
||||||
|
|
||||||
if (nodeVersion < "12") {
|
if (nodeVersion <= "12") {
|
||||||
println("Error: Required Node.js 14");
|
println("Error: Required Node.js 14");
|
||||||
call("exit", "1");
|
call("exit", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeVersion == "12") {
|
|
||||||
println("Warning: NodeJS " ++ nodeVersion ++ " is not tested.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deb() {
|
function deb() {
|
||||||
@ -60,8 +56,8 @@ function deb() {
|
|||||||
bash("apt --yes install curl");
|
bash("apt --yes install curl");
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Installing Node.js 14");
|
println("Installing Node.js 16");
|
||||||
bash("curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt");
|
bash("curl -sL https://deb.nodesource.com/setup_16.x | bash - > log.txt");
|
||||||
bash("apt --yes install nodejs");
|
bash("apt --yes install nodejs");
|
||||||
bash("node -v");
|
bash("node -v");
|
||||||
|
|
||||||
@ -91,6 +87,10 @@ if (type == "local") {
|
|||||||
bash("os=$(head -n1 /etc/issue | cut -f 1 -d ' ')");
|
bash("os=$(head -n1 /etc/issue | cut -f 1 -d ' ')");
|
||||||
if (os == "Ubuntu") {
|
if (os == "Ubuntu") {
|
||||||
distribution = "ubuntu";
|
distribution = "ubuntu";
|
||||||
|
|
||||||
|
// Get ubuntu version
|
||||||
|
bash(". /etc/lsb-release");
|
||||||
|
version = DISTRIB_RELEASE;
|
||||||
}
|
}
|
||||||
if (os == "Debian") {
|
if (os == "Debian") {
|
||||||
distribution = "debian";
|
distribution = "debian";
|
||||||
@ -101,6 +101,7 @@ if (type == "local") {
|
|||||||
|
|
||||||
println("Your OS: " ++ os);
|
println("Your OS: " ++ os);
|
||||||
println("Distribution: " ++ distribution);
|
println("Distribution: " ++ distribution);
|
||||||
|
println("Version: " ++ version);
|
||||||
println("Arch: " ++ arch);
|
println("Arch: " ++ arch);
|
||||||
|
|
||||||
if ("$3" != "") {
|
if ("$3" != "") {
|
||||||
@ -131,15 +132,32 @@ if (type == "local") {
|
|||||||
checkNode();
|
checkNode();
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
bash("dnfCheck=$(dnf --version)");
|
||||||
|
|
||||||
|
// Use yum
|
||||||
|
if (dnfCheck == "") {
|
||||||
bash("curlCheck=$(curl --version)");
|
bash("curlCheck=$(curl --version)");
|
||||||
if (curlCheck == "") {
|
if (curlCheck == "") {
|
||||||
println("Installing Curl");
|
println("Installing Curl");
|
||||||
bash("yum -y -q install curl");
|
bash("yum -y -q install curl");
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Installing Node.js 14");
|
println("Installing Node.js 16");
|
||||||
bash("curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt");
|
bash("curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt");
|
||||||
bash("yum install -y -q nodejs");
|
bash("yum install -y -q nodejs");
|
||||||
|
} else {
|
||||||
|
bash("curlCheck=$(curl --version)");
|
||||||
|
if (curlCheck == "") {
|
||||||
|
println("Installing Curl");
|
||||||
|
bash("dnf -y install curl");
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Installing Node.js 16");
|
||||||
|
bash("curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt");
|
||||||
|
bash("dnf install -y nodejs");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bash("node -v");
|
bash("node -v");
|
||||||
|
|
||||||
bash("nodeCheckAgain=$(node -v)");
|
bash("nodeCheckAgain=$(node -v)");
|
||||||
@ -193,6 +211,14 @@ if (type == "local") {
|
|||||||
bash("pm2 startup");
|
bash("pm2 startup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check again
|
||||||
|
bash("check=$(pm2 --version)");
|
||||||
|
if (check == "") {
|
||||||
|
println("Error: pm2 is not found!");
|
||||||
|
bash("exit 1");
|
||||||
|
}
|
||||||
|
|
||||||
bash("mkdir -p $installPath");
|
bash("mkdir -p $installPath");
|
||||||
bash("cd $installPath");
|
bash("cd $installPath");
|
||||||
bash("git clone https://github.com/louislam/uptime-kuma.git .");
|
bash("git clone https://github.com/louislam/uptime-kuma.git .");
|
||||||
|
42
install.sh
42
install.sh
@ -3,15 +3,15 @@
|
|||||||
# The command is working on Windows PowerShell and Docker for Windows only.
|
# 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
|
# curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||||
"echo" "-e" "====================="
|
"echo" "-e" "====================="
|
||||||
"echo" "-e" "Uptime Kuma Installer"
|
"echo" "-e" "Uptime Kuma Install Script"
|
||||||
"echo" "-e" "====================="
|
"echo" "-e" "====================="
|
||||||
"echo" "-e" "Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian"
|
"echo" "-e" "Supported OS: Ubuntu >= 16.04, Debian and CentOS/RHEL 7/8"
|
||||||
"echo" "-e" "---------------------------------------"
|
"echo" "-e" "---------------------------------------"
|
||||||
"echo" "-e" "This script is designed for Linux and basic usage."
|
"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" "For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation"
|
||||||
"echo" "-e" "---------------------------------------"
|
"echo" "-e" "---------------------------------------"
|
||||||
"echo" "-e" ""
|
"echo" "-e" ""
|
||||||
"echo" "-e" "Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2"
|
"echo" "-e" "Local - Install Uptime Kuma on your current machine with git, Node.js and pm2"
|
||||||
"echo" "-e" "Docker - Install Uptime Kuma Docker container"
|
"echo" "-e" "Docker - Install Uptime Kuma Docker container"
|
||||||
"echo" "-e" ""
|
"echo" "-e" ""
|
||||||
if [ "$1" != "" ]; then
|
if [ "$1" != "" ]; then
|
||||||
@ -25,12 +25,9 @@ function checkNode {
|
|||||||
nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')
|
nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')
|
||||||
"echo" "-e" "Node Version: ""$nodeVersion"
|
"echo" "-e" "Node Version: ""$nodeVersion"
|
||||||
_0="12"
|
_0="12"
|
||||||
if [ $(($nodeVersion < $_0)) == 1 ]; then
|
if [ $(($nodeVersion <= $_0)) == 1 ]; then
|
||||||
"echo" "-e" "Error: Required Node.js 14"
|
"echo" "-e" "Error: Required Node.js 14"
|
||||||
"exit" "1"
|
"exit" "1"
|
||||||
fi
|
|
||||||
if [ "$nodeVersion" == "12" ]; then
|
|
||||||
"echo" "-e" "Warning: NodeJS ""$nodeVersion"" is not tested."
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
function deb {
|
function deb {
|
||||||
@ -50,8 +47,8 @@ fi
|
|||||||
"echo" "-e" "Installing Curl"
|
"echo" "-e" "Installing Curl"
|
||||||
apt --yes install curl
|
apt --yes install curl
|
||||||
fi
|
fi
|
||||||
"echo" "-e" "Installing Node.js 14"
|
"echo" "-e" "Installing Node.js 16"
|
||||||
curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt
|
curl -sL https://deb.nodesource.com/setup_16.x | bash - > log.txt
|
||||||
apt --yes install nodejs
|
apt --yes install nodejs
|
||||||
node -v
|
node -v
|
||||||
nodeCheckAgain=$(node -v)
|
nodeCheckAgain=$(node -v)
|
||||||
@ -76,6 +73,9 @@ if [ "$type" == "local" ]; then
|
|||||||
os=$(head -n1 /etc/issue | cut -f 1 -d ' ')
|
os=$(head -n1 /etc/issue | cut -f 1 -d ' ')
|
||||||
if [ "$os" == "Ubuntu" ]; then
|
if [ "$os" == "Ubuntu" ]; then
|
||||||
distribution="ubuntu"
|
distribution="ubuntu"
|
||||||
|
# Get ubuntu version
|
||||||
|
. /etc/lsb-release
|
||||||
|
version="$DISTRIB_RELEASE"
|
||||||
fi
|
fi
|
||||||
if [ "$os" == "Debian" ]; then
|
if [ "$os" == "Debian" ]; then
|
||||||
distribution="debian"
|
distribution="debian"
|
||||||
@ -85,6 +85,7 @@ fi
|
|||||||
arch=$(uname -i)
|
arch=$(uname -i)
|
||||||
"echo" "-e" "Your OS: ""$os"
|
"echo" "-e" "Your OS: ""$os"
|
||||||
"echo" "-e" "Distribution: ""$distribution"
|
"echo" "-e" "Distribution: ""$distribution"
|
||||||
|
"echo" "-e" "Version: ""$version"
|
||||||
"echo" "-e" "Arch: ""$arch"
|
"echo" "-e" "Arch: ""$arch"
|
||||||
if [ "$3" != "" ]; then
|
if [ "$3" != "" ]; then
|
||||||
port="$3"
|
port="$3"
|
||||||
@ -108,14 +109,27 @@ fi
|
|||||||
if [ "$nodeCheck" != "" ]; then
|
if [ "$nodeCheck" != "" ]; then
|
||||||
"checkNode"
|
"checkNode"
|
||||||
else
|
else
|
||||||
|
dnfCheck=$(dnf --version)
|
||||||
|
# Use yum
|
||||||
|
if [ "$dnfCheck" == "" ]; then
|
||||||
curlCheck=$(curl --version)
|
curlCheck=$(curl --version)
|
||||||
if [ "$curlCheck" == "" ]; then
|
if [ "$curlCheck" == "" ]; then
|
||||||
"echo" "-e" "Installing Curl"
|
"echo" "-e" "Installing Curl"
|
||||||
yum -y -q install curl
|
yum -y -q install curl
|
||||||
fi
|
fi
|
||||||
"echo" "-e" "Installing Node.js 14"
|
"echo" "-e" "Installing Node.js 16"
|
||||||
curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt
|
curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt
|
||||||
yum install -y -q nodejs
|
yum install -y -q nodejs
|
||||||
|
else
|
||||||
|
curlCheck=$(curl --version)
|
||||||
|
if [ "$curlCheck" == "" ]; then
|
||||||
|
"echo" "-e" "Installing Curl"
|
||||||
|
dnf -y install curl
|
||||||
|
fi
|
||||||
|
"echo" "-e" "Installing Node.js 16"
|
||||||
|
curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt
|
||||||
|
dnf install -y nodejs
|
||||||
|
fi
|
||||||
node -v
|
node -v
|
||||||
nodeCheckAgain=$(node -v)
|
nodeCheckAgain=$(node -v)
|
||||||
if [ "$nodeCheckAgain" == "" ]; then
|
if [ "$nodeCheckAgain" == "" ]; then
|
||||||
@ -161,6 +175,12 @@ fi
|
|||||||
"echo" "-e" "Installing PM2"
|
"echo" "-e" "Installing PM2"
|
||||||
npm install pm2 -g && pm2 install pm2-logrotate
|
npm install pm2 -g && pm2 install pm2-logrotate
|
||||||
pm2 startup
|
pm2 startup
|
||||||
|
fi
|
||||||
|
# Check again
|
||||||
|
check=$(pm2 --version)
|
||||||
|
if [ "$check" == "" ]; then
|
||||||
|
"echo" "-e" "Error: pm2 is not found!"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
mkdir -p $installPath
|
mkdir -p $installPath
|
||||||
cd $installPath
|
cd $installPath
|
||||||
|
3546
package-lock.json
generated
3546
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -46,11 +46,14 @@
|
|||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
"remove-2fa": "node extra/remove-2fa.js",
|
"remove-2fa": "node extra/remove-2fa.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-rockylinux": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/rockylinux.dockerfile .",
|
||||||
"test-install-script-centos7": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/centos7.dockerfile .",
|
"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-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
|
||||||
|
"test-install-script-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.dockerfile .",
|
||||||
|
"test-install-script-debian-buster": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian-buster.dockerfile .",
|
||||||
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
|
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
|
||||||
|
"test-install-script-ubuntu1804": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1804.dockerfile .",
|
||||||
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
||||||
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
|
|
||||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||||
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
||||||
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
||||||
@ -97,6 +100,7 @@
|
|||||||
"http-proxy-agent": "~5.0.0",
|
"http-proxy-agent": "~5.0.0",
|
||||||
"https-proxy-agent": "~5.0.1",
|
"https-proxy-agent": "~5.0.1",
|
||||||
"iconv-lite": "~0.6.3",
|
"iconv-lite": "~0.6.3",
|
||||||
|
"isomorphic-ws": "^5.0.0",
|
||||||
"jsesc": "~3.0.2",
|
"jsesc": "~3.0.2",
|
||||||
"jsonata": "^2.0.3",
|
"jsonata": "^2.0.3",
|
||||||
"jsonwebtoken": "~9.0.0",
|
"jsonwebtoken": "~9.0.0",
|
||||||
@ -112,6 +116,7 @@
|
|||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
"node-radius-client": "~1.0.0",
|
"node-radius-client": "~1.0.0",
|
||||||
"nodemailer": "~6.6.5",
|
"nodemailer": "~6.6.5",
|
||||||
|
"nostr-tools": "^1.13.1",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
"password-hash": "~1.2.2",
|
"password-hash": "~1.2.2",
|
||||||
"pg": "~8.8.0",
|
"pg": "~8.8.0",
|
||||||
@ -129,7 +134,8 @@
|
|||||||
"socks-proxy-agent": "6.1.1",
|
"socks-proxy-agent": "6.1.1",
|
||||||
"tar": "~6.1.11",
|
"tar": "~6.1.11",
|
||||||
"tcp-ping": "~0.1.1",
|
"tcp-ping": "~0.1.1",
|
||||||
"thirty-two": "~1.0.2"
|
"thirty-two": "~1.0.2",
|
||||||
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "~5.0.1",
|
"@actions/github": "~5.0.1",
|
||||||
|
95
server/monitor-types/tailscale-ping.js
Normal file
95
server/monitor-types/tailscale-ping.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
const { MonitorType } = require("./monitor-type");
|
||||||
|
const { UP, log } = require("../../src/util");
|
||||||
|
const exec = require("child_process").exec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TailscalePing class extends the MonitorType.
|
||||||
|
* It runs Tailscale ping to monitor the status of a specific node.
|
||||||
|
*/
|
||||||
|
class TailscalePing extends MonitorType {
|
||||||
|
|
||||||
|
name = "tailscale-ping";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the ping status of the URL associated with the monitor.
|
||||||
|
* It then parses the Tailscale ping command output to update the heatrbeat.
|
||||||
|
*
|
||||||
|
* @param {Object} monitor - The monitor object associated with the check.
|
||||||
|
* @param {Object} heartbeat - The heartbeat object to update.
|
||||||
|
* @throws Will throw an error if checking Tailscale ping encounters any error
|
||||||
|
*/
|
||||||
|
async check(monitor, heartbeat) {
|
||||||
|
try {
|
||||||
|
let tailscaleOutput = await this.runTailscalePing(monitor.hostname, monitor.interval);
|
||||||
|
this.parseTailscaleOutput(tailscaleOutput, heartbeat);
|
||||||
|
} catch (err) {
|
||||||
|
log.debug("Tailscale", err);
|
||||||
|
// trigger log function somewhere to display a notification or alert to the user (but how?)
|
||||||
|
throw new Error(`Error checking Tailscale ping: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the Tailscale ping command to the given URL.
|
||||||
|
*
|
||||||
|
* @param {string} hostname - The hostname to ping.
|
||||||
|
* @returns {Promise<string>} - A Promise that resolves to the output of the Tailscale ping command
|
||||||
|
* @throws Will throw an error if the command execution encounters any error.
|
||||||
|
*/
|
||||||
|
async runTailscalePing(hostname, interval) {
|
||||||
|
let cmd = `tailscale ping ${hostname}`;
|
||||||
|
|
||||||
|
log.debug("Tailscale", cmd);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let timeout = interval * 1000 * 0.8;
|
||||||
|
exec(cmd, { timeout: timeout }, (error, stdout, stderr) => {
|
||||||
|
// we may need to handle more cases if tailscale reports an error that isn't necessarily an error (such as not-logged in or DERP health-related issues)
|
||||||
|
if (error) {
|
||||||
|
reject(`Execution error: ${error.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
reject(`Error in output: ${stderr}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(stdout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the output of the Tailscale ping command to update the heartbeat.
|
||||||
|
*
|
||||||
|
* @param {string} tailscaleOutput - The output of the Tailscale ping command.
|
||||||
|
* @param {Object} heartbeat - The heartbeat object to update.
|
||||||
|
* @throws Will throw an eror if the output contains any unexpected string.
|
||||||
|
*/
|
||||||
|
parseTailscaleOutput(tailscaleOutput, heartbeat) {
|
||||||
|
let lines = tailscaleOutput.split("\n");
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
if (line.includes("pong from")) {
|
||||||
|
heartbeat.status = UP;
|
||||||
|
let time = line.split(" in ")[1].split(" ")[0];
|
||||||
|
heartbeat.ping = parseInt(time);
|
||||||
|
heartbeat.msg = line;
|
||||||
|
break;
|
||||||
|
} else if (line.includes("timed out")) {
|
||||||
|
throw new Error(`Ping timed out: "${line}"`);
|
||||||
|
// Immediately throws upon "timed out" message, the server is expected to re-call the check function
|
||||||
|
} else if (line.includes("no matching peer")) {
|
||||||
|
throw new Error(`Nonexistant or inaccessible due to ACLs: "${line}"`);
|
||||||
|
} else if (line.includes("is local Tailscale IP")) {
|
||||||
|
throw new Error(`Tailscale only works if used on other machines: "${line}"`);
|
||||||
|
} else if (line !== "") {
|
||||||
|
throw new Error(`Unexpected output: "${line}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
TailscalePing,
|
||||||
|
};
|
119
server/notification-providers/nostr.js
Normal file
119
server/notification-providers/nostr.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const { log } = require("../../src/util");
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const {
|
||||||
|
relayInit,
|
||||||
|
getPublicKey,
|
||||||
|
getEventHash,
|
||||||
|
getSignature,
|
||||||
|
nip04,
|
||||||
|
nip19
|
||||||
|
} = require("nostr-tools");
|
||||||
|
|
||||||
|
// polyfills for node versions
|
||||||
|
const semver = require("semver");
|
||||||
|
const nodeVersion = process.version;
|
||||||
|
if (semver.lt(nodeVersion, "16.0.0")) {
|
||||||
|
log.warn("monitor", "Node <= 16 is unsupported for nostr, sorry :(");
|
||||||
|
} else if (semver.lt(nodeVersion, "18.0.0")) {
|
||||||
|
// polyfills for node 16
|
||||||
|
global.crypto = require("crypto");
|
||||||
|
global.WebSocket = require("isomorphic-ws");
|
||||||
|
if (typeof crypto !== "undefined" && !crypto.subtle && crypto.webcrypto) {
|
||||||
|
crypto.subtle = crypto.webcrypto.subtle;
|
||||||
|
}
|
||||||
|
} else if (semver.lt(nodeVersion, "20.0.0")) {
|
||||||
|
// polyfills for node 18
|
||||||
|
global.crypto = require("crypto");
|
||||||
|
global.WebSocket = require("isomorphic-ws");
|
||||||
|
} else {
|
||||||
|
// polyfills for node 20
|
||||||
|
global.WebSocket = require("isomorphic-ws");
|
||||||
|
}
|
||||||
|
|
||||||
|
class Nostr extends NotificationProvider {
|
||||||
|
name = "nostr";
|
||||||
|
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
// All DMs should have same timestamp
|
||||||
|
const createdAt = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
const senderPrivateKey = await this.getPrivateKey(notification.sender);
|
||||||
|
const senderPublicKey = getPublicKey(senderPrivateKey);
|
||||||
|
const recipientsPublicKeys = await this.getPublicKeys(notification.recipients);
|
||||||
|
|
||||||
|
// Create NIP-04 encrypted direct message event for each recipient
|
||||||
|
const events = [];
|
||||||
|
for (const recipientPublicKey of recipientsPublicKeys) {
|
||||||
|
const ciphertext = await nip04.encrypt(senderPrivateKey, recipientPublicKey, msg);
|
||||||
|
let event = {
|
||||||
|
kind: 4,
|
||||||
|
pubkey: senderPublicKey,
|
||||||
|
created_at: createdAt,
|
||||||
|
tags: [[ "p", recipientPublicKey ]],
|
||||||
|
content: ciphertext,
|
||||||
|
};
|
||||||
|
event.id = getEventHash(event);
|
||||||
|
event.sig = getSignature(event, senderPrivateKey);
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish events to each relay
|
||||||
|
const relays = notification.relays.split("\n");
|
||||||
|
let successfulRelays = 0;
|
||||||
|
|
||||||
|
// Connect to each relay
|
||||||
|
for (const relayUrl of relays) {
|
||||||
|
const relay = relayInit(relayUrl);
|
||||||
|
try {
|
||||||
|
await relay.connect();
|
||||||
|
successfulRelays++;
|
||||||
|
|
||||||
|
// Publish events
|
||||||
|
for (const event of events) {
|
||||||
|
relay.publish(event);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
continue;
|
||||||
|
} finally {
|
||||||
|
relay.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report success or failure
|
||||||
|
if (successfulRelays === 0) {
|
||||||
|
throw Error("Failed to connect to any relays.");
|
||||||
|
}
|
||||||
|
return `${successfulRelays}/${relays.length} relays connected.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrivateKey(sender) {
|
||||||
|
try {
|
||||||
|
const senderDecodeResult = await nip19.decode(sender);
|
||||||
|
const { data } = senderDecodeResult;
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to get private key: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPublicKeys(recipients) {
|
||||||
|
const recipientsList = recipients.split("\n");
|
||||||
|
const publicKeys = [];
|
||||||
|
for (const recipient of recipientsList) {
|
||||||
|
try {
|
||||||
|
const recipientDecodeResult = await nip19.decode(recipient);
|
||||||
|
const { type, data } = recipientDecodeResult;
|
||||||
|
if (type === "npub") {
|
||||||
|
publicKeys.push(data);
|
||||||
|
} else {
|
||||||
|
throw new Error("not an npub");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error decoding recipient: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return publicKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Nostr;
|
@ -13,7 +13,7 @@ class SMTP extends NotificationProvider {
|
|||||||
port: notification.smtpPort,
|
port: notification.smtpPort,
|
||||||
secure: notification.smtpSecure,
|
secure: notification.smtpSecure,
|
||||||
tls: {
|
tls: {
|
||||||
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
|
rejectUnauthorized: !notification.smtpIgnoreTLSError || false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ const LineNotify = require("./notification-providers/linenotify");
|
|||||||
const LunaSea = require("./notification-providers/lunasea");
|
const LunaSea = require("./notification-providers/lunasea");
|
||||||
const Matrix = require("./notification-providers/matrix");
|
const Matrix = require("./notification-providers/matrix");
|
||||||
const Mattermost = require("./notification-providers/mattermost");
|
const Mattermost = require("./notification-providers/mattermost");
|
||||||
|
const Nostr = require("./notification-providers/nostr");
|
||||||
const Ntfy = require("./notification-providers/ntfy");
|
const Ntfy = require("./notification-providers/ntfy");
|
||||||
const Octopush = require("./notification-providers/octopush");
|
const Octopush = require("./notification-providers/octopush");
|
||||||
const OneBot = require("./notification-providers/onebot");
|
const OneBot = require("./notification-providers/onebot");
|
||||||
@ -84,6 +85,7 @@ class Notification {
|
|||||||
new LunaSea(),
|
new LunaSea(),
|
||||||
new Matrix(),
|
new Matrix(),
|
||||||
new Mattermost(),
|
new Mattermost(),
|
||||||
|
new Nostr(),
|
||||||
new Ntfy(),
|
new Ntfy(),
|
||||||
new Octopush(),
|
new Octopush(),
|
||||||
new OneBot(),
|
new OneBot(),
|
||||||
|
@ -49,6 +49,7 @@ if (! process.env.NODE_ENV) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info("server", "Node Env: " + process.env.NODE_ENV);
|
log.info("server", "Node Env: " + process.env.NODE_ENV);
|
||||||
|
log.info("server", "Inside Container: " + process.env.UPTIME_KUMA_IS_CONTAINER === "1");
|
||||||
|
|
||||||
log.info("server", "Importing Node libraries");
|
log.info("server", "Importing Node libraries");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@ -1589,6 +1590,8 @@ let needSetup = false;
|
|||||||
await shutdownFunction();
|
await shutdownFunction();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
server.httpServer.listen(port, hostname, () => {
|
server.httpServer.listen(port, hostname, () => {
|
||||||
if (hostname) {
|
if (hostname) {
|
||||||
log.info("server", `Listening on ${hostname}:${port}`);
|
log.info("server", `Listening on ${hostname}:${port}`);
|
||||||
|
@ -10,6 +10,7 @@ const util = require("util");
|
|||||||
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
|
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
const childProcess = require("child_process");
|
||||||
// DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`, put at the bottom of this file instead.
|
// DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`, put at the bottom of this file instead.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,6 +100,7 @@ class UptimeKumaServer {
|
|||||||
|
|
||||||
// Set Monitor Types
|
// Set Monitor Types
|
||||||
UptimeKumaServer.monitorTypeList["real-browser"] = new RealBrowserMonitorType();
|
UptimeKumaServer.monitorTypeList["real-browser"] = new RealBrowserMonitorType();
|
||||||
|
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
||||||
|
|
||||||
this.io = new Server(this.httpServer);
|
this.io = new Server(this.httpServer);
|
||||||
}
|
}
|
||||||
@ -333,9 +335,49 @@ class UptimeKumaServer {
|
|||||||
dayjs.tz.setDefault(timezone);
|
dayjs.tz.setDefault(timezone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop the server */
|
/**
|
||||||
async stop() {
|
* TODO: Listen logic should be moved to here
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async start() {
|
||||||
|
this.startServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the server
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async stop() {
|
||||||
|
this.stopServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start all system services (e.g. nscd)
|
||||||
|
* For now, only used in Docker
|
||||||
|
*/
|
||||||
|
startServices() {
|
||||||
|
if (process.env.UPTIME_KUMA_IS_CONTAINER) {
|
||||||
|
try {
|
||||||
|
log.info("services", "Starting nscd");
|
||||||
|
childProcess.execSync("sudo service nscd start", { stdio: "pipe" });
|
||||||
|
} catch (e) {
|
||||||
|
log.info("services", "Failed to start nscd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop all system services
|
||||||
|
*/
|
||||||
|
stopServices() {
|
||||||
|
if (process.env.UPTIME_KUMA_IS_CONTAINER) {
|
||||||
|
try {
|
||||||
|
log.info("services", "Stopping nscd");
|
||||||
|
childProcess.execSync("sudo service nscd stop");
|
||||||
|
} catch (e) {
|
||||||
|
log.info("services", "Failed to stop nscd");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,3 +387,4 @@ module.exports = {
|
|||||||
|
|
||||||
// Must be at the end to avoid circular dependencies
|
// Must be at the end to avoid circular dependencies
|
||||||
const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor-type");
|
const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor-type");
|
||||||
|
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
||||||
|
@ -111,6 +111,10 @@ optgroup {
|
|||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
@ -158,6 +162,26 @@ optgroup {
|
|||||||
background-color: #161B22;
|
background-color: #161B22;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-outline-normal {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 25px;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
color: $dark-font-color;
|
||||||
|
border: 1px solid $dark-font-color2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $highlight-white;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
background-color: $dark-font-color2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 550px) {
|
@media (max-width: 550px) {
|
||||||
.table-shadow-box {
|
.table-shadow-box {
|
||||||
padding: 10px !important;
|
padding: 10px !important;
|
||||||
@ -436,7 +460,6 @@ optgroup {
|
|||||||
.monitor-list {
|
.monitor-list {
|
||||||
&.scrollbar {
|
&.scrollbar {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: calc(100% - 107px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 770px) {
|
@media (max-width: 770px) {
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
<div class="shadow-box mb-3" :style="boxStyle">
|
<div class="shadow-box mb-3" :style="boxStyle">
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<div class="header-top">
|
<div class="header-top">
|
||||||
|
<button class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button" @click="selectMode = !selectMode">
|
||||||
|
{{ $t("Select") }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="placeholder"></div>
|
<div class="placeholder"></div>
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<a v-if="searchText == ''" class="search-icon">
|
<a v-if="searchText == ''" class="search-icon">
|
||||||
@ -21,27 +25,55 @@
|
|||||||
<div class="header-filter">
|
<div class="header-filter">
|
||||||
<MonitorListFilter :filterState="filterState" @update-filter="updateFilter" />
|
<MonitorListFilter :filterState="filterState" @update-filter="updateFilter" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Selection Controls -->
|
||||||
|
<div v-if="selectMode" class="selection-controls px-2 pt-2">
|
||||||
|
<input
|
||||||
|
v-model="selectAll"
|
||||||
|
class="form-check-input select-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button>
|
||||||
|
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button>
|
||||||
|
|
||||||
|
<span v-if="selectedMonitorCount > 0">
|
||||||
|
{{ $t("selectedMonitorCount", [ selectedMonitorCount ]) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="monitor-list" :class="{ scrollbar: scrollbar }">
|
</div>
|
||||||
|
<div ref="monitorList" class="monitor-list" :class="{ scrollbar: scrollbar }" :style="monitorListStyle">
|
||||||
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
||||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MonitorListItem
|
<MonitorListItem
|
||||||
v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item"
|
v-for="(item, index) in sortedMonitorList"
|
||||||
|
:key="index"
|
||||||
|
:monitor="item"
|
||||||
:isSearch="searchText !== ''"
|
:isSearch="searchText !== ''"
|
||||||
|
:isSelectMode="selectMode"
|
||||||
|
:isSelected="isSelected"
|
||||||
|
:select="select"
|
||||||
|
:deselect="deselect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseSelected">
|
||||||
|
{{ $t("pauseMonitorMsg") }}
|
||||||
|
</Confirm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Confirm from "../components/Confirm.vue";
|
||||||
import MonitorListItem from "../components/MonitorListItem.vue";
|
import MonitorListItem from "../components/MonitorListItem.vue";
|
||||||
import MonitorListFilter from "./MonitorListFilter.vue";
|
import MonitorListFilter from "./MonitorListFilter.vue";
|
||||||
import { getMonitorRelativeURL } from "../util.ts";
|
import { getMonitorRelativeURL } from "../util.ts";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Confirm,
|
||||||
MonitorListItem,
|
MonitorListItem,
|
||||||
MonitorListFilter,
|
MonitorListFilter,
|
||||||
},
|
},
|
||||||
@ -54,6 +86,10 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searchText: "",
|
searchText: "",
|
||||||
|
selectMode: false,
|
||||||
|
selectAll: false,
|
||||||
|
disableSelectAllWatcher: false,
|
||||||
|
selectedMonitors: {},
|
||||||
windowTop: 0,
|
windowTop: 0,
|
||||||
filterState: {
|
filterState: {
|
||||||
status: null,
|
status: null,
|
||||||
@ -146,6 +182,58 @@ export default {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isDarkTheme() {
|
||||||
|
return document.body.classList.contains("dark");
|
||||||
|
},
|
||||||
|
|
||||||
|
monitorListStyle() {
|
||||||
|
let listHeaderHeight = 107;
|
||||||
|
|
||||||
|
if (this.selectMode) {
|
||||||
|
listHeaderHeight += 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"height": `calc(100% - ${listHeaderHeight}px)`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedMonitorCount() {
|
||||||
|
return Object.keys(this.selectedMonitors).length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
searchText() {
|
||||||
|
for (let monitor of this.sortedMonitorList) {
|
||||||
|
if (!this.selectedMonitors[monitor.id]) {
|
||||||
|
if (this.selectAll) {
|
||||||
|
this.disableSelectAllWatcher = true;
|
||||||
|
this.selectAll = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectAll() {
|
||||||
|
if (!this.disableSelectAllWatcher) {
|
||||||
|
this.selectedMonitors = {};
|
||||||
|
|
||||||
|
if (this.selectAll) {
|
||||||
|
this.sortedMonitorList.forEach((item) => {
|
||||||
|
this.selectedMonitors[item.id] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.disableSelectAllWatcher = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectMode() {
|
||||||
|
if (!this.selectMode) {
|
||||||
|
this.selectAll = false;
|
||||||
|
this.selectedMonitors = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener("scroll", this.onScroll);
|
window.addEventListener("scroll", this.onScroll);
|
||||||
@ -181,6 +269,53 @@ export default {
|
|||||||
updateFilter(newFilter) {
|
updateFilter(newFilter) {
|
||||||
this.filterState = newFilter;
|
this.filterState = newFilter;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Deselect a monitor
|
||||||
|
* @param {number} id ID of monitor
|
||||||
|
*/
|
||||||
|
deselect(id) {
|
||||||
|
delete this.selectedMonitors[id];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Select a monitor
|
||||||
|
* @param {number} id ID of monitor
|
||||||
|
*/
|
||||||
|
select(id) {
|
||||||
|
this.selectedMonitors[id] = true;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Determine if monitor is selected
|
||||||
|
* @param {number} id ID of monitor
|
||||||
|
* @returns {bool}
|
||||||
|
*/
|
||||||
|
isSelected(id) {
|
||||||
|
return id in this.selectedMonitors;
|
||||||
|
},
|
||||||
|
/** Disable select mode and reset selection */
|
||||||
|
cancelSelectMode() {
|
||||||
|
this.selectMode = false;
|
||||||
|
this.selectedMonitors = {};
|
||||||
|
},
|
||||||
|
/** Show dialog to confirm pause */
|
||||||
|
pauseDialog() {
|
||||||
|
this.$refs.confirmPause.show();
|
||||||
|
},
|
||||||
|
/** Pause each selected monitor */
|
||||||
|
pauseSelected() {
|
||||||
|
Object.keys(this.selectedMonitors)
|
||||||
|
.filter(id => this.$root.monitorList[id].active)
|
||||||
|
.forEach(id => this.$root.getSocket().emit("pauseMonitor", id));
|
||||||
|
|
||||||
|
this.cancelSelectMode();
|
||||||
|
},
|
||||||
|
/** Resume each selected monitor */
|
||||||
|
resumeSelected() {
|
||||||
|
Object.keys(this.selectedMonitors)
|
||||||
|
.filter(id => !this.$root.monitorList[id].active)
|
||||||
|
.forEach(id => this.$root.getSocket().emit("resumeMonitor", id));
|
||||||
|
|
||||||
|
this.cancelSelectMode();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -271,4 +406,12 @@ export default {
|
|||||||
padding-left: 67px;
|
padding-left: 67px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selection-controls {
|
||||||
|
margin-top: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -44,6 +44,7 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../assets/vars.scss";
|
@import "../assets/vars.scss";
|
||||||
|
@import "../assets/app.scss";
|
||||||
|
|
||||||
.filter-dropdown-menu {
|
.filter-dropdown-menu {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@ -102,18 +103,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-dropdown-status {
|
.filter-dropdown-status {
|
||||||
|
@extend .btn-outline-normal;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 10px;
|
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
border: 1px solid #ced4da;
|
|
||||||
border-radius: 25px;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
color: $dark-font-color;
|
|
||||||
border: 1px solid $dark-font-color2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border: 1px solid $highlight;
|
border: 1px solid $highlight;
|
||||||
|
@ -1,34 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<div :style="depthMargin">
|
||||||
|
<!-- Checkbox -->
|
||||||
|
<div v-if="isSelectMode" class="select-input-wrapper">
|
||||||
|
<input
|
||||||
|
class="form-check-input select-input"
|
||||||
|
type="checkbox"
|
||||||
|
:aria-label="$t('Check/Uncheck')"
|
||||||
|
:checked="isSelected(monitor.id)"
|
||||||
|
@click.stop="toggleSelection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||||
<div class="info" :style="depthMargin">
|
<div class="info">
|
||||||
<Uptime :monitor="monitor" type="24" :pill="true" />
|
<Uptime :monitor="monitor" type="24" :pill="true" />
|
||||||
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
||||||
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
||||||
</span>
|
</span>
|
||||||
{{ monitorName }}
|
{{ monitorName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="tags">
|
<div v-if="monitor.tags.length > 0" class="tags">
|
||||||
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||||
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||||
<div class="col-12 bottom-style">
|
<div class="col-12 bottom-style">
|
||||||
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<transition name="slide-fade-up">
|
<transition name="slide-fade-up">
|
||||||
<div v-if="!isCollapsed" class="childs">
|
<div v-if="!isCollapsed" class="childs">
|
||||||
<MonitorListItem v-for="(item, index) in sortedChildMonitorList" :key="index" :monitor="item" :isSearch="isSearch" :depth="depth + 1" />
|
<MonitorListItem
|
||||||
|
v-for="(item, index) in sortedChildMonitorList"
|
||||||
|
:key="index" :monitor="item"
|
||||||
|
:isSearch="isSearch"
|
||||||
|
:isSelectMode="isSelectMode"
|
||||||
|
:isSelected="isSelected"
|
||||||
|
:select="select"
|
||||||
|
:deselect="deselect"
|
||||||
|
:depth="depth + 1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@ -58,11 +80,31 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
/** If the user is in select mode */
|
||||||
|
isSelectMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
/** How many ancestors are above this monitor */
|
/** How many ancestors are above this monitor */
|
||||||
depth: {
|
depth: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
/** Callback to determine if monitor is selected */
|
||||||
|
isSelected: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
/** Callback fired when monitor is selected */
|
||||||
|
select: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
/** Callback fired when monitor is deselected */
|
||||||
|
deselect: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -118,6 +160,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
isSelectMode() {
|
||||||
|
// TODO: Resize the heartbeat bar, but too slow
|
||||||
|
// this.$refs.heartbeatBar.resize();
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
|
||||||
// Always unfold if monitor is accessed directly
|
// Always unfold if monitor is accessed directly
|
||||||
@ -164,6 +212,16 @@ export default {
|
|||||||
monitorURL(id) {
|
monitorURL(id) {
|
||||||
return getMonitorRelativeURL(id);
|
return getMonitorRelativeURL(id);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Toggle selection of monitor
|
||||||
|
*/
|
||||||
|
toggleSelection() {
|
||||||
|
if (this.isSelected(this.monitor.id)) {
|
||||||
|
this.deselect(this.monitor.id);
|
||||||
|
} else {
|
||||||
|
this.select(this.monitor.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -201,4 +259,14 @@ export default {
|
|||||||
transition: all 0.2s $easing-in;
|
transition: all 0.2s $easing-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-input-wrapper {
|
||||||
|
float: left;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 10px;
|
||||||
|
padding-left: 4px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -126,6 +126,7 @@ export default {
|
|||||||
"lunasea": "LunaSea",
|
"lunasea": "LunaSea",
|
||||||
"matrix": "Matrix",
|
"matrix": "Matrix",
|
||||||
"mattermost": "Mattermost",
|
"mattermost": "Mattermost",
|
||||||
|
"nostr": "Nostr",
|
||||||
"ntfy": "Ntfy",
|
"ntfy": "Ntfy",
|
||||||
"octopush": "Octopush",
|
"octopush": "Octopush",
|
||||||
"OneBot": "OneBot",
|
"OneBot": "OneBot",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<label for="gorush-platform" class="form-label">{{ $t("Platform") }}</label><span style="color: red;"><sup>*</sup></span>
|
<label for="gorush-platform" class="form-label">{{ $t("Platform") }}</label><span style="color: red;"><sup>*</sup></span>
|
||||||
<select id="gorush-platform" v-model="$parent.notification.gorushPlatform" class="form-select">
|
<select id="gorush-platform" v-model="$parent.notification.gorushPlatform" class="form-select">
|
||||||
<option value="ios">iOS</option>
|
<option value="ios">iOS</option>
|
||||||
<option value="android">{{ $t("Android") }}</option>
|
<option value="android">Android</option>
|
||||||
<option value="huawei">{{ $t("Huawei") }}</option>
|
<option value="huawei">{{ $t("Huawei") }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
||||||
<a href="https://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">https://docs.mattermost.com/developer/webhooks-incoming.html</a>
|
<a href="https://developers.mattermost.com/integrate/webhooks/incoming/" target="_blank">https://developers.mattermost.com/integrate/webhooks/incoming/</a>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
<p style="margin-top: 8px;">
|
<p style="margin-top: 8px;">
|
||||||
{{ $t("aboutMattermostChannelName") }}
|
{{ $t("aboutMattermostChannelName") }}
|
||||||
|
26
src/components/notifications/Nostr.vue
Normal file
26
src/components/notifications/Nostr.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nostr-relays" class="form-label">{{ $t("nostrRelays") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<textarea id="nostr-relays" v-model="$parent.notification.relays" class="form-control" :required="true" placeholder="wss://127.0.0.1:7777/"></textarea>
|
||||||
|
<small class="form-text text-muted">{{ $t("nostrRelaysHelp") }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nostr-sender" class="form-label">{{ $t("nostrSender") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<HiddenInput id="nostr-sender" v-model="$parent.notification.sender" autocomplete="new-password" :required="true"></HiddenInput>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nostr-recipients" class="form-label">{{ $t("nostrRecipients") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<textarea id="nostr-recipients" v-model="$parent.notification.recipients" class="form-control" :required="true" placeholder="npub123... npub789..."></textarea>
|
||||||
|
<small class="form-text text-muted">{{ $t("nostrRecipientsHelp") }}</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../HiddenInput.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@ -7,8 +7,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ntfy-server-url" class="form-label">{{ $t("Server URL") }}</label>
|
<label for="ntfy-server-url" class="form-label">{{ $t("Server URL") }}</label>
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input id="ntfy-server-url" v-model="$parent.notification.ntfyserverurl" type="text" class="form-control" required>
|
<input id="ntfy-server-url" v-model="$parent.notification.ntfyserverurl" type="text" class="form-control" required>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Server URL should not contain the nfty topic") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -19,6 +19,7 @@ import LineNotify from "./LineNotify.vue";
|
|||||||
import LunaSea from "./LunaSea.vue";
|
import LunaSea from "./LunaSea.vue";
|
||||||
import Matrix from "./Matrix.vue";
|
import Matrix from "./Matrix.vue";
|
||||||
import Mattermost from "./Mattermost.vue";
|
import Mattermost from "./Mattermost.vue";
|
||||||
|
import Nostr from "./Nostr.vue";
|
||||||
import Ntfy from "./Ntfy.vue";
|
import Ntfy from "./Ntfy.vue";
|
||||||
import Octopush from "./Octopush.vue";
|
import Octopush from "./Octopush.vue";
|
||||||
import OneBot from "./OneBot.vue";
|
import OneBot from "./OneBot.vue";
|
||||||
@ -77,6 +78,7 @@ const NotificationFormList = {
|
|||||||
"lunasea": LunaSea,
|
"lunasea": LunaSea,
|
||||||
"matrix": Matrix,
|
"matrix": Matrix,
|
||||||
"mattermost": Mattermost,
|
"mattermost": Mattermost,
|
||||||
|
"nostr": Nostr,
|
||||||
"ntfy": Ntfy,
|
"ntfy": Ntfy,
|
||||||
"octopush": Octopush,
|
"octopush": Octopush,
|
||||||
"OneBot": OneBot,
|
"OneBot": OneBot,
|
||||||
|
@ -455,8 +455,6 @@
|
|||||||
"For safety, must use secret key": "للسلامة يجب استخدام المفتاح السري",
|
"For safety, must use secret key": "للسلامة يجب استخدام المفتاح السري",
|
||||||
"Device Token": "رمز الجهاز",
|
"Device Token": "رمز الجهاز",
|
||||||
"Platform": "منصة",
|
"Platform": "منصة",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "ذكري المظهر",
|
|
||||||
"Huawei": "هواوي",
|
"Huawei": "هواوي",
|
||||||
"High": "عالٍ",
|
"High": "عالٍ",
|
||||||
"Retry": "إعادة المحاولة",
|
"Retry": "إعادة المحاولة",
|
||||||
|
@ -592,7 +592,6 @@
|
|||||||
"For safety, must use secret key": "للسلامة يجب استخدام المفتاح السري",
|
"For safety, must use secret key": "للسلامة يجب استخدام المفتاح السري",
|
||||||
"Device Token": "رمز الجهاز",
|
"Device Token": "رمز الجهاز",
|
||||||
"Platform": "منصة",
|
"Platform": "منصة",
|
||||||
"Android": "ذكري المظهر",
|
|
||||||
"Huawei": "هواوي",
|
"Huawei": "هواوي",
|
||||||
"High": "عالٍ",
|
"High": "عالٍ",
|
||||||
"Retry": "إعادة المحاولة",
|
"Retry": "إعادة المحاولة",
|
||||||
|
@ -396,8 +396,6 @@
|
|||||||
"For safety, must use secret key": "За сигурност, трябва да се използва таен ключ",
|
"For safety, must use secret key": "За сигурност, трябва да се използва таен ключ",
|
||||||
"Device Token": "Токен за устройство",
|
"Device Token": "Токен за устройство",
|
||||||
"Platform": "Платформа",
|
"Platform": "Платформа",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Висок",
|
"High": "Висок",
|
||||||
"Retry": "Повтори",
|
"Retry": "Повтори",
|
||||||
|
@ -454,8 +454,6 @@
|
|||||||
"For safety, must use secret key": "Z důvodu bezpečnosti použijte secret key",
|
"For safety, must use secret key": "Z důvodu bezpečnosti použijte secret key",
|
||||||
"Device Token": "Token zařízení",
|
"Device Token": "Token zařízení",
|
||||||
"Platform": "Platforma",
|
"Platform": "Platforma",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Vysoký",
|
"High": "Vysoký",
|
||||||
"Retry": "Opakovat",
|
"Retry": "Opakovat",
|
||||||
|
@ -558,7 +558,6 @@
|
|||||||
"high": "høj",
|
"high": "høj",
|
||||||
"Base URL": "Base URL",
|
"Base URL": "Base URL",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"Retry": "Forsøg igen",
|
"Retry": "Forsøg igen",
|
||||||
"Topic": "Emne",
|
"Topic": "Emne",
|
||||||
|
@ -403,8 +403,6 @@
|
|||||||
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
|
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
|
||||||
"Device Token": "Gerätetoken",
|
"Device Token": "Gerätetoken",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Hoch",
|
"High": "Hoch",
|
||||||
"Retry": "Wiederholungen",
|
"Retry": "Wiederholungen",
|
||||||
|
@ -403,8 +403,6 @@
|
|||||||
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
|
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
|
||||||
"Device Token": "Gerätetoken",
|
"Device Token": "Gerätetoken",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Hoch",
|
"High": "Hoch",
|
||||||
"Retry": "Wiederholungen",
|
"Retry": "Wiederholungen",
|
||||||
|
@ -420,8 +420,6 @@
|
|||||||
"For safety, must use secret key": "Για ασφάλεια, πρέπει να χρησιμοποιήσετε secret key",
|
"For safety, must use secret key": "Για ασφάλεια, πρέπει να χρησιμοποιήσετε secret key",
|
||||||
"Device Token": "Device Token",
|
"Device Token": "Device Token",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "Ξαναδοκιμάσετε",
|
"Retry": "Ξαναδοκιμάσετε",
|
||||||
|
@ -269,6 +269,9 @@
|
|||||||
"Services": "Services",
|
"Services": "Services",
|
||||||
"Discard": "Discard",
|
"Discard": "Discard",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
"Select": "Select",
|
||||||
|
"selectedMonitorCount": "Selected: {0}",
|
||||||
|
"Check/Uncheck": "Check/Uncheck",
|
||||||
"Powered by": "Powered by",
|
"Powered by": "Powered by",
|
||||||
"shrinkDatabaseDescription": "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
|
"shrinkDatabaseDescription": "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
|
||||||
"Customize": "Customize",
|
"Customize": "Customize",
|
||||||
@ -364,6 +367,7 @@
|
|||||||
"deleteDockerHostMsg": "Are you sure want to delete this docker host for all monitors?",
|
"deleteDockerHostMsg": "Are you sure want to delete this docker host for all monitors?",
|
||||||
"socket": "Socket",
|
"socket": "Socket",
|
||||||
"tcp": "TCP / HTTP",
|
"tcp": "TCP / HTTP",
|
||||||
|
"tailscalePingWarning": "In order to use the Tailscale Ping monitor, you need to install Uptime Kuma without Docker and also install Tailscale client on your server.",
|
||||||
"Docker Container": "Docker Container",
|
"Docker Container": "Docker Container",
|
||||||
"Container Name / ID": "Container Name / ID",
|
"Container Name / ID": "Container Name / ID",
|
||||||
"Docker Host": "Docker Host",
|
"Docker Host": "Docker Host",
|
||||||
@ -619,7 +623,6 @@
|
|||||||
"For safety, must use secret key": "For safety, must use secret key",
|
"For safety, must use secret key": "For safety, must use secret key",
|
||||||
"Device Token": "Device Token",
|
"Device Token": "Device Token",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "Retry",
|
"Retry": "Retry",
|
||||||
@ -690,6 +693,7 @@
|
|||||||
"Octopush API Version": "Octopush API Version",
|
"Octopush API Version": "Octopush API Version",
|
||||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||||
"ntfy Topic": "ntfy Topic",
|
"ntfy Topic": "ntfy Topic",
|
||||||
|
"Server URL should not contain the nfty topic": "Server URL should not contain the nfty topic",
|
||||||
"onebotHttpAddress": "OneBot HTTP Address",
|
"onebotHttpAddress": "OneBot HTTP Address",
|
||||||
"onebotMessageType": "OneBot Message Type",
|
"onebotMessageType": "OneBot Message Type",
|
||||||
"onebotGroupMessage": "Group",
|
"onebotGroupMessage": "Group",
|
||||||
@ -785,6 +789,11 @@
|
|||||||
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
|
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Request Body": "Request Body",
|
"Request Body": "Request Body",
|
||||||
|
"nostrRelays": "Nostr relays",
|
||||||
|
"nostrRelaysHelp": "One relay URL per line",
|
||||||
|
"nostrSender": "Sender Private Key (nsec)",
|
||||||
|
"nostrRecipients": "Recipients Public Keys (npub)",
|
||||||
|
"nostrRecipientsHelp": "npub format, one per line",
|
||||||
"showCertificateExpiry": "Show Certificate Expiry",
|
"showCertificateExpiry": "Show Certificate Expiry",
|
||||||
"noOrBadCertificate": "No/Bad Certificate"
|
"noOrBadCertificate": "No/Bad Certificate"
|
||||||
}
|
}
|
||||||
|
@ -497,8 +497,6 @@
|
|||||||
"Proto Method": "Método Proto",
|
"Proto Method": "Método Proto",
|
||||||
"Proto Content": "Contenido Proto",
|
"Proto Content": "Contenido Proto",
|
||||||
"Economy": "Económico",
|
"Economy": "Económico",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Platform": "Plataforma",
|
"Platform": "Plataforma",
|
||||||
"onebotPrivateMessage": "Privado",
|
"onebotPrivateMessage": "Privado",
|
||||||
"onebotMessageType": "Tipo de Mensaje OneBot",
|
"onebotMessageType": "Tipo de Mensaje OneBot",
|
||||||
|
@ -415,8 +415,6 @@
|
|||||||
"For safety, must use secret key": "For safety, must use secret key",
|
"For safety, must use secret key": "For safety, must use secret key",
|
||||||
"Device Token": "Gailu tokena",
|
"Device Token": "Gailu tokena",
|
||||||
"Platform": "Plataforma",
|
"Platform": "Plataforma",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Altua",
|
"High": "Altua",
|
||||||
"Retry": "Errepikatu",
|
"Retry": "Errepikatu",
|
||||||
|
@ -568,7 +568,6 @@
|
|||||||
"SendKey": "کلید ارسال (SendKey)",
|
"SendKey": "کلید ارسال (SendKey)",
|
||||||
"SecretAccessKey": "کلید دسترسی مخفی (AccessKey Secret)",
|
"SecretAccessKey": "کلید دسترسی مخفی (AccessKey Secret)",
|
||||||
"SignName": "نام امضا (SignName)",
|
"SignName": "نام امضا (SignName)",
|
||||||
"Android": "اندروید",
|
|
||||||
"Huawei": "هواوی",
|
"Huawei": "هواوی",
|
||||||
"WeCom Bot Key": "کلید ربات WeCom",
|
"WeCom Bot Key": "کلید ربات WeCom",
|
||||||
"Setup Proxy": "تنظیم پروکسی",
|
"Setup Proxy": "تنظیم پروکسی",
|
||||||
|
@ -547,7 +547,6 @@
|
|||||||
"For safety, must use secret key": "Turvallisuuden vuoksi on käytettävä salaista avainta",
|
"For safety, must use secret key": "Turvallisuuden vuoksi on käytettävä salaista avainta",
|
||||||
"Device Token": "Laitteen tunnus",
|
"Device Token": "Laitteen tunnus",
|
||||||
"Platform": "Alusta",
|
"Platform": "Alusta",
|
||||||
"iOS": "iOS",
|
|
||||||
"Bark Endpoint": "Bark päätepiste",
|
"Bark Endpoint": "Bark päätepiste",
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Korkea",
|
"High": "Korkea",
|
||||||
@ -564,7 +563,6 @@
|
|||||||
"promosmsAllowLongSMS": "Salli pitkät tekstiviestit",
|
"promosmsAllowLongSMS": "Salli pitkät tekstiviestit",
|
||||||
"Feishu WebHookUrl": "Feishu WebHookURL-osoite",
|
"Feishu WebHookUrl": "Feishu WebHookURL-osoite",
|
||||||
"Internal Room Id": "Huoneen sisäinen tunnus",
|
"Internal Room Id": "Huoneen sisäinen tunnus",
|
||||||
"Android": "Android",
|
|
||||||
"Channel Name": "Kanavan nimi",
|
"Channel Name": "Kanavan nimi",
|
||||||
"Uptime Kuma URL": "Uptime Kuma URL-osoite",
|
"Uptime Kuma URL": "Uptime Kuma URL-osoite",
|
||||||
"Icon Emoji": "Ikoni Emoji",
|
"Icon Emoji": "Ikoni Emoji",
|
||||||
|
@ -451,8 +451,6 @@
|
|||||||
"For safety, must use secret key": "Par sécurité, utilisation obligatoire de la clé secrète",
|
"For safety, must use secret key": "Par sécurité, utilisation obligatoire de la clé secrète",
|
||||||
"Device Token": "Jeton d'appareil",
|
"Device Token": "Jeton d'appareil",
|
||||||
"Platform": "Plateforme",
|
"Platform": "Plateforme",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Haute",
|
"High": "Haute",
|
||||||
"Retry": "Recommencez",
|
"Retry": "Recommencez",
|
||||||
|
@ -445,8 +445,6 @@
|
|||||||
"For safety, must use secret key": "לבטיחות, חייב להשתמש במפתח סודיy",
|
"For safety, must use secret key": "לבטיחות, חייב להשתמש במפתח סודיy",
|
||||||
"Device Token": "אסימון מכשיר",
|
"Device Token": "אסימון מכשיר",
|
||||||
"Platform": "פּלַטפוֹרמָה",
|
"Platform": "פּלַטפוֹרמָה",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "דְמוּי אָדָם",
|
|
||||||
"Huawei": "huawei",
|
"Huawei": "huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "נסה שוב",
|
"Retry": "נסה שוב",
|
||||||
|
@ -420,8 +420,6 @@
|
|||||||
"For safety, must use secret key": "Korištenje tajnog ključa je obavezno",
|
"For safety, must use secret key": "Korištenje tajnog ključa je obavezno",
|
||||||
"Device Token": "Token uređaja",
|
"Device Token": "Token uređaja",
|
||||||
"Platform": "Platforma",
|
"Platform": "Platforma",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Visoko",
|
"High": "Visoko",
|
||||||
"Retry": "Ponovnih pokušaja",
|
"Retry": "Ponovnih pokušaja",
|
||||||
|
@ -418,8 +418,6 @@
|
|||||||
"For safety, must use secret key": "Untuk keamaan Anda harus menggunakan kunci rahasia",
|
"For safety, must use secret key": "Untuk keamaan Anda harus menggunakan kunci rahasia",
|
||||||
"Device Token": "Token Perangkat",
|
"Device Token": "Token Perangkat",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Tinggi",
|
"High": "Tinggi",
|
||||||
"Retry": "Ulang",
|
"Retry": "Ulang",
|
||||||
|
@ -507,7 +507,6 @@
|
|||||||
"lineDevConsoleTo": "Line Developers Console - {0}",
|
"lineDevConsoleTo": "Line Developers Console - {0}",
|
||||||
"Basic Settings": "基本設定",
|
"Basic Settings": "基本設定",
|
||||||
"User ID": "User ID",
|
"User ID": "User ID",
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"Device Token": "デバイストークン",
|
"Device Token": "デバイストークン",
|
||||||
"recurringIntervalMessage": "毎日1回実行する|{0} 日に1回実行する",
|
"recurringIntervalMessage": "毎日1回実行する|{0} 日に1回実行する",
|
||||||
|
@ -413,8 +413,6 @@
|
|||||||
"For safety, must use secret key": "안전을 위해 꼭 Secret Key를 사용하세요.",
|
"For safety, must use secret key": "안전을 위해 꼭 Secret Key를 사용하세요.",
|
||||||
"Device Token": "기기 Token",
|
"Device Token": "기기 Token",
|
||||||
"Platform": "플랫폼",
|
"Platform": "플랫폼",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "재시도",
|
"Retry": "재시도",
|
||||||
|
@ -404,8 +404,6 @@
|
|||||||
"For safety, must use secret key": "Voor de veiligheid moet je de secret key gebruiken",
|
"For safety, must use secret key": "Voor de veiligheid moet je de secret key gebruiken",
|
||||||
"Device Token": "Apparaat Token",
|
"Device Token": "Apparaat Token",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Hoog",
|
"High": "Hoog",
|
||||||
"Retry": "Opnieuw",
|
"Retry": "Opnieuw",
|
||||||
|
@ -414,8 +414,6 @@
|
|||||||
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
|
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
|
||||||
"Device Token": "Token urządzenia",
|
"Device Token": "Token urządzenia",
|
||||||
"Platform": "Platforma",
|
"Platform": "Platforma",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Wysoki",
|
"High": "Wysoki",
|
||||||
"Retry": "Ponów",
|
"Retry": "Ponów",
|
||||||
|
@ -523,7 +523,6 @@
|
|||||||
"Example:": "Exemplo: {0}",
|
"Example:": "Exemplo: {0}",
|
||||||
"Read more:": "Leia mais em: {0}",
|
"Read more:": "Leia mais em: {0}",
|
||||||
"promosmsAllowLongSMS": "Permitir SMS grandes",
|
"promosmsAllowLongSMS": "Permitir SMS grandes",
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"smseagleTo": "Números Dos Telefones",
|
"smseagleTo": "Números Dos Telefones",
|
||||||
"smseaglePriority": "Prioridade da mensagem (0-9, padrão=0)",
|
"smseaglePriority": "Prioridade da mensagem (0-9, padrão=0)",
|
||||||
|
@ -421,8 +421,6 @@
|
|||||||
"For safety, must use secret key": "В целях безопасности необходимо использовать секретный ключ",
|
"For safety, must use secret key": "В целях безопасности необходимо использовать секретный ключ",
|
||||||
"Device Token": "Токен устройства",
|
"Device Token": "Токен устройства",
|
||||||
"Platform": "Платформа",
|
"Platform": "Платформа",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "Повторить",
|
"Retry": "Повторить",
|
||||||
|
@ -404,8 +404,6 @@
|
|||||||
"For safety, must use secret key": "เพื่อความปลอดภัย จำเป็นต้องตั้งค่ากุญแจการเข้าถึง",
|
"For safety, must use secret key": "เพื่อความปลอดภัย จำเป็นต้องตั้งค่ากุญแจการเข้าถึง",
|
||||||
"Device Token": "Device Token",
|
"Device Token": "Device Token",
|
||||||
"Platform": "แพลตฟอร์ม",
|
"Platform": "แพลตฟอร์ม",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "สูง",
|
"High": "สูง",
|
||||||
"Retry": "ลองใหม่",
|
"Retry": "ลองใหม่",
|
||||||
|
@ -408,8 +408,6 @@
|
|||||||
"For safety, must use secret key": "Güvenlik için gizli anahtar kullanılmalıdır",
|
"For safety, must use secret key": "Güvenlik için gizli anahtar kullanılmalıdır",
|
||||||
"Device Token": "Cihaz Tokeni",
|
"Device Token": "Cihaz Tokeni",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "Tekrar",
|
"Retry": "Tekrar",
|
||||||
|
@ -413,8 +413,6 @@
|
|||||||
"For safety, must use secret key": "Для безпеки необхідно використовувати секретний ключ",
|
"For safety, must use secret key": "Для безпеки необхідно використовувати секретний ключ",
|
||||||
"Device Token": "Токен пристрою",
|
"Device Token": "Токен пристрою",
|
||||||
"Platform": "Платформа",
|
"Platform": "Платформа",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "Високий",
|
"High": "Високий",
|
||||||
"Retry": "Повтор",
|
"Retry": "Повтор",
|
||||||
|
@ -403,8 +403,6 @@
|
|||||||
"For safety, must use secret key": "Để an toàn, hãy dùng secret key",
|
"For safety, must use secret key": "Để an toàn, hãy dùng secret key",
|
||||||
"Device Token": "Device Token",
|
"Device Token": "Device Token",
|
||||||
"Platform": "Platform",
|
"Platform": "Platform",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "Huawei",
|
"Huawei": "Huawei",
|
||||||
"High": "High",
|
"High": "High",
|
||||||
"Retry": "Retry",
|
"Retry": "Retry",
|
||||||
|
@ -452,8 +452,6 @@
|
|||||||
"For safety, must use secret key": "出于安全考虑,必须使用加签密钥",
|
"For safety, must use secret key": "出于安全考虑,必须使用加签密钥",
|
||||||
"Device Token": "Apple Device Token",
|
"Device Token": "Apple Device Token",
|
||||||
"Platform": "平台",
|
"Platform": "平台",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "华为",
|
"Huawei": "华为",
|
||||||
"High": "高",
|
"High": "高",
|
||||||
"Retry": "重试次数",
|
"Retry": "重试次数",
|
||||||
|
@ -694,7 +694,6 @@
|
|||||||
"Retry": "重試",
|
"Retry": "重試",
|
||||||
"High": "高",
|
"High": "高",
|
||||||
"Huawei": "華為",
|
"Huawei": "華為",
|
||||||
"Android": "Android",
|
|
||||||
"For safety, must use secret key": "為安全起見,必須使用 Secret Key",
|
"For safety, must use secret key": "為安全起見,必須使用 Secret Key",
|
||||||
"SecretKey": "SecretKey",
|
"SecretKey": "SecretKey",
|
||||||
"WebHookUrl": "WebHookUrl",
|
"WebHookUrl": "WebHookUrl",
|
||||||
|
@ -445,8 +445,6 @@
|
|||||||
"For safety, must use secret key": "為了安全起見,必須使用秘密金鑰",
|
"For safety, must use secret key": "為了安全起見,必須使用秘密金鑰",
|
||||||
"Device Token": "裝置權杖",
|
"Device Token": "裝置權杖",
|
||||||
"Platform": "平台",
|
"Platform": "平台",
|
||||||
"iOS": "iOS",
|
|
||||||
"Android": "Android",
|
|
||||||
"Huawei": "華為",
|
"Huawei": "華為",
|
||||||
"High": "高",
|
"High": "高",
|
||||||
"Retry": "重試",
|
"Retry": "重試",
|
||||||
|
@ -82,10 +82,17 @@
|
|||||||
<option value="redis">
|
<option value="redis">
|
||||||
Redis
|
Redis
|
||||||
</option>
|
</option>
|
||||||
|
<option value="tailscale-ping">
|
||||||
|
Tailscale Ping
|
||||||
|
</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="monitor.type === 'tailscale-ping'" class="alert alert-warning" role="alert">
|
||||||
|
{{ $t("tailscalePingWarning") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Friendly Name -->
|
<!-- Friendly Name -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
||||||
@ -221,8 +228,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Hostname -->
|
<!-- Hostname -->
|
||||||
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius only -->
|
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping only -->
|
||||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' ||monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3">
|
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' ||monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping'" class="my-3">
|
||||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`" required>
|
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`" required>
|
||||||
</div>
|
</div>
|
||||||
@ -366,42 +373,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- SQL Server / PostgreSQL / MySQL / Redis / MongoDB -->
|
||||||
|
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql' || monitor.type === 'redis' || monitor.type === 'mongodb'">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="connectionString" class="form-label">{{ $t("Connection String") }}</label>
|
||||||
|
<input id="connectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<!-- SQL Server / PostgreSQL / MySQL -->
|
<!-- SQL Server / PostgreSQL / MySQL -->
|
||||||
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
||||||
<div class="my-3">
|
|
||||||
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
|
||||||
|
|
||||||
<template v-if="monitor.type === 'sqlserver'">
|
|
||||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
|
||||||
</template>
|
|
||||||
<template v-if="monitor.type === 'postgres'">
|
|
||||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
|
||||||
</template>
|
|
||||||
<template v-if="monitor.type === 'mysql'">
|
|
||||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
||||||
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
|
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" :placeholder="$t('Example:', [ 'select getdate()' ])" required></textarea>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<!-- Redis -->
|
|
||||||
<template v-if="monitor.type === 'redis'">
|
|
||||||
<div class="my-3">
|
|
||||||
<label for="redisConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
|
||||||
<input id="redisConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- MongoDB -->
|
|
||||||
<template v-if="monitor.type === 'mongodb'">
|
|
||||||
<div class="my-3">
|
|
||||||
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
|
||||||
|
|
||||||
<template v-if="monitor.type === 'mongodb'">
|
|
||||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
10
test/test_install_script/debian-buster.dockerfile
Normal file
10
test/test_install_script/debian-buster.dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM debian:buster-slim
|
||||||
|
|
||||||
|
# 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
|
@ -1,4 +1,4 @@
|
|||||||
FROM debian
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
# Test invalid node version, these commands install nodejs 10
|
# Test invalid node version, these commands install nodejs 10
|
||||||
# RUN apt-get update
|
# RUN apt-get update
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM centos:8
|
FROM rockylinux:9
|
||||||
|
|
||||||
COPY ./install.sh .
|
COPY ./install.sh .
|
||||||
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
@ -6,4 +6,5 @@ FROM ubuntu
|
|||||||
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
# RUN node -v
|
# RUN node -v
|
||||||
|
|
||||||
RUN curl -o kuma_install.sh http://git.kuma.pet/install.sh && bash kuma_install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
FROM ubuntu:16.04
|
FROM ubuntu:16.04
|
||||||
RUN apt-get update
|
|
||||||
RUN apt --yes install curl
|
|
||||||
|
|
||||||
# Test invalid node version, these commands install nodejs 10
|
# Test invalid node version, these commands install nodejs 10
|
||||||
#RUN apt --yes install nodejs
|
#RUN apt --yes install nodejs
|
||||||
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
# RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
# RUN node -v
|
# RUN node -v
|
||||||
|
|
||||||
RUN curl -o kuma_install.sh http://git.kuma.pet/install.sh && bash kuma_install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
||||||
|
4
test/test_install_script/ubuntu1804.dockerfile
Normal file
4
test/test_install_script/ubuntu1804.dockerfile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
|
COPY ./install.sh .
|
||||||
|
RUN bash install.sh local /opt/uptime-kuma 3000 0.0.0.0
|
@ -1,10 +0,0 @@
|
|||||||
FROM ubuntu
|
|
||||||
WORKDIR /app
|
|
||||||
RUN apt update && apt --yes install git curl
|
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash -
|
|
||||||
RUN apt --yes install nodejs
|
|
||||||
RUN git clone https://github.com/louislam/uptime-kuma.git .
|
|
||||||
RUN npm run setup
|
|
||||||
|
|
||||||
# Option 1. Try it
|
|
||||||
RUN node server/server.js
|
|
Loading…
Reference in New Issue
Block a user