mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-03-13 09:52:49 +08:00
fix: preserve Domain Name Expiry Notification setting when editing monitor (#6994)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
This commit is contained in:
@@ -220,17 +220,11 @@ class DomainExpiry extends BeanModel {
|
||||
|
||||
const tld = parseTld(target);
|
||||
|
||||
// Avoid logging for incomplete/invalid input while editing monitors.
|
||||
if (tld.isIp) {
|
||||
throw new TranslatableError("domain_expiry_unsupported_is_ip", { hostname: tld.hostname });
|
||||
}
|
||||
// No one-letter public suffix exists; treat this as an incomplete/invalid input while typing.
|
||||
if (tld.publicSuffix.length < 2) {
|
||||
throw new TranslatableError("domain_expiry_public_suffix_too_short", { publicSuffix: tld.publicSuffix });
|
||||
}
|
||||
// It must be checked first, filter out non-ICANN domains.
|
||||
if (!tld.isIcann) {
|
||||
throw new TranslatableError("domain_expiry_unsupported_is_icann", {
|
||||
domain: tld.domain,
|
||||
// If domain is null, use hostname as fallback for better error message.
|
||||
domain: tld.domain ?? tld.hostname ?? "EMPTY DOMAIN",
|
||||
publicSuffix: tld.publicSuffix,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1003,7 +1003,8 @@ let needSetup = false;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("checkMointor", async (partial, callback) => {
|
||||
// partial { type, url, hostname, grpcUrl }
|
||||
socket.on("checkDomain", async (partial, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
const DomainExpiry = require("./model/domain_expiry");
|
||||
|
||||
@@ -1407,9 +1407,7 @@
|
||||
"domainExpiryDescription": "Trigger notification when domain names expires in:",
|
||||
"domain_expiry_unsupported_monitor_type": "Domain expiry monitoring is not supported for this monitor type",
|
||||
"domain_expiry_unsupported_missing_target": "No valid domain or hostname is configured for this monitor",
|
||||
"domain_expiry_public_suffix_too_short": "\".{publicSuffix}\" is too short for a top level domain",
|
||||
"domain_expiry_unsupported_is_icann": "The domain \"{domain}\" is not a candidate for domain expiry monitoring, because its public suffix \".{publicSuffix}\" is not ICAN",
|
||||
"domain_expiry_unsupported_is_ip": "\"{hostname}\" is an IP address. Domain expiry monitoring requires a domain name",
|
||||
"domain_expiry_unsupported_is_icann": "The domain \"{domain}\" is not a candidate for domain expiry monitoring, because its public suffix \".{publicSuffix}\" is not managed by ICANN",
|
||||
"domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint": "Domain expiry monitoring is not available for \".{publicSuffix}\" because no RDAP service is listed by IANA",
|
||||
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
|
||||
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",
|
||||
|
||||
@@ -1548,15 +1548,17 @@
|
||||
v-model="monitor.domainExpiryNotification"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
:disabled="!hasDomain"
|
||||
/>
|
||||
<label class="form-check-label" for="domain-expiry-notification">
|
||||
{{ $t("labelDomainNameExpiryNotification") }}
|
||||
</label>
|
||||
<div v-if="hasDomain" class="form-text">
|
||||
<div class="form-text">
|
||||
{{ $t("domainExpiryNotificationHelp") }}
|
||||
</div>
|
||||
<div v-if="!hasDomain && domainExpiryUnsupportedReason" class="form-text">
|
||||
<div
|
||||
v-if="monitor.domainExpiryNotification && domainExpiryUnsupportedReason"
|
||||
class="form-text"
|
||||
>
|
||||
{{ domainExpiryUnsupportedReason }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2867,7 +2869,7 @@ const monitorDefaults = {
|
||||
ignoreTls: false,
|
||||
upsideDown: false,
|
||||
expiryNotification: false,
|
||||
domainExpiryNotification: false,
|
||||
domainExpiryNotification: true,
|
||||
maxredirects: 10,
|
||||
accepted_statuscodes: defaultValueList.http.accepted_statuscodes,
|
||||
saveResponse: false,
|
||||
@@ -2929,9 +2931,8 @@ export default {
|
||||
notificationIDList: {},
|
||||
// Do not add default value here, please check init() method
|
||||
},
|
||||
hasDomain: false,
|
||||
domainExpiryUnsupportedReason: null,
|
||||
checkMonitorDebounce: null,
|
||||
checkDomainDebounce: null,
|
||||
acceptedStatusCodeOptions: [],
|
||||
acceptedWebsocketCodeOptions: [],
|
||||
dnsresolvetypeOptions: [],
|
||||
@@ -2990,16 +2991,6 @@ export default {
|
||||
return this.$t("defaultFriendlyName");
|
||||
},
|
||||
|
||||
monitorTypeUrlHost() {
|
||||
const { type, url, hostname, grpcUrl } = this.monitor;
|
||||
return {
|
||||
type,
|
||||
url,
|
||||
hostname,
|
||||
grpcUrl,
|
||||
};
|
||||
},
|
||||
|
||||
showDomainExpiryNotification() {
|
||||
return this.monitor.type in TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD;
|
||||
},
|
||||
@@ -3293,30 +3284,25 @@ message HealthCheckResponse {
|
||||
}
|
||||
},
|
||||
|
||||
monitorTypeUrlHost(data) {
|
||||
if (this.checkMonitorDebounce != null) {
|
||||
clearTimeout(this.checkMonitorDebounce);
|
||||
}
|
||||
showDomainExpiryNotification() {
|
||||
this.checkDomain();
|
||||
},
|
||||
|
||||
if (!this.showDomainExpiryNotification) {
|
||||
this.hasDomain = false;
|
||||
this.domainExpiryUnsupportedReason = null;
|
||||
return;
|
||||
}
|
||||
"monitor.hostname"() {
|
||||
this.checkDomain();
|
||||
},
|
||||
|
||||
this.checkMonitorDebounce = setTimeout(() => {
|
||||
this.$root.getSocket().emit("checkMointor", data, (res) => {
|
||||
const wasSupported = this.hasDomain;
|
||||
this.hasDomain = !!res?.ok;
|
||||
if (this.hasDomain !== wasSupported) {
|
||||
this.monitor.domainExpiryNotification = this.hasDomain;
|
||||
}
|
||||
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
|
||||
});
|
||||
}, 500);
|
||||
"monitor.url"() {
|
||||
this.checkDomain();
|
||||
},
|
||||
|
||||
"monitor.grpcUrl"() {
|
||||
this.checkDomain();
|
||||
},
|
||||
|
||||
"monitor.type"(newType, oldType) {
|
||||
this.checkDomain();
|
||||
|
||||
if (newType === "globalping" && !this.monitor.subtype) {
|
||||
this.monitor.subtype = "ping";
|
||||
}
|
||||
@@ -4015,6 +4001,39 @@ message HealthCheckResponse {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Check Domain
|
||||
// Do nothing if not checked
|
||||
checkDomain() {
|
||||
console.log("checkDomain called");
|
||||
if (this.checkDomainDebounce != null) {
|
||||
clearTimeout(this.checkDomainDebounce);
|
||||
}
|
||||
|
||||
if (!this.showDomainExpiryNotification) {
|
||||
this.domainExpiryUnsupportedReason = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkDomainDebounce = setTimeout(() => {
|
||||
const { type, url, hostname, grpcUrl } = this.monitor;
|
||||
const data = {
|
||||
type,
|
||||
url,
|
||||
hostname,
|
||||
grpcUrl,
|
||||
};
|
||||
|
||||
this.$root.getSocket().emit("checkDomain", data, (res) => {
|
||||
console.log(data);
|
||||
if (!res.ok) {
|
||||
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
|
||||
} else {
|
||||
this.domainExpiryUnsupportedReason = null;
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -96,38 +96,6 @@ describe("Domain Expiry", () => {
|
||||
});
|
||||
|
||||
describe("Domain Parsing", () => {
|
||||
test("throws error for IP address (isIp check)", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://127.0.0.1",
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_is_ip");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for too short suffix(example.a)", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.a",
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_public_suffix_too_short");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for non-ICANN TLD (e.g. .local)", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
|
||||
@@ -6,7 +6,7 @@ test.describe("Domain Expiry Notification", () => {
|
||||
await restoreSqliteSnapshot(page);
|
||||
});
|
||||
|
||||
test("supported TLD auto-enables checkbox", async ({ page }, testInfo) => {
|
||||
test("TLD enabled for new monitor", async ({ page }, testInfo) => {
|
||||
await page.goto("./add");
|
||||
await login(page);
|
||||
|
||||
@@ -17,88 +17,6 @@ test.describe("Domain Expiry Notification", () => {
|
||||
|
||||
const checkbox = page.getByLabel("Domain Name Expiry Notification");
|
||||
await expect(checkbox).toBeChecked();
|
||||
await expect(checkbox).toBeEnabled();
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
test("unsupported TLD leaves checkbox disabled", async ({ page }, testInfo) => {
|
||||
await page.goto("./add");
|
||||
await login(page);
|
||||
|
||||
const monitorTypeSelect = page.getByTestId("monitor-type-select");
|
||||
await monitorTypeSelect.selectOption("http");
|
||||
|
||||
await page.getByTestId("url-input").fill("https://example.co");
|
||||
|
||||
const checkbox = page.getByLabel("Domain Name Expiry Notification");
|
||||
await expect(checkbox).not.toBeChecked();
|
||||
await expect(checkbox).toBeDisabled();
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
test("switching from supported to unsupported TLD disables checkbox", async ({ page }, testInfo) => {
|
||||
await page.goto("./add");
|
||||
await login(page);
|
||||
|
||||
const monitorTypeSelect = page.getByTestId("monitor-type-select");
|
||||
await monitorTypeSelect.selectOption("http");
|
||||
|
||||
const urlInput = page.getByTestId("url-input");
|
||||
const checkbox = page.getByLabel("Domain Name Expiry Notification");
|
||||
|
||||
await urlInput.fill("https://example.com");
|
||||
await expect(checkbox).toBeChecked();
|
||||
|
||||
await urlInput.fill("https://example.co");
|
||||
await expect(checkbox).not.toBeChecked();
|
||||
await expect(checkbox).toBeDisabled();
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
test("switching from unsupported to supported TLD enables checkbox", async ({ page }, testInfo) => {
|
||||
await page.goto("./add");
|
||||
await login(page);
|
||||
|
||||
const monitorTypeSelect = page.getByTestId("monitor-type-select");
|
||||
await monitorTypeSelect.selectOption("http");
|
||||
|
||||
const urlInput = page.getByTestId("url-input");
|
||||
const checkbox = page.getByLabel("Domain Name Expiry Notification");
|
||||
|
||||
await urlInput.fill("https://example.co");
|
||||
await expect(checkbox).not.toBeChecked();
|
||||
|
||||
await urlInput.fill("https://example.com");
|
||||
await expect(checkbox).toBeChecked();
|
||||
await expect(checkbox).toBeEnabled();
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
test("manual uncheck preserved when URL changes within same TLD", async ({ page }, testInfo) => {
|
||||
await page.goto("./add");
|
||||
await login(page);
|
||||
|
||||
const monitorTypeSelect = page.getByTestId("monitor-type-select");
|
||||
await monitorTypeSelect.selectOption("http");
|
||||
|
||||
const urlInput = page.getByTestId("url-input");
|
||||
const checkbox = page.getByLabel("Domain Name Expiry Notification");
|
||||
|
||||
await urlInput.fill("https://example.com");
|
||||
await expect(checkbox).toBeChecked();
|
||||
|
||||
await checkbox.uncheck();
|
||||
await expect(checkbox).not.toBeChecked();
|
||||
|
||||
await urlInput.fill("https://example.com/different-path");
|
||||
// Wait for debounce to fire and verify checkbox stays unchecked
|
||||
await page.waitForTimeout(600);
|
||||
await expect(checkbox).not.toBeChecked();
|
||||
await expect(checkbox).toBeEnabled();
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user