zjncs opened a new pull request, #5511:
URL: https://github.com/apache/rocketmq-dashboard/pull/5511

   ## Problem
   
   The K8s certificate expiry column shows a time shifted by the viewer's (or 
server's) UTC offset on any non-UTC machine. Two sides of the same wire 
contract are wrong:
   
   **Backend** — `K8sCertService` is the only service in the codebase still on 
`Clock.systemDefaultZone()` (auth, query-history and MCP services all use 
`Clock.systemUTC()`), and it converts X509 instants with 
`ZoneId.systemDefault()`:
   
   ```java
   notAfter = LocalDateTime.ofInstant(parsed.getNotAfter().toInstant(), 
ZoneId.systemDefault());
   ```
   
   The API serializes `notBefore`/`notAfter` as **zoneless** `LocalDateTime`, 
so the wire value carries the *server OS zone's* wall time with no way for the 
client to know.
   
   **Frontend** — the expiry column rendered it with `formatDateTime`, whose 
`new Date(value)` treats a zoneless string as **browser-local** time. A UTC 
server value read by a GMT+8 viewer renders 8 hours early; a GMT+8 server value 
read by any viewer is wrong too.
   
   The project already documents the intended contract in `utils/format.ts`:
   
   > Alert APIs serialize UTC LocalDateTime values without an offset, so normal 
Date parsing would incorrectly treat them as browser-local timestamps.
   
   `formatUtcDateTime` exists precisely for this; the certs page just never 
used it, and the certs service never produced UTC.
   
   ## Fix
   
   - **service**: default clock → `Clock.systemUTC()`, X509 conversion → 
`ZoneOffset.UTC`. Generated-cert validity (`now.plusYears(1)`) and 
`daysRemaining`/`status` computation follow the same clock, staying 
self-consistent;
   - **page**: expiry column uses `formatUtcDateTime(iso, undefined, { zone: 
false })` — interpret the zoneless value as UTC, render in the viewer's zone, 
compact-list form without a per-row zone suffix (same idiom as the conversation 
history list);
   - **test fixtures**: `notAfter: '2027-01-01T00:00:00Z'` → 
`'2027-01-01T00:00:00'` — the fixtures used the idealized Z-suffixed form while 
Jackson actually serializes `LocalDateTime` without a zone designator, so the 
suite never exercised the real wire format.
   
   ## Verification
   
   Backend (`K8sCertServiceTest`, new case 
`createCertShouldParseCertificateInstantsAsUtcWallTime` forces 
`TimeZone.setDefault("Asia/Shanghai")` and parses an embedded self-signed PEM, 
asserting the returned wall time equals `LocalDateTime.ofInstant(x509.notAfter, 
UTC)`):
   
   - before the fix: **fails** — service returned the Shanghai wall time 
(`2027-10-04T00:31:59`, +8h) where UTC is `2027-10-03T16:31:59`;
   - after: **21/21** in `K8sCertServiceTest`, **35/35** across the three K8s 
cert test classes.
   
   Frontend (`K8sCertsPage.test.tsx`, new case `renders the zoneless backend 
expiry timestamp in the viewer timezone` asserts the rendered cell equals the 
viewer-zone rendering of `2027-01-01T00:00:00Z` computed via 
`Intl.DateTimeFormat`):
   
   - before the fix: **1 failed** (old column rendered `2027-01-01 00:00:00`, 
the raw UTC wall time) / 7 passed;
   - after: **8/8 passed**; mutation (revert the page change) → back to 1 
failed; `tsc -b` clean.
   
   Verified on a GMT+8 runner where the offset manifests; on a UTC runner both 
renderings coincide by construction (the backend TZ-forced test discriminates 
on any machine).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to