commit
42bba73ffe
|
@ -1,6 +1,6 @@
|
||||||
/.idea
|
/.idea
|
||||||
/node_modules
|
/node_modules
|
||||||
/data
|
/data*
|
||||||
/cypress
|
/cypress
|
||||||
/out
|
/out
|
||||||
/test
|
/test
|
||||||
|
|
45
.eslintrc.js
45
.eslintrc.js
|
@ -1,6 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: [
|
ignorePatterns: [
|
||||||
"test/*",
|
"test/*.js",
|
||||||
|
"test/cypress",
|
||||||
"server/modules/apicache/*",
|
"server/modules/apicache/*",
|
||||||
"src/util.js"
|
"src/util.js"
|
||||||
],
|
],
|
||||||
|
@ -14,6 +15,7 @@ module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:vue/vue3-recommended",
|
"plugin:vue/vue3-recommended",
|
||||||
|
"plugin:jsdoc/recommended-error",
|
||||||
],
|
],
|
||||||
parser: "vue-eslint-parser",
|
parser: "vue-eslint-parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
@ -21,6 +23,9 @@ module.exports = {
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
requireConfigFile: false,
|
requireConfigFile: false,
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
"jsdoc"
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
"yoda": "error",
|
"yoda": "error",
|
||||||
eqeqeq: [ "warn", "smart" ],
|
eqeqeq: [ "warn", "smart" ],
|
||||||
|
@ -97,7 +102,43 @@ module.exports = {
|
||||||
}],
|
}],
|
||||||
"no-control-regex": "off",
|
"no-control-regex": "off",
|
||||||
"one-var": [ "error", "never" ],
|
"one-var": [ "error", "never" ],
|
||||||
"max-statements-per-line": [ "error", { "max": 1 }]
|
"max-statements-per-line": [ "error", { "max": 1 }],
|
||||||
|
"jsdoc/check-tag-names": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"definedTags": [ "link" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jsdoc/no-undefined-types": "off",
|
||||||
|
"jsdoc/no-defaults": [
|
||||||
|
"error",
|
||||||
|
{ "noOptionalParamNames": true }
|
||||||
|
],
|
||||||
|
"jsdoc/require-throws": "warn",
|
||||||
|
"jsdoc/require-jsdoc": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"FunctionDeclaration": true,
|
||||||
|
"MethodDefinition": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jsdoc/no-blank-block-descriptions": "error",
|
||||||
|
"jsdoc/require-returns-description": "warn",
|
||||||
|
"jsdoc/require-returns-check": [
|
||||||
|
"error",
|
||||||
|
{ "reportMissingReturnForUndefinedTypes": false }
|
||||||
|
],
|
||||||
|
"jsdoc/require-returns": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"forceRequireReturn": true,
|
||||||
|
"forceReturnsWithAsync": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jsdoc/require-param-type": "warn",
|
||||||
|
"jsdoc/require-param-description": "warn"
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,27 +71,28 @@ jobs:
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Use Node.js 14
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
|
|
||||||
e2e-tests:
|
# TODO: Temporarily disable, as it cannot pass the test in 2.0.0 yet
|
||||||
needs: [ check-linters ]
|
# e2e-tests:
|
||||||
runs-on: ubuntu-latest
|
# needs: [ check-linters ]
|
||||||
steps:
|
# runs-on: ubuntu-latest
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
# steps:
|
||||||
- uses: actions/checkout@v3
|
# - run: git config --global core.autocrlf false # Mainly for Windows
|
||||||
|
# - uses: actions/checkout@v3
|
||||||
- name: Use Node.js 14
|
#
|
||||||
uses: actions/setup-node@v3
|
# - name: Use Node.js 14
|
||||||
with:
|
# uses: actions/setup-node@v3
|
||||||
node-version: 14
|
# with:
|
||||||
- run: npm install
|
# node-version: 14
|
||||||
- run: npm run build
|
# - run: npm install
|
||||||
- run: npm run cy:test
|
# - run: npm run build
|
||||||
|
# - run: npm run cy:test
|
||||||
|
|
||||||
frontend-unit-tests:
|
frontend-unit-tests:
|
||||||
needs: [ check-linters ]
|
needs: [ check-linters ]
|
||||||
|
|
|
@ -7,6 +7,7 @@ dist-ssr
|
||||||
|
|
||||||
/data
|
/data
|
||||||
!/data/.gitkeep
|
!/data/.gitkeep
|
||||||
|
/data*
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
/private
|
/private
|
||||||
|
|
|
@ -0,0 +1,559 @@
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
const { log } = require("../src/util");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⚠️⚠️⚠️⚠️⚠️⚠️ DO NOT ADD ANYTHING HERE!
|
||||||
|
* IF YOU NEED TO ADD FIELDS, ADD IT TO ./db/knex_migrations
|
||||||
|
* See ./db/knex_migrations/README.md for more information
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function createTables() {
|
||||||
|
log.info("mariadb", "Creating basic tables for MariaDB");
|
||||||
|
const knex = R.knex;
|
||||||
|
|
||||||
|
// TODO: Should check later if it is really the final patch sql file.
|
||||||
|
|
||||||
|
// docker_host
|
||||||
|
await knex.schema.createTable("docker_host", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("user_id").unsigned().notNullable();
|
||||||
|
table.string("docker_daemon", 255);
|
||||||
|
table.string("docker_type", 255);
|
||||||
|
table.string("name", 255);
|
||||||
|
});
|
||||||
|
|
||||||
|
// group
|
||||||
|
await knex.schema.createTable("group", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("name", 255).notNullable();
|
||||||
|
table.datetime("created_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
table.boolean("public").notNullable().defaultTo(false);
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.integer("weight").notNullable().defaultTo(1000);
|
||||||
|
table.integer("status_page_id").unsigned();
|
||||||
|
});
|
||||||
|
|
||||||
|
// proxy
|
||||||
|
await knex.schema.createTable("proxy", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("user_id").unsigned().notNullable();
|
||||||
|
table.string("protocol", 10).notNullable();
|
||||||
|
table.string("host", 255).notNullable();
|
||||||
|
table.smallint("port").notNullable(); // TODO: Maybe a issue with MariaDB, need migration to int
|
||||||
|
table.boolean("auth").notNullable();
|
||||||
|
table.string("username", 255).nullable();
|
||||||
|
table.string("password", 255).nullable();
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.boolean("default").notNullable().defaultTo(false);
|
||||||
|
table.datetime("created_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
|
||||||
|
table.index("user_id", "proxy_user_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
// user
|
||||||
|
await knex.schema.createTable("user", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("username", 255).notNullable().unique().collate("utf8_general_ci");
|
||||||
|
table.string("password", 255);
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.string("timezone", 150);
|
||||||
|
table.string("twofa_secret", 64);
|
||||||
|
table.boolean("twofa_status").notNullable().defaultTo(false);
|
||||||
|
table.string("twofa_last_token", 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
// monitor
|
||||||
|
await knex.schema.createTable("monitor", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("name", 150);
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.integer("user_id").unsigned()
|
||||||
|
.references("id").inTable("user")
|
||||||
|
.onDelete("SET NULL")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("interval").notNullable().defaultTo(20);
|
||||||
|
table.text("url");
|
||||||
|
table.string("type", 20);
|
||||||
|
table.integer("weight").defaultTo(2000);
|
||||||
|
table.string("hostname", 255);
|
||||||
|
table.integer("port");
|
||||||
|
table.datetime("created_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
table.string("keyword", 255);
|
||||||
|
table.integer("maxretries").notNullable().defaultTo(0);
|
||||||
|
table.boolean("ignore_tls").notNullable().defaultTo(false);
|
||||||
|
table.boolean("upside_down").notNullable().defaultTo(false);
|
||||||
|
table.integer("maxredirects").notNullable().defaultTo(10);
|
||||||
|
table.text("accepted_statuscodes_json").notNullable().defaultTo("[\"200-299\"]");
|
||||||
|
table.string("dns_resolve_type", 5);
|
||||||
|
table.string("dns_resolve_server", 255);
|
||||||
|
table.string("dns_last_result", 255);
|
||||||
|
table.integer("retry_interval").notNullable().defaultTo(0);
|
||||||
|
table.string("push_token", 20).defaultTo(null);
|
||||||
|
table.text("method").notNullable().defaultTo("GET");
|
||||||
|
table.text("body").defaultTo(null);
|
||||||
|
table.text("headers").defaultTo(null);
|
||||||
|
table.text("basic_auth_user").defaultTo(null);
|
||||||
|
table.text("basic_auth_pass").defaultTo(null);
|
||||||
|
table.integer("docker_host").unsigned()
|
||||||
|
.references("id").inTable("docker_host");
|
||||||
|
table.string("docker_container", 255);
|
||||||
|
table.integer("proxy_id").unsigned()
|
||||||
|
.references("id").inTable("proxy");
|
||||||
|
table.boolean("expiry_notification").defaultTo(true);
|
||||||
|
table.text("mqtt_topic");
|
||||||
|
table.string("mqtt_success_message", 255);
|
||||||
|
table.string("mqtt_username", 255);
|
||||||
|
table.string("mqtt_password", 255);
|
||||||
|
table.string("database_connection_string", 2000);
|
||||||
|
table.text("database_query");
|
||||||
|
table.string("auth_method", 250);
|
||||||
|
table.text("auth_domain");
|
||||||
|
table.text("auth_workstation");
|
||||||
|
table.string("grpc_url", 255).defaultTo(null);
|
||||||
|
table.text("grpc_protobuf").defaultTo(null);
|
||||||
|
table.text("grpc_body").defaultTo(null);
|
||||||
|
table.text("grpc_metadata").defaultTo(null);
|
||||||
|
table.text("grpc_method").defaultTo(null);
|
||||||
|
table.text("grpc_service_name").defaultTo(null);
|
||||||
|
table.boolean("grpc_enable_tls").notNullable().defaultTo(false);
|
||||||
|
table.string("radius_username", 255);
|
||||||
|
table.string("radius_password", 255);
|
||||||
|
table.string("radius_calling_station_id", 50);
|
||||||
|
table.string("radius_called_station_id", 50);
|
||||||
|
table.string("radius_secret", 255);
|
||||||
|
table.integer("resend_interval").notNullable().defaultTo(0);
|
||||||
|
table.integer("packet_size").notNullable().defaultTo(56);
|
||||||
|
table.string("game", 255);
|
||||||
|
});
|
||||||
|
|
||||||
|
// heartbeat
|
||||||
|
await knex.schema.createTable("heartbeat", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.boolean("important").notNullable().defaultTo(false);
|
||||||
|
table.integer("monitor_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.smallint("status").notNullable();
|
||||||
|
|
||||||
|
table.text("msg");
|
||||||
|
table.datetime("time").notNullable();
|
||||||
|
table.integer("ping");
|
||||||
|
table.integer("duration").notNullable().defaultTo(0);
|
||||||
|
table.integer("down_count").notNullable().defaultTo(0);
|
||||||
|
|
||||||
|
table.index("important");
|
||||||
|
table.index([ "monitor_id", "time" ], "monitor_time_index");
|
||||||
|
table.index("monitor_id");
|
||||||
|
table.index([ "monitor_id", "important", "time" ], "monitor_important_time_index");
|
||||||
|
});
|
||||||
|
|
||||||
|
// incident
|
||||||
|
await knex.schema.createTable("incident", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("title", 255).notNullable();
|
||||||
|
table.text("content", 255).notNullable();
|
||||||
|
table.string("style", 30).notNullable().defaultTo("warning");
|
||||||
|
table.datetime("created_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
table.datetime("last_updated_date");
|
||||||
|
table.boolean("pin").notNullable().defaultTo(true);
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.integer("status_page_id").unsigned();
|
||||||
|
});
|
||||||
|
|
||||||
|
// maintenance
|
||||||
|
await knex.schema.createTable("maintenance", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("title", 150).notNullable();
|
||||||
|
table.text("description").notNullable();
|
||||||
|
table.integer("user_id").unsigned()
|
||||||
|
.references("id").inTable("user")
|
||||||
|
.onDelete("SET NULL")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.string("strategy", 50).notNullable().defaultTo("single");
|
||||||
|
table.datetime("start_date");
|
||||||
|
table.datetime("end_date");
|
||||||
|
table.time("start_time");
|
||||||
|
table.time("end_time");
|
||||||
|
table.string("weekdays", 250).defaultTo("[]");
|
||||||
|
table.text("days_of_month").defaultTo("[]");
|
||||||
|
table.integer("interval_day");
|
||||||
|
|
||||||
|
table.index("active");
|
||||||
|
table.index([ "strategy", "active" ], "manual_active");
|
||||||
|
table.index("user_id", "maintenance_user_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
// status_page
|
||||||
|
await knex.schema.createTable("status_page", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("slug", 255).notNullable().unique().collate("utf8_general_ci");
|
||||||
|
table.string("title", 255).notNullable();
|
||||||
|
table.text("description");
|
||||||
|
table.string("icon", 255).notNullable();
|
||||||
|
table.string("theme", 30).notNullable();
|
||||||
|
table.boolean("published").notNullable().defaultTo(true);
|
||||||
|
table.boolean("search_engine_index").notNullable().defaultTo(true);
|
||||||
|
table.boolean("show_tags").notNullable().defaultTo(false);
|
||||||
|
table.string("password");
|
||||||
|
table.datetime("created_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
table.datetime("modified_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
table.text("footer_text");
|
||||||
|
table.text("custom_css");
|
||||||
|
table.boolean("show_powered_by").notNullable().defaultTo(true);
|
||||||
|
table.string("google_analytics_tag_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
// maintenance_status_page
|
||||||
|
await knex.schema.createTable("maintenance_status_page", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
|
||||||
|
table.integer("status_page_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("status_page")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
|
||||||
|
table.integer("maintenance_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("maintenance")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
});
|
||||||
|
|
||||||
|
// maintenance_timeslot
|
||||||
|
await knex.schema.createTable("maintenance_timeslot", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("maintenance_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("maintenance")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.datetime("start_date").notNullable();
|
||||||
|
table.datetime("end_date");
|
||||||
|
table.boolean("generated_next").defaultTo(false);
|
||||||
|
|
||||||
|
table.index("maintenance_id");
|
||||||
|
table.index([ "maintenance_id", "start_date", "end_date" ], "active_timeslot_index");
|
||||||
|
table.index("generated_next", "generated_next_index");
|
||||||
|
});
|
||||||
|
|
||||||
|
// monitor_group
|
||||||
|
await knex.schema.createTable("monitor_group", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("monitor_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("group_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("group")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("weight").notNullable().defaultTo(1000);
|
||||||
|
table.boolean("send_url").notNullable().defaultTo(false);
|
||||||
|
|
||||||
|
table.index([ "monitor_id", "group_id" ], "fk");
|
||||||
|
});
|
||||||
|
// monitor_maintenance
|
||||||
|
await knex.schema.createTable("monitor_maintenance", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("monitor_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("maintenance_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("maintenance")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
|
||||||
|
table.index("maintenance_id", "maintenance_id_index2");
|
||||||
|
table.index("monitor_id", "monitor_id_index");
|
||||||
|
});
|
||||||
|
|
||||||
|
// notification
|
||||||
|
await knex.schema.createTable("notification", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("name", 255);
|
||||||
|
table.string("config", 255); // TODO: should use TEXT!
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
table.integer("user_id").unsigned();
|
||||||
|
table.boolean("is_default").notNullable().defaultTo(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// monitor_notification
|
||||||
|
await knex.schema.createTable("monitor_notification", (table) => {
|
||||||
|
table.increments("id").unsigned(); // TODO: no auto increment????
|
||||||
|
table.integer("monitor_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("notification_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("notification")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
|
||||||
|
table.index([ "monitor_id", "notification_id" ], "monitor_notification_index");
|
||||||
|
});
|
||||||
|
|
||||||
|
// tag
|
||||||
|
await knex.schema.createTable("tag", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("name", 255).notNullable();
|
||||||
|
table.string("color", 255).notNullable();
|
||||||
|
table.datetime("created_date").notNullable().defaultTo(knex.fn.now());
|
||||||
|
});
|
||||||
|
|
||||||
|
// monitor_tag
|
||||||
|
await knex.schema.createTable("monitor_tag", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("monitor_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.integer("tag_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("tag")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.text("value");
|
||||||
|
});
|
||||||
|
|
||||||
|
// monitor_tls_info
|
||||||
|
await knex.schema.createTable("monitor_tls_info", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("monitor_id").unsigned().notNullable(); //TODO: no fk ?
|
||||||
|
table.text("info_json");
|
||||||
|
});
|
||||||
|
|
||||||
|
// notification_sent_history
|
||||||
|
await knex.schema.createTable("notification_sent_history", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("type", 50).notNullable();
|
||||||
|
table.integer("monitor_id").unsigned().notNullable();
|
||||||
|
table.integer("days").notNullable();
|
||||||
|
table.unique([ "type", "monitor_id", "days" ]);
|
||||||
|
table.index([ "type", "monitor_id", "days" ], "good_index");
|
||||||
|
});
|
||||||
|
|
||||||
|
// setting
|
||||||
|
await knex.schema.createTable("setting", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("key", 200).notNullable().unique().collate("utf8_general_ci");
|
||||||
|
table.text("value");
|
||||||
|
table.string("type", 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
// status_page_cname
|
||||||
|
await knex.schema.createTable("status_page_cname", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.integer("status_page_id").unsigned()
|
||||||
|
.references("id").inTable("status_page")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.string("domain").notNullable().unique().collate("utf8_general_ci");
|
||||||
|
});
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
* Converted Patch here
|
||||||
|
*********************/
|
||||||
|
|
||||||
|
// 2023-06-30-1348-http-body-encoding.js
|
||||||
|
// ALTER TABLE monitor ADD http_body_encoding VARCHAR(25);
|
||||||
|
// UPDATE monitor SET http_body_encoding = 'json' WHERE (type = 'http' or type = 'keyword') AND http_body_encoding IS NULL;
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.string("http_body_encoding", 25);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex("monitor")
|
||||||
|
.where(function () {
|
||||||
|
this.where("type", "http").orWhere("type", "keyword");
|
||||||
|
})
|
||||||
|
.whereNull("http_body_encoding")
|
||||||
|
.update({
|
||||||
|
http_body_encoding: "json",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2023-06-30-1354-add-description-monitor.js
|
||||||
|
// ALTER TABLE monitor ADD description TEXT default null;
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.text("description").defaultTo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2023-06-30-1357-api-key-table.js
|
||||||
|
/*
|
||||||
|
CREATE TABLE [api_key] (
|
||||||
|
[id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
[key] VARCHAR(255) NOT NULL,
|
||||||
|
[name] VARCHAR(255) NOT NULL,
|
||||||
|
[user_id] INTEGER NOT NULL,
|
||||||
|
[created_date] DATETIME DEFAULT (DATETIME('now')) NOT NULL,
|
||||||
|
[active] BOOLEAN DEFAULT 1 NOT NULL,
|
||||||
|
[expires] DATETIME DEFAULT NULL,
|
||||||
|
CONSTRAINT FK_user FOREIGN KEY ([user_id]) REFERENCES [user]([id]) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
await knex.schema.createTable("api_key", function (table) {
|
||||||
|
table.increments("id").primary();
|
||||||
|
table.string("key", 255).notNullable();
|
||||||
|
table.string("name", 255).notNullable();
|
||||||
|
table.integer("user_id").unsigned().notNullable()
|
||||||
|
.references("id").inTable("user")
|
||||||
|
.onDelete("CASCADE")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
table.dateTime("created_date").defaultTo(knex.fn.now()).notNullable();
|
||||||
|
table.boolean("active").defaultTo(1).notNullable();
|
||||||
|
table.dateTime("expires").defaultTo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2023-06-30-1400-monitor-tls.js
|
||||||
|
/*
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD tls_ca TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD tls_cert TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD tls_key TEXT default null;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.text("tls_ca").defaultTo(null);
|
||||||
|
table.text("tls_cert").defaultTo(null);
|
||||||
|
table.text("tls_key").defaultTo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2023-06-30-1401-maintenance-cron.js
|
||||||
|
/*
|
||||||
|
-- 999 characters. https://stackoverflow.com/questions/46134830/maximum-length-for-cron-job
|
||||||
|
DROP TABLE maintenance_timeslot;
|
||||||
|
ALTER TABLE maintenance ADD cron TEXT;
|
||||||
|
ALTER TABLE maintenance ADD timezone VARCHAR(255);
|
||||||
|
ALTER TABLE maintenance ADD duration INTEGER;
|
||||||
|
*/
|
||||||
|
await knex.schema
|
||||||
|
.dropTableIfExists("maintenance_timeslot")
|
||||||
|
.table("maintenance", function (table) {
|
||||||
|
table.text("cron");
|
||||||
|
table.string("timezone", 255);
|
||||||
|
table.integer("duration");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2023-06-30-1413-add-parent-monitor.js.
|
||||||
|
/*
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD parent INTEGER REFERENCES [monitor] ([id]) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.integer("parent").unsigned()
|
||||||
|
.references("id").inTable("monitor")
|
||||||
|
.onDelete("SET NULL")
|
||||||
|
.onUpdate("CASCADE");
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-add-invert-keyword.sql
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD invert_keyword BOOLEAN default 0 not null;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.boolean("invert_keyword").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-added-json-query.sql
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD json_path TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD expected_value VARCHAR(255);
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.text("json_path");
|
||||||
|
table.string("expected_value", 255);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-added-kafka-producer.sql
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD kafka_producer_topic VARCHAR(255);
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD kafka_producer_brokers TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD kafka_producer_ssl INTEGER;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD kafka_producer_allow_auto_topic_creation VARCHAR(255);
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD kafka_producer_sasl_options TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD kafka_producer_message TEXT;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.string("kafka_producer_topic", 255);
|
||||||
|
table.text("kafka_producer_brokers");
|
||||||
|
table.integer("kafka_producer_ssl");
|
||||||
|
table.string("kafka_producer_allow_auto_topic_creation", 255);
|
||||||
|
table.text("kafka_producer_sasl_options");
|
||||||
|
table.text("kafka_producer_message");
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-add-certificate-expiry-status-page.sql
|
||||||
|
ALTER TABLE status_page
|
||||||
|
ADD show_certificate_expiry BOOLEAN default 0 NOT NULL;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("status_page", function (table) {
|
||||||
|
table.boolean("show_certificate_expiry").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-monitor-oauth-cc.sql
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD oauth_client_id TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD oauth_client_secret TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD oauth_token_url TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD oauth_scopes TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD oauth_auth_method TEXT default null;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.text("oauth_client_id").defaultTo(null);
|
||||||
|
table.text("oauth_client_secret").defaultTo(null);
|
||||||
|
table.text("oauth_token_url").defaultTo(null);
|
||||||
|
table.text("oauth_scopes").defaultTo(null);
|
||||||
|
table.text("oauth_auth_method").defaultTo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-add-timeout-monitor.sql
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD timeout DOUBLE default 0 not null;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.double("timeout").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
patch-add-gamedig-given-port.sql
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD gamedig_given_port_only BOOLEAN default 1 not null;
|
||||||
|
*/
|
||||||
|
await knex.schema.table("monitor", function (table) {
|
||||||
|
table.boolean("gamedig_given_port_only").defaultTo(1).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("mariadb", "Created basic tables for MariaDB");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createTables,
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.createTable("stat_minutely", function (table) {
|
||||||
|
table.increments("id");
|
||||||
|
table.comment("This table contains the minutely 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 minute");
|
||||||
|
table.float("ping").notNullable().comment("Average ping in milliseconds");
|
||||||
|
table.smallint("up").notNullable();
|
||||||
|
table.smallint("down").notNullable();
|
||||||
|
|
||||||
|
table.unique([ "monitor_id", "timestamp" ]);
|
||||||
|
})
|
||||||
|
.createTable("stat_daily", function (table) {
|
||||||
|
table.increments("id");
|
||||||
|
table.comment("This table contains the daily 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 day");
|
||||||
|
table.float("ping").notNullable().comment("Average ping in milliseconds");
|
||||||
|
table.smallint("up").notNullable();
|
||||||
|
table.smallint("down").notNullable();
|
||||||
|
|
||||||
|
table.unique([ "monitor_id", "timestamp" ]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.dropTable("stat_minutely")
|
||||||
|
.dropTable("stat_daily");
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
exports.up = function (knex) {
|
||||||
|
// Add new column heartbeat.end_time
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("heartbeat", function (table) {
|
||||||
|
table.datetime("end_time").nullable().defaultTo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
// Rename heartbeat.start_time to heartbeat.time
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("heartbeat", function (table) {
|
||||||
|
table.dropColumn("end_time");
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,55 @@
|
||||||
|
## Info
|
||||||
|
|
||||||
|
https://knexjs.org/guide/migrations.html#knexfile-in-other-languages
|
||||||
|
|
||||||
|
## Basic rules
|
||||||
|
- All tables must have a primary key named `id`
|
||||||
|
- Filename format: `YYYY-MM-DD-HHMM-patch-name.js`
|
||||||
|
- Avoid native SQL syntax, use knex methods, because Uptime Kuma supports SQLite and MariaDB.
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
```js
|
||||||
|
exports.up = function(knex) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// exports.config = { transaction: false };
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Filename: 2023-06-30-1348-create-user-and-product.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema
|
||||||
|
.createTable('user', function (table) {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('first_name', 255).notNullable();
|
||||||
|
table.string('last_name', 255).notNullable();
|
||||||
|
})
|
||||||
|
.createTable('product', function (table) {
|
||||||
|
table.increments('id');
|
||||||
|
table.decimal('price').notNullable();
|
||||||
|
table.string('name', 1000).notNullable();
|
||||||
|
}).then(() => {
|
||||||
|
knex("products").insert([
|
||||||
|
{ price: 10, name: "Apple" },
|
||||||
|
{ price: 20, name: "Orange" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema
|
||||||
|
.dropTable("product")
|
||||||
|
.dropTable("user");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
https://knexjs.org/guide/migrations.html#transactions-in-migrations
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Don't create a new migration file here
|
||||||
|
|
||||||
|
Please go to ./db/knex_migrations/README.md
|
|
@ -1,8 +0,0 @@
|
||||||
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
|
|
||||||
FROM node:16-alpine3.12
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install apprise, iputils for non-root ping, setpriv
|
|
||||||
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib git && \
|
|
||||||
pip3 --no-cache-dir install apprise==1.4.0 && \
|
|
||||||
rm -rf /root/.cache
|
|
|
@ -1,12 +1,11 @@
|
||||||
# DON'T UPDATE TO node:14-bullseye-slim, see #372.
|
|
||||||
# If the image changed, the second stage image should be changed too
|
# If the image changed, the second stage image should be changed too
|
||||||
FROM node:16-buster-slim
|
FROM node:20-bookworm-slim AS base2-slim
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
||||||
# python3* = apprise's dependencies
|
# apprise = for notifications (From testing repo)
|
||||||
# sqlite3 = for debugging
|
# sqlite3 = for debugging
|
||||||
# iputils-ping = for ping
|
# iputils-ping = for ping
|
||||||
# util-linux = for setpriv (Should be dropped in 2.0.0?)
|
# util-linux = for setpriv (Should be dropped in 2.0.0?)
|
||||||
|
@ -15,29 +14,25 @@ WORKDIR /app
|
||||||
# ca-certificates = keep the cert up-to-date
|
# ca-certificates = keep the cert up-to-date
|
||||||
# sudo = for start service nscd with non-root user
|
# sudo = for start service nscd with non-root user
|
||||||
# nscd = for better DNS caching
|
# nscd = for better DNS caching
|
||||||
# (pip) apprise = for notifications
|
RUN echo "deb http://deb.debian.org/debian testing main" >> /etc/apt/sources.list && \
|
||||||
RUN apt-get update && \
|
apt update && \
|
||||||
apt-get --yes --no-install-recommends install \
|
apt --yes --no-install-recommends -t testing install apprise sqlite3 ca-certificates && \
|
||||||
python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
apt --yes --no-install-recommends -t stable install \
|
||||||
sqlite3 \
|
|
||||||
iputils-ping \
|
iputils-ping \
|
||||||
util-linux \
|
util-linux \
|
||||||
dumb-init \
|
dumb-init \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
|
||||||
sudo \
|
sudo \
|
||||||
nscd && \
|
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
|
||||||
|
|
||||||
|
|
||||||
# Install cloudflared
|
# Install cloudflared
|
||||||
RUN set -eux && \
|
RUN curl https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
|
||||||
mkdir -p --mode=0755 /usr/share/keyrings && \
|
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bullseye main' | tee /etc/apt/sources.list.d/cloudflared.list && \
|
||||||
curl --fail --show-error --silent --location --insecure https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
|
apt update && \
|
||||||
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | tee /etc/apt/sources.list.d/cloudflared.list && \
|
apt install --yes --no-install-recommends -t stable cloudflared && \
|
||||||
apt-get update && \
|
|
||||||
apt-get install --yes --no-install-recommends cloudflared && \
|
|
||||||
cloudflared version && \
|
cloudflared version && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt --yes autoremove
|
apt --yes autoremove
|
||||||
|
@ -46,3 +41,16 @@ RUN set -eux && \
|
||||||
COPY ./docker/etc/nscd.conf /etc/nscd.conf
|
COPY ./docker/etc/nscd.conf /etc/nscd.conf
|
||||||
COPY ./docker/etc/sudoers /etc/sudoers
|
COPY ./docker/etc/sudoers /etc/sudoers
|
||||||
|
|
||||||
|
|
||||||
|
# Full Base Image
|
||||||
|
# MariaDB, Chromium and fonts
|
||||||
|
# Not working for armv7, so use the older version (10.5) of MariaDB from the debian repo
|
||||||
|
# curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | bash -s -- --mariadb-server-version="mariadb-11.1" && \
|
||||||
|
FROM base2-slim AS base2
|
||||||
|
ENV UPTIME_KUMA_ENABLE_EMBEDDED_MARIADB=1
|
||||||
|
RUN apt update && \
|
||||||
|
apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk mariadb-server && \
|
||||||
|
apt --yes remove curl && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
apt --yes autoremove && \
|
||||||
|
chown -R node:node /var/lib/mysql
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
uptime-kuma:
|
||||||
|
container_name: uptime-kuma-dev
|
||||||
|
image: louislam/uptime-kuma:nightly2
|
||||||
|
volumes:
|
||||||
|
#- ./data:/app/data
|
||||||
|
- ../server:/app/server
|
||||||
|
- ../db:/app/db
|
||||||
|
ports:
|
||||||
|
- "3001:3001" # <Host Port>:<Container Port>
|
||||||
|
- "3307:3306"
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
# Simple docker-compose.yml
|
version: '3.8'
|
||||||
# You can change your port or volume location
|
|
||||||
|
|
||||||
version: '3.3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
uptime-kuma:
|
uptime-kuma:
|
||||||
image: louislam/uptime-kuma:1
|
image: louislam/uptime-kuma:2
|
||||||
container_name: uptime-kuma
|
container_name: uptime-kuma
|
||||||
volumes:
|
volumes:
|
||||||
- ./uptime-kuma-data:/app/data
|
- uptime-kuma:/app/data
|
||||||
ports:
|
ports:
|
||||||
- 3001:3001 # <Host Port>:<Container Port>
|
- "3001:3001" # <Host Port>:<Container Port>
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
uptime-kuma:
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
ARG BASE_IMAGE=louislam/uptime-kuma:base2
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Build in Golang
|
# Build in Golang
|
||||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
# Run npm run build-healthcheck-armv7 in the host first, otherwise it will be super slow where it is building the armv7 healthcheck
|
||||||
# Check file: builder-go.dockerfile
|
# Check file: builder-go.dockerfile
|
||||||
############################################
|
############################################
|
||||||
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
||||||
|
@ -8,49 +10,47 @@ FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
||||||
############################################
|
############################################
|
||||||
# Build in Node.js
|
# Build in Node.js
|
||||||
############################################
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS build
|
FROM louislam/uptime-kuma:base2 AS build
|
||||||
|
USER node
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||||
COPY .npmrc .npmrc
|
COPY --chown=node:node .npmrc .npmrc
|
||||||
COPY package.json package.json
|
COPY --chown=node:node package.json package.json
|
||||||
COPY package-lock.json package-lock.json
|
COPY --chown=node:node package-lock.json package-lock.json
|
||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
|
COPY --chown=node:node --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
|
||||||
RUN chmod +x /app/extra/entrypoint.sh
|
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# ⭐ Main Image
|
# ⭐ Main Image
|
||||||
############################################
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS release
|
FROM $BASE_IMAGE AS release
|
||||||
|
USER node
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV UPTIME_KUMA_IS_CONTAINER=1
|
ENV UPTIME_KUMA_IS_CONTAINER=1
|
||||||
|
|
||||||
# Copy app files from build layer
|
# Copy app files from build layer
|
||||||
COPY --from=build /app /app
|
COPY --chown=node:node --from=build /app /app
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
VOLUME ["/app/data"]
|
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD extra/healthcheck
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD extra/healthcheck
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||||
CMD ["node", "server/server.js"]
|
CMD ["node", "server/server.js"]
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Mark as Nightly
|
# Mark as Nightly
|
||||||
############################################
|
############################################
|
||||||
FROM release AS nightly
|
FROM release AS nightly
|
||||||
|
USER node
|
||||||
RUN npm run mark-as-nightly
|
RUN npm run mark-as-nightly
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Build an image for testing pr
|
# Build an image for testing pr
|
||||||
############################################
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS pr-test
|
FROM louislam/uptime-kuma:base2 AS pr-test2
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||||
|
|
||||||
## Install Git
|
## Install Git
|
||||||
|
@ -78,7 +78,7 @@ CMD ["npm", "run", "start-pr-test"]
|
||||||
############################################
|
############################################
|
||||||
# Upload the artifact to Github
|
# Upload the artifact to Github
|
||||||
############################################
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS upload-artifact
|
FROM louislam/uptime-kuma:base2 AS upload-artifact
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt --yes install curl file
|
apt --yes install curl file
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
FROM louislam/uptime-kuma:base-alpine AS build
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
|
||||||
|
|
||||||
COPY .npmrc .npmrc
|
|
||||||
COPY package.json package.json
|
|
||||||
COPY package-lock.json package-lock.json
|
|
||||||
RUN npm ci --omit=dev
|
|
||||||
COPY . .
|
|
||||||
RUN chmod +x /app/extra/entrypoint.sh
|
|
||||||
|
|
||||||
FROM louislam/uptime-kuma:base-alpine AS release
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy app files from build layer
|
|
||||||
COPY --from=build /app /app
|
|
||||||
|
|
||||||
EXPOSE 3001
|
|
||||||
VOLUME ["/app/data"]
|
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
|
||||||
CMD ["node", "server/server.js"]
|
|
||||||
|
|
||||||
|
|
||||||
FROM release AS nightly
|
|
||||||
RUN npm run mark-as-nightly
|
|
|
@ -36,6 +36,8 @@ if (! exists) {
|
||||||
/**
|
/**
|
||||||
* Commit updated files
|
* Commit updated files
|
||||||
* @param {string} version Version to update to
|
* @param {string} version Version to update to
|
||||||
|
* @returns {void}
|
||||||
|
* @throws Error committing files
|
||||||
*/
|
*/
|
||||||
function commit(version) {
|
function commit(version) {
|
||||||
let msg = "Update to " + version;
|
let msg = "Update to " + version;
|
||||||
|
@ -55,6 +57,7 @@ function commit(version) {
|
||||||
/**
|
/**
|
||||||
* Create a tag with the specified version
|
* Create a tag with the specified version
|
||||||
* @param {string} version Tag to create
|
* @param {string} version Tag to create
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function tag(version) {
|
function tag(version) {
|
||||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
||||||
|
@ -68,6 +71,7 @@ function tag(version) {
|
||||||
* Check if a tag exists for the specified version
|
* Check if a tag exists for the specified version
|
||||||
* @param {string} version Version to check
|
* @param {string} version Version to check
|
||||||
* @returns {boolean} Does the tag already exist
|
* @returns {boolean} Does the tag already exist
|
||||||
|
* @throws Version is not valid
|
||||||
*/
|
*/
|
||||||
function tagExists(version) {
|
function tagExists(version) {
|
||||||
if (! version) {
|
if (! version) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ download(url);
|
||||||
/**
|
/**
|
||||||
* Downloads the latest version of the dist from a GitHub release.
|
* Downloads the latest version of the dist from a GitHub release.
|
||||||
* @param {string} url The URL to download from.
|
* @param {string} url The URL to download from.
|
||||||
|
* @returns {void}
|
||||||
*
|
*
|
||||||
* Generated by Trelent
|
* Generated by Trelent
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
# set -e Exit the script if an error happens
|
|
||||||
set -e
|
|
||||||
PUID=${PUID=0}
|
|
||||||
PGID=${PGID=0}
|
|
||||||
|
|
||||||
files_ownership () {
|
|
||||||
# -h Changes the ownership of an encountered symbolic link and not that of the file or directory pointed to by the symbolic link.
|
|
||||||
# -R Recursively descends the specified directories
|
|
||||||
# -c Like verbose but report only when a change is made
|
|
||||||
chown -hRc "$PUID":"$PGID" /app/data
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "==> Performing startup jobs and maintenance tasks"
|
|
||||||
files_ownership
|
|
||||||
|
|
||||||
echo "==> Starting application with user $PUID group $PGID"
|
|
||||||
|
|
||||||
# --clear-groups Clear supplementary groups.
|
|
||||||
exec setpriv --reuid "$PUID" --regid "$PGID" --clear-groups "$@"
|
|
|
@ -4,12 +4,12 @@ const fs = require("fs");
|
||||||
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
|
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
|
||||||
* or the `recursive` property removing completely in the future Node.js version.
|
* or the `recursive` property removing completely in the future Node.js version.
|
||||||
* See the link below.
|
* See the link below.
|
||||||
*
|
|
||||||
* @todo Once we drop the support for Node.js v14 (or at least versions before v14.14.0), we can safely replace this function with `fs.rmSync`, since `fs.rmSync` was add in Node.js v14.14.0 and currently we supports all the Node.js v14 versions that include the versions before the v14.14.0, and this function have almost the same signature with `fs.rmSync`.
|
* @todo Once we drop the support for Node.js v14 (or at least versions before v14.14.0), we can safely replace this function with `fs.rmSync`, since `fs.rmSync` was add in Node.js v14.14.0 and currently we supports all the Node.js v14 versions that include the versions before the v14.14.0, and this function have almost the same signature with `fs.rmSync`.
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true- the deprecation infomation of `fs.rmdirSync`
|
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true- the deprecation infomation of `fs.rmdirSync`
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/fs.html#fsrmsyncpath-options the document of `fs.rmSync`
|
* @link https://nodejs.org/docs/latest-v16.x/api/fs.html#fsrmsyncpath-options the document of `fs.rmSync`
|
||||||
* @param {fs.PathLike} path Valid types for path values in "fs".
|
* @param {fs.PathLike} path Valid types for path values in "fs".
|
||||||
* @param {fs.RmDirOptions} [options] options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
|
* @param {fs.RmDirOptions} options options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const rmSync = (path, options) => {
|
const rmSync = (path, options) => {
|
||||||
if (typeof fs.rmSync === "function") {
|
if (typeof fs.rmSync === "function") {
|
||||||
|
|
|
@ -12,7 +12,7 @@ const rl = readline.createInterface({
|
||||||
});
|
});
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
Database.init(args);
|
Database.initDataDir(args);
|
||||||
await Database.connect();
|
await Database.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -13,7 +13,7 @@ const rl = readline.createInterface({
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
console.log("Connecting the database");
|
console.log("Connecting the database");
|
||||||
Database.init(args);
|
Database.initDataDir(args);
|
||||||
await Database.connect(false, false, true);
|
await Database.connect(false, false, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -138,7 +138,7 @@ server.listen({
|
||||||
/**
|
/**
|
||||||
* Get human readable request type from request code
|
* Get human readable request type from request code
|
||||||
* @param {number} code Request code to translate
|
* @param {number} code Request code to translate
|
||||||
* @returns {string} Human readable request type
|
* @returns {string|void} Human readable request type
|
||||||
*/
|
*/
|
||||||
function type(code) {
|
function type(code) {
|
||||||
for (let name in Packet.TYPE) {
|
for (let name in Packet.TYPE) {
|
||||||
|
|
|
@ -7,11 +7,17 @@ class SimpleMqttServer {
|
||||||
aedes = require("aedes")();
|
aedes = require("aedes")();
|
||||||
server = require("net").createServer(this.aedes.handle);
|
server = require("net").createServer(this.aedes.handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} port Port to listen on
|
||||||
|
*/
|
||||||
constructor(port) {
|
constructor(port) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Start the MQTT server */
|
/**
|
||||||
|
* Start the MQTT server
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
start() {
|
start() {
|
||||||
this.server.listen(this.port, () => {
|
this.server.listen(this.port, () => {
|
||||||
console.log("server started and listening on port ", this.port);
|
console.log("server started and listening on port ", this.port);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import rmSync from "../fs-rmSync.js";
|
||||||
* created with this code if one does not already exist
|
* created with this code if one does not already exist
|
||||||
* @param {string} baseLang The second base language file to copy. This
|
* @param {string} baseLang The second base language file to copy. This
|
||||||
* will be ignored if set to "en" as en.js is copied by default
|
* will be ignored if set to "en" as en.js is copied by default
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function copyFiles(langCode, baseLang) {
|
function copyFiles(langCode, baseLang) {
|
||||||
if (fs.existsSync("./languages")) {
|
if (fs.existsSync("./languages")) {
|
||||||
|
@ -33,7 +34,8 @@ function copyFiles(langCode, baseLang) {
|
||||||
/**
|
/**
|
||||||
* Update the specified language file
|
* Update the specified language file
|
||||||
* @param {string} langCode Language code to update
|
* @param {string} langCode Language code to update
|
||||||
* @param {string} baseLang Second language to copy keys from
|
* @param {string} baseLangCode Second language to copy keys from
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
async function updateLanguage(langCode, baseLangCode) {
|
async function updateLanguage(langCode, baseLangCode) {
|
||||||
const en = (await import("./languages/en.js")).default;
|
const en = (await import("./languages/en.js")).default;
|
||||||
|
|
|
@ -39,6 +39,8 @@ if (! exists) {
|
||||||
/**
|
/**
|
||||||
* Commit updated files
|
* Commit updated files
|
||||||
* @param {string} version Version to update to
|
* @param {string} version Version to update to
|
||||||
|
* @returns {void}
|
||||||
|
* @throws Error when committing files
|
||||||
*/
|
*/
|
||||||
function commit(version) {
|
function commit(version) {
|
||||||
let msg = "Update to " + version;
|
let msg = "Update to " + version;
|
||||||
|
@ -55,6 +57,7 @@ function commit(version) {
|
||||||
/**
|
/**
|
||||||
* Create a tag with the specified version
|
* Create a tag with the specified version
|
||||||
* @param {string} version Tag to create
|
* @param {string} version Tag to create
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function tag(version) {
|
function tag(version) {
|
||||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
||||||
|
@ -65,6 +68,7 @@ function tag(version) {
|
||||||
* Check if a tag exists for the specified version
|
* Check if a tag exists for the specified version
|
||||||
* @param {string} version Version to check
|
* @param {string} version Version to check
|
||||||
* @returns {boolean} Does the tag already exist
|
* @returns {boolean} Does the tag already exist
|
||||||
|
* @throws Version is not valid
|
||||||
*/
|
*/
|
||||||
function tagExists(version) {
|
function tagExists(version) {
|
||||||
if (! version) {
|
if (! version) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ updateWiki(newVersion);
|
||||||
/**
|
/**
|
||||||
* Update the wiki with new version number
|
* Update the wiki with new version number
|
||||||
* @param {string} newVersion Version to update to
|
* @param {string} newVersion Version to update to
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function updateWiki(newVersion) {
|
function updateWiki(newVersion) {
|
||||||
const wikiDir = "./tmp/wiki";
|
const wikiDir = "./tmp/wiki";
|
||||||
|
@ -46,6 +47,7 @@ function updateWiki(newVersion) {
|
||||||
/**
|
/**
|
||||||
* Check if a directory exists and then delete it
|
* Check if a directory exists and then delete it
|
||||||
* @param {string} dir Directory to delete
|
* @param {string} dir Directory to delete
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function safeDelete(dir) {
|
function safeDelete(dir) {
|
||||||
if (fs.existsSync(dir)) {
|
if (fs.existsSync(dir)) {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -24,20 +24,22 @@
|
||||||
"start-server": "node server/server.js",
|
"start-server": "node server/server.js",
|
||||||
"start-server-dev": "cross-env NODE_ENV=development node server/server.js",
|
"start-server-dev": "cross-env NODE_ENV=development node server/server.js",
|
||||||
"build": "vite build --config ./config/vite.config.js",
|
"build": "vite build --config ./config/vite.config.js",
|
||||||
"test": "node test/prepare-test-server.js && npm run jest-backend",
|
"test": "node test/prepare-test-server.js && npm run test-backend",
|
||||||
"test-with-build": "npm run build && npm test",
|
"test-with-build": "npm run build && npm test",
|
||||||
|
"test-backend": "node test/backend-test-entry.js && npm run jest-backend",
|
||||||
|
"test-backend:14": "cross-env TEST_BACKEND=1 NODE_OPTIONS=\"--experimental-abortcontroller --no-warnings\" node--test test/backend-test",
|
||||||
|
"test-backend:18": "cross-env TEST_BACKEND=1 node --test test/backend-test",
|
||||||
"jest-backend": "cross-env TEST_BACKEND=1 jest --runInBand --detectOpenHandles --forceExit --config=./config/jest-backend.config.js",
|
"jest-backend": "cross-env TEST_BACKEND=1 jest --runInBand --detectOpenHandles --forceExit --config=./config/jest-backend.config.js",
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
||||||
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
|
"build-docker": "npm run build && npm run build-docker-full && npm run build-docker-slim",
|
||||||
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
|
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2 --target base2 . --push",
|
||||||
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
|
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2-slim --target base2-slim . --push",
|
||||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
||||||
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
|
"build-docker-slim": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-slim -t louislam/uptime-kuma:$VERSION-slim --target release --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
||||||
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
|
"build-docker-full": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2 -t louislam/uptime-kuma:$VERSION --target release . --push",
|
||||||
"build-docker-nightly": "node ./extra/test-docker.js && npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
"build-docker-nightly": "node ./extra/test-docker.js && npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly2 --target nightly . --push",
|
||||||
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
|
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .",
|
||||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
|
||||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
||||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||||
"setup": "git checkout 1.23.1 && npm ci --production && npm run download-dist",
|
"setup": "git checkout 1.23.1 && npm ci --production && npm run download-dist",
|
||||||
|
@ -48,7 +50,6 @@
|
||||||
"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-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-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.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-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 .",
|
||||||
|
@ -57,7 +58,6 @@
|
||||||
"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",
|
||||||
"ncu-patch": "npm-check-updates -u -t patch",
|
|
||||||
"release-final": "node ./extra/test-docker.js && node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
"release-final": "node ./extra/test-docker.js && node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||||
"release-beta": "node ./extra/test-docker.js && node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
"release-beta": "node ./extra/test-docker.js && node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
||||||
"git-remove-tag": "git tag -d",
|
"git-remove-tag": "git tag -d",
|
||||||
|
@ -69,7 +69,9 @@
|
||||||
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
||||||
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
|
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
|
||||||
"deploy-demo-server": "node extra/deploy-demo-server.js",
|
"deploy-demo-server": "node extra/deploy-demo-server.js",
|
||||||
"sort-contributors": "node extra/sort-contributors.js"
|
"sort-contributors": "node extra/sort-contributors.js",
|
||||||
|
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
||||||
|
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.7.3",
|
"@grpc/grpc-js": "~1.7.3",
|
||||||
|
@ -106,6 +108,7 @@
|
||||||
"jsonwebtoken": "~9.0.0",
|
"jsonwebtoken": "~9.0.0",
|
||||||
"jwt-decode": "~3.1.2",
|
"jwt-decode": "~3.1.2",
|
||||||
"kafkajs": "^2.2.4",
|
"kafkajs": "^2.2.4",
|
||||||
|
"knex": "^2.4.2",
|
||||||
"limiter": "~2.1.0",
|
"limiter": "~2.1.0",
|
||||||
"liquidjs": "^10.7.0",
|
"liquidjs": "^10.7.0",
|
||||||
"mongodb": "~4.14.0",
|
"mongodb": "~4.14.0",
|
||||||
|
@ -165,6 +168,7 @@
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
"dompurify": "~2.4.3",
|
"dompurify": "~2.4.3",
|
||||||
"eslint": "~8.14.0",
|
"eslint": "~8.14.0",
|
||||||
|
"eslint-plugin-jsdoc": "^46.4.6",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
"favico.js": "~0.3.10",
|
"favico.js": "~0.3.10",
|
||||||
"jest": "~29.6.1",
|
"jest": "~29.6.1",
|
||||||
|
@ -180,6 +184,7 @@
|
||||||
"stylelint": "^15.10.1",
|
"stylelint": "^15.10.1",
|
||||||
"stylelint-config-standard": "~25.0.0",
|
"stylelint-config-standard": "~25.0.0",
|
||||||
"terser": "~5.15.0",
|
"terser": "~5.15.0",
|
||||||
|
"test": "~3.3.0",
|
||||||
"timezones-list": "~3.0.1",
|
"timezones-list": "~3.0.1",
|
||||||
"typescript": "~4.4.4",
|
"typescript": "~4.4.4",
|
||||||
"v-pagination-3": "~0.1.7",
|
"v-pagination-3": "~0.1.7",
|
||||||
|
|
|
@ -9,9 +9,9 @@ const dayjs = require("dayjs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login to web app
|
* Login to web app
|
||||||
* @param {string} username
|
* @param {string} username Username to login with
|
||||||
* @param {string} password
|
* @param {string} password Password to login with
|
||||||
* @returns {Promise<(Bean|null)>}
|
* @returns {Promise<(Bean|null)>} User or null if login failed
|
||||||
*/
|
*/
|
||||||
exports.login = async function (username, password) {
|
exports.login = async function (username, password) {
|
||||||
if (typeof username !== "string" || typeof password !== "string") {
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
|
@ -39,6 +39,7 @@ exports.login = async function (username, password) {
|
||||||
/**
|
/**
|
||||||
* Validate a provided API key
|
* Validate a provided API key
|
||||||
* @param {string} key API key to verify
|
* @param {string} key API key to verify
|
||||||
|
* @returns {boolean} API is ok?
|
||||||
*/
|
*/
|
||||||
async function verifyAPIKey(key) {
|
async function verifyAPIKey(key) {
|
||||||
if (typeof key !== "string") {
|
if (typeof key !== "string") {
|
||||||
|
@ -73,9 +74,10 @@ async function verifyAPIKey(key) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom authorizer for express-basic-auth
|
* Custom authorizer for express-basic-auth
|
||||||
* @param {string} username
|
* @param {string} username Username to login with
|
||||||
* @param {string} password
|
* @param {string} password Password to login with
|
||||||
* @param {authCallback} callback
|
* @param {authCallback} callback Callback to handle login result
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function apiAuthorizer(username, password, callback) {
|
function apiAuthorizer(username, password, callback) {
|
||||||
// API Rate Limit
|
// API Rate Limit
|
||||||
|
@ -99,9 +101,10 @@ function apiAuthorizer(username, password, callback) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom authorizer for express-basic-auth
|
* Custom authorizer for express-basic-auth
|
||||||
* @param {string} username
|
* @param {string} username Username to login with
|
||||||
* @param {string} password
|
* @param {string} password Password to login with
|
||||||
* @param {authCallback} callback
|
* @param {authCallback} callback Callback to handle login result
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function userAuthorizer(username, password, callback) {
|
function userAuthorizer(username, password, callback) {
|
||||||
// Login Rate Limit
|
// Login Rate Limit
|
||||||
|
@ -126,7 +129,8 @@ function userAuthorizer(username, password, callback) {
|
||||||
* Use basic auth if auth is not disabled
|
* Use basic auth if auth is not disabled
|
||||||
* @param {express.Request} req Express request object
|
* @param {express.Request} req Express request object
|
||||||
* @param {express.Response} res Express response object
|
* @param {express.Response} res Express response object
|
||||||
* @param {express.NextFunction} next
|
* @param {express.NextFunction} next Next handler in chain
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
exports.basicAuth = async function (req, res, next) {
|
exports.basicAuth = async function (req, res, next) {
|
||||||
const middleware = basicAuth({
|
const middleware = basicAuth({
|
||||||
|
@ -148,7 +152,8 @@ exports.basicAuth = async function (req, res, next) {
|
||||||
* Use use API Key if API keys enabled, else use basic auth
|
* Use use API Key if API keys enabled, else use basic auth
|
||||||
* @param {express.Request} req Express request object
|
* @param {express.Request} req Express request object
|
||||||
* @param {express.Response} res Express response object
|
* @param {express.Response} res Express response object
|
||||||
* @param {express.NextFunction} next
|
* @param {express.NextFunction} next Next handler in chain
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
exports.apiAuth = async function (req, res, next) {
|
exports.apiAuth = async function (req, res, next) {
|
||||||
if (!await Settings.get("disableAuth")) {
|
if (!await Settings.get("disableAuth")) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ class CacheableDnsHttpAgent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register/Disable cacheable to global agents
|
* Register/Disable cacheable to global agents
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async update() {
|
static async update() {
|
||||||
log.debug("CacheableDnsHttpAgent", "update");
|
log.debug("CacheableDnsHttpAgent", "update");
|
||||||
|
@ -40,14 +41,15 @@ class CacheableDnsHttpAgent {
|
||||||
/**
|
/**
|
||||||
* Attach cacheable to HTTP agent
|
* Attach cacheable to HTTP agent
|
||||||
* @param {http.Agent} agent Agent to install
|
* @param {http.Agent} agent Agent to install
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static install(agent) {
|
static install(agent) {
|
||||||
this.cacheable.install(agent);
|
this.cacheable.install(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var {https.AgentOptions} agentOptions
|
* @param {https.AgentOptions} agentOptions Options to pass to HTTPS agent
|
||||||
* @return {https.Agent}
|
* @returns {https.Agent} The new HTTPS agent
|
||||||
*/
|
*/
|
||||||
static getHttpsAgent(agentOptions) {
|
static getHttpsAgent(agentOptions) {
|
||||||
if (!this.enable) {
|
if (!this.enable) {
|
||||||
|
@ -63,8 +65,8 @@ class CacheableDnsHttpAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var {http.AgentOptions} agentOptions
|
* @param {http.AgentOptions} agentOptions Options to pass to the HTTP agent
|
||||||
* @return {https.Agents}
|
* @returns {https.Agents} The new HTTP agent
|
||||||
*/
|
*/
|
||||||
static getHttpAgent(agentOptions) {
|
static getHttpAgent(agentOptions) {
|
||||||
if (!this.enable) {
|
if (!this.enable) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ const checkVersion = require("./check-version");
|
||||||
/**
|
/**
|
||||||
* Send list of notification providers to client
|
* Send list of notification providers to client
|
||||||
* @param {Socket} socket Socket.io socket instance
|
* @param {Socket} socket Socket.io socket instance
|
||||||
* @returns {Promise<Bean[]>}
|
* @returns {Promise<Bean[]>} List of notifications
|
||||||
*/
|
*/
|
||||||
async function sendNotificationList(socket) {
|
async function sendNotificationList(socket) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
|
@ -40,13 +40,11 @@ async function sendNotificationList(socket) {
|
||||||
* Send Heartbeat History list to socket
|
* Send Heartbeat History list to socket
|
||||||
* @param {Socket} socket Socket.io instance
|
* @param {Socket} socket Socket.io instance
|
||||||
* @param {number} monitorID ID of monitor to send heartbeat history
|
* @param {number} monitorID ID of monitor to send heartbeat history
|
||||||
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
|
* @param {boolean} toUser True = send to all browsers with the same user id, False = send to the current browser only
|
||||||
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
|
* @param {boolean} overwrite Overwrite client-side's heartbeat list
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||||
const timeLogger = new TimeLogger();
|
|
||||||
|
|
||||||
let list = await R.getAll(`
|
let list = await R.getAll(`
|
||||||
SELECT * FROM heartbeat
|
SELECT * FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
|
@ -63,16 +61,14 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite =
|
||||||
} else {
|
} else {
|
||||||
socket.emit("heartbeatList", monitorID, result, overwrite);
|
socket.emit("heartbeatList", monitorID, result, overwrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Important Heart beat list (aka event list)
|
* Important Heart beat list (aka event list)
|
||||||
* @param {Socket} socket Socket.io instance
|
* @param {Socket} socket Socket.io instance
|
||||||
* @param {number} monitorID ID of monitor to send heartbeat history
|
* @param {number} monitorID ID of monitor to send heartbeat history
|
||||||
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
|
* @param {boolean} toUser True = send to all browsers with the same user id, False = send to the current browser only
|
||||||
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
|
* @param {boolean} overwrite Overwrite client-side's heartbeat list
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||||
|
@ -100,7 +96,7 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
|
||||||
/**
|
/**
|
||||||
* Emit proxy list to client
|
* Emit proxy list to client
|
||||||
* @param {Socket} socket Socket.io socket instance
|
* @param {Socket} socket Socket.io socket instance
|
||||||
* @return {Promise<Bean[]>}
|
* @returns {Promise<Bean[]>} List of proxies
|
||||||
*/
|
*/
|
||||||
async function sendProxyList(socket) {
|
async function sendProxyList(socket) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
|
@ -141,7 +137,7 @@ async function sendAPIKeyList(socket) {
|
||||||
/**
|
/**
|
||||||
* Emits the version information to the client.
|
* Emits the version information to the client.
|
||||||
* @param {Socket} socket Socket.io socket instance
|
* @param {Socket} socket Socket.io socket instance
|
||||||
* @param {boolean} hideVersion
|
* @param {boolean} hideVersion Should we hide the version information in the response?
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function sendInfo(socket, hideVersion = false) {
|
async function sendInfo(socket, hideVersion = false) {
|
||||||
|
@ -168,7 +164,7 @@ async function sendInfo(socket, hideVersion = false) {
|
||||||
/**
|
/**
|
||||||
* Send list of docker hosts to client
|
* Send list of docker hosts to client
|
||||||
* @param {Socket} socket Socket.io socket instance
|
* @param {Socket} socket Socket.io socket instance
|
||||||
* @returns {Promise<Bean[]>}
|
* @returns {Promise<Bean[]>} List of docker hosts
|
||||||
*/
|
*/
|
||||||
async function sendDockerHostList(socket) {
|
async function sendDockerHostList(socket) {
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
|
|
|
@ -4,6 +4,8 @@ const { setSetting, setting } = require("./util-server");
|
||||||
const { log, sleep } = require("../src/util");
|
const { log, sleep } = require("../src/util");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||||
|
const mysql = require("mysql2/promise");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database & App Data Folder
|
* Database & App Data Folder
|
||||||
|
@ -24,7 +26,7 @@ class Database {
|
||||||
|
|
||||||
static screenshotDir;
|
static screenshotDir;
|
||||||
|
|
||||||
static path;
|
static sqlitePath;
|
||||||
|
|
||||||
static dockerTLSDir;
|
static dockerTLSDir;
|
||||||
|
|
||||||
|
@ -34,11 +36,13 @@ class Database {
|
||||||
static patched = false;
|
static patched = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* SQLite only
|
||||||
* Add patch filename in key
|
* Add patch filename in key
|
||||||
* Values:
|
* Values:
|
||||||
* true: Add it regardless of order
|
* true: Add it regardless of order
|
||||||
* false: Do nothing
|
* false: Do nothing
|
||||||
* { parents: []}: Need parents before add it
|
* { parents: []}: Need parents before add it
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
static patchList = {
|
static patchList = {
|
||||||
"patch-setting-value-type.sql": true,
|
"patch-setting-value-type.sql": true,
|
||||||
|
@ -80,7 +84,7 @@ class Database {
|
||||||
"patch-add-certificate-expiry-status-page.sql": true,
|
"patch-add-certificate-expiry-status-page.sql": true,
|
||||||
"patch-monitor-oauth-cc.sql": true,
|
"patch-monitor-oauth-cc.sql": true,
|
||||||
"patch-add-timeout-monitor.sql": true,
|
"patch-add-timeout-monitor.sql": true,
|
||||||
"patch-add-gamedig-given-port.sql": true,
|
"patch-add-gamedig-given-port.sql": true, // The last file so far converted to a knex migration file
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,15 +95,20 @@ class Database {
|
||||||
|
|
||||||
static noReject = true;
|
static noReject = true;
|
||||||
|
|
||||||
|
static dbConfig = {};
|
||||||
|
|
||||||
|
static knexMigrationsPath = "./db/knex_migrations";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the database
|
* Initialize the data directory
|
||||||
* @param {Object} args Arguments to initialize DB with
|
* @param {object} args Arguments to initialize DB with
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static init(args) {
|
static initDataDir(args) {
|
||||||
// Data Directory (must be end with "/")
|
// Data Directory (must be end with "/")
|
||||||
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||||
|
|
||||||
Database.path = path.join(Database.dataDir, "kuma.db");
|
Database.sqlitePath = path.join(Database.dataDir, "kuma.db");
|
||||||
if (! fs.existsSync(Database.dataDir)) {
|
if (! fs.existsSync(Database.dataDir)) {
|
||||||
fs.mkdirSync(Database.dataDir, { recursive: true });
|
fs.mkdirSync(Database.dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
@ -124,36 +133,143 @@ class Database {
|
||||||
log.info("db", `Data Dir: ${Database.dataDir}`);
|
log.info("db", `Data Dir: ${Database.dataDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static readDBConfig() {
|
||||||
|
let dbConfig;
|
||||||
|
|
||||||
|
let dbConfigString = fs.readFileSync(path.join(Database.dataDir, "db-config.json")).toString("utf-8");
|
||||||
|
dbConfig = JSON.parse(dbConfigString);
|
||||||
|
|
||||||
|
if (typeof dbConfig !== "object") {
|
||||||
|
throw new Error("Invalid db-config.json, it must be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof dbConfig.type !== "string") {
|
||||||
|
throw new Error("Invalid db-config.json, type must be a string");
|
||||||
|
}
|
||||||
|
return dbConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dbConfig
|
||||||
|
*/
|
||||||
|
static writeDBConfig(dbConfig) {
|
||||||
|
fs.writeFileSync(path.join(Database.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to the database
|
* Connect to the database
|
||||||
* @param {boolean} [testMode=false] Should the connection be
|
* @param {boolean} testMode Should the connection be
|
||||||
* started in test mode?
|
* started in test mode?
|
||||||
* @param {boolean} [autoloadModels=true] Should models be
|
* @param {boolean} autoloadModels Should models be
|
||||||
* automatically loaded?
|
* automatically loaded?
|
||||||
* @param {boolean} [noLog=false] Should logs not be output?
|
* @param {boolean} noLog Should logs not be output?
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
||||||
const acquireConnectionTimeout = 120 * 1000;
|
const acquireConnectionTimeout = 120 * 1000;
|
||||||
|
let dbConfig;
|
||||||
|
try {
|
||||||
|
dbConfig = this.readDBConfig();
|
||||||
|
Database.dbConfig = dbConfig;
|
||||||
|
} catch (err) {
|
||||||
|
log.warn("db", err.message);
|
||||||
|
dbConfig = {
|
||||||
|
type: "sqlite",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
let config = {};
|
||||||
Dialect.prototype._driver = () => require("@louislam/sqlite3");
|
|
||||||
|
let mariadbPoolConfig = {
|
||||||
|
afterCreate: function (conn, done) {
|
||||||
|
|
||||||
const knexInstance = knex({
|
|
||||||
client: Dialect,
|
|
||||||
connection: {
|
|
||||||
filename: Database.path,
|
|
||||||
acquireConnectionTimeout: acquireConnectionTimeout,
|
|
||||||
},
|
|
||||||
useNullAsDefault: true,
|
|
||||||
pool: {
|
|
||||||
min: 1,
|
|
||||||
max: 1,
|
|
||||||
idleTimeoutMillis: 120 * 1000,
|
|
||||||
propagateCreateError: false,
|
|
||||||
acquireTimeoutMillis: acquireConnectionTimeout,
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
log.info("db", `Database Type: ${dbConfig.type}`);
|
||||||
|
|
||||||
|
if (dbConfig.type === "sqlite") {
|
||||||
|
|
||||||
|
if (! fs.existsSync(Database.sqlitePath)) {
|
||||||
|
log.info("server", "Copying Database");
|
||||||
|
fs.copyFileSync(Database.templatePath, Database.sqlitePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||||
|
Dialect.prototype._driver = () => require("@louislam/sqlite3");
|
||||||
|
|
||||||
|
config = {
|
||||||
|
client: Dialect,
|
||||||
|
connection: {
|
||||||
|
filename: Database.sqlitePath,
|
||||||
|
acquireConnectionTimeout: acquireConnectionTimeout,
|
||||||
|
},
|
||||||
|
useNullAsDefault: true,
|
||||||
|
pool: {
|
||||||
|
min: 1,
|
||||||
|
max: 1,
|
||||||
|
idleTimeoutMillis: 120 * 1000,
|
||||||
|
propagateCreateError: false,
|
||||||
|
acquireTimeoutMillis: acquireConnectionTimeout,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (dbConfig.type === "mariadb") {
|
||||||
|
if (!/^\w+$/.test(dbConfig.dbName)) {
|
||||||
|
throw Error("Invalid database name. A database name can only consist of letters, numbers and underscores");
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = await mysql.createConnection({
|
||||||
|
host: dbConfig.hostname,
|
||||||
|
port: dbConfig.port,
|
||||||
|
user: dbConfig.username,
|
||||||
|
password: dbConfig.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
await connection.execute("CREATE DATABASE IF NOT EXISTS " + dbConfig.dbName + " CHARACTER SET utf8mb4");
|
||||||
|
connection.end();
|
||||||
|
|
||||||
|
config = {
|
||||||
|
client: "mysql2",
|
||||||
|
connection: {
|
||||||
|
host: dbConfig.hostname,
|
||||||
|
port: dbConfig.port,
|
||||||
|
user: dbConfig.username,
|
||||||
|
password: dbConfig.password,
|
||||||
|
database: dbConfig.dbName,
|
||||||
|
timezone: "UTC",
|
||||||
|
},
|
||||||
|
pool: mariadbPoolConfig,
|
||||||
|
};
|
||||||
|
} else if (dbConfig.type === "embedded-mariadb") {
|
||||||
|
let embeddedMariaDB = EmbeddedMariaDB.getInstance();
|
||||||
|
await embeddedMariaDB.start();
|
||||||
|
log.info("mariadb", "Embedded MariaDB started");
|
||||||
|
config = {
|
||||||
|
client: "mysql2",
|
||||||
|
connection: {
|
||||||
|
socketPath: embeddedMariaDB.socketPath,
|
||||||
|
user: "node",
|
||||||
|
database: "kuma",
|
||||||
|
},
|
||||||
|
pool: mariadbPoolConfig,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error("Unknown Database type: " + dbConfig.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to utf8mb4 for MariaDB
|
||||||
|
if (dbConfig.type.endsWith("mariadb")) {
|
||||||
|
config.pool = {
|
||||||
|
afterCreate(conn, done) {
|
||||||
|
conn.query("SET CHARACTER SET utf8mb4;", (err) => done(err, conn));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const knexInstance = knex(config);
|
||||||
|
|
||||||
R.setup(knexInstance);
|
R.setup(knexInstance);
|
||||||
|
|
||||||
|
@ -168,6 +284,18 @@ class Database {
|
||||||
await R.autoloadModels("./server/model");
|
await R.autoloadModels("./server/model");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dbConfig.type === "sqlite") {
|
||||||
|
await this.initSQLite(testMode, noLog);
|
||||||
|
} else if (dbConfig.type.endsWith("mariadb")) {
|
||||||
|
await this.initMariaDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param testMode
|
||||||
|
* @param noLog
|
||||||
|
*/
|
||||||
|
static async initSQLite(testMode, noLog) {
|
||||||
await R.exec("PRAGMA foreign_keys = ON");
|
await R.exec("PRAGMA foreign_keys = ON");
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
// Change to MEMORY
|
// Change to MEMORY
|
||||||
|
@ -192,8 +320,57 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Patch the database */
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static async initMariaDB() {
|
||||||
|
log.debug("db", "Checking if MariaDB database exists...");
|
||||||
|
|
||||||
|
let hasTable = await R.hasTable("docker_host");
|
||||||
|
if (!hasTable) {
|
||||||
|
const { createTables } = require("../db/knex_init_db");
|
||||||
|
await createTables();
|
||||||
|
} else {
|
||||||
|
log.debug("db", "MariaDB database already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch the database
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
static async patch() {
|
static async patch() {
|
||||||
|
// Still need to keep this for old versions of Uptime Kuma
|
||||||
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
|
await this.patchSqlite();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using knex migrations
|
||||||
|
// https://knexjs.org/guide/migrations.html
|
||||||
|
// https://gist.github.com/NigelEarle/70db130cc040cc2868555b29a0278261
|
||||||
|
try {
|
||||||
|
await R.knex.migrate.latest({
|
||||||
|
directory: Database.knexMigrationsPath,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.error("db", "Database migration failed");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async rollbackLatestPatch() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch the database for SQLite
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
static async patchSqlite() {
|
||||||
let version = parseInt(await setting("database_version"));
|
let version = parseInt(await setting("database_version"));
|
||||||
|
|
||||||
if (! version) {
|
if (! version) {
|
||||||
|
@ -213,7 +390,7 @@ class Database {
|
||||||
// Try catch anything here
|
// Try catch anything here
|
||||||
try {
|
try {
|
||||||
for (let i = version + 1; i <= this.latestVersion; i++) {
|
for (let i = version + 1; i <= this.latestVersion; i++) {
|
||||||
const sqlFile = `./db/patch${i}.sql`;
|
const sqlFile = `./db/old_migrations/patch${i}.sql`;
|
||||||
log.info("db", `Patching ${sqlFile}`);
|
log.info("db", `Patching ${sqlFile}`);
|
||||||
await Database.importSQLFile(sqlFile);
|
await Database.importSQLFile(sqlFile);
|
||||||
log.info("db", `Patched ${sqlFile}`);
|
log.info("db", `Patched ${sqlFile}`);
|
||||||
|
@ -230,17 +407,18 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.patch2();
|
await this.patchSqlite2();
|
||||||
await this.migrateNewStatusPage();
|
await this.migrateNewStatusPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patch DB using new process
|
* Patch DB using new process
|
||||||
* Call it from patch() only
|
* Call it from patch() only
|
||||||
|
* @deprecated
|
||||||
* @private
|
* @private
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async patch2() {
|
static async patchSqlite2() {
|
||||||
log.info("db", "Database Patch 2.0 Process");
|
log.info("db", "Database Patch 2.0 Process");
|
||||||
let databasePatchedFiles = await setting("databasePatchedFiles");
|
let databasePatchedFiles = await setting("databasePatchedFiles");
|
||||||
|
|
||||||
|
@ -274,6 +452,7 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* SQlite only
|
||||||
* Migrate status page value in setting to "status_page" table
|
* Migrate status page value in setting to "status_page" table
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
|
@ -345,8 +524,8 @@ class Database {
|
||||||
* Patch database using new patching process
|
* Patch database using new patching process
|
||||||
* Used it patch2() only
|
* Used it patch2() only
|
||||||
* @private
|
* @private
|
||||||
* @param sqlFilename
|
* @param {string} sqlFilename Name of SQL file to load
|
||||||
* @param databasePatchedFiles
|
* @param {object} databasePatchedFiles Patch status of database files
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
||||||
|
@ -370,7 +549,7 @@ class Database {
|
||||||
|
|
||||||
log.info("db", sqlFilename + " is patching");
|
log.info("db", sqlFilename + " is patching");
|
||||||
this.patched = true;
|
this.patched = true;
|
||||||
await this.importSQLFile("./db/" + sqlFilename);
|
await this.importSQLFile("./db/old_migrations/" + sqlFilename);
|
||||||
databasePatchedFiles[sqlFilename] = true;
|
databasePatchedFiles[sqlFilename] = true;
|
||||||
log.info("db", sqlFilename + " was patched successfully");
|
log.info("db", sqlFilename + " was patched successfully");
|
||||||
|
|
||||||
|
@ -381,7 +560,7 @@ class Database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an SQL file and execute it
|
* Load an SQL file and execute it
|
||||||
* @param filename Filename of SQL file to import
|
* @param {string} filename Filename of SQL file to import
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async importSQLFile(filename) {
|
static async importSQLFile(filename) {
|
||||||
|
@ -413,14 +592,6 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Aquire a direct connection to database
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
static getBetterSQLite3Database() {
|
|
||||||
return R.knex.client.acquireConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special handle, because tarn.js throw a promise reject that cannot be caught
|
* Special handle, because tarn.js throw a promise reject that cannot be caught
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
|
@ -434,7 +605,9 @@ class Database {
|
||||||
log.info("db", "Closing the database");
|
log.info("db", "Closing the database");
|
||||||
|
|
||||||
// Flush WAL to main database
|
// Flush WAL to main database
|
||||||
await R.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
|
await R.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Database.noReject = true;
|
Database.noReject = true;
|
||||||
|
@ -447,17 +620,23 @@ class Database {
|
||||||
log.info("db", "Waiting to close the database");
|
log.info("db", "Waiting to close the database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("db", "SQLite closed");
|
log.info("db", "Database closed");
|
||||||
|
|
||||||
process.removeListener("unhandledRejection", listener);
|
process.removeListener("unhandledRejection", listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the size of the database */
|
/**
|
||||||
|
* Get the size of the database (SQLite only)
|
||||||
|
* @returns {number} Size of database
|
||||||
|
*/
|
||||||
static getSize() {
|
static getSize() {
|
||||||
log.debug("db", "Database.getSize()");
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
let stats = fs.statSync(Database.path);
|
log.debug("db", "Database.getSize()");
|
||||||
log.debug("db", stats);
|
let stats = fs.statSync(Database.sqlitePath);
|
||||||
return stats.size;
|
log.debug("db", stats);
|
||||||
|
return stats.size;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,8 +644,22 @@ class Database {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async shrink() {
|
static async shrink() {
|
||||||
await R.exec("VACUUM");
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
|
await R.exec("VACUUM");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static sqlHourOffset() {
|
||||||
|
if (this.dbConfig.client === "sqlite3") {
|
||||||
|
return "DATETIME('now', ? || ' hours')";
|
||||||
|
} else {
|
||||||
|
return "DATE_ADD(NOW(), INTERVAL ? HOUR)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Database;
|
module.exports = Database;
|
||||||
|
|
|
@ -14,10 +14,10 @@ class DockerHost {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a docker host
|
* Save a docker host
|
||||||
* @param {Object} dockerHost Docker host to save
|
* @param {object} dockerHost Docker host to save
|
||||||
* @param {?number} dockerHostID ID of the docker host to update
|
* @param {?number} dockerHostID ID of the docker host to update
|
||||||
* @param {number} userID ID of the user who adds the docker host
|
* @param {number} userID ID of the user who adds the docker host
|
||||||
* @returns {Promise<Bean>}
|
* @returns {Promise<Bean>} Updated docker host
|
||||||
*/
|
*/
|
||||||
static async save(dockerHost, dockerHostID, userID) {
|
static async save(dockerHost, dockerHostID, userID) {
|
||||||
let bean;
|
let bean;
|
||||||
|
@ -64,7 +64,7 @@ class DockerHost {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the amount of containers on the Docker host
|
* Fetches the amount of containers on the Docker host
|
||||||
* @param {Object} dockerHost Docker host to check for
|
* @param {object} dockerHost Docker host to check for
|
||||||
* @returns {number} Total amount of containers on the host
|
* @returns {number} Total amount of containers on the host
|
||||||
*/
|
*/
|
||||||
static async testDockerHost(dockerHost) {
|
static async testDockerHost(dockerHost) {
|
||||||
|
@ -108,6 +108,8 @@ class DockerHost {
|
||||||
/**
|
/**
|
||||||
* Since axios 0.27.X, it does not accept `tcp://` protocol.
|
* Since axios 0.27.X, it does not accept `tcp://` protocol.
|
||||||
* Change it to `http://` on the fly in order to fix it. (https://github.com/louislam/uptime-kuma/issues/2165)
|
* Change it to `http://` on the fly in order to fix it. (https://github.com/louislam/uptime-kuma/issues/2165)
|
||||||
|
* @param {any} url URL to fix
|
||||||
|
* @returns {any} URL with tcp:// replaced by http://
|
||||||
*/
|
*/
|
||||||
static patchDockerURL(url) {
|
static patchDockerURL(url) {
|
||||||
if (typeof url === "string") {
|
if (typeof url === "string") {
|
||||||
|
@ -129,11 +131,10 @@ class DockerHost {
|
||||||
* 'data/docker-tls/example.com/' would be searched for certificate files),
|
* 'data/docker-tls/example.com/' would be searched for certificate files),
|
||||||
* then 'ca.pem', 'key.pem' and 'cert.pem' files are included in the agent options.
|
* then 'ca.pem', 'key.pem' and 'cert.pem' files are included in the agent options.
|
||||||
* File names can also be overridden via 'DOCKER_TLS_FILE_NAME_(CA|KEY|CERT)'.
|
* File names can also be overridden via 'DOCKER_TLS_FILE_NAME_(CA|KEY|CERT)'.
|
||||||
*
|
* @param {string} dockerType i.e. "tcp" or "socket"
|
||||||
* @param {String} dockerType i.e. "tcp" or "socket"
|
* @param {string} url The docker host URL rewritten to https://
|
||||||
* @param {String} url The docker host URL rewritten to https://
|
* @returns {object} HTTP agent options
|
||||||
* @return {Object}
|
*/
|
||||||
* */
|
|
||||||
static getHttpsAgentOptions(dockerType, url) {
|
static getHttpsAgentOptions(dockerType, url) {
|
||||||
let baseOptions = {
|
let baseOptions = {
|
||||||
maxCachedSessions: 0,
|
maxCachedSessions: 0,
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
const { log } = require("../src/util");
|
||||||
|
const childProcess = require("child_process");
|
||||||
|
const fs = require("fs");
|
||||||
|
const mysql = require("mysql2");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is only used inside the docker container
|
||||||
|
*/
|
||||||
|
class EmbeddedMariaDB {
|
||||||
|
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
exec = "mariadbd";
|
||||||
|
|
||||||
|
mariadbDataDir = "/app/data/mariadb";
|
||||||
|
|
||||||
|
runDir = "/app/data/run/mariadb";
|
||||||
|
|
||||||
|
socketPath = this.runDir + "/mysqld.sock";
|
||||||
|
|
||||||
|
childProcess = null;
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
started = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {EmbeddedMariaDB}
|
||||||
|
*/
|
||||||
|
static getInstance() {
|
||||||
|
if (!EmbeddedMariaDB.instance) {
|
||||||
|
EmbeddedMariaDB.instance = new EmbeddedMariaDB();
|
||||||
|
}
|
||||||
|
return EmbeddedMariaDB.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static hasInstance() {
|
||||||
|
return !!EmbeddedMariaDB.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
if (this.childProcess) {
|
||||||
|
log.info("mariadb", "Already started");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initDB();
|
||||||
|
|
||||||
|
this.running = true;
|
||||||
|
log.info("mariadb", "Starting Embedded MariaDB");
|
||||||
|
this.childProcess = childProcess.spawn(this.exec, [
|
||||||
|
"--user=node",
|
||||||
|
"--datadir=" + this.mariadbDataDir,
|
||||||
|
`--socket=${this.socketPath}`,
|
||||||
|
`--pid-file=${this.runDir}/mysqld.pid`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.childProcess.on("close", (code) => {
|
||||||
|
this.running = false;
|
||||||
|
this.childProcess = null;
|
||||||
|
this.started = false;
|
||||||
|
log.info("mariadb", "Stopped Embedded MariaDB: " + code);
|
||||||
|
|
||||||
|
if (code !== 0) {
|
||||||
|
log.info("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user");
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.childProcess.on("error", (err) => {
|
||||||
|
if (err.code === "ENOENT") {
|
||||||
|
log.error("mariadb", `Embedded MariaDB: ${this.exec} is not found`);
|
||||||
|
} else {
|
||||||
|
log.error("mariadb", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let handler = (data) => {
|
||||||
|
log.debug("mariadb", data.toString("utf-8"));
|
||||||
|
if (data.toString("utf-8").includes("ready for connections")) {
|
||||||
|
this.initDBAfterStarted();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.childProcess.stdout.on("data", handler);
|
||||||
|
this.childProcess.stderr.on("data", handler);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
if (this.started) {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
log.info("mariadb", "Waiting for Embedded MariaDB to start...");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
if (this.childProcess) {
|
||||||
|
this.childProcess.kill("SIGINT");
|
||||||
|
this.childProcess = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
initDB() {
|
||||||
|
if (!fs.existsSync(this.mariadbDataDir)) {
|
||||||
|
log.info("mariadb", `Embedded MariaDB: ${this.mariadbDataDir} is not found, create one now.`);
|
||||||
|
fs.mkdirSync(this.mariadbDataDir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = childProcess.spawnSync("mysql_install_db", [
|
||||||
|
"--user=node",
|
||||||
|
"--ldata=" + this.mariadbDataDir,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
let error = result.stderr.toString("utf-8");
|
||||||
|
log.error("mariadb", error);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log.info("mariadb", "Embedded MariaDB: mysql_install_db done:" + result.stdout.toString("utf-8"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.runDir)) {
|
||||||
|
log.info("mariadb", `Embedded MariaDB: ${this.runDir} is not found, create one now.`);
|
||||||
|
fs.mkdirSync(this.runDir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async initDBAfterStarted() {
|
||||||
|
const connection = mysql.createConnection({
|
||||||
|
socketPath: this.socketPath,
|
||||||
|
user: "node",
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = await connection.execute("CREATE DATABASE IF NOT EXISTS `kuma`");
|
||||||
|
log.debug("mariadb", "CREATE DATABASE: " + JSON.stringify(result));
|
||||||
|
|
||||||
|
log.info("mariadb", "Embedded MariaDB is ready for connections");
|
||||||
|
this.started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
EmbeddedMariaDB,
|
||||||
|
};
|
|
@ -3,8 +3,8 @@ const jsesc = require("jsesc");
|
||||||
/**
|
/**
|
||||||
* Returns a string that represents the javascript that is required to insert the Google Analytics scripts
|
* Returns a string that represents the javascript that is required to insert the Google Analytics scripts
|
||||||
* into a webpage.
|
* into a webpage.
|
||||||
* @param tagId Google UA/G/AW/DC Property ID to use with the Google Analytics script.
|
* @param {string} tagId Google UA/G/AW/DC Property ID to use with the Google Analytics script.
|
||||||
* @returns {string}
|
* @returns {string} HTML script tags to inject into page
|
||||||
*/
|
*/
|
||||||
function getGoogleAnalyticsScript(tagId) {
|
function getGoogleAnalyticsScript(tagId) {
|
||||||
let escapedTagId = jsesc(tagId, { isScriptContext: true });
|
let escapedTagId = jsesc(tagId, { isScriptContext: true });
|
||||||
|
|
|
@ -10,7 +10,7 @@ let ImageDataURI = (() => {
|
||||||
/**
|
/**
|
||||||
* Decode the data:image/ URI
|
* Decode the data:image/ URI
|
||||||
* @param {string} dataURI data:image/ URI to decode
|
* @param {string} dataURI data:image/ URI to decode
|
||||||
* @returns {?Object} An object with properties "imageType" and "dataBase64".
|
* @returns {?object} An object with properties "imageType" and "dataBase64".
|
||||||
* The former is the image type, e.g., "png", and the latter is a base64
|
* The former is the image type, e.g., "png", and the latter is a base64
|
||||||
* encoded string of the image's binary data. If it fails to parse, returns
|
* encoded string of the image's binary data. If it fails to parse, returns
|
||||||
* null instead of an object.
|
* null instead of an object.
|
||||||
|
@ -52,8 +52,8 @@ let ImageDataURI = (() => {
|
||||||
/**
|
/**
|
||||||
* Write data URI to file
|
* Write data URI to file
|
||||||
* @param {string} dataURI data:image/ URI
|
* @param {string} dataURI data:image/ URI
|
||||||
* @param {string} [filePath] Path to write file to
|
* @param {string} filePath Path to write file to
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string|void>} Write file error
|
||||||
*/
|
*/
|
||||||
function outputFile(dataURI, filePath) {
|
function outputFile(dataURI, filePath) {
|
||||||
filePath = filePath || "./";
|
filePath = filePath || "./";
|
||||||
|
|
|
@ -39,7 +39,10 @@ const initBackgroundJobs = async function () {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Stop all background jobs if running */
|
/**
|
||||||
|
* Stop all background jobs if running
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
const stopBackgroundJobs = function () {
|
const stopBackgroundJobs = function () {
|
||||||
for (const job of jobs) {
|
for (const job of jobs) {
|
||||||
if (job.croner) {
|
if (job.croner) {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
const { setSetting, setting } = require("../util-server");
|
const { setSetting, setting } = require("../util-server");
|
||||||
|
const Database = require("../database");
|
||||||
|
|
||||||
const DEFAULT_KEEP_PERIOD = 180;
|
const DEFAULT_KEEP_PERIOD = 180;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears old data from the heartbeat table of the database.
|
* Clears old data from the heartbeat table of the database.
|
||||||
* @return {Promise<void>} A promise that resolves when the data has been cleared.
|
* @returns {Promise<void>} A promise that resolves when the data has been cleared.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const clearOldData = async () => {
|
const clearOldData = async () => {
|
||||||
|
@ -34,13 +35,17 @@ const clearOldData = async () => {
|
||||||
|
|
||||||
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
||||||
|
|
||||||
|
const sqlHourOffset = Database.sqlHourOffset();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await R.exec(
|
await R.exec(
|
||||||
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
|
"DELETE FROM heartbeat WHERE time < " + sqlHourOffset,
|
||||||
[ parsedPeriod ]
|
[ parsedPeriod * -24 ]
|
||||||
);
|
);
|
||||||
|
|
||||||
await R.exec("PRAGMA optimize;");
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
|
await R.exec("PRAGMA optimize;");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
|
const Database = require("../database");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run incremental_vacuum and checkpoint the WAL.
|
* Run incremental_vacuum and checkpoint the WAL.
|
||||||
* @return {Promise<void>} A promise that resolves when the process is finished.
|
* @returns {Promise<void>} A promise that resolves when the process is finished.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const incrementalVacuum = async () => {
|
const incrementalVacuum = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (Database.dbConfig.type !== "sqlite") {
|
||||||
|
log.debug("incrementalVacuum", "Skipping incremental_vacuum, not using SQLite.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("incrementalVacuum", "Running incremental_vacuum and wal_checkpoint(PASSIVE)...");
|
log.debug("incrementalVacuum", "Running incremental_vacuum and wal_checkpoint(PASSIVE)...");
|
||||||
await R.exec("PRAGMA incremental_vacuum(200)");
|
await R.exec("PRAGMA incremental_vacuum(200)");
|
||||||
await R.exec("PRAGMA wal_checkpoint(PASSIVE)");
|
await R.exec("PRAGMA wal_checkpoint(PASSIVE)");
|
||||||
|
|
|
@ -19,7 +19,7 @@ class APIKey extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object that ready to parse to JSON
|
* Returns an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
@ -37,7 +37,7 @@ class APIKey extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Returns an object that ready to parse to JSON with sensitive fields
|
* Returns an object that ready to parse to JSON with sensitive fields
|
||||||
* removed
|
* removed
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toPublicJSON() {
|
toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
|
@ -53,9 +53,9 @@ class APIKey extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new API Key and store it in the database
|
* Create a new API Key and store it in the database
|
||||||
* @param {Object} key Object sent by client
|
* @param {object} key Object sent by client
|
||||||
* @param {int} userID ID of socket user
|
* @param {int} userID ID of socket user
|
||||||
* @returns {Promise<bean>}
|
* @returns {Promise<bean>} API key
|
||||||
*/
|
*/
|
||||||
static async save(key, userID) {
|
static async save(key, userID) {
|
||||||
let bean;
|
let bean;
|
||||||
|
|
|
@ -3,7 +3,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
class DockerHost extends BeanModel {
|
class DockerHost extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Returns an object that ready to parse to JSON
|
* Returns an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,10 +4,12 @@ const { R } = require("redbean-node");
|
||||||
class Group extends BeanModel {
|
class Group extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON for public
|
* Return an object that ready to parse to JSON for public Only show
|
||||||
* Only show necessary data to public
|
* necessary data to public
|
||||||
* @param {boolean} [showTags=false] Should the JSON include monitor tags
|
* @param {boolean} showTags Should the JSON include monitor tags
|
||||||
* @returns {Object}
|
* @param {boolean} certExpiry Should JSON include info about
|
||||||
|
* certificate expiry?
|
||||||
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toPublicJSON(showTags = false, certExpiry = false) {
|
async toPublicJSON(showTags = false, certExpiry = false) {
|
||||||
let monitorBeanList = await this.getMonitorList();
|
let monitorBeanList = await this.getMonitorList();
|
||||||
|
@ -27,7 +29,7 @@ class Group extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all monitors
|
* Get all monitors
|
||||||
* @returns {Bean[]}
|
* @returns {Bean[]} List of monitors
|
||||||
*/
|
*/
|
||||||
async getMonitorList() {
|
async getMonitorList() {
|
||||||
return R.convertToBeans("monitor", await R.getAll(`
|
return R.convertToBeans("monitor", await R.getAll(`
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Heartbeat extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON for public
|
* Return an object that ready to parse to JSON for public
|
||||||
* Only show necessary data to public
|
* Only show necessary data to public
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toPublicJSON() {
|
toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
|
@ -25,7 +25,7 @@ class Heartbeat extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -5,7 +5,7 @@ class Incident extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON for public
|
* Return an object that ready to parse to JSON for public
|
||||||
* Only show necessary data to public
|
* Only show necessary data to public
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toPublicJSON() {
|
toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Maintenance extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON for public
|
* Return an object that ready to parse to JSON for public
|
||||||
* Only show necessary data to public
|
* Only show necessary data to public
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toPublicJSON() {
|
async toPublicJSON() {
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class Maintenance extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
* @param {string} timezone If not specified, the timeRange will be in UTC
|
* @param {string} timezone If not specified, the timeRange will be in UTC
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toJSON(timezone = null) {
|
async toJSON(timezone = null) {
|
||||||
return this.toPublicJSON(timezone);
|
return this.toPublicJSON(timezone);
|
||||||
|
@ -142,7 +142,7 @@ class Maintenance extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Convert data from socket to bean
|
* Convert data from socket to bean
|
||||||
* @param {Bean} bean Bean to fill in
|
* @param {Bean} bean Bean to fill in
|
||||||
* @param {Object} obj Data to fill bean with
|
* @param {object} obj Data to fill bean with
|
||||||
* @returns {Bean} Filled bean
|
* @returns {Bean} Filled bean
|
||||||
*/
|
*/
|
||||||
static async jsonToBean(bean, obj) {
|
static async jsonToBean(bean, obj) {
|
||||||
|
@ -188,7 +188,7 @@ class Maintenance extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throw error if cron is invalid
|
* Throw error if cron is invalid
|
||||||
* @param cron
|
* @param {string|Date} cron Pattern or date
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async validateCron(cron) {
|
static async validateCron(cron) {
|
||||||
|
@ -198,6 +198,8 @@ class Maintenance extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the cron
|
* Run the cron
|
||||||
|
* @param {boolean} throwError Should an error be thrown on failure
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async run(throwError = false) {
|
async run(throwError = false) {
|
||||||
if (this.beanMeta.job) {
|
if (this.beanMeta.job) {
|
||||||
|
@ -290,6 +292,10 @@ class Maintenance extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeslots where maintenance is running
|
||||||
|
* @returns {object|null} Maintenance time slot
|
||||||
|
*/
|
||||||
getRunningTimeslot() {
|
getRunningTimeslot() {
|
||||||
let start = dayjs(this.beanMeta.job.nextRun(dayjs().add(-this.duration, "second").toDate()));
|
let start = dayjs(this.beanMeta.job.nextRun(dayjs().add(-this.duration, "second").toDate()));
|
||||||
let end = start.add(this.duration, "second");
|
let end = start.add(this.duration, "second");
|
||||||
|
@ -305,6 +311,10 @@ class Maintenance extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the maintenance
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
if (this.beanMeta.job) {
|
if (this.beanMeta.job) {
|
||||||
this.beanMeta.job.stop();
|
this.beanMeta.job.stop();
|
||||||
|
@ -312,10 +322,18 @@ class Maintenance extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this maintenance currently active
|
||||||
|
* @returns {boolean} The maintenance is active?
|
||||||
|
*/
|
||||||
async isUnderMaintenance() {
|
async isUnderMaintenance() {
|
||||||
return (await this.getStatus()) === "under-maintenance";
|
return (await this.getStatus()) === "under-maintenance";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the timezone of the maintenance
|
||||||
|
* @returns {string} timezone
|
||||||
|
*/
|
||||||
async getTimezone() {
|
async getTimezone() {
|
||||||
if (!this.timezone || this.timezone === "SAME_AS_SERVER") {
|
if (!this.timezone || this.timezone === "SAME_AS_SERVER") {
|
||||||
return await UptimeKumaServer.getInstance().getTimezone();
|
return await UptimeKumaServer.getInstance().getTimezone();
|
||||||
|
@ -323,10 +341,18 @@ class Maintenance extends BeanModel {
|
||||||
return this.timezone;
|
return this.timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get offset for timezone
|
||||||
|
* @returns {string} offset
|
||||||
|
*/
|
||||||
async getTimezoneOffset() {
|
async getTimezoneOffset() {
|
||||||
return dayjs.tz(dayjs(), await this.getTimezone()).format("Z");
|
return dayjs.tz(dayjs(), await this.getTimezone()).format("Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current status of the maintenance
|
||||||
|
* @returns {string} Current status
|
||||||
|
*/
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
if (!this.active) {
|
if (!this.active) {
|
||||||
return "inactive";
|
return "inactive";
|
||||||
|
|
|
@ -18,10 +18,10 @@ const apicache = require("../modules/apicache");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
||||||
const { DockerHost } = require("../docker");
|
const { DockerHost } = require("../docker");
|
||||||
const { UptimeCacheList } = require("../uptime-cache-list");
|
|
||||||
const Gamedig = require("gamedig");
|
const Gamedig = require("gamedig");
|
||||||
const jsonata = require("jsonata");
|
const jsonata = require("jsonata");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
|
const { UptimeCalculator } = require("../uptime-calculator");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status:
|
* status:
|
||||||
|
@ -33,9 +33,12 @@ const jwt = require("jsonwebtoken");
|
||||||
class Monitor extends BeanModel {
|
class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON for public
|
* Return an object that ready to parse to JSON for public Only show
|
||||||
* Only show necessary data to public
|
* necessary data to public
|
||||||
* @returns {Object}
|
* @param {boolean} showTags Include tags in JSON
|
||||||
|
* @param {boolean} certExpiry Include certificate expiry info in
|
||||||
|
* JSON
|
||||||
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toPublicJSON(showTags = false, certExpiry = false) {
|
async toPublicJSON(showTags = false, certExpiry = false) {
|
||||||
let obj = {
|
let obj = {
|
||||||
|
@ -64,7 +67,9 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @param {boolean} includeSensitiveData Include sensitive data in
|
||||||
|
* JSON
|
||||||
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toJSON(includeSensitiveData = true) {
|
async toJSON(includeSensitiveData = true) {
|
||||||
|
|
||||||
|
@ -182,9 +187,9 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the monitor is active based on itself and its parents
|
* Checks if the monitor is active based on itself and its parents
|
||||||
* @returns {Promise<Boolean>}
|
* @returns {Promise<boolean>} Is the monitor active?
|
||||||
*/
|
*/
|
||||||
async isActive() {
|
async isActive() {
|
||||||
const parentActive = await Monitor.isParentActive(this.id);
|
const parentActive = await Monitor.isParentActive(this.id);
|
||||||
|
|
||||||
|
@ -193,7 +198,8 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags applied to this monitor
|
* Get all tags applied to this monitor
|
||||||
* @returns {Promise<LooseObject<any>[]>}
|
* @returns {Promise<LooseObject<any>[]>} List of tags on the
|
||||||
|
* monitor
|
||||||
*/
|
*/
|
||||||
async getTags() {
|
async getTags() {
|
||||||
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]);
|
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]);
|
||||||
|
@ -202,7 +208,8 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Gets certificate expiry for this monitor
|
* Gets certificate expiry for this monitor
|
||||||
* @param {number} monitorID ID of monitor to send
|
* @param {number} monitorID ID of monitor to send
|
||||||
* @returns {Promise<LooseObject<any>>}
|
* @returns {Promise<LooseObject<any>>} Certificate expiry info for
|
||||||
|
* monitor
|
||||||
*/
|
*/
|
||||||
async getCertExpiry(monitorID) {
|
async getCertExpiry(monitorID) {
|
||||||
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||||
|
@ -227,7 +234,9 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Encode user and password to Base64 encoding
|
* Encode user and password to Base64 encoding
|
||||||
* for HTTP "basic" auth, as per RFC-7617
|
* for HTTP "basic" auth, as per RFC-7617
|
||||||
* @returns {string}
|
* @param {string} user Username to encode
|
||||||
|
* @param {string} pass Password to encode
|
||||||
|
* @returns {string} Encoded username:password
|
||||||
*/
|
*/
|
||||||
encodeBase64(user, pass) {
|
encodeBase64(user, pass) {
|
||||||
return Buffer.from(user + ":" + pass).toString("base64");
|
return Buffer.from(user + ":" + pass).toString("base64");
|
||||||
|
@ -235,7 +244,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the TLS expiry notification enabled?
|
* Is the TLS expiry notification enabled?
|
||||||
* @returns {boolean}
|
* @returns {boolean} Enabled?
|
||||||
*/
|
*/
|
||||||
isEnabledExpiryNotification() {
|
isEnabledExpiryNotification() {
|
||||||
return Boolean(this.expiryNotification);
|
return Boolean(this.expiryNotification);
|
||||||
|
@ -243,7 +252,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse to boolean
|
* Parse to boolean
|
||||||
* @returns {boolean}
|
* @returns {boolean} Should TLS errors be ignored?
|
||||||
*/
|
*/
|
||||||
getIgnoreTls() {
|
getIgnoreTls() {
|
||||||
return Boolean(this.ignoreTls);
|
return Boolean(this.ignoreTls);
|
||||||
|
@ -251,7 +260,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse to boolean
|
* Parse to boolean
|
||||||
* @returns {boolean}
|
* @returns {boolean} Is the monitor in upside down mode?
|
||||||
*/
|
*/
|
||||||
isUpsideDown() {
|
isUpsideDown() {
|
||||||
return Boolean(this.upsideDown);
|
return Boolean(this.upsideDown);
|
||||||
|
@ -259,7 +268,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse to boolean
|
* Parse to boolean
|
||||||
* @returns {boolean}
|
* @returns {boolean} Invert keyword match?
|
||||||
*/
|
*/
|
||||||
isInvertKeyword() {
|
isInvertKeyword() {
|
||||||
return Boolean(this.invertKeyword);
|
return Boolean(this.invertKeyword);
|
||||||
|
@ -267,7 +276,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse to boolean
|
* Parse to boolean
|
||||||
* @returns {boolean}
|
* @returns {boolean} Enable TLS for gRPC?
|
||||||
*/
|
*/
|
||||||
getGrpcEnableTls() {
|
getGrpcEnableTls() {
|
||||||
return Boolean(this.grpcEnableTls);
|
return Boolean(this.grpcEnableTls);
|
||||||
|
@ -275,12 +284,15 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get accepted status codes
|
* Get accepted status codes
|
||||||
* @returns {Object}
|
* @returns {object} Accepted status codes
|
||||||
*/
|
*/
|
||||||
getAcceptedStatuscodes() {
|
getAcceptedStatuscodes() {
|
||||||
return JSON.parse(this.accepted_statuscodes_json);
|
return JSON.parse(this.accepted_statuscodes_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
getGameDigGivenPortOnly() {
|
getGameDigGivenPortOnly() {
|
||||||
return Boolean(this.gamedigGivenPortOnly);
|
return Boolean(this.gamedigGivenPortOnly);
|
||||||
}
|
}
|
||||||
|
@ -288,6 +300,7 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Start monitor
|
* Start monitor
|
||||||
* @param {Server} io Socket server instance
|
* @param {Server} io Socket server instance
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
start(io) {
|
start(io) {
|
||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
|
@ -332,13 +345,6 @@ class Monitor extends BeanModel {
|
||||||
bean.status = flipStatus(bean.status);
|
bean.status = flipStatus(bean.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration
|
|
||||||
if (!isFirstBeat) {
|
|
||||||
bean.duration = dayjs(bean.time).diff(dayjs(previousBeat.time), "second");
|
|
||||||
} else {
|
|
||||||
bean.duration = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||||
bean.msg = "Monitor under maintenance";
|
bean.msg = "Monitor under maintenance";
|
||||||
|
@ -957,11 +963,17 @@ class Monitor extends BeanModel {
|
||||||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate uptime
|
||||||
|
let uptimeCalculator = await UptimeCalculator.getUptimeCalculator(this.id);
|
||||||
|
let endTimeDayjs = await uptimeCalculator.update(bean.status, parseFloat(bean.ping));
|
||||||
|
bean.end_time = R.isoDateTimeMillis(endTimeDayjs);
|
||||||
|
|
||||||
|
// Send to frontend
|
||||||
log.debug("monitor", `[${this.name}] Send to socket`);
|
log.debug("monitor", `[${this.name}] Send to socket`);
|
||||||
UptimeCacheList.clearCache(this.id);
|
|
||||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
Monitor.sendStats(io, this.id, this.user_id);
|
Monitor.sendStats(io, this.id, this.user_id);
|
||||||
|
|
||||||
|
// Store to database
|
||||||
log.debug("monitor", `[${this.name}] Store`);
|
log.debug("monitor", `[${this.name}] Store`);
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
|
@ -979,7 +991,10 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Get a heartbeat and handle errors */
|
/**
|
||||||
|
* Get a heartbeat and handle errors7
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
const safeBeat = async () => {
|
const safeBeat = async () => {
|
||||||
try {
|
try {
|
||||||
await beat();
|
await beat();
|
||||||
|
@ -1007,10 +1022,10 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a request using axios
|
* Make a request using axios
|
||||||
* @param {Object} options Options for Axios
|
* @param {object} options Options for Axios
|
||||||
* @param {boolean} finalCall Should this be the final call i.e
|
* @param {boolean} finalCall Should this be the final call i.e
|
||||||
* don't retry on faliure
|
* don't retry on failure
|
||||||
* @returns {Object} Axios response
|
* @returns {object} Axios response
|
||||||
*/
|
*/
|
||||||
async makeAxiosRequest(options, finalCall = false) {
|
async makeAxiosRequest(options, finalCall = false) {
|
||||||
try {
|
try {
|
||||||
|
@ -1045,7 +1060,10 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop monitor */
|
/**
|
||||||
|
* Stop monitor
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
clearTimeout(this.heartbeatInterval);
|
clearTimeout(this.heartbeatInterval);
|
||||||
this.isStop = true;
|
this.isStop = true;
|
||||||
|
@ -1055,7 +1073,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get prometheus instance
|
* Get prometheus instance
|
||||||
* @returns {Prometheus|undefined}
|
* @returns {Prometheus|undefined} Current prometheus instance
|
||||||
*/
|
*/
|
||||||
getPrometheus() {
|
getPrometheus() {
|
||||||
return this.prometheus;
|
return this.prometheus;
|
||||||
|
@ -1065,7 +1083,7 @@ class Monitor extends BeanModel {
|
||||||
* Helper Method:
|
* Helper Method:
|
||||||
* returns URL object for further usage
|
* returns URL object for further usage
|
||||||
* returns null if url is invalid
|
* returns null if url is invalid
|
||||||
* @returns {(null|URL)}
|
* @returns {(null|URL)} Monitor URL
|
||||||
*/
|
*/
|
||||||
getUrl() {
|
getUrl() {
|
||||||
try {
|
try {
|
||||||
|
@ -1077,8 +1095,8 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store TLS info to database
|
* Store TLS info to database
|
||||||
* @param checkCertificateResult
|
* @param {object} checkCertificateResult Certificate to update
|
||||||
* @returns {Promise<Object>}
|
* @returns {Promise<object>} Updated certificate
|
||||||
*/
|
*/
|
||||||
async updateTlsInfo(checkCertificateResult) {
|
async updateTlsInfo(checkCertificateResult) {
|
||||||
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||||
|
@ -1125,47 +1143,41 @@ class Monitor extends BeanModel {
|
||||||
* @param {Server} io Socket server instance
|
* @param {Server} io Socket server instance
|
||||||
* @param {number} monitorID ID of monitor to send
|
* @param {number} monitorID ID of monitor to send
|
||||||
* @param {number} userID ID of user to send to
|
* @param {number} userID ID of user to send to
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async sendStats(io, monitorID, userID) {
|
static async sendStats(io, monitorID, userID) {
|
||||||
const hasClients = getTotalClientInRoom(io, userID) > 0;
|
const hasClients = getTotalClientInRoom(io, userID) > 0;
|
||||||
|
let uptimeCalculator = await UptimeCalculator.getUptimeCalculator(monitorID);
|
||||||
|
|
||||||
if (hasClients) {
|
if (hasClients) {
|
||||||
await Monitor.sendAvgPing(24, io, monitorID, userID);
|
// Send 24 hour average ping
|
||||||
await Monitor.sendUptime(24, io, monitorID, userID);
|
let data24h = await uptimeCalculator.get24Hour();
|
||||||
await Monitor.sendUptime(24 * 30, io, monitorID, userID);
|
io.to(userID).emit("avgPing", monitorID, (data24h.avgPing) ? data24h.avgPing.toFixed(2) : null);
|
||||||
|
|
||||||
|
// Send 24 hour uptime
|
||||||
|
io.to(userID).emit("uptime", monitorID, 24, data24h.uptime);
|
||||||
|
|
||||||
|
// Send 30 day uptime
|
||||||
|
let data30d = await uptimeCalculator.get30Day();
|
||||||
|
io.to(userID).emit("uptime", monitorID, 720, data30d.uptime);
|
||||||
|
|
||||||
|
// Send 1-year uptime
|
||||||
|
let data1y = await uptimeCalculator.get1Year();
|
||||||
|
io.to(userID).emit("uptime", monitorID, "1y", data1y.uptime);
|
||||||
|
|
||||||
|
// Send Cert Info
|
||||||
await Monitor.sendCertInfo(io, monitorID, userID);
|
await Monitor.sendCertInfo(io, monitorID, userID);
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor", "No clients in the room, no need to send stats");
|
log.debug("monitor", "No clients in the room, no need to send stats");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the average ping to user
|
|
||||||
* @param {number} duration Hours
|
|
||||||
*/
|
|
||||||
static async sendAvgPing(duration, io, monitorID, userID) {
|
|
||||||
const timeLogger = new TimeLogger();
|
|
||||||
|
|
||||||
let avgPing = parseInt(await R.getCell(`
|
|
||||||
SELECT AVG(ping)
|
|
||||||
FROM heartbeat
|
|
||||||
WHERE time > DATETIME('now', ? || ' hours')
|
|
||||||
AND ping IS NOT NULL
|
|
||||||
AND monitor_id = ? `, [
|
|
||||||
-duration,
|
|
||||||
monitorID,
|
|
||||||
]));
|
|
||||||
|
|
||||||
timeLogger.print(`[Monitor: ${monitorID}] avgPing`);
|
|
||||||
|
|
||||||
io.to(userID).emit("avgPing", monitorID, avgPing);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send certificate information to client
|
* Send certificate information to client
|
||||||
* @param {Server} io Socket server instance
|
* @param {Server} io Socket server instance
|
||||||
* @param {number} monitorID ID of monitor to send
|
* @param {number} monitorID ID of monitor to send
|
||||||
* @param {number} userID ID of user to send to
|
* @param {number} userID ID of user to send to
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async sendCertInfo(io, monitorID, userID) {
|
static async sendCertInfo(io, monitorID, userID) {
|
||||||
let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||||
|
@ -1176,98 +1188,6 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uptime with calculation
|
|
||||||
* Calculation based on:
|
|
||||||
* https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime
|
|
||||||
* @param {number} duration Hours
|
|
||||||
* @param {number} monitorID ID of monitor to calculate
|
|
||||||
*/
|
|
||||||
static async calcUptime(duration, monitorID, forceNoCache = false) {
|
|
||||||
|
|
||||||
if (!forceNoCache) {
|
|
||||||
let cachedUptime = UptimeCacheList.getUptime(monitorID, duration);
|
|
||||||
if (cachedUptime != null) {
|
|
||||||
return cachedUptime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeLogger = new TimeLogger();
|
|
||||||
|
|
||||||
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
|
|
||||||
|
|
||||||
// Handle if heartbeat duration longer than the target duration
|
|
||||||
// e.g. If the last beat's duration is bigger that the 24hrs window, it will use the duration between the (beat time - window margin) (THEN case in SQL)
|
|
||||||
let result = await R.getRow(`
|
|
||||||
SELECT
|
|
||||||
-- SUM all duration, also trim off the beat out of time window
|
|
||||||
SUM(
|
|
||||||
CASE
|
|
||||||
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
|
||||||
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
|
|
||||||
ELSE duration
|
|
||||||
END
|
|
||||||
) AS total_duration,
|
|
||||||
|
|
||||||
-- SUM all uptime duration, also trim off the beat out of time window
|
|
||||||
SUM(
|
|
||||||
CASE
|
|
||||||
WHEN (status = 1 OR status = 3)
|
|
||||||
THEN
|
|
||||||
CASE
|
|
||||||
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
|
||||||
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
|
|
||||||
ELSE duration
|
|
||||||
END
|
|
||||||
END
|
|
||||||
) AS uptime_duration
|
|
||||||
FROM heartbeat
|
|
||||||
WHERE time > ?
|
|
||||||
AND monitor_id = ?
|
|
||||||
`, [
|
|
||||||
startTime, startTime, startTime, startTime, startTime,
|
|
||||||
monitorID,
|
|
||||||
]);
|
|
||||||
|
|
||||||
timeLogger.print(`[Monitor: ${monitorID}][${duration}] sendUptime`);
|
|
||||||
|
|
||||||
let totalDuration = result.total_duration;
|
|
||||||
let uptimeDuration = result.uptime_duration;
|
|
||||||
let uptime = 0;
|
|
||||||
|
|
||||||
if (totalDuration > 0) {
|
|
||||||
uptime = uptimeDuration / totalDuration;
|
|
||||||
if (uptime < 0) {
|
|
||||||
uptime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Handle new monitor with only one beat, because the beat's duration = 0
|
|
||||||
let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ]));
|
|
||||||
|
|
||||||
if (status === UP) {
|
|
||||||
uptime = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache
|
|
||||||
UptimeCacheList.addUptime(monitorID, duration, uptime);
|
|
||||||
|
|
||||||
return uptime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send Uptime
|
|
||||||
* @param {number} duration Hours
|
|
||||||
* @param {Server} io Socket server instance
|
|
||||||
* @param {number} monitorID ID of monitor to send
|
|
||||||
* @param {number} userID ID of user to send to
|
|
||||||
*/
|
|
||||||
static async sendUptime(duration, io, monitorID, userID) {
|
|
||||||
const uptime = await this.calcUptime(duration, monitorID);
|
|
||||||
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has status of monitor changed since last beat?
|
* Has status of monitor changed since last beat?
|
||||||
* @param {boolean} isFirstBeat Is this the first beat of this monitor?
|
* @param {boolean} isFirstBeat Is this the first beat of this monitor?
|
||||||
|
@ -1336,6 +1256,7 @@ class Monitor extends BeanModel {
|
||||||
* @param {boolean} isFirstBeat Is this beat the first of this monitor?
|
* @param {boolean} isFirstBeat Is this beat the first of this monitor?
|
||||||
* @param {Monitor} monitor The monitor to send a notificaton about
|
* @param {Monitor} monitor The monitor to send a notificaton about
|
||||||
* @param {Bean} bean Status information about monitor
|
* @param {Bean} bean Status information about monitor
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async sendNotification(isFirstBeat, monitor, bean) {
|
static async sendNotification(isFirstBeat, monitor, bean) {
|
||||||
if (!isFirstBeat || bean.status === DOWN) {
|
if (!isFirstBeat || bean.status === DOWN) {
|
||||||
|
@ -1376,7 +1297,7 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Get list of notification providers for a given monitor
|
* Get list of notification providers for a given monitor
|
||||||
* @param {Monitor} monitor Monitor to get notification providers for
|
* @param {Monitor} monitor Monitor to get notification providers for
|
||||||
* @returns {Promise<LooseObject<any>[]>}
|
* @returns {Promise<LooseObject<any>[]>} List of notifications
|
||||||
*/
|
*/
|
||||||
static async getNotificationList(monitor) {
|
static async getNotificationList(monitor) {
|
||||||
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
|
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
|
||||||
|
@ -1387,7 +1308,8 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks certificate chain for expiring certificates
|
* checks certificate chain for expiring certificates
|
||||||
* @param {Object} tlsInfoObject Information about certificate
|
* @param {object} tlsInfoObject Information about certificate
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
async checkCertExpiryNotifications(tlsInfoObject) {
|
async checkCertExpiryNotifications(tlsInfoObject) {
|
||||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||||
|
@ -1474,7 +1396,7 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Get the status of the previous heartbeat
|
* Get the status of the previous heartbeat
|
||||||
* @param {number} monitorID ID of monitor to check
|
* @param {number} monitorID ID of monitor to check
|
||||||
* @returns {Promise<LooseObject<any>>}
|
* @returns {Promise<LooseObject<any>>} Previous heartbeat
|
||||||
*/
|
*/
|
||||||
static async getPreviousHeartbeat(monitorID) {
|
static async getPreviousHeartbeat(monitorID) {
|
||||||
return await R.getRow(`
|
return await R.getRow(`
|
||||||
|
@ -1488,7 +1410,7 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Check if monitor is under maintenance
|
* Check if monitor is under maintenance
|
||||||
* @param {number} monitorID ID of monitor to check
|
* @param {number} monitorID ID of monitor to check
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>} Is the monitor under maintenance
|
||||||
*/
|
*/
|
||||||
static async isUnderMaintenance(monitorID) {
|
static async isUnderMaintenance(monitorID) {
|
||||||
const maintenanceIDList = await R.getCol(`
|
const maintenanceIDList = await R.getCol(`
|
||||||
|
@ -1511,7 +1433,11 @@ class Monitor extends BeanModel {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Make sure monitor interval is between bounds */
|
/**
|
||||||
|
* Make sure monitor interval is between bounds
|
||||||
|
* @returns {void}
|
||||||
|
* @throws Interval is outside of range
|
||||||
|
*/
|
||||||
validate() {
|
validate() {
|
||||||
if (this.interval > MAX_INTERVAL_SECOND) {
|
if (this.interval > MAX_INTERVAL_SECOND) {
|
||||||
throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`);
|
throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`);
|
||||||
|
@ -1524,7 +1450,7 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Gets Parent of the monitor
|
* Gets Parent of the monitor
|
||||||
* @param {number} monitorID ID of monitor to get
|
* @param {number} monitorID ID of monitor to get
|
||||||
* @returns {Promise<LooseObject<any>>}
|
* @returns {Promise<LooseObject<any>>} Parent
|
||||||
*/
|
*/
|
||||||
static async getParent(monitorID) {
|
static async getParent(monitorID) {
|
||||||
return await R.getRow(`
|
return await R.getRow(`
|
||||||
|
@ -1540,7 +1466,7 @@ class Monitor extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Gets all Children of the monitor
|
* Gets all Children of the monitor
|
||||||
* @param {number} monitorID ID of monitor to get
|
* @param {number} monitorID ID of monitor to get
|
||||||
* @returns {Promise<LooseObject<any>>}
|
* @returns {Promise<LooseObject<any>>} Children
|
||||||
*/
|
*/
|
||||||
static async getChildren(monitorID) {
|
static async getChildren(monitorID) {
|
||||||
return await R.getAll(`
|
return await R.getAll(`
|
||||||
|
@ -1553,7 +1479,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets Full Path-Name (Groups and Name)
|
* Gets Full Path-Name (Groups and Name)
|
||||||
* @returns {Promise<String>}
|
* @returns {Promise<string>} Full path name of this monitor
|
||||||
*/
|
*/
|
||||||
async getPathName() {
|
async getPathName() {
|
||||||
let path = this.name;
|
let path = this.name;
|
||||||
|
@ -1573,8 +1499,8 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets recursive all child ids
|
* Gets recursive all child ids
|
||||||
* @param {number} monitorID ID of the monitor to get
|
* @param {number} monitorID ID of the monitor to get
|
||||||
* @returns {Promise<Array>}
|
* @returns {Promise<Array>} IDs of all children
|
||||||
*/
|
*/
|
||||||
static async getAllChildrenIDs(monitorID) {
|
static async getAllChildrenIDs(monitorID) {
|
||||||
const childs = await Monitor.getChildren(monitorID);
|
const childs = await Monitor.getChildren(monitorID);
|
||||||
|
@ -1605,10 +1531,10 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks recursive if parent (ancestors) are active
|
* Checks recursive if parent (ancestors) are active
|
||||||
* @param {number} monitorID ID of the monitor to get
|
* @param {number} monitorID ID of the monitor to get
|
||||||
* @returns {Promise<Boolean>}
|
* @returns {Promise<boolean>} Is the parent monitor active?
|
||||||
*/
|
*/
|
||||||
static async isParentActive(monitorID) {
|
static async isParentActive(monitorID) {
|
||||||
const parent = await Monitor.getParent(monitorID);
|
const parent = await Monitor.getParent(monitorID);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
class Proxy extends BeanModel {
|
class Proxy extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -14,10 +14,11 @@ class StatusPage extends BeanModel {
|
||||||
static domainMappingList = { };
|
static domainMappingList = { };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Handle responses to status page
|
||||||
* @param {Response} response
|
* @param {Response} response Response object
|
||||||
* @param {string} indexHTML
|
* @param {string} indexHTML HTML to render
|
||||||
* @param {string} slug
|
* @param {string} slug Status page slug
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async handleStatusPageResponse(response, indexHTML, slug) {
|
static async handleStatusPageResponse(response, indexHTML, slug) {
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
@ -33,8 +34,9 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSR for status pages
|
* SSR for status pages
|
||||||
* @param {string} indexHTML
|
* @param {string} indexHTML HTML page to render
|
||||||
* @param {StatusPage} statusPage
|
* @param {StatusPage} statusPage Status page populate HTML with
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static async renderHTML(indexHTML, statusPage) {
|
static async renderHTML(indexHTML, statusPage) {
|
||||||
const $ = cheerio.load(indexHTML);
|
const $ = cheerio.load(indexHTML);
|
||||||
|
@ -87,7 +89,8 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all status page data in one call
|
* Get all status page data in one call
|
||||||
* @param {StatusPage} statusPage
|
* @param {StatusPage} statusPage Status page to get data for
|
||||||
|
* @returns {object} Status page data
|
||||||
*/
|
*/
|
||||||
static async getStatusPageData(statusPage) {
|
static async getStatusPageData(statusPage) {
|
||||||
const config = await statusPage.toPublicJSON();
|
const config = await statusPage.toPublicJSON();
|
||||||
|
@ -142,7 +145,7 @@ class StatusPage extends BeanModel {
|
||||||
* Send status page list to client
|
* Send status page list to client
|
||||||
* @param {Server} io io Socket server instance
|
* @param {Server} io io Socket server instance
|
||||||
* @param {Socket} socket Socket.io instance
|
* @param {Socket} socket Socket.io instance
|
||||||
* @returns {Promise<Bean[]>}
|
* @returns {Promise<Bean[]>} Status page list
|
||||||
*/
|
*/
|
||||||
static async sendStatusPageList(io, socket) {
|
static async sendStatusPageList(io, socket) {
|
||||||
let result = {};
|
let result = {};
|
||||||
|
@ -159,7 +162,7 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update list of domain names
|
* Update list of domain names
|
||||||
* @param {string[]} domainNameList
|
* @param {string[]} domainNameList List of status page domains
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async updateDomainNameList(domainNameList) {
|
async updateDomainNameList(domainNameList) {
|
||||||
|
@ -203,7 +206,7 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of domain names
|
* Get list of domain names
|
||||||
* @returns {Object[]}
|
* @returns {object[]} List of status page domains
|
||||||
*/
|
*/
|
||||||
getDomainNameList() {
|
getDomainNameList() {
|
||||||
let domainList = [];
|
let domainList = [];
|
||||||
|
@ -219,7 +222,7 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toJSON() {
|
async toJSON() {
|
||||||
return {
|
return {
|
||||||
|
@ -243,7 +246,7 @@ class StatusPage extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON for public
|
* Return an object that ready to parse to JSON for public
|
||||||
* Only show necessary data to public
|
* Only show necessary data to public
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toPublicJSON() {
|
async toPublicJSON() {
|
||||||
return {
|
return {
|
||||||
|
@ -264,7 +267,8 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert slug to status page ID
|
* Convert slug to status page ID
|
||||||
* @param {string} slug
|
* @param {string} slug Status page slug
|
||||||
|
* @returns {Promise<number>} ID of status page
|
||||||
*/
|
*/
|
||||||
static async slugToID(slug) {
|
static async slugToID(slug) {
|
||||||
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
||||||
|
@ -274,7 +278,7 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get path to the icon for the page
|
* Get path to the icon for the page
|
||||||
* @returns {string}
|
* @returns {string} Path
|
||||||
*/
|
*/
|
||||||
getIcon() {
|
getIcon() {
|
||||||
if (!this.icon) {
|
if (!this.icon) {
|
||||||
|
@ -287,7 +291,7 @@ class StatusPage extends BeanModel {
|
||||||
/**
|
/**
|
||||||
* Get list of maintenances
|
* Get list of maintenances
|
||||||
* @param {number} statusPageId ID of status page to get maintenance for
|
* @param {number} statusPageId ID of status page to get maintenance for
|
||||||
* @returns {Object} Object representing maintenances sanitized for public
|
* @returns {object} Object representing maintenances sanitized for public
|
||||||
*/
|
*/
|
||||||
static async getMaintenanceList(statusPageId) {
|
static async getMaintenanceList(statusPageId) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,7 +4,7 @@ class Tag extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
* @returns {Object}
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,7 +7,7 @@ class User extends BeanModel {
|
||||||
* Reset user password
|
* Reset user password
|
||||||
* Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead.
|
* Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead.
|
||||||
* @param {number} userID ID of user to update
|
* @param {number} userID ID of user to update
|
||||||
* @param {string} newPassword
|
* @param {string} newPassword Users new password
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async resetPassword(userID, newPassword) {
|
static async resetPassword(userID, newPassword) {
|
||||||
|
@ -19,7 +19,7 @@ class User extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset this users password
|
* Reset this users password
|
||||||
* @param {string} newPassword
|
* @param {string} newPassword Users new password
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async resetPassword(newPassword) {
|
async resetPassword(newPassword) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue