diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index dbeea30c4..7186e01aa 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -1,5 +1,9 @@ name: Auto Test +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-server + cancel-in-progress: true + on: push: branches: [master, 1.23.X, 3.0.0] diff --git a/db/knex_migrations/2026-01-16-0000-add-screenshot-delay.js b/db/knex_migrations/2026-01-16-0000-add-screenshot-delay.js new file mode 100644 index 000000000..fa0829bdf --- /dev/null +++ b/db/knex_migrations/2026-01-16-0000-add-screenshot-delay.js @@ -0,0 +1,11 @@ +exports.up = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.integer("screenshot_delay").notNullable().unsigned().defaultTo(0); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("screenshot_delay"); + }); +}; diff --git a/server/analytics/plausible-analytics.js b/server/analytics/plausible-analytics.js index a043c2464..015f63331 100644 --- a/server/analytics/plausible-analytics.js +++ b/server/analytics/plausible-analytics.js @@ -5,7 +5,7 @@ const { escape } = require("html-escaper"); * Returns a string that represents the javascript that is required to insert the Plausible Analytics script * into a webpage. * @param {string} scriptUrl the Plausible Analytics script url. - * @param {string} domainsToMonitor Domains to track seperated by a ',' to add Plausible Analytics script. + * @param {string} domainsToMonitor Domains to track separated by a ',' to add Plausible Analytics script. * @returns {string} HTML script tags to inject into page */ function getPlausibleAnalyticsScript(scriptUrl, domainsToMonitor) { diff --git a/server/model/domain_expiry.js b/server/model/domain_expiry.js index 3502a4b08..6d91b5d63 100644 --- a/server/model/domain_expiry.js +++ b/server/model/domain_expiry.js @@ -30,10 +30,13 @@ async function getRdapServer(tld) { return null; } - for (const service of rdapList["services"]) { - const [tlds, urls] = service; - if (tlds.includes(tld)) { - return urls[0]; + const services = rdapList["services"] ?? []; + const rootTld = tld?.split(".").pop(); + if (rootTld) { + for (const [tlds, urls] of services) { + if (tlds.includes(rootTld)) { + return urls[0]; + } } } log.debug("rdap", `No RDAP server found for TLD ${tld}`); @@ -173,16 +176,18 @@ class DomainExpiry extends BeanModel { }); } - const rdap = await getRdapServer(tld.publicSuffix); + const publicSuffix = tld.publicSuffix; + const rootTld = publicSuffix.split(".").pop(); + const rdap = await getRdapServer(publicSuffix); if (!rdap) { throw new TranslatableError("domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint", { - publicSuffix: tld.publicSuffix, + publicSuffix, }); } return { domain: tld.domain, - tld: tld.publicSuffix, + tld: rootTld, }; } diff --git a/server/model/monitor.js b/server/model/monitor.js index 7a18f69cf..5c57d58ca 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1770,6 +1770,28 @@ class Monitor extends BeanModel { this.timeout = pingGlobalTimeout; } } + + if (this.type === "real-browser") { + // screenshot_delay validation + if (this.screenshot_delay !== undefined && this.screenshot_delay !== null) { + const delay = Number(this.screenshot_delay); + if (isNaN(delay) || delay < 0) { + throw new Error("Screenshot delay must be a non-negative number"); + } + + // Must not exceed 0.8 * timeout (page.goto timeout is interval * 1000 * 0.8) + const maxDelayFromTimeout = this.interval * 1000 * 0.8; + if (delay >= maxDelayFromTimeout) { + throw new Error(`Screenshot delay must be less than ${maxDelayFromTimeout}ms (0.8 × interval)`); + } + + // Must not exceed 0.5 * interval to prevent blocking next check + const maxDelayFromInterval = this.interval * 1000 * 0.5; + if (delay >= maxDelayFromInterval) { + throw new Error(`Screenshot delay must be less than ${maxDelayFromInterval}ms (0.5 × interval)`); + } + } + } } /** diff --git a/server/monitor-types/real-browser-monitor-type.js b/server/monitor-types/real-browser-monitor-type.js index 2b62a10a3..32fa36f1a 100644 --- a/server/monitor-types/real-browser-monitor-type.js +++ b/server/monitor-types/real-browser-monitor-type.js @@ -269,6 +269,11 @@ class RealBrowserMonitorType extends MonitorType { timeout: monitor.interval * 1000 * 0.8, }); + // Wait for additional time before taking screenshot if configured + if (monitor.screenshot_delay > 0) { + await page.waitForTimeout(monitor.screenshot_delay); + } + let filename = jwt.sign(monitor.id, server.jwtSecret) + ".png"; await page.screenshot({ diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index b541a8ad8..ed354e3a0 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -1,8 +1,24 @@