feat(dashboard): SSL card expired/expiring split + HAProxy frontend labels
Expired certs now show a red error alert ("renew immediately") instead
of being silently folded into the orange "expiring soon" warning.
HAProxy frontend technical names (public_http, public_https, mgmt_https)
now display as human-readable labels (HTTP In, HTTPS In, Management).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,7 @@ import (
|
|||||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.118"
|
var version = "1.1.119"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.118"
|
var version = "1.1.119"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.118"
|
var version = "1.1.119"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -745,6 +745,7 @@
|
|||||||
"sslCard": {
|
"sslCard": {
|
||||||
"title": "SSL-Zertifikate",
|
"title": "SSL-Zertifikate",
|
||||||
"total": "Verwaltete Zertifikate",
|
"total": "Verwaltete Zertifikate",
|
||||||
|
"certExpired": "{{count}} Zertifikat(e) abgelaufen — sofort erneuern",
|
||||||
"expiringSoon": "{{count}} läuft bald ab (< 30 Tage)",
|
"expiringSoon": "{{count}} läuft bald ab (< 30 Tage)",
|
||||||
"allFresh": "Alle Zertifikate haben > 30 Tage Restlaufzeit."
|
"allFresh": "Alle Zertifikate haben > 30 Tage Restlaufzeit."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -745,6 +745,7 @@
|
|||||||
"sslCard": {
|
"sslCard": {
|
||||||
"title": "SSL certificates",
|
"title": "SSL certificates",
|
||||||
"total": "Managed certificates",
|
"total": "Managed certificates",
|
||||||
|
"certExpired": "{{count}} certificate(s) expired — renew immediately",
|
||||||
"expiringSoon": "{{count}} expiring soon (< 30 days)",
|
"expiringSoon": "{{count}} expiring soon (< 30 days)",
|
||||||
"allFresh": "All certs have > 30 days remaining."
|
"allFresh": "All certs have > 30 days remaining."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -283,6 +283,13 @@ export default function DashboardPage() {
|
|||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const maintenanceDomains = (domains.data ?? []).filter(d => d.maintenance_mode)
|
const maintenanceDomains = (domains.data ?? []).filter(d => d.maintenance_mode)
|
||||||
|
|
||||||
|
// HAProxy frontend technical name → display label.
|
||||||
|
const HA_FRONTEND_LABELS: Record<string, string> = {
|
||||||
|
public_http: 'HTTP In',
|
||||||
|
public_https: 'HTTPS In',
|
||||||
|
mgmt_https: 'Management',
|
||||||
|
}
|
||||||
|
|
||||||
// eg_backend_<id> → friendly name lookup for HAProxy stats display.
|
// eg_backend_<id> → friendly name lookup for HAProxy stats display.
|
||||||
const backendIdToName = useMemo(() => {
|
const backendIdToName = useMemo(() => {
|
||||||
const m = new Map<number, string>()
|
const m = new Map<number, string>()
|
||||||
@@ -320,10 +327,14 @@ export default function DashboardPage() {
|
|||||||
return down
|
return down
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
const certsExpired = (tlsCerts.data ?? []).filter(c => {
|
||||||
|
if (!c.not_after) return false
|
||||||
|
return new Date(c.not_after).getTime() < now
|
||||||
|
})
|
||||||
const certsSoon = (tlsCerts.data ?? []).filter(c => {
|
const certsSoon = (tlsCerts.data ?? []).filter(c => {
|
||||||
if (!c.not_after) return false
|
if (!c.not_after) return false
|
||||||
const exp = new Date(c.not_after).getTime()
|
const exp = new Date(c.not_after).getTime()
|
||||||
return exp - now < 30 * 86_400_000
|
return exp >= now && exp - now < 30 * 86_400_000
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -501,7 +512,7 @@ export default function DashboardPage() {
|
|||||||
{(haproxyStats.data?.frontends ?? []).map((f) => (
|
{(haproxyStats.data?.frontends ?? []).map((f) => (
|
||||||
<div key={f.name} style={{ fontSize: 12, color: '#334155' }}>
|
<div key={f.name} style={{ fontSize: 12, color: '#334155' }}>
|
||||||
<Space size={6} wrap>
|
<Space size={6} wrap>
|
||||||
<code style={{ fontSize: 11 }}>{f.name}</code>
|
<code style={{ fontSize: 11 }}>{HA_FRONTEND_LABELS[f.name] ?? f.name}</code>
|
||||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||||
{f.sessions} sess
|
{f.sessions} sess
|
||||||
{f.req_rate > 0 ? ` · ${f.req_rate}/s` : ''}
|
{f.req_rate > 0 ? ` · ${f.req_rate}/s` : ''}
|
||||||
@@ -650,14 +661,23 @@ export default function DashboardPage() {
|
|||||||
<Col xs={24} md={12} xl={8}>
|
<Col xs={24} md={12} xl={8}>
|
||||||
<Card size="small" title={<><SafetyCertificateOutlined /> {t('dashboard.sslCard.title')}</>} className="h-100">
|
<Card size="small" title={<><SafetyCertificateOutlined /> {t('dashboard.sslCard.title')}</>} className="h-100">
|
||||||
<Statistic title={t('dashboard.sslCard.total')} value={(tlsCerts.data ?? []).length} />
|
<Statistic title={t('dashboard.sslCard.total')} value={(tlsCerts.data ?? []).length} />
|
||||||
{certsSoon.length > 0 ? (
|
{certsExpired.length > 0 && (
|
||||||
|
<Alert
|
||||||
|
style={{ marginTop: 8 }}
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
message={t('dashboard.sslCard.certExpired', { count: certsExpired.length })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{certsSoon.length > 0 && (
|
||||||
<Alert
|
<Alert
|
||||||
style={{ marginTop: 8 }}
|
style={{ marginTop: 8 }}
|
||||||
type="warning"
|
type="warning"
|
||||||
showIcon
|
showIcon
|
||||||
message={t('dashboard.sslCard.expiringSoon', { count: certsSoon.length })}
|
message={t('dashboard.sslCard.expiringSoon', { count: certsSoon.length })}
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
{certsExpired.length === 0 && certsSoon.length === 0 && (
|
||||||
<div style={{ marginTop: 8, fontSize: 12, color: '#64748B' }}>
|
<div style={{ marginTop: 8, fontSize: 12, color: '#64748B' }}>
|
||||||
{t('dashboard.sslCard.allFresh')}
|
{t('dashboard.sslCard.allFresh')}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user