Private
Public Access
1
0

feat: group certificate pairs in CertificatesPage
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 5s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m11s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 14s
CI Pipeline / Build .deb & Release (push) Has been skipped

This commit is contained in:
2026-05-16 18:07:09 +00:00
parent f9bdc0a5af
commit 6e2c270c75

View File

@ -463,58 +463,70 @@ export default function CertificatesPage() {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{certs.map((cert) => { {Object.values(
const expiring = cert.status === 'active' && isExpiringSoon(cert.expires_at) certs.reduce((acc, cert) => {
const groupKey = cert.host_id || 'unassigned'
if (!acc[groupKey]) acc[groupKey] = []
acc[groupKey].push(cert)
return acc
}, {} as Record<string, Certificate[]>)
).map((group) => {
const primary = group[0]
const isPair = group.length > 1
const expiring = primary.status === 'active' && isExpiringSoon(primary.expires_at)
return ( return (
<TableRow key={cert.id} hover> <TableRow key={primary.id} hover>
<TableCell> <TableCell>
<Typography variant="body2" fontWeight={500}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{cert.common_name} <Typography variant="body2" fontWeight={500}>
</Typography> {primary.common_name}
</Typography>
{isPair && <Chip label={`${group.length} items`} size="small" color="secondary" variant="outlined" />}
</Box>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12 }}> <Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12 }}>
{cert.serial_number} {primary.serial_number}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell>{statusChip(cert.status)}</TableCell> <TableCell>{statusChip(primary.status)}</TableCell>
<TableCell> <TableCell>
<Typography variant="body2">{fmtDate(cert.issued_at)}</Typography> <Typography variant="body2">{fmtDate(primary.issued_at)}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography <Typography
variant="body2" variant="body2"
sx={{ color: expiring ? 'error.main' : 'inherit', fontWeight: expiring ? 600 : 400 }} sx={{ color: expiring ? 'error.main' : 'inherit', fontWeight: expiring ? 600 : 400 }}
> >
{fmtDate(cert.expires_at)} {fmtDate(primary.expires_at)}
{expiring && ' ⚠️'} {expiring && ' ⚠️'}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 11 }}> <Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 11 }}>
{cert.host_id ?? <em>Root CA</em>} {primary.host_id ?? <em>Root CA</em>}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
{canWrite && ( {canWrite && (
<> <>
<Tooltip title="Renew certificate"> <Tooltip title={`Renew certificate ${isPair ? 'pair' : ''}`}>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
sx={{ mr: 1 }} sx={{ mr: 1 }}
onClick={() => handleRenew(cert.id)} onClick={() => handleRenew(primary.id)}
> >
Renew Renew
</Button> </Button>
</Tooltip> </Tooltip>
{cert.status === 'active' && ( {primary.status === 'active' && (
<Tooltip title="Revoke certificate"> <Tooltip title={`Revoke certificate ${isPair ? 'pair' : ''}`}>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
color="error" color="error"
onClick={() => handleRevoke(cert.id)} onClick={() => handleRevoke(primary.id)}
> >
Revoke Revoke
</Button> </Button>