Skip to content

Commit 73fee89

Browse files
authored
refactor(console): restyle the root CA, certificate, enrollment, profile and protocol screens (#182)
* refactor(console): restyle the root CA screens onto the kit Rebuilds the roots list onto the kit DataTable (matching the nodes list pattern from the dashboard/fleet/node wave) and the root detail page's connection panel onto FieldTiles, replacing the local DataTable and the hand-rolled Panel/Field markup. Route, data and permission checks are unchanged; the rename dialog already ran on the kit from the node wave. Adds an axe check to each page's test. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * refactor(console): restyle the certificate screens onto the kit Moves the fleet-wide certificate list onto the kit DataTable, with the status column now the kit's shape-coded StatusBadge (expired/expiring tints the still-VALID days-left reading, matching cert-inventory's convention), and the node certificate detail page onto FieldTiles. Same columns, filters, sort and the renew/issue actions. Adds an axe check to each page's test. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * refactor(console): restyle the enrollment screens onto the kit Moves the enrollment queue onto the kit DataTable and its create form onto the kit Dialog, and the enrollment detail page's field grid onto FieldTiles with the reject confirm also on the kit Dialog; the inline LINK approval material form moves onto Field/Input. The kind badge now comes from @cryptos-pki/ui's Badge, which drops the console's local Badge copy -- nothing else imported it. Adds an axe check to each page's test. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * refactor(console): restyle the certificate profile screens onto the kit Moves the profile catalog list onto the kit DataTable, the profile form (name, key algorithm, subject, SANs and extra extensions) onto Field/Input, and the per-node profile drift view onto Card and StatusBadge (mapping drifted/in-sync onto the kit's existing statuses, node-only/not-applied onto its muted tone). The pill-style key-usage toggles stay bespoke -- the kit has no multi-select chip group. Same columns, validation, drift computation and admin gating throughout. Adds an axe check to each page's test. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * refactor(console): restyle the protocol screens onto the kit Moves the protocol list and the per-node protocol detail table onto the kit DataTable, its not-implemented notice onto the kit Notice, and the per-row enable/disable switch onto the kit Button. Same serving/reboot counts, role and identity facets, and the admin-only gate on the switch. Adds an axe check to each page's test. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> --------- Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> Co-authored-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
1 parent 5b400d6 commit 73fee89

25 files changed

Lines changed: 1076 additions & 698 deletions

‎apps/console/src/components/profile-form.tsx‎

Lines changed: 34 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { Button, cn, Field, Input, inputClasses, Notice } from "@cryptos-pki/ui";
1718
import { useState } from "react";
1819

19-
import { Button } from "@/components/ui/button";
2020
import { useAuth } from "@/context/auth";
2121
import {
2222
type CertProfile,
@@ -30,10 +30,8 @@ import {
3030
type ProfileSans,
3131
updateProfile,
3232
} from "@/lib/profiles";
33-
import { cn } from "@/lib/utils";
3433

35-
const field = "w-full rounded-md border bg-card px-3 py-2 font-mono text-sm";
36-
const label = "font-mono text-[11px] uppercase tracking-wider text-muted-foreground";
34+
const groupLabelClasses = "font-mono text-[11px] uppercase tracking-wider text-muted-foreground";
3735

3836
const empty: CertProfile = {
3937
extKeyUsage: [],
@@ -70,7 +68,7 @@ const CheckGroup = ({
7068
selected: string[];
7169
}) => (
7270
<div className="space-y-1">
73-
<span className={label}>{groupLabel}</span>
71+
<span className={groupLabelClasses}>{groupLabel}</span>
7472
<div className="flex flex-wrap gap-1.5">
7573
{options.map((o) => (
7674
<button
@@ -100,16 +98,13 @@ const SanInput = ({
10098
onChange: (next: string[]) => void;
10199
values: string[];
102100
}) => (
103-
<label className="block space-y-1">
104-
<span className={label}>{category.toUpperCase()} SANs</span>
105-
<input
106-
aria-label={`${category} SANs`}
107-
className={field}
101+
<Field label={`${category.toUpperCase()} SANs`}>
102+
<Input
108103
onChange={(e) => onChange(parseList(e.target.value))}
109104
placeholder="comma or newline separated"
110105
value={values.join(", ")}
111106
/>
112-
</label>
107+
</Field>
113108
);
114109

115110
export const ProfileForm = ({
@@ -167,20 +162,17 @@ export const ProfileForm = ({
167162

168163
return (
169164
<div className="max-w-lg space-y-4">
170-
<label className="block space-y-1">
171-
<span className={label}>Name</span>
172-
<input
173-
className={field}
165+
<Field label="Name">
166+
<Input
174167
disabled={mode === "edit" || !isAdmin}
175168
onChange={(e) => setP({ ...p, name: e.target.value })}
176169
value={p.name}
177170
/>
178-
</label>
171+
</Field>
179172

180-
<label className="block space-y-1">
181-
<span className={label}>Key algorithm</span>
173+
<Field label="Key algorithm">
182174
<select
183-
className={field}
175+
className={inputClasses}
184176
disabled={!isAdmin}
185177
onChange={(e) => setP({ ...p, keyAlg: e.target.value })}
186178
value={p.keyAlg}
@@ -191,54 +183,42 @@ export const ProfileForm = ({
191183
</option>
192184
))}
193185
</select>
194-
</label>
186+
</Field>
195187

196-
<label className="block space-y-1">
197-
<span className={label}>Validity (days)</span>
198-
<input
199-
aria-label="Validity (days)"
200-
className={field}
188+
<Field label="Validity (days)">
189+
<Input
201190
disabled={!isAdmin}
202191
onChange={(e) => setP({ ...p, validityDays: Number(e.target.value) })}
203192
type="number"
204193
value={p.validityDays}
205194
/>
206-
</label>
195+
</Field>
207196

208197
<fieldset className="space-y-2 rounded-md border p-3">
209-
<legend className={label}>Subject</legend>
210-
<label className="block space-y-1">
211-
<span className={label}>Common name</span>
212-
<input
213-
aria-label="Common name"
214-
className={field}
198+
<legend className={groupLabelClasses}>Subject</legend>
199+
<Field label="Common name">
200+
<Input
215201
disabled={!isAdmin}
216202
onChange={(e) => setP({ ...p, subject: { ...p.subject, commonName: e.target.value } })}
217203
value={p.subject.commonName}
218204
/>
219-
</label>
220-
<label className="block space-y-1">
221-
<span className={label}>Organization</span>
222-
<input
223-
aria-label="Organization"
224-
className={field}
205+
</Field>
206+
<Field label="Organization">
207+
<Input
225208
disabled={!isAdmin}
226209
onChange={(e) =>
227210
setP({ ...p, subject: { ...p.subject, organization: e.target.value } })
228211
}
229212
value={p.subject.organization}
230213
/>
231-
</label>
232-
<label className="block space-y-1">
233-
<span className={label}>Country</span>
234-
<input
235-
aria-label="Country"
236-
className={field}
214+
</Field>
215+
<Field label="Country">
216+
<Input
237217
disabled={!isAdmin}
238218
onChange={(e) => setP({ ...p, subject: { ...p.subject, country: e.target.value } })}
239219
value={p.subject.country}
240220
/>
241-
</label>
221+
</Field>
242222
</fieldset>
243223

244224
<label className="flex items-center gap-2 font-mono text-sm">
@@ -252,17 +232,14 @@ export const ProfileForm = ({
252232
</label>
253233

254234
{p.isCA ? (
255-
<label className="block space-y-1">
256-
<span className={label}>Path length</span>
257-
<input
258-
aria-label="Path length"
259-
className={field}
235+
<Field label="Path length">
236+
<Input
260237
disabled={!isAdmin}
261238
onChange={(e) => setP({ ...p, pathLen: Number(e.target.value) })}
262239
type="number"
263240
value={p.pathLen ?? 0}
264241
/>
265-
</label>
242+
</Field>
266243
) : null}
267244

268245
<CheckGroup
@@ -279,28 +256,26 @@ export const ProfileForm = ({
279256
/>
280257

281258
<fieldset className="space-y-2 rounded-md border p-3">
282-
<legend className={label}>Subject alternative names</legend>
259+
<legend className={groupLabelClasses}>Subject alternative names</legend>
283260
<SanInput category="dns" onChange={(v) => setSan("dns", v)} values={p.sans.dns} />
284261
<SanInput category="ip" onChange={(v) => setSan("ip", v)} values={p.sans.ip} />
285262
<SanInput category="email" onChange={(v) => setSan("email", v)} values={p.sans.email} />
286263
<SanInput category="uri" onChange={(v) => setSan("uri", v)} values={p.sans.uri} />
287264
</fieldset>
288265

289266
<fieldset className="space-y-2 rounded-md border p-3">
290-
<legend className={label}>Extra extensions</legend>
267+
<legend className={groupLabelClasses}>Extra extensions</legend>
291268
{p.extraExtensions.map((ext, i) => (
292269
<div className="space-y-1 rounded-md border bg-card p-2" key={i}>
293-
<input
270+
<Input
294271
aria-label={`Extension ${i + 1} OID`}
295-
className={field}
296272
disabled={!isAdmin}
297273
onChange={(e) => setExtension(i, { oid: e.target.value })}
298274
placeholder="OID, e.g. 1.3.6.1.5.5.7.1.1"
299275
value={ext.oid}
300276
/>
301-
<input
277+
<Input
302278
aria-label={`Extension ${i + 1} value`}
303-
className={field}
304279
disabled={!isAdmin}
305280
onChange={(e) => setExtension(i, { value: e.target.value })}
306281
placeholder="DER value (base64)"
@@ -337,9 +312,9 @@ export const ProfileForm = ({
337312
</fieldset>
338313

339314
{error ? (
340-
<p className="font-mono text-xs text-destructive" role="alert">
315+
<Notice role="alert" tone="danger">
341316
{error}
342-
</p>
317+
</Notice>
343318
) : null}
344319

345320
{isAdmin ? (

‎apps/console/src/components/ui/badge.tsx‎

Lines changed: 0 additions & 48 deletions
This file was deleted.

‎apps/console/src/pages/certificates.test.tsx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,23 @@ import { beforeEach, describe, expect, it } from "vitest";
2121
import { __resetCerts, allCerts, canIssue } from "@/lib/certs";
2222
import { __resetNodes, nodesList } from "@/lib/nodes";
2323
import { CertificatesPage } from "@/pages/certificates";
24+
import { expectNoA11yViolations } from "@/test/axe";
2425

2526
describe("CertificatesPage", () => {
2627
beforeEach(() => {
2728
__resetNodes();
2829
__resetCerts();
2930
});
3031

32+
it("has no axe violations", async () => {
33+
const { container } = render(
34+
<MemoryRouter>
35+
<CertificatesPage />
36+
</MemoryRouter>,
37+
);
38+
await expectNoA11yViolations(container);
39+
});
40+
3141
it("lists seeded certs and links the subject to the cert detail", () => {
3242
render(
3343
<MemoryRouter>

0 commit comments

Comments
 (0)