commit
20e7a0c40c
@ -0,0 +1,43 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master", "1.23.X"]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master", "1.23.X"]
|
||||||
|
schedule:
|
||||||
|
- cron: '16 22 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 360
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go', 'javascript-typescript' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
uptime-kuma:
|
||||||
|
image: louislam/uptime-kuma:1
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
ports:
|
||||||
|
# <Host Port>:<Container Port>
|
||||||
|
- 3001:3001
|
||||||
|
restart: unless-stopped
|
@ -0,0 +1,60 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
const port = 30001;
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// Look for test files in the "tests" directory, relative to this configuration file.
|
||||||
|
testDir: "../test/e2e",
|
||||||
|
outputDir: "../private/playwright-test-results",
|
||||||
|
fullyParallel: false,
|
||||||
|
locale: "en-US",
|
||||||
|
|
||||||
|
// Fail the build on CI if you accidentally left test.only in the source code.
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
|
||||||
|
// Retry on CI only.
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
|
||||||
|
// Opt out of parallel tests on CI.
|
||||||
|
workers: 1,
|
||||||
|
|
||||||
|
// Reporter to use
|
||||||
|
reporter: [
|
||||||
|
[
|
||||||
|
"html", {
|
||||||
|
outputFolder: "../private/playwright-report",
|
||||||
|
open: "never",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
use: {
|
||||||
|
// Base URL to use in actions like `await page.goto('/')`.
|
||||||
|
baseURL: url,
|
||||||
|
|
||||||
|
// Collect trace when retrying the failed test.
|
||||||
|
trace: "on-first-retry",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure projects for major browsers.
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
name: "firefox",
|
||||||
|
use: { browserName: "firefox" }
|
||||||
|
},*/
|
||||||
|
],
|
||||||
|
|
||||||
|
// Run your local dev server before starting the tests.
|
||||||
|
webServer: {
|
||||||
|
command: `node extra/remove-playwright-test-data.js && node server/server.js --port=${port} --data-dir=./data/playwright-test`,
|
||||||
|
url,
|
||||||
|
reuseExistingServer: false,
|
||||||
|
cwd: "../",
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,15 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
// Add new column heartbeat.retries
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("heartbeat", function (table) {
|
||||||
|
table.integer("retries").notNullable().defaultTo(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("heartbeat", function (table) {
|
||||||
|
table.dropColumn("retries");
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
// Add new column monitor.mqtt_check_type
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("monitor", function (table) {
|
||||||
|
table.string("mqtt_check_type", 255).notNullable().defaultTo("keyword");
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
// Drop column monitor.mqtt_check_type
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("monitor", function (table) {
|
||||||
|
table.dropColumn("mqtt_check_type");
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.createTable("remote_browser", function (table) {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("name", 255).notNullable();
|
||||||
|
table.string("url", 255).notNullable();
|
||||||
|
table.integer("user_id").unsigned();
|
||||||
|
}).alterTable("monitor", function (table) {
|
||||||
|
// Add new column monitor.remote_browser
|
||||||
|
table.integer("remote_browser").nullable().defaultTo(null).unsigned()
|
||||||
|
.index()
|
||||||
|
.references("id")
|
||||||
|
.inTable("remote_browser");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTable("remote_browser").alterTable("monitor", function (table) {
|
||||||
|
table.dropColumn("remote_browser");
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("stat_daily", function (table) {
|
||||||
|
table.float("ping_min").notNullable().defaultTo(0).comment("Minimum ping during this period in milliseconds");
|
||||||
|
table.float("ping_max").notNullable().defaultTo(0).comment("Maximum ping during this period in milliseconds");
|
||||||
|
})
|
||||||
|
.alterTable("stat_minutely", function (table) {
|
||||||
|
table.float("ping_min").notNullable().defaultTo(0).comment("Minimum ping during this period in milliseconds");
|
||||||
|
table.float("ping_max").notNullable().defaultTo(0).comment("Maximum ping during this period in milliseconds");
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("stat_daily", function (table) {
|
||||||
|
table.dropColumn("ping_min");
|
||||||
|
table.dropColumn("ping_max");
|
||||||
|
})
|
||||||
|
.alterTable("stat_minutely", function (table) {
|
||||||
|
table.dropColumn("ping_min");
|
||||||
|
table.dropColumn("ping_max");
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.createTable("stat_hourly", function (table) {
|
||||||
|
table.increments("id");
|
||||||
|
table.comment("This table contains the hourly aggregate statistics for each monitor");
|
||||||
|
table.integer("monitor_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("timestamp")
|
||||||
|
.notNullable()
|
||||||
|
.comment("Unix timestamp rounded down to the nearest hour");
|
||||||
|
table.float("ping").notNullable().comment("Average ping in milliseconds");
|
||||||
|
table.float("ping_min").notNullable().defaultTo(0).comment("Minimum ping during this period in milliseconds");
|
||||||
|
table.float("ping_max").notNullable().defaultTo(0).comment("Maximum ping during this period in milliseconds");
|
||||||
|
table.smallint("up").notNullable();
|
||||||
|
table.smallint("down").notNullable();
|
||||||
|
|
||||||
|
table.unique([ "monitor_id", "timestamp" ]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.dropTable("stat_hourly");
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- Rename COLUMNs to another one (suffixed by `_old`)
|
||||||
|
ALTER TABLE monitor
|
||||||
|
RENAME COLUMN kafka_producer_ssl TO kafka_producer_ssl_old;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
RENAME COLUMN kafka_producer_allow_auto_topic_creation TO kafka_producer_allow_auto_topic_creation_old;
|
||||||
|
|
||||||
|
-- Add correct COLUMNs
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD COLUMN kafka_producer_ssl BOOLEAN default 0 NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD COLUMN kafka_producer_allow_auto_topic_creation BOOLEAN default 0 NOT NULL;
|
||||||
|
|
||||||
|
-- These SQL is still not fully safe. See https://github.com/louislam/uptime-kuma/issues/4039.
|
||||||
|
|
||||||
|
-- Set bring old values from `_old` COLUMNs to correct ones
|
||||||
|
-- UPDATE monitor SET kafka_producer_allow_auto_topic_creation = monitor.kafka_producer_allow_auto_topic_creation_old
|
||||||
|
-- WHERE monitor.kafka_producer_allow_auto_topic_creation_old IS NOT NULL;
|
||||||
|
|
||||||
|
-- UPDATE monitor SET kafka_producer_ssl = monitor.kafka_producer_ssl_old
|
||||||
|
-- WHERE monitor.kafka_producer_ssl_old IS NOT NULL;
|
||||||
|
|
||||||
|
-- Remove old COLUMNs
|
||||||
|
ALTER TABLE monitor
|
||||||
|
DROP COLUMN kafka_producer_allow_auto_topic_creation_old;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
DROP COLUMN kafka_producer_ssl_old;
|
||||||
|
|
||||||
|
COMMIT;
|
@ -0,0 +1,7 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
UPDATE monitor SET timeout = (interval * 0.8)
|
||||||
|
WHERE timeout IS NULL OR timeout <= 0;
|
||||||
|
|
||||||
|
COMMIT;
|
@ -1,15 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
uptime-kuma:
|
|
||||||
image: louislam/uptime-kuma:1
|
|
||||||
container_name: uptime-kuma
|
|
||||||
volumes:
|
|
||||||
- uptime-kuma:/app/data
|
|
||||||
ports:
|
|
||||||
- "3001:3001" # <Host Port>:<Container Port>
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
uptime-kuma:
|
|
||||||
|
|
@ -1,276 +0,0 @@
|
|||||||
// 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 Install Script");
|
|
||||||
println("=====================");
|
|
||||||
println("Supported OS: Ubuntu >= 16.04, Debian and CentOS/RHEL 7/8");
|
|
||||||
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 on your current machine with git, Node.js 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 16");
|
|
||||||
bash("curl -sL https://deb.nodesource.com/setup_16.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";
|
|
||||||
|
|
||||||
// Get ubuntu version
|
|
||||||
bash(". /etc/lsb-release");
|
|
||||||
version = DISTRIB_RELEASE;
|
|
||||||
}
|
|
||||||
if (os == "Debian") {
|
|
||||||
distribution = "debian";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bash("arch=$(uname -i)");
|
|
||||||
|
|
||||||
println("Your OS: " ++ os);
|
|
||||||
println("Distribution: " ++ distribution);
|
|
||||||
println("Version: " ++ version);
|
|
||||||
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("dnfCheck=$(dnf --version)");
|
|
||||||
|
|
||||||
// Use yum
|
|
||||||
if (dnfCheck == "") {
|
|
||||||
bash("curlCheck=$(curl --version)");
|
|
||||||
if (curlCheck == "") {
|
|
||||||
println("Installing Curl");
|
|
||||||
bash("yum -y -q install curl");
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Installing Node.js 16");
|
|
||||||
bash("curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt");
|
|
||||||
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("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 not found!");
|
|
||||||
println("help: an installation guide is available at https://git-scm.com/book/en/v2/Getting-Started-Installing-Git");
|
|
||||||
}
|
|
||||||
|
|
||||||
bash("check=$(node -v)");
|
|
||||||
if (check == "") {
|
|
||||||
error = 1;
|
|
||||||
println("Error: node is not found");
|
|
||||||
println("help: an installation guide is available at https://nodejs.org/en/download");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 && pm2 install pm2-logrotate");
|
|
||||||
bash("pm2 startup");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check again
|
|
||||||
bash("check=$(pm2 --version)");
|
|
||||||
if (check == "") {
|
|
||||||
println("Error: pm2 is not found!");
|
|
||||||
println("help: an installation guide is available at https://pm2.keymetrics.io/docs/usage/quick-start/");
|
|
||||||
bash("exit 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
bash("mkdir -p $installPath");
|
|
||||||
bash("cd $installPath");
|
|
||||||
bash("git clone https://github.com/louislam/uptime-kuma.git .");
|
|
||||||
bash("npm run setup");
|
|
||||||
|
|
||||||
bash("pm2 start server/server.js --name uptime-kuma -- --port=$port");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
defaultVolume = "uptime-kuma";
|
|
||||||
|
|
||||||
bash("check=$(docker -v)");
|
|
||||||
if (check == "") {
|
|
||||||
println("Error: docker is not found!");
|
|
||||||
println("help: an installation guide is available at https://docs.docker.com/desktop/");
|
|
||||||
bash("exit 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
bash("check=$(docker info)");
|
|
||||||
|
|
||||||
bash("if [[ \"$check\" == *\"Is the docker daemon running\"* ]]; then
|
|
||||||
\"echo\" \"Error: docker is not running\"
|
|
||||||
\"echo\" \"help: a troubleshooting guide is available at https://docs.docker.com/config/daemon/troubleshoot/\"
|
|
||||||
\"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");
|
|
@ -0,0 +1,6 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
fs.rmSync("./data/playwright-test", {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 1.1 KiB |
@ -1,88 +0,0 @@
|
|||||||
const https = require("https");
|
|
||||||
const http = require("http");
|
|
||||||
const CacheableLookup = require("cacheable-lookup");
|
|
||||||
const { Settings } = require("./settings");
|
|
||||||
const { log } = require("../src/util");
|
|
||||||
|
|
||||||
class CacheableDnsHttpAgent {
|
|
||||||
|
|
||||||
static cacheable = new CacheableLookup();
|
|
||||||
|
|
||||||
static httpAgentList = {};
|
|
||||||
static httpsAgentList = {};
|
|
||||||
|
|
||||||
static enable = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register/Disable cacheable to global agents
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
static async update() {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "update");
|
|
||||||
let isEnable = await Settings.get("dnsCache");
|
|
||||||
|
|
||||||
if (isEnable !== this.enable) {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "value changed");
|
|
||||||
|
|
||||||
if (isEnable) {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "enable");
|
|
||||||
this.cacheable.install(http.globalAgent);
|
|
||||||
this.cacheable.install(https.globalAgent);
|
|
||||||
} else {
|
|
||||||
log.debug("CacheableDnsHttpAgent", "disable");
|
|
||||||
this.cacheable.uninstall(http.globalAgent);
|
|
||||||
this.cacheable.uninstall(https.globalAgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.enable = isEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach cacheable to HTTP agent
|
|
||||||
* @param {http.Agent} agent Agent to install
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
static install(agent) {
|
|
||||||
this.cacheable.install(agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {https.AgentOptions} agentOptions Options to pass to HTTPS agent
|
|
||||||
* @returns {https.Agent} The new HTTPS agent
|
|
||||||
*/
|
|
||||||
static getHttpsAgent(agentOptions) {
|
|
||||||
if (!this.enable) {
|
|
||||||
return new https.Agent(agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = JSON.stringify(agentOptions);
|
|
||||||
if (!(key in this.httpsAgentList)) {
|
|
||||||
this.httpsAgentList[key] = new https.Agent(agentOptions);
|
|
||||||
this.cacheable.install(this.httpsAgentList[key]);
|
|
||||||
}
|
|
||||||
return this.httpsAgentList[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {http.AgentOptions} agentOptions Options to pass to the HTTP agent
|
|
||||||
* @returns {https.Agents} The new HTTP agent
|
|
||||||
*/
|
|
||||||
static getHttpAgent(agentOptions) {
|
|
||||||
if (!this.enable) {
|
|
||||||
return new http.Agent(agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = JSON.stringify(agentOptions);
|
|
||||||
if (!(key in this.httpAgentList)) {
|
|
||||||
this.httpAgentList[key] = new http.Agent(agentOptions);
|
|
||||||
this.cacheable.install(this.httpAgentList[key]);
|
|
||||||
}
|
|
||||||
return this.httpAgentList[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
CacheableDnsHttpAgent,
|
|
||||||
};
|
|
@ -1,29 +1,46 @@
|
|||||||
|
const isFreeBSD = /^freebsd/.test(process.platform);
|
||||||
|
|
||||||
// Interop with browser
|
// Interop with browser
|
||||||
const args = (typeof process !== "undefined") ? require("args-parser")(process.argv) : {};
|
const args = (typeof process !== "undefined") ? require("args-parser")(process.argv) : {};
|
||||||
const demoMode = args["demo"] || false;
|
|
||||||
|
|
||||||
const badgeConstants = {
|
// 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.
|
||||||
naColor: "#999",
|
// Dual-stack support for (::)
|
||||||
defaultUpColor: "#66c20a",
|
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
|
||||||
defaultWarnColor: "#eed202",
|
let hostEnv = isFreeBSD ? null : process.env.HOST;
|
||||||
defaultDownColor: "#c2290a",
|
const hostname = args.host || process.env.UPTIME_KUMA_HOST || hostEnv;
|
||||||
defaultPendingColor: "#f8a306",
|
|
||||||
defaultMaintenanceColor: "#1747f5",
|
const port = [ args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001 ]
|
||||||
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
.map(portValue => parseInt(portValue))
|
||||||
defaultStyle: "flat",
|
.find(portValue => !isNaN(portValue));
|
||||||
defaultPingValueSuffix: "ms",
|
|
||||||
defaultPingLabelSuffix: "h",
|
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
|
||||||
defaultUptimeValueSuffix: "%",
|
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||||
defaultUptimeLabelSuffix: "h",
|
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;
|
||||||
defaultCertExpValueSuffix: " days",
|
|
||||||
defaultCertExpLabelSuffix: "h",
|
const isSSL = sslKey && sslCert;
|
||||||
// Values Come From Default Notification Times
|
|
||||||
defaultCertExpireWarnDays: "14",
|
/**
|
||||||
defaultCertExpireDownDays: "7"
|
* Get the local WebSocket URL
|
||||||
};
|
* @returns {string} The local WebSocket URL
|
||||||
|
*/
|
||||||
|
function getLocalWebSocketURL() {
|
||||||
|
const protocol = isSSL ? "wss" : "ws";
|
||||||
|
const host = hostname || "localhost";
|
||||||
|
return `${protocol}://${host}:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localWebSocketURL = getLocalWebSocketURL();
|
||||||
|
|
||||||
|
const demoMode = args["demo"] || false;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
args,
|
args,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
sslKey,
|
||||||
|
sslCert,
|
||||||
|
sslKeyPassphrase,
|
||||||
|
isSSL,
|
||||||
|
localWebSocketURL,
|
||||||
demoMode,
|
demoMode,
|
||||||
badgeConstants,
|
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
|
||||||
|
class RemoteBrowser extends BeanModel {
|
||||||
|
/**
|
||||||
|
* Returns an object that ready to parse to JSON
|
||||||
|
* @returns {object} Object ready to parse
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
url: this.url,
|
||||||
|
name: this.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RemoteBrowser;
|
@ -0,0 +1,122 @@
|
|||||||
|
const { MonitorType } = require("./monitor-type");
|
||||||
|
const { log, UP } = require("../../src/util");
|
||||||
|
const mqtt = require("mqtt");
|
||||||
|
const jsonata = require("jsonata");
|
||||||
|
|
||||||
|
class MqttMonitorType extends MonitorType {
|
||||||
|
|
||||||
|
name = "mqtt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the monitoring check on the MQTT monitor
|
||||||
|
* @param {Monitor} monitor Monitor to check
|
||||||
|
* @param {Heartbeat} heartbeat Monitor heartbeat to update
|
||||||
|
* @param {UptimeKumaServer} server Uptime Kuma server
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async check(monitor, heartbeat, server) {
|
||||||
|
const receivedMessage = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||||
|
port: monitor.port,
|
||||||
|
username: monitor.mqttUsername,
|
||||||
|
password: monitor.mqttPassword,
|
||||||
|
interval: monitor.interval,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (monitor.mqttCheckType == null || monitor.mqttCheckType === "") {
|
||||||
|
// use old default
|
||||||
|
monitor.mqttCheckType = "keyword";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor.mqttCheckType === "keyword") {
|
||||||
|
if (receivedMessage != null && receivedMessage.includes(monitor.mqttSuccessMessage)) {
|
||||||
|
heartbeat.msg = `Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`;
|
||||||
|
heartbeat.status = UP;
|
||||||
|
} else {
|
||||||
|
throw Error(`Message Mismatch - Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`);
|
||||||
|
}
|
||||||
|
} else if (monitor.mqttCheckType === "json-query") {
|
||||||
|
const parsedMessage = JSON.parse(receivedMessage);
|
||||||
|
|
||||||
|
let expression = jsonata(monitor.jsonPath);
|
||||||
|
|
||||||
|
let result = await expression.evaluate(parsedMessage);
|
||||||
|
|
||||||
|
if (result?.toString() === monitor.expectedValue) {
|
||||||
|
heartbeat.msg = "Message received, expected value is found";
|
||||||
|
heartbeat.status = UP;
|
||||||
|
} else {
|
||||||
|
throw new Error("Message received but value is not equal to expected value, value was: [" + result + "]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Error("Unknown MQTT Check Type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to MQTT Broker, subscribe to topic and receive message as String
|
||||||
|
* @param {string} hostname Hostname / address of machine to test
|
||||||
|
* @param {string} topic MQTT topic
|
||||||
|
* @param {object} options MQTT options. Contains port, username,
|
||||||
|
* password and interval (interval defaults to 20)
|
||||||
|
* @returns {Promise<string>} Received MQTT message
|
||||||
|
*/
|
||||||
|
mqttAsync(hostname, topic, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const { port, username, password, interval = 20 } = options;
|
||||||
|
|
||||||
|
// Adds MQTT protocol to the hostname if not already present
|
||||||
|
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) {
|
||||||
|
hostname = "mqtt://" + hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutID = setTimeout(() => {
|
||||||
|
log.debug("mqtt", "MQTT timeout triggered");
|
||||||
|
client.end();
|
||||||
|
reject(new Error("Timeout, Message not received"));
|
||||||
|
}, interval * 1000 * 0.8);
|
||||||
|
|
||||||
|
const mqttUrl = `${hostname}:${port}`;
|
||||||
|
|
||||||
|
log.debug("mqtt", `MQTT connecting to ${mqttUrl}`);
|
||||||
|
|
||||||
|
let client = mqtt.connect(mqttUrl, {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8)
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
log.debug("mqtt", "MQTT connected");
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.subscribe(topic, () => {
|
||||||
|
log.debug("mqtt", "MQTT subscribed to topic");
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
client.end();
|
||||||
|
clearTimeout(timeoutID);
|
||||||
|
reject(new Error("Cannot subscribe topic"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("error", (error) => {
|
||||||
|
client.end();
|
||||||
|
clearTimeout(timeoutID);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("message", (messageTopic, message) => {
|
||||||
|
if (messageTopic === topic) {
|
||||||
|
client.end();
|
||||||
|
clearTimeout(timeoutID);
|
||||||
|
resolve(message.toString("utf8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MqttMonitorType,
|
||||||
|
};
|
@ -0,0 +1,51 @@
|
|||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
const { DOWN, UP } = require("../../src/util");
|
||||||
|
|
||||||
|
class GrafanaOncall extends NotificationProvider {
|
||||||
|
name = "GrafanaOncall";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
const okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
|
if (!notification.GrafanaOncallURL) {
|
||||||
|
throw new Error("GrafanaOncallURL cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (heartbeatJSON === null) {
|
||||||
|
let grafanaupdata = {
|
||||||
|
title: "General notification",
|
||||||
|
message: msg,
|
||||||
|
state: "alerting",
|
||||||
|
};
|
||||||
|
await axios.post(notification.GrafanaOncallURL, grafanaupdata);
|
||||||
|
return okMsg;
|
||||||
|
} else if (heartbeatJSON["status"] === DOWN) {
|
||||||
|
let grafanadowndata = {
|
||||||
|
title: monitorJSON["name"] + " is down",
|
||||||
|
message: heartbeatJSON["msg"],
|
||||||
|
state: "alerting",
|
||||||
|
};
|
||||||
|
await axios.post(notification.GrafanaOncallURL, grafanadowndata);
|
||||||
|
return okMsg;
|
||||||
|
} else if (heartbeatJSON["status"] === UP) {
|
||||||
|
let grafanaupdata = {
|
||||||
|
title: monitorJSON["name"] + " is up",
|
||||||
|
message: heartbeatJSON["msg"],
|
||||||
|
state: "ok",
|
||||||
|
};
|
||||||
|
await axios.post(notification.GrafanaOncallURL, grafanaupdata);
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GrafanaOncall;
|
@ -0,0 +1,52 @@
|
|||||||
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
|
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
class HeiiOnCall extends NotificationProvider {
|
||||||
|
name = "HeiiOnCall";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
const okMsg = "Sent Successfully.";
|
||||||
|
const payload = heartbeatJSON || {};
|
||||||
|
|
||||||
|
const baseURL = await setting("primaryBaseURL");
|
||||||
|
if (baseURL && monitorJSON) {
|
||||||
|
payload["url"] = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + notification.heiiOnCallApiKey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const heiiUrl = `https://heiioncall.com/triggers/${notification.heiiOnCallTriggerId}/`;
|
||||||
|
// docs https://heiioncall.com/docs#manual-triggers
|
||||||
|
try {
|
||||||
|
if (!heartbeatJSON) {
|
||||||
|
// Testing or general notification like certificate expiry
|
||||||
|
payload["msg"] = msg;
|
||||||
|
await axios.post(heiiUrl + "alert", payload, config);
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heartbeatJSON.status === DOWN) {
|
||||||
|
await axios.post(heiiUrl + "alert", payload, config);
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
if (heartbeatJSON.status === UP) {
|
||||||
|
await axios.post(heiiUrl + "resolve", payload, config);
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HeiiOnCall;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue