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]