mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-12 15:42:35 +02:00
Update text
This commit is contained in:
parent
379656335d
commit
c460385da1
4 changed files with 664 additions and 340 deletions
|
@ -3,11 +3,25 @@ const { R } = require("redbean-node");
|
||||||
const { setSetting, setting } = require("./util-server");
|
const { setSetting, setting } = require("./util-server");
|
||||||
const { debug, sleep } = require("../src/util");
|
const { debug, sleep } = require("../src/util");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
const knex = require("knex");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database & App Data Folder
|
||||||
|
*/
|
||||||
class Database {
|
class Database {
|
||||||
|
|
||||||
static templatePath = "./db/kuma.db";
|
static templatePath = "./db/kuma.db";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Dir (Default: ./data)
|
||||||
|
*/
|
||||||
static dataDir;
|
static dataDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Upload Dir (Default: ./data/upload)
|
||||||
|
*/
|
||||||
|
static uploadDir;
|
||||||
|
|
||||||
static path;
|
static path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +45,11 @@ class Database {
|
||||||
"patch-setting-value-type.sql": true,
|
"patch-setting-value-type.sql": true,
|
||||||
"patch-improve-performance.sql": true,
|
"patch-improve-performance.sql": true,
|
||||||
"patch-2fa.sql": true,
|
"patch-2fa.sql": true,
|
||||||
|
"patch-add-retry-interval-monitor.sql": true,
|
||||||
|
"patch-incident-table.sql": true,
|
||||||
|
"patch-group-table.sql": true,
|
||||||
|
"patch-monitor-push_token.sql": true,
|
||||||
|
"patch-http-monitor-method-body-and-headers.sql": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,29 +60,56 @@ class Database {
|
||||||
|
|
||||||
static noReject = true;
|
static noReject = true;
|
||||||
|
|
||||||
|
static init(args) {
|
||||||
|
// Data Directory (must be end with "/")
|
||||||
|
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||||
|
Database.path = Database.dataDir + "kuma.db";
|
||||||
|
if (! fs.existsSync(Database.dataDir)) {
|
||||||
|
fs.mkdirSync(Database.dataDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
Database.uploadDir = Database.dataDir + "upload/";
|
||||||
|
|
||||||
|
if (! fs.existsSync(Database.uploadDir)) {
|
||||||
|
fs.mkdirSync(Database.uploadDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Data Dir: ${Database.dataDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
static async connect() {
|
static async connect() {
|
||||||
const acquireConnectionTimeout = 120 * 1000;
|
const acquireConnectionTimeout = 120 * 1000;
|
||||||
|
|
||||||
R.setup("sqlite", {
|
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||||
filename: Database.path,
|
Dialect.prototype._driver = () => require("@louislam/sqlite3");
|
||||||
|
|
||||||
|
const knexInstance = knex({
|
||||||
|
client: Dialect,
|
||||||
|
connection: {
|
||||||
|
filename: Database.path,
|
||||||
|
acquireConnectionTimeout: acquireConnectionTimeout,
|
||||||
|
},
|
||||||
useNullAsDefault: true,
|
useNullAsDefault: true,
|
||||||
acquireConnectionTimeout: acquireConnectionTimeout,
|
pool: {
|
||||||
}, {
|
min: 1,
|
||||||
min: 1,
|
max: 1,
|
||||||
max: 1,
|
idleTimeoutMillis: 120 * 1000,
|
||||||
idleTimeoutMillis: 120 * 1000,
|
propagateCreateError: false,
|
||||||
propagateCreateError: false,
|
acquireTimeoutMillis: acquireConnectionTimeout,
|
||||||
acquireTimeoutMillis: acquireConnectionTimeout,
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
R.setup(knexInstance);
|
||||||
|
|
||||||
if (process.env.SQL_LOG === "1") {
|
if (process.env.SQL_LOG === "1") {
|
||||||
R.debug(true);
|
R.debug(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto map the model to a bean object
|
// Auto map the model to a bean object
|
||||||
R.freeze(true)
|
R.freeze(true);
|
||||||
await R.autoloadModels("./server/model");
|
await R.autoloadModels("./server/model");
|
||||||
|
|
||||||
|
await R.exec("PRAGMA foreign_keys = ON");
|
||||||
// Change to WAL
|
// Change to WAL
|
||||||
await R.exec("PRAGMA journal_mode = WAL");
|
await R.exec("PRAGMA journal_mode = WAL");
|
||||||
await R.exec("PRAGMA cache_size = -12000");
|
await R.exec("PRAGMA cache_size = -12000");
|
||||||
|
@ -71,6 +117,7 @@ class Database {
|
||||||
console.log("SQLite config:");
|
console.log("SQLite config:");
|
||||||
console.log(await R.getAll("PRAGMA journal_mode"));
|
console.log(await R.getAll("PRAGMA journal_mode"));
|
||||||
console.log(await R.getAll("PRAGMA cache_size"));
|
console.log(await R.getAll("PRAGMA cache_size"));
|
||||||
|
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async patch() {
|
static async patch() {
|
||||||
|
@ -84,11 +131,11 @@ class Database {
|
||||||
console.info("Latest database version: " + this.latestVersion);
|
console.info("Latest database version: " + this.latestVersion);
|
||||||
|
|
||||||
if (version === this.latestVersion) {
|
if (version === this.latestVersion) {
|
||||||
console.info("Database no need to patch");
|
console.info("Database patch not needed");
|
||||||
} else if (version > this.latestVersion) {
|
} else if (version > this.latestVersion) {
|
||||||
console.info("Warning: Database version is newer than expected");
|
console.info("Warning: Database version is newer than expected");
|
||||||
} else {
|
} else {
|
||||||
console.info("Database patch is needed")
|
console.info("Database patch is needed");
|
||||||
|
|
||||||
this.backup(version);
|
this.backup(version);
|
||||||
|
|
||||||
|
@ -103,11 +150,12 @@ class Database {
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await Database.close();
|
await Database.close();
|
||||||
this.restore();
|
|
||||||
|
|
||||||
console.error(ex)
|
console.error(ex);
|
||||||
console.error("Start Uptime-Kuma failed due to patch db failed")
|
console.error("Start Uptime-Kuma failed due to issue patching the database");
|
||||||
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues")
|
console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
|
this.restore();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +180,7 @@ class Database {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let sqlFilename in this.patchList) {
|
for (let sqlFilename in this.patchList) {
|
||||||
await this.patch2Recursion(sqlFilename, databasePatchedFiles)
|
await this.patch2Recursion(sqlFilename, databasePatchedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.patched) {
|
if (this.patched) {
|
||||||
|
@ -141,11 +189,13 @@ class Database {
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await Database.close();
|
await Database.close();
|
||||||
|
|
||||||
|
console.error(ex);
|
||||||
|
console.error("Start Uptime-Kuma failed due to issue patching the database");
|
||||||
|
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
this.restore();
|
this.restore();
|
||||||
|
|
||||||
console.error(ex)
|
|
||||||
console.error("Start Uptime-Kuma failed due to patch db failed");
|
|
||||||
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,10 +232,10 @@ class Database {
|
||||||
this.patched = true;
|
this.patched = true;
|
||||||
await this.importSQLFile("./db/" + sqlFilename);
|
await this.importSQLFile("./db/" + sqlFilename);
|
||||||
databasePatchedFiles[sqlFilename] = true;
|
databasePatchedFiles[sqlFilename] = true;
|
||||||
console.log(sqlFilename + " is patched successfully");
|
console.log(sqlFilename + " was patched successfully");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log(sqlFilename + " is already patched, skip");
|
debug(sqlFilename + " is already patched, skip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,12 +253,12 @@ class Database {
|
||||||
// Remove all comments (--)
|
// Remove all comments (--)
|
||||||
let lines = text.split("\n");
|
let lines = text.split("\n");
|
||||||
lines = lines.filter((line) => {
|
lines = lines.filter((line) => {
|
||||||
return ! line.startsWith("--")
|
return ! line.startsWith("--");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Split statements by semicolon
|
// Split statements by semicolon
|
||||||
// Filter out empty line
|
// Filter out empty line
|
||||||
text = lines.join("\n")
|
text = lines.join("\n");
|
||||||
|
|
||||||
let statements = text.split(";")
|
let statements = text.split(";")
|
||||||
.map((statement) => {
|
.map((statement) => {
|
||||||
|
@ -216,7 +266,7 @@ class Database {
|
||||||
})
|
})
|
||||||
.filter((statement) => {
|
.filter((statement) => {
|
||||||
return statement !== "";
|
return statement !== "";
|
||||||
})
|
});
|
||||||
|
|
||||||
for (let statement of statements) {
|
for (let statement of statements) {
|
||||||
await R.exec(statement);
|
await R.exec(statement);
|
||||||
|
@ -237,7 +287,7 @@ class Database {
|
||||||
};
|
};
|
||||||
process.addListener("unhandledRejection", listener);
|
process.addListener("unhandledRejection", listener);
|
||||||
|
|
||||||
console.log("Closing DB");
|
console.log("Closing the database");
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Database.noReject = true;
|
Database.noReject = true;
|
||||||
|
@ -247,7 +297,7 @@ class Database {
|
||||||
if (Database.noReject) {
|
if (Database.noReject) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
console.log("Waiting to close the db");
|
console.log("Waiting to close the database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("SQLite closed");
|
console.log("SQLite closed");
|
||||||
|
@ -262,7 +312,7 @@ class Database {
|
||||||
*/
|
*/
|
||||||
static backup(version) {
|
static backup(version) {
|
||||||
if (! this.backupPath) {
|
if (! this.backupPath) {
|
||||||
console.info("Backup the db")
|
console.info("Backup the database");
|
||||||
this.backupPath = this.dataDir + "kuma.db.bak" + version;
|
this.backupPath = this.dataDir + "kuma.db.bak" + version;
|
||||||
fs.copyFileSync(Database.path, this.backupPath);
|
fs.copyFileSync(Database.path, this.backupPath);
|
||||||
|
|
||||||
|
@ -285,7 +335,7 @@ class Database {
|
||||||
*/
|
*/
|
||||||
static restore() {
|
static restore() {
|
||||||
if (this.backupPath) {
|
if (this.backupPath) {
|
||||||
console.error("Patch db failed!!! Restoring the backup");
|
console.error("Patching the database failed!!! Restoring the backup");
|
||||||
|
|
||||||
const shmPath = Database.path + "-shm";
|
const shmPath = Database.path + "-shm";
|
||||||
const walPath = Database.path + "-wal";
|
const walPath = Database.path + "-wal";
|
||||||
|
@ -304,7 +354,7 @@ class Database {
|
||||||
fs.unlinkSync(walPath);
|
fs.unlinkSync(walPath);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Restore failed, you may need to restore the backup manually");
|
console.log("Restore failed; you may need to restore the backup manually");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
603
server/server.js
603
server/server.js
File diff suppressed because it is too large
Load diff
|
@ -26,19 +26,35 @@
|
||||||
<option value="dns">
|
<option value="dns">
|
||||||
DNS
|
DNS
|
||||||
</option>
|
</option>
|
||||||
|
<option value="push">
|
||||||
|
Push
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Friendly Name -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
||||||
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- URL -->
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
||||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||||
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Push URL -->
|
||||||
|
<div v-if="monitor.type === 'push' " class="my-3">
|
||||||
|
<label for="push-url" class="form-label">{{ $t("PushUrl") }}</label>
|
||||||
|
<CopyableInput id="push-url" v-model="pushURL" type="url" disabled="disabled" />
|
||||||
|
<div class="form-text">
|
||||||
|
You should call this url every {{ monitor.interval }} seconds.<br />
|
||||||
|
Optional parameters: msg, ping
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Keyword -->
|
||||||
<div v-if="monitor.type === 'keyword' " class="my-3">
|
<div v-if="monitor.type === 'keyword' " class="my-3">
|
||||||
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
||||||
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
||||||
|
@ -47,10 +63,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hostname -->
|
||||||
<!-- TCP Port / Ping / DNS only -->
|
<!-- TCP Port / Ping / DNS only -->
|
||||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
||||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required>
|
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- For TCP Port Type -->
|
<!-- For TCP Port Type -->
|
||||||
|
@ -93,6 +110,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Interval -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
||||||
<input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1">
|
<input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1">
|
||||||
|
@ -106,7 +124,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
<div class="my-3">
|
||||||
|
<label for="retry-interval" class="form-label">
|
||||||
|
{{ $t("Heartbeat Retry Interval") }}
|
||||||
|
<span>({{ $t("retryCheckEverySecond", [ monitor.retryInterval ]) }})</span>
|
||||||
|
</label>
|
||||||
|
<input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required min="20" step="1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||||
|
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
||||||
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
||||||
|
@ -170,6 +196,7 @@
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div v-if="$root.isMobile" class="mt-3" />
|
<div v-if="$root.isMobile" class="mt-3" />
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
<h2 class="mb-2">{{ $t("Notifications") }}</h2>
|
<h2 class="mb-2">{{ $t("Notifications") }}</h2>
|
||||||
<p v-if="$root.notificationList.length === 0">
|
<p v-if="$root.notificationList.length === 0">
|
||||||
{{ $t("Not available, please setup.") }}
|
{{ $t("Not available, please setup.") }}
|
||||||
|
@ -189,6 +216,51 @@
|
||||||
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
|
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
|
||||||
{{ $t("Setup Notification") }}
|
{{ $t("Setup Notification") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- HTTP Options -->
|
||||||
|
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
|
||||||
|
<h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>
|
||||||
|
|
||||||
|
<!-- Method -->
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="method" class="form-label">{{ $t("Method") }}</label>
|
||||||
|
<select id="method" v-model="monitor.method" class="form-select">
|
||||||
|
<option value="GET">
|
||||||
|
GET
|
||||||
|
</option>
|
||||||
|
<option value="POST">
|
||||||
|
POST
|
||||||
|
</option>
|
||||||
|
<option value="PUT">
|
||||||
|
PUT
|
||||||
|
</option>
|
||||||
|
<option value="PATCH">
|
||||||
|
PATCH
|
||||||
|
</option>
|
||||||
|
<option value="DELETE">
|
||||||
|
DELETE
|
||||||
|
</option>
|
||||||
|
<option value="HEAD">
|
||||||
|
HEAD
|
||||||
|
</option>
|
||||||
|
<option value="OPTIONS">
|
||||||
|
OPTIONS
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="body" class="form-label">{{ $t("Body") }}</label>
|
||||||
|
<textarea id="body" v-model="monitor.body" class="form-control" :placeholder="bodyPlaceholder"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Headers -->
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="headers" class="form-label">{{ $t("Headers") }}</label>
|
||||||
|
<textarea id="headers" v-model="monitor.headers" class="form-control" :placeholder="headersPlaceholder"></textarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -202,13 +274,17 @@
|
||||||
<script>
|
<script>
|
||||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||||
import TagsManager from "../components/TagsManager.vue";
|
import TagsManager from "../components/TagsManager.vue";
|
||||||
import { useToast } from "vue-toastification"
|
import CopyableInput from "../components/CopyableInput.vue";
|
||||||
import VueMultiselect from "vue-multiselect"
|
|
||||||
import { isDev } from "../util.ts";
|
import { useToast } from "vue-toastification";
|
||||||
const toast = useToast()
|
import VueMultiselect from "vue-multiselect";
|
||||||
|
import { genSecret, isDev } from "../util.ts";
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
CopyableInput,
|
||||||
NotificationDialog,
|
NotificationDialog,
|
||||||
TagsManager,
|
TagsManager,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
|
@ -225,9 +301,10 @@ export default {
|
||||||
dnsresolvetypeOptions: [],
|
dnsresolvetypeOptions: [],
|
||||||
|
|
||||||
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
||||||
// eslint-disable-next-line
|
ipRegexPattern: "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))",
|
||||||
ipRegexPattern: "((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))",
|
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
||||||
}
|
hostnameRegexPattern: "^(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\\-_]*[a-zA-Z0-9_])\\.)*([A-Za-z0-9_]|[A-Za-z0-9_][A-Za-z0-9\\-_]*[A-Za-z0-9_])$"
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -244,17 +321,49 @@ export default {
|
||||||
pageName() {
|
pageName() {
|
||||||
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
||||||
},
|
},
|
||||||
|
|
||||||
isAdd() {
|
isAdd() {
|
||||||
return this.$route.path === "/add";
|
return this.$route.path === "/add";
|
||||||
},
|
},
|
||||||
|
|
||||||
isEdit() {
|
isEdit() {
|
||||||
return this.$route.path.startsWith("/edit");
|
return this.$route.path.startsWith("/edit");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pushURL() {
|
||||||
|
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?msg=OK&ping=";
|
||||||
|
},
|
||||||
|
|
||||||
|
bodyPlaceholder() {
|
||||||
|
return this.decodeHtml("{\n\t\"id\": 124357,\n\t\"username\": \"admin\",\n\t\"password\": \"myAdminPassword\"\n}");
|
||||||
|
},
|
||||||
|
|
||||||
|
headersPlaceholder() {
|
||||||
|
return this.decodeHtml("{\n\t\"Authorization\": \"Bearer abc123\",\n\t\"Content-Type\": \"application/json\"\n}");
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
"$route.fullPath"() {
|
"$route.fullPath"() {
|
||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"monitor.interval"(value, oldValue) {
|
||||||
|
// Link interval and retryInterval if they are the same value.
|
||||||
|
if (this.monitor.retryInterval === oldValue) {
|
||||||
|
this.monitor.retryInterval = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"monitor.type"() {
|
||||||
|
if (this.monitor.type === "push") {
|
||||||
|
if (! this.monitor.pushToken) {
|
||||||
|
this.monitor.pushToken = genSecret(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
@ -295,7 +404,9 @@ export default {
|
||||||
type: "http",
|
type: "http",
|
||||||
name: "",
|
name: "",
|
||||||
url: "https://",
|
url: "https://",
|
||||||
|
method: "GET",
|
||||||
interval: 60,
|
interval: 60,
|
||||||
|
retryInterval: this.interval,
|
||||||
maxretries: 0,
|
maxretries: 0,
|
||||||
notificationIDList: {},
|
notificationIDList: {},
|
||||||
ignoreTls: false,
|
ignoreTls: false,
|
||||||
|
@ -304,7 +415,7 @@ export default {
|
||||||
accepted_statuscodes: ["200-299"],
|
accepted_statuscodes: ["200-299"],
|
||||||
dns_resolve_type: "A",
|
dns_resolve_type: "A",
|
||||||
dns_resolve_server: "1.1.1.1",
|
dns_resolve_server: "1.1.1.1",
|
||||||
}
|
};
|
||||||
|
|
||||||
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
||||||
if (this.$root.notificationList[i].isDefault == true) {
|
if (this.$root.notificationList[i].isDefault == true) {
|
||||||
|
@ -315,17 +426,56 @@ export default {
|
||||||
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.monitor = res.monitor;
|
this.monitor = res.monitor;
|
||||||
|
|
||||||
|
// Handling for monitors that are created before 1.7.0
|
||||||
|
if (this.monitor.retryInterval === 0) {
|
||||||
|
this.monitor.retryInterval = this.monitor.interval;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg)
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isInputValid() {
|
||||||
|
if (this.monitor.body) {
|
||||||
|
try {
|
||||||
|
JSON.parse(this.monitor.body);
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(this.$t("BodyInvalidFormat") + err.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.monitor.headers) {
|
||||||
|
try {
|
||||||
|
JSON.parse(this.monitor.headers);
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(this.$t("HeadersInvalidFormat") + err.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
|
if (!this.isInputValid()) {
|
||||||
|
this.processing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beautify the JSON format
|
||||||
|
if (this.monitor.body) {
|
||||||
|
this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.monitor.headers) {
|
||||||
|
this.monitor.headers = JSON.stringify(JSON.parse(this.monitor.headers), null, 4);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
this.$root.add(this.monitor, async (res) => {
|
this.$root.add(this.monitor, async (res) => {
|
||||||
|
|
||||||
|
@ -335,13 +485,13 @@ export default {
|
||||||
toast.success(res.msg);
|
toast.success(res.msg);
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.getMonitorList();
|
this.$root.getMonitorList();
|
||||||
this.$router.push("/dashboard/" + res.monitorID)
|
this.$router.push("/dashboard/" + res.monitorID);
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.$refs.tagsManager.submit(this.monitor.id);
|
await this.$refs.tagsManager.submit(this.monitor.id);
|
||||||
|
|
||||||
|
@ -349,7 +499,7 @@ export default {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
this.init();
|
this.init();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -358,64 +508,22 @@ export default {
|
||||||
addedNotification(id) {
|
addedNotification(id) {
|
||||||
this.monitor.notificationIDList[id] = true;
|
this.monitor.notificationIDList[id] = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
decodeHtml(html) {
|
||||||
|
const txt = document.createElement("textarea");
|
||||||
|
txt.innerHTML = html;
|
||||||
|
return txt.value;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import "../assets/vars.scss";
|
|
||||||
|
|
||||||
.multiselect__tags {
|
|
||||||
border-radius: 1.5rem;
|
|
||||||
border: 1px solid #ced4da;
|
|
||||||
min-height: 38px;
|
|
||||||
padding: 6px 40px 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect--active .multiselect__tags {
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect__option--highlight {
|
|
||||||
background: $primary !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect__option--highlight::after {
|
|
||||||
background: $primary !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect__tag {
|
|
||||||
border-radius: 50rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding: 6px 26px 6px 10px;
|
|
||||||
background: $primary !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect__placeholder {
|
|
||||||
font-size: 1rem;
|
|
||||||
padding-left: 6px;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
opacity: 0.67;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect__input, .multiselect__single {
|
|
||||||
line-height: 14px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
.multiselect__tag {
|
|
||||||
color: $dark-font-color2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.shadow-box {
|
.shadow-box {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -46,8 +46,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useToast } from "vue-toastification"
|
import { useToast } from "vue-toastification";
|
||||||
const toast = useToast()
|
const toast = useToast();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -56,7 +56,7 @@ export default {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
repeatPassword: "",
|
repeatPassword: "",
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"$i18n.locale"() {
|
"$i18n.locale"() {
|
||||||
|
@ -66,7 +66,7 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$root.getSocket().emit("needSetup", (needSetup) => {
|
this.$root.getSocket().emit("needSetup", (needSetup) => {
|
||||||
if (! needSetup) {
|
if (! needSetup) {
|
||||||
this.$router.push("/")
|
this.$router.push("/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -75,31 +75,30 @@ export default {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
if (this.password !== this.repeatPassword) {
|
if (this.password !== this.repeatPassword) {
|
||||||
toast.error("Repeat password do not match.")
|
toast.error("Passwords do not match.");
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$root.getSocket().emit("setup", this.username, this.password, (res) => {
|
this.$root.getSocket().emit("setup", this.username, this.password, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res)
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.login(this.username, this.password, "", (res) => {
|
this.$root.login(this.username, this.password, "", () => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$router.push("/")
|
this.$router.push("/");
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -107,6 +106,26 @@ export default {
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-floating {
|
||||||
|
> .form-select {
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
padding-top: 1.525rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
|
||||||
|
~ label {
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .form-control {
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
Loading…
Add table
Reference in a new issue