fix: polish CertificatesPage layout
All checks were successful
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 44s

- Truncate fingerprint with hover tooltip
- Remove duplicate warning icon in stale banner
- Style file inputs to match design system
- Bump grid min-width for better card spacing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-10 19:04:16 +02:00
parent a48c4bfd08
commit 22752ffcb1

View File

@@ -7,7 +7,7 @@ import {
Spinner,
useToast,
} from '@cameleer/design-system';
import { Upload, ShieldCheck, RotateCcw, Trash2, AlertTriangle } from 'lucide-react';
import { Upload, ShieldCheck, RotateCcw, Trash2 } from 'lucide-react';
import {
useVendorCertificates,
useStageCertificate,
@@ -35,6 +35,12 @@ function expiryColor(iso: string | null | undefined): string | undefined {
return undefined;
}
function shortenFingerprint(fp: string | null | undefined): string {
if (!fp) return '—';
// Show first 23 chars (8 hex pairs) + ellipsis
return fp.length > 23 ? fp.slice(0, 23) + '...' : fp;
}
function CertCard({
cert,
title,
@@ -68,8 +74,8 @@ function CertCard({
</div>
<div className={styles.kvRow}>
<span className={styles.kvLabel}>Fingerprint</span>
<span className={styles.kvValueMono} style={{ fontSize: '0.7rem', maxWidth: 220, textAlign: 'right' }}>
{cert.fingerprint}
<span className={styles.kvValueMono} title={cert.fingerprint} style={{ fontSize: '0.7rem' }}>
{shortenFingerprint(cert.fingerprint)}
</span>
</div>
<div className={styles.kvRow}>
@@ -94,6 +100,34 @@ function CertCard({
);
}
function FileField({ label, inputRef, accept }: {
label: string;
inputRef: React.RefObject<HTMLInputElement | null>;
accept: string;
}) {
return (
<div>
<label style={{ fontSize: 13, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 }}>
{label}
</label>
<input
ref={inputRef}
type="file"
accept={accept}
style={{
fontSize: 13,
color: 'var(--text-primary)',
background: 'var(--bg-inset)',
border: '1px solid var(--border)',
borderRadius: 6,
padding: '6px 8px',
width: '100%',
}}
/>
</div>
);
}
export function CertificatesPage() {
const { toast } = useToast();
const { data, isLoading, isError } = useVendorCertificates();
@@ -143,7 +177,6 @@ export function CertificatesPage() {
const result = await stageMutation.mutateAsync(formData);
if (result.valid) {
toast({ title: 'Certificate staged successfully', variant: 'success' });
// Clear file inputs
if (certInputRef.current) certInputRef.current.value = '';
if (keyInputRef.current) keyInputRef.current.value = '';
if (caInputRef.current) caInputRef.current.value = '';
@@ -192,13 +225,12 @@ export function CertificatesPage() {
{data.staleTenantCount > 0 && (
<Alert variant="warning" title="CA bundle updated">
<AlertTriangle size={14} style={{ verticalAlign: 'middle', marginRight: 6 }} />
{data.staleTenantCount} tenant{data.staleTenantCount > 1 ? 's' : ''} need a restart
to pick up the updated CA bundle.
</Alert>
)}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 16 }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))', gap: 16 }}>
{data.active && (
<CertCard cert={data.active} title="Active Certificate" />
)}
@@ -251,24 +283,9 @@ export function CertificatesPage() {
{/* Upload card */}
<Card title="Upload Certificate">
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div>
<label style={{ fontSize: 13, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 }}>
Certificate (PEM) *
</label>
<input ref={certInputRef} type="file" accept=".pem,.crt,.cer" />
</div>
<div>
<label style={{ fontSize: 13, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 }}>
Private Key (PEM) *
</label>
<input ref={keyInputRef} type="file" accept=".pem,.key" />
</div>
<div>
<label style={{ fontSize: 13, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 }}>
CA Bundle (PEM, optional)
</label>
<input ref={caInputRef} type="file" accept=".pem,.crt,.cer" />
</div>
<FileField label="Certificate (PEM) *" inputRef={certInputRef} accept=".pem,.crt,.cer" />
<FileField label="Private Key (PEM) *" inputRef={keyInputRef} accept=".pem,.key" />
<FileField label="CA Bundle (PEM, optional)" inputRef={caInputRef} accept=".pem,.crt,.cer" />
<Button
variant="primary"
onClick={handleUpload}