-
+
@@ -329,6 +332,18 @@
+
+
+
+
+ {{ $t("smtpHelpText") }}
+
+
+
@@ -595,10 +610,14 @@
-
-
-
-
+
+
+
+
+
{{ $t("pingGlobalTimeoutDescription") }}
@@ -660,10 +679,39 @@
-
+
+
+
+
+
+ {{ $t("pingCountDescription") }}
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+ {{ $t("pingPerRequestTimeoutDescription") }}
+
@@ -1060,7 +1108,13 @@ import DockerHostDialog from "../components/DockerHostDialog.vue";
import RemoteBrowserDialog from "../components/RemoteBrowserDialog.vue";
import ProxyDialog from "../components/ProxyDialog.vue";
import TagsManager from "../components/TagsManager.vue";
-import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, sleep } from "../util.ts";
+import {
+ genSecret,
+ isDev,
+ MAX_INTERVAL_SECOND,
+ MIN_INTERVAL_SECOND,
+ sleep,
+} from "../util.ts";
import { hostNameRegexPattern } from "../util-frontend";
import HiddenInput from "../components/HiddenInput.vue";
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
@@ -1082,7 +1136,6 @@ const monitorDefaults = {
notificationIDList: {},
ignoreTls: false,
upsideDown: false,
- packetSize: 56,
expiryNotification: false,
maxredirects: 10,
accepted_statuscodes: [ "200-299" ],
@@ -1157,6 +1210,48 @@ export default {
},
computed: {
+ timeoutStep() {
+ return this.monitor.type === "ping" ? 1 : 0.1;
+ },
+
+ timeoutMin() {
+ return this.monitor.type === "ping" ? 1 : 0;
+ },
+
+ timeoutMax() {
+ return this.monitor.type === "ping" ? 60 : undefined;
+ },
+
+ timeoutLabel() {
+ return this.monitor.type === "ping" ? this.$t("pingTimeoutLabel") : this.$t("Request Timeout");
+ },
+
+ timeoutDescription() {
+ if (this.monitor.type === "ping") {
+ return this.$t("pingTimeoutDescription");
+ }
+ return "";
+ },
+
+ defaultFriendlyName() {
+ if (this.monitor.hostname) {
+ return this.monitor.hostname;
+ }
+ if (this.monitor.url) {
+ if (this.monitor.url !== "http://" && this.monitor.url !== "https://") {
+ // Ensure monitor without a URL is not affected by invisible URL.
+ try {
+ const url = new URL(this.monitor.url);
+ return url.hostname;
+ } catch (e) {
+ return this.monitor.url.replace(/https?:\/\//, "");
+ }
+ }
+ }
+ // Default placeholder if neither hostname nor URL is available
+ return this.$t("defaultFriendlyName");
+ },
+
ipRegex() {
// Allow to test with simple dns server with port (127.0.0.1:5300)
@@ -1175,6 +1270,7 @@ export default {
}
return this.$t(name);
},
+
remoteBrowsersOptions() {
return this.$root.remoteBrowserList.map(browser => {
return {
@@ -1183,6 +1279,7 @@ export default {
};
});
},
+
remoteBrowsersToggle: {
get() {
return this.remoteBrowsersEnabled || this.monitor.remote_browser != null;
@@ -1200,6 +1297,7 @@ export default {
}
}
},
+
isAdd() {
return this.$route.path === "/add";
},
@@ -1250,6 +1348,7 @@ message HealthCheckResponse {
}
` ]);
},
+
bodyPlaceholder() {
if (this.monitor && this.monitor.httpBodyEncoding && this.monitor.httpBodyEncoding === "xml") {
return this.$t("Example:", [ `
@@ -1415,9 +1514,25 @@ message HealthCheckResponse {
},
"monitor.timeout"(value, oldValue) {
- // keep timeout within 80% range
- if (value && value !== oldValue) {
- this.monitor.timeout = this.clampTimeout(value);
+ if (this.monitor.type === "ping") {
+ this.finishUpdateInterval();
+ } else {
+ // keep timeout within 80% range
+ if (value && value !== oldValue) {
+ this.monitor.timeout = this.clampTimeout(value);
+ }
+ }
+ },
+
+ "monitor.ping_count"() {
+ if (this.monitor.type === "ping") {
+ this.finishUpdateInterval();
+ }
+ },
+
+ "monitor.ping_per_request_timeout"() {
+ if (this.monitor.type === "ping") {
+ this.finishUpdateInterval();
}
},
@@ -1446,8 +1561,10 @@ message HealthCheckResponse {
// Set a default timeout if the monitor type has changed or if it's a new monitor
if (oldType || this.isAdd) {
if (this.monitor.type === "snmp") {
- // snmp is not expected to be executed via the internet => we can choose a lower default timeout
+ // snmp is not expected to be executed via the internet => we can choose a lower default timeout
this.monitor.timeout = 5;
+ } else if (this.monitor.type === "ping") {
+ this.monitor.timeout = 10;
} else {
this.monitor.timeout = 48;
}
@@ -1564,7 +1681,11 @@ message HealthCheckResponse {
if (this.isAdd) {
this.monitor = {
- ...monitorDefaults
+ ...monitorDefaults,
+ ping_count: 3,
+ ping_numeric: true,
+ packetSize: 56,
+ ping_per_request_timeout: 2,
};
if (this.$root.proxyList && !this.monitor.proxyId) {
@@ -1627,7 +1748,12 @@ message HealthCheckResponse {
}
// Handling for monitors that are missing/zeroed timeout
if (!this.monitor.timeout) {
- this.monitor.timeout = ~~(this.monitor.interval * 8) / 10;
+ if (this.monitor.type === "ping") {
+ // set to default
+ this.monitor.timeout = 10;
+ } else {
+ this.monitor.timeout = ~~(this.monitor.interval * 8) / 10;
+ }
}
} else {
this.$root.toastError(res.msg);
@@ -1700,6 +1826,10 @@ message HealthCheckResponse {
this.processing = true;
+ if (!this.monitor.name) {
+ this.monitor.name = this.defaultFriendlyName;
+ }
+
if (!this.isInputValid()) {
this.processing = false;
return;
@@ -1840,11 +1970,48 @@ message HealthCheckResponse {
return Number.isFinite(clamped) ? clamped : maxTimeout;
},
+ calculatePingInterval() {
+ // If monitor.type is not "ping", simply return the configured interval
+ if (this.monitor.type !== "ping") {
+ return this.monitor.interval;
+ }
+
+ // Calculate the maximum theoretical time needed if every ping request times out
+ const theoreticalTotal = this.monitor.ping_count * this.monitor.ping_per_request_timeout;
+
+ // The global timeout (aka deadline) forces ping to terminate, so the effective limit
+ // is the smaller value between deadline and theoreticalTotal
+ const effectiveLimit = Math.min(this.monitor.timeout, theoreticalTotal);
+
+ // Add a 10% margin to the effective limit to ensure proper handling
+ const adjustedLimit = Math.ceil(effectiveLimit * 1.1);
+
+ // If the calculated limit is lower than the minimum allowed interval, use the minimum interval
+ if (adjustedLimit < this.minInterval) {
+ return this.minInterval;
+ }
+
+ return adjustedLimit;
+ },
+
finishUpdateInterval() {
- // Update timeout if it is greater than the clamp timeout
- let clampedValue = this.clampTimeout(this.monitor.interval);
- if (this.monitor.timeout > clampedValue) {
- this.monitor.timeout = clampedValue;
+ if (this.monitor.type === "ping") {
+ // Calculate the minimum required interval based on ping configuration
+ const calculatedPingInterval = this.calculatePingInterval();
+
+ // If the configured interval is too small, adjust it to the minimum required value
+ if (this.monitor.interval < calculatedPingInterval) {
+ this.monitor.interval = calculatedPingInterval;
+
+ // Notify the user that the interval has been automatically adjusted
+ toast.info(this.$t("pingIntervalAdjustedInfo"));
+ }
+ } else {
+ // Update timeout if it is greater than the clamp timeout
+ let clampedValue = this.clampTimeout(this.monitor.interval);
+ if (this.monitor.timeout > clampedValue) {
+ this.monitor.timeout = clampedValue;
+ }
}
},
diff --git a/src/util.js b/src/util.js
index df89cf92b..f094cdd63 100644
--- a/src/util.js
+++ b/src/util.js
@@ -8,17 +8,34 @@
// Backend uses the compiled file util.js
// Frontend uses util.ts
*/
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+ o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+ if (mod && mod.__esModule) return mod;
+ var result = {};
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
-exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
-exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
-exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
+exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.PING_PER_REQUEST_TIMEOUT_DEFAULT = exports.PING_PER_REQUEST_TIMEOUT_MAX = exports.PING_PER_REQUEST_TIMEOUT_MIN = exports.PING_COUNT_DEFAULT = exports.PING_COUNT_MAX = exports.PING_COUNT_MIN = exports.PING_GLOBAL_TIMEOUT_DEFAULT = exports.PING_GLOBAL_TIMEOUT_MAX = exports.PING_GLOBAL_TIMEOUT_MIN = exports.PING_PACKET_SIZE_DEFAULT = exports.PING_PACKET_SIZE_MAX = exports.PING_PACKET_SIZE_MIN = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
+exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = void 0;
const dayjs_1 = __importDefault(require("dayjs"));
-const dayjs = require("dayjs");
-const jsonata = require("jsonata");
+const jsonata = __importStar(require("jsonata"));
exports.isDev = process.env.NODE_ENV === "development";
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
exports.appName = "Uptime Kuma";
@@ -35,6 +52,18 @@ exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
exports.MAX_INTERVAL_SECOND = 2073600;
exports.MIN_INTERVAL_SECOND = 20;
+exports.PING_PACKET_SIZE_MIN = 1;
+exports.PING_PACKET_SIZE_MAX = 65500;
+exports.PING_PACKET_SIZE_DEFAULT = 56;
+exports.PING_GLOBAL_TIMEOUT_MIN = 1;
+exports.PING_GLOBAL_TIMEOUT_MAX = 300;
+exports.PING_GLOBAL_TIMEOUT_DEFAULT = 10;
+exports.PING_COUNT_MIN = 1;
+exports.PING_COUNT_MAX = 100;
+exports.PING_COUNT_DEFAULT = 1;
+exports.PING_PER_REQUEST_TIMEOUT_MIN = 1;
+exports.PING_PER_REQUEST_TIMEOUT_MAX = 60;
+exports.PING_PER_REQUEST_TIMEOUT_DEFAULT = 2;
exports.CONSOLE_STYLE_Reset = "\x1b[0m";
exports.CONSOLE_STYLE_Bright = "\x1b[1m";
exports.CONSOLE_STYLE_Dim = "\x1b[2m";
@@ -66,7 +95,6 @@ exports.CONSOLE_STYLE_BgMagenta = "\x1b[45m";
exports.CONSOLE_STYLE_BgCyan = "\x1b[46m";
exports.CONSOLE_STYLE_BgWhite = "\x1b[47m";
exports.CONSOLE_STYLE_BgGray = "\x1b[100m";
-
const consoleModuleColors = [
exports.CONSOLE_STYLE_FgCyan,
exports.CONSOLE_STYLE_FgGreen,
@@ -458,4 +486,4 @@ async function evaluateJsonQuery(data, jsonPath, jsonPathOperator, expectedValue
throw new Error(`Error evaluating JSON query: ${err.message}. Response from server was: ${response}`);
}
}
-exports.evaluateJsonQuery = evaluateJsonQuery;
\ No newline at end of file
+exports.evaluateJsonQuery = evaluateJsonQuery;
diff --git a/src/util.ts b/src/util.ts
index b3bab4fff..277d860f4 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -39,6 +39,26 @@ export const SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
export const MAX_INTERVAL_SECOND = 2073600; // 24 days
export const MIN_INTERVAL_SECOND = 20; // 20 seconds
+// Packet Size limits
+export const PING_PACKET_SIZE_MIN = 1;
+export const PING_PACKET_SIZE_MAX = 65500;
+export const PING_PACKET_SIZE_DEFAULT = 56;
+
+// Global timeout (aka deadline) limits in seconds
+export const PING_GLOBAL_TIMEOUT_MIN = 1;
+export const PING_GLOBAL_TIMEOUT_MAX = 300;
+export const PING_GLOBAL_TIMEOUT_DEFAULT = 10;
+
+// Ping count limits
+export const PING_COUNT_MIN = 1;
+export const PING_COUNT_MAX = 100;
+export const PING_COUNT_DEFAULT = 1;
+
+// per-request timeout (aka timeout) limits in seconds
+export const PING_PER_REQUEST_TIMEOUT_MIN = 1;
+export const PING_PER_REQUEST_TIMEOUT_MAX = 60;
+export const PING_PER_REQUEST_TIMEOUT_DEFAULT = 2;
+
// Console colors
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
export const CONSOLE_STYLE_Reset = "\x1b[0m";
diff --git a/test/e2e/specs/fridendly-name.spec.js b/test/e2e/specs/fridendly-name.spec.js
new file mode 100644
index 000000000..7dbe9dd06
--- /dev/null
+++ b/test/e2e/specs/fridendly-name.spec.js
@@ -0,0 +1,83 @@
+import { expect, test } from "@playwright/test";
+import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
+
+test.describe("Friendly Name Tests", () => {
+ test.beforeEach(async ({ page }) => {
+ await restoreSqliteSnapshot(page);
+ });
+
+ test("hostname", async ({ page }, testInfo) => {
+ // Test DNS monitor with hostname
+ await page.goto("./add");
+ await login(page);
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("monitor-type-select").selectOption("dns");
+ await page.getByTestId("hostname-input").fill("example.com");
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ expect(page.getByTestId("monitor-list")).toContainText("example.com");
+ await screenshot(testInfo, page);
+ });
+
+ test("URL hostname", async ({ page }, testInfo) => {
+ // Test HTTP monitor with URL
+ await page.goto("./add");
+ await login(page);
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("monitor-type-select").selectOption("http");
+ await page.getByTestId("url-input").fill("https://www.example.com/");
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ expect(page.getByTestId("monitor-list")).toContainText("www.example.com");
+ await screenshot(testInfo, page);
+ });
+
+ test("custom friendly name", async ({ page }, testInfo) => {
+ // Test custom friendly name for HTTP monitor
+ await page.goto("./add");
+ await login(page);
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("monitor-type-select").selectOption("http");
+ await page.getByTestId("url-input").fill("https://www.example.com/");
+
+ // Check if the friendly name placeholder is set to the hostname
+ const friendlyNameInput = page.getByTestId("friendly-name-input");
+ expect(friendlyNameInput).toHaveAttribute("placeholder", "www.example.com");
+ await screenshot(testInfo, page);
+
+ const customName = "Example Monitor";
+ await friendlyNameInput.fill(customName);
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ expect(page.getByTestId("monitor-list")).toContainText(customName);
+ await screenshot(testInfo, page);
+ });
+
+ test("default friendly name", async ({ page }, testInfo) => {
+ // Test default friendly name when no custom name is provided
+ await page.goto("./add");
+ await login(page);
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("monitor-type-select").selectOption("group");
+ await screenshot(testInfo, page);
+
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ expect(page.getByTestId("monitor-list")).toContainText("New Monitor");
+ await screenshot(testInfo, page);
+ });
+});
diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js
index f525dfc6f..0231aa225 100644
--- a/test/e2e/specs/status-page.spec.js
+++ b/test/e2e/specs/status-page.spec.js
@@ -12,6 +12,8 @@ test.describe("Status Page", () => {
const monitorName = "Monitor for Status Page";
const tagName = "Client";
const tagValue = "Acme Inc";
+ const monitorUrl = "https://www.example.com/status";
+ const monitorCustomUrl = "https://www.example.com";
// Status Page
const footerText = "This is footer text.";
@@ -30,7 +32,7 @@ test.describe("Status Page", () => {
await expect(page.getByTestId("monitor-type-select")).toBeVisible();
await page.getByTestId("monitor-type-select").selectOption("http");
await page.getByTestId("friendly-name-input").fill(monitorName);
- await page.getByTestId("url-input").fill("https://www.example.com/");
+ await page.getByTestId("url-input").fill(monitorUrl);
await page.getByTestId("add-tag-button").click();
await page.getByTestId("tag-name-input").fill(tagName);
await page.getByTestId("tag-value-input").fill(tagValue);
@@ -79,6 +81,13 @@ test.describe("Status Page", () => {
await page.getByTestId("monitor-select").getByRole("option", { name: monitorName }).click();
await expect(page.getByTestId("monitor")).toHaveCount(1);
await expect(page.getByTestId("monitor-name")).toContainText(monitorName);
+ await expect(page.getByTestId("monitor-name")).not.toHaveAttribute("href");
+
+ // Set public url on
+ await page.getByTestId("monitor-settings").click();
+ await page.getByTestId("show-clickable-link").check();
+ await page.getByTestId("custom-url-input").fill(monitorCustomUrl);
+ await page.getByTestId("monitor-settings-close").click();
// Save the changes
await screenshot(testInfo, page);
@@ -94,6 +103,8 @@ test.describe("Status Page", () => {
await expect(page.getByTestId("footer-text")).toContainText(footerText);
await expect(page.getByTestId("powered-by")).toHaveCount(0);
+ await expect(page.getByTestId("monitor-name")).toHaveAttribute("href", monitorCustomUrl);
+
await expect(page.getByTestId("update-countdown-text")).toContainText("00:");
const updateCountdown = Number((await page.getByTestId("update-countdown-text").textContent()).match(/(\d+):(\d+)/)[2]);
expect(updateCountdown).toBeGreaterThanOrEqual(refreshInterval); // cant be certain when the timer will start, so ensure it's within expected range