Skip to content

Commit ce74072

Browse files
fix: preserve Domain Name Expiry Notification setting when editing monitor (louislam#6994)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
1 parent 075aa61 commit ce74072

File tree

6 files changed

+61
-163
lines changed

6 files changed

+61
-163
lines changed

server/model/domain_expiry.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,17 +220,11 @@ class DomainExpiry extends BeanModel {
220220

221221
const tld = parseTld(target);
222222

223-
// Avoid logging for incomplete/invalid input while editing monitors.
224-
if (tld.isIp) {
225-
throw new TranslatableError("domain_expiry_unsupported_is_ip", { hostname: tld.hostname });
226-
}
227-
// No one-letter public suffix exists; treat this as an incomplete/invalid input while typing.
228-
if (tld.publicSuffix.length < 2) {
229-
throw new TranslatableError("domain_expiry_public_suffix_too_short", { publicSuffix: tld.publicSuffix });
230-
}
223+
// It must be checked first, filter out non-ICANN domains.
231224
if (!tld.isIcann) {
232225
throw new TranslatableError("domain_expiry_unsupported_is_icann", {
233-
domain: tld.domain,
226+
// If domain is null, use hostname as fallback for better error message.
227+
domain: tld.domain ?? tld.hostname ?? "EMPTY DOMAIN",
234228
publicSuffix: tld.publicSuffix,
235229
});
236230
}

server/server.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,8 @@ let needSetup = false;
10031003
}
10041004
});
10051005

1006-
socket.on("checkMointor", async (partial, callback) => {
1006+
// partial { type, url, hostname, grpcUrl }
1007+
socket.on("checkDomain", async (partial, callback) => {
10071008
try {
10081009
checkLogin(socket);
10091010
const DomainExpiry = require("./model/domain_expiry");

src/lang/en.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,9 +1407,7 @@
14071407
"domainExpiryDescription": "Trigger notification when domain names expires in:",
14081408
"domain_expiry_unsupported_monitor_type": "Domain expiry monitoring is not supported for this monitor type",
14091409
"domain_expiry_unsupported_missing_target": "No valid domain or hostname is configured for this monitor",
1410-
"domain_expiry_public_suffix_too_short": "\".{publicSuffix}\" is too short for a top level domain",
1411-
"domain_expiry_unsupported_is_icann": "The domain \"{domain}\" is not a candidate for domain expiry monitoring, because its public suffix \".{publicSuffix}\" is not ICAN",
1412-
"domain_expiry_unsupported_is_ip": "\"{hostname}\" is an IP address. Domain expiry monitoring requires a domain name",
1410+
"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",
14131411
"domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint": "Domain expiry monitoring is not available for \".{publicSuffix}\" because no RDAP service is listed by IANA",
14141412
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
14151413
"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.",

src/pages/EditMonitor.vue

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,15 +1548,17 @@
15481548
v-model="monitor.domainExpiryNotification"
15491549
class="form-check-input"
15501550
type="checkbox"
1551-
:disabled="!hasDomain"
15521551
/>
15531552
<label class="form-check-label" for="domain-expiry-notification">
15541553
{{ $t("labelDomainNameExpiryNotification") }}
15551554
</label>
1556-
<div v-if="hasDomain" class="form-text">
1555+
<div class="form-text">
15571556
{{ $t("domainExpiryNotificationHelp") }}
15581557
</div>
1559-
<div v-if="!hasDomain && domainExpiryUnsupportedReason" class="form-text">
1558+
<div
1559+
v-if="monitor.domainExpiryNotification && domainExpiryUnsupportedReason"
1560+
class="form-text"
1561+
>
15601562
{{ domainExpiryUnsupportedReason }}
15611563
</div>
15621564
</div>
@@ -2867,7 +2869,7 @@ const monitorDefaults = {
28672869
ignoreTls: false,
28682870
upsideDown: false,
28692871
expiryNotification: false,
2870-
domainExpiryNotification: false,
2872+
domainExpiryNotification: true,
28712873
maxredirects: 10,
28722874
accepted_statuscodes: defaultValueList.http.accepted_statuscodes,
28732875
saveResponse: false,
@@ -2929,9 +2931,8 @@ export default {
29292931
notificationIDList: {},
29302932
// Do not add default value here, please check init() method
29312933
},
2932-
hasDomain: false,
29332934
domainExpiryUnsupportedReason: null,
2934-
checkMonitorDebounce: null,
2935+
checkDomainDebounce: null,
29352936
acceptedStatusCodeOptions: [],
29362937
acceptedWebsocketCodeOptions: [],
29372938
dnsresolvetypeOptions: [],
@@ -2990,16 +2991,6 @@ export default {
29902991
return this.$t("defaultFriendlyName");
29912992
},
29922993
2993-
monitorTypeUrlHost() {
2994-
const { type, url, hostname, grpcUrl } = this.monitor;
2995-
return {
2996-
type,
2997-
url,
2998-
hostname,
2999-
grpcUrl,
3000-
};
3001-
},
3002-
30032994
showDomainExpiryNotification() {
30042995
return this.monitor.type in TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD;
30052996
},
@@ -3293,30 +3284,25 @@ message HealthCheckResponse {
32933284
}
32943285
},
32953286
3296-
monitorTypeUrlHost(data) {
3297-
if (this.checkMonitorDebounce != null) {
3298-
clearTimeout(this.checkMonitorDebounce);
3299-
}
3287+
showDomainExpiryNotification() {
3288+
this.checkDomain();
3289+
},
33003290
3301-
if (!this.showDomainExpiryNotification) {
3302-
this.hasDomain = false;
3303-
this.domainExpiryUnsupportedReason = null;
3304-
return;
3305-
}
3291+
"monitor.hostname"() {
3292+
this.checkDomain();
3293+
},
33063294
3307-
this.checkMonitorDebounce = setTimeout(() => {
3308-
this.$root.getSocket().emit("checkMointor", data, (res) => {
3309-
const wasSupported = this.hasDomain;
3310-
this.hasDomain = !!res?.ok;
3311-
if (this.hasDomain !== wasSupported) {
3312-
this.monitor.domainExpiryNotification = this.hasDomain;
3313-
}
3314-
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
3315-
});
3316-
}, 500);
3295+
"monitor.url"() {
3296+
this.checkDomain();
3297+
},
3298+
3299+
"monitor.grpcUrl"() {
3300+
this.checkDomain();
33173301
},
33183302
33193303
"monitor.type"(newType, oldType) {
3304+
this.checkDomain();
3305+
33203306
if (newType === "globalping" && !this.monitor.subtype) {
33213307
this.monitor.subtype = "ping";
33223308
}
@@ -4015,6 +4001,39 @@ message HealthCheckResponse {
40154001
}
40164002
}
40174003
},
4004+
4005+
// Check Domain
4006+
// Do nothing if not checked
4007+
checkDomain() {
4008+
console.log("checkDomain called");
4009+
if (this.checkDomainDebounce != null) {
4010+
clearTimeout(this.checkDomainDebounce);
4011+
}
4012+
4013+
if (!this.showDomainExpiryNotification) {
4014+
this.domainExpiryUnsupportedReason = null;
4015+
return;
4016+
}
4017+
4018+
this.checkDomainDebounce = setTimeout(() => {
4019+
const { type, url, hostname, grpcUrl } = this.monitor;
4020+
const data = {
4021+
type,
4022+
url,
4023+
hostname,
4024+
grpcUrl,
4025+
};
4026+
4027+
this.$root.getSocket().emit("checkDomain", data, (res) => {
4028+
console.log(data);
4029+
if (!res.ok) {
4030+
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
4031+
} else {
4032+
this.domainExpiryUnsupportedReason = null;
4033+
}
4034+
});
4035+
}, 500);
4036+
},
40184037
},
40194038
};
40204039
</script>

test/backend-test/test-domain.js

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -96,38 +96,6 @@ describe("Domain Expiry", () => {
9696
});
9797

9898
describe("Domain Parsing", () => {
99-
test("throws error for IP address (isIp check)", async () => {
100-
const monitor = {
101-
type: "http",
102-
url: "https://127.0.0.1",
103-
domainExpiryNotification: true,
104-
};
105-
await assert.rejects(
106-
async () => await DomainExpiry.checkSupport(monitor),
107-
(error) => {
108-
assert.strictEqual(error.constructor.name, "TranslatableError");
109-
assert.strictEqual(error.message, "domain_expiry_unsupported_is_ip");
110-
return true;
111-
}
112-
);
113-
});
114-
115-
test("throws error for too short suffix(example.a)", async () => {
116-
const monitor = {
117-
type: "http",
118-
url: "https://example.a",
119-
domainExpiryNotification: true,
120-
};
121-
await assert.rejects(
122-
async () => await DomainExpiry.checkSupport(monitor),
123-
(error) => {
124-
assert.strictEqual(error.constructor.name, "TranslatableError");
125-
assert.strictEqual(error.message, "domain_expiry_public_suffix_too_short");
126-
return true;
127-
}
128-
);
129-
});
130-
13199
test("throws error for non-ICANN TLD (e.g. .local)", async () => {
132100
const monitor = {
133101
type: "http",

test/e2e/specs/domain-expiry-notification.spec.js

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ test.describe("Domain Expiry Notification", () => {
66
await restoreSqliteSnapshot(page);
77
});
88

9-
test("supported TLD auto-enables checkbox", async ({ page }, testInfo) => {
9+
test("TLD enabled for new monitor", async ({ page }, testInfo) => {
1010
await page.goto("./add");
1111
await login(page);
1212

@@ -17,88 +17,6 @@ test.describe("Domain Expiry Notification", () => {
1717

1818
const checkbox = page.getByLabel("Domain Name Expiry Notification");
1919
await expect(checkbox).toBeChecked();
20-
await expect(checkbox).toBeEnabled();
21-
22-
await screenshot(testInfo, page);
23-
});
24-
25-
test("unsupported TLD leaves checkbox disabled", async ({ page }, testInfo) => {
26-
await page.goto("./add");
27-
await login(page);
28-
29-
const monitorTypeSelect = page.getByTestId("monitor-type-select");
30-
await monitorTypeSelect.selectOption("http");
31-
32-
await page.getByTestId("url-input").fill("https://example.co");
33-
34-
const checkbox = page.getByLabel("Domain Name Expiry Notification");
35-
await expect(checkbox).not.toBeChecked();
36-
await expect(checkbox).toBeDisabled();
37-
38-
await screenshot(testInfo, page);
39-
});
40-
41-
test("switching from supported to unsupported TLD disables checkbox", async ({ page }, testInfo) => {
42-
await page.goto("./add");
43-
await login(page);
44-
45-
const monitorTypeSelect = page.getByTestId("monitor-type-select");
46-
await monitorTypeSelect.selectOption("http");
47-
48-
const urlInput = page.getByTestId("url-input");
49-
const checkbox = page.getByLabel("Domain Name Expiry Notification");
50-
51-
await urlInput.fill("https://example.com");
52-
await expect(checkbox).toBeChecked();
53-
54-
await urlInput.fill("https://example.co");
55-
await expect(checkbox).not.toBeChecked();
56-
await expect(checkbox).toBeDisabled();
57-
58-
await screenshot(testInfo, page);
59-
});
60-
61-
test("switching from unsupported to supported TLD enables checkbox", async ({ page }, testInfo) => {
62-
await page.goto("./add");
63-
await login(page);
64-
65-
const monitorTypeSelect = page.getByTestId("monitor-type-select");
66-
await monitorTypeSelect.selectOption("http");
67-
68-
const urlInput = page.getByTestId("url-input");
69-
const checkbox = page.getByLabel("Domain Name Expiry Notification");
70-
71-
await urlInput.fill("https://example.co");
72-
await expect(checkbox).not.toBeChecked();
73-
74-
await urlInput.fill("https://example.com");
75-
await expect(checkbox).toBeChecked();
76-
await expect(checkbox).toBeEnabled();
77-
78-
await screenshot(testInfo, page);
79-
});
80-
81-
test("manual uncheck preserved when URL changes within same TLD", async ({ page }, testInfo) => {
82-
await page.goto("./add");
83-
await login(page);
84-
85-
const monitorTypeSelect = page.getByTestId("monitor-type-select");
86-
await monitorTypeSelect.selectOption("http");
87-
88-
const urlInput = page.getByTestId("url-input");
89-
const checkbox = page.getByLabel("Domain Name Expiry Notification");
90-
91-
await urlInput.fill("https://example.com");
92-
await expect(checkbox).toBeChecked();
93-
94-
await checkbox.uncheck();
95-
await expect(checkbox).not.toBeChecked();
96-
97-
await urlInput.fill("https://example.com/different-path");
98-
// Wait for debounce to fire and verify checkbox stays unchecked
99-
await page.waitForTimeout(600);
100-
await expect(checkbox).not.toBeChecked();
101-
await expect(checkbox).toBeEnabled();
10220

10321
await screenshot(testInfo, page);
10422
});

0 commit comments

Comments
 (0)