Compare commits
2 Commits
60358a6d47
...
84112d399b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84112d399b | ||
|
|
0f2fba4a62 |
@@ -74,12 +74,28 @@ interface HAProxyStat {
|
||||
req_tot: number; req_rate: number
|
||||
last_change_sec: number; health: string
|
||||
}
|
||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||
// Der Cache-Eintrag ['haproxy','stats'] wird mit dem Dashboard geteilt,
|
||||
// das aus derselben Antwort zusaetzlich `frontends` liest. Deshalb hier
|
||||
// IMMER die vollstaendige Antwort cachen und erst per `select` auf die
|
||||
// Backends reduzieren, die diese Seite braucht.
|
||||
//
|
||||
// Befund 2026-09-11: Lieferte diese Funktion nur das Backend-Array, hing
|
||||
// es vom zuletzt besuchten Screen ab, welche Form unter dem Key lag —
|
||||
// nach einem Wechsel hierher und zurueck riss das Dashboard mit
|
||||
// "Cannot read properties of undefined (reading 'length')" die ganze
|
||||
// Oberflaeche in die ErrorBoundary.
|
||||
interface HAProxyStatsPayload {
|
||||
backends: HAProxyStat[]
|
||||
frontends: unknown[]
|
||||
error?: string
|
||||
}
|
||||
async function fetchHAProxyStats(): Promise<HAProxyStatsPayload> {
|
||||
try {
|
||||
const r = await apiClient.get('/haproxy/stats')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { backends?: HAProxyStat[] }).backends ?? []
|
||||
} catch { return [] }
|
||||
if (!isEnvelope(r.data)) return { backends: [], frontends: [] }
|
||||
const d = r.data.data as Partial<HAProxyStatsPayload>
|
||||
return { backends: d.backends ?? [], frontends: d.frontends ?? [], error: d.error }
|
||||
} catch { return { backends: [], frontends: [] } }
|
||||
}
|
||||
|
||||
function fmtBytes(n: number): string {
|
||||
@@ -112,7 +128,8 @@ export default function BackendDetailPage() {
|
||||
const { data: domains } = useQuery({ queryKey: ['domains'], queryFn: listDomains })
|
||||
const { data: haproxyStats } = useQuery({
|
||||
queryKey: ['haproxy', 'stats'],
|
||||
queryFn: listHAProxyStats,
|
||||
queryFn: fetchHAProxyStats,
|
||||
select: (d: HAProxyStatsPayload) => d.backends,
|
||||
refetchInterval: 10_000,
|
||||
})
|
||||
const [form] = Form.useForm<BackendFormValues>()
|
||||
|
||||
@@ -118,12 +118,28 @@ function fmtBytes(n: number): string {
|
||||
if (n >= 1_024) return (n / 1_024).toFixed(0) + ' KB'
|
||||
return n + ' B'
|
||||
}
|
||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||
// Der Cache-Eintrag ['haproxy','stats'] wird mit dem Dashboard geteilt,
|
||||
// das aus derselben Antwort zusaetzlich `frontends` liest. Deshalb hier
|
||||
// IMMER die vollstaendige Antwort cachen und erst per `select` auf die
|
||||
// Backends reduzieren, die diese Seite braucht.
|
||||
//
|
||||
// Befund 2026-09-11: Lieferte diese Funktion nur das Backend-Array, hing
|
||||
// es vom zuletzt besuchten Screen ab, welche Form unter dem Key lag —
|
||||
// nach einem Wechsel hierher und zurueck riss das Dashboard mit
|
||||
// "Cannot read properties of undefined (reading 'length')" die ganze
|
||||
// Oberflaeche in die ErrorBoundary.
|
||||
interface HAProxyStatsPayload {
|
||||
backends: HAProxyStat[]
|
||||
frontends: unknown[]
|
||||
error?: string
|
||||
}
|
||||
async function fetchHAProxyStats(): Promise<HAProxyStatsPayload> {
|
||||
try {
|
||||
const r = await apiClient.get('/haproxy/stats')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { backends?: HAProxyStat[] }).backends ?? []
|
||||
} catch { return [] }
|
||||
if (!isEnvelope(r.data)) return { backends: [], frontends: [] }
|
||||
const d = r.data.data as Partial<HAProxyStatsPayload>
|
||||
return { backends: d.backends ?? [], frontends: d.frontends ?? [], error: d.error }
|
||||
} catch { return { backends: [], frontends: [] } }
|
||||
}
|
||||
|
||||
export default function BackendsPage() {
|
||||
@@ -146,7 +162,8 @@ export default function BackendsPage() {
|
||||
const haproxyService = services?.find(s => s.unit === 'haproxy.service' || s.unit === 'haproxy')
|
||||
const { data: haproxyStats } = useQuery({
|
||||
queryKey: ['haproxy', 'stats'],
|
||||
queryFn: listHAProxyStats,
|
||||
queryFn: fetchHAProxyStats,
|
||||
select: (d: HAProxyStatsPayload) => d.backends,
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
|
||||
|
||||
@@ -613,11 +613,11 @@ function VIPCard({ data }: { data?: VIPStatus | null }) {
|
||||
>
|
||||
{!data ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>—</Text>
|
||||
) : data.vips.length === 0 ? (
|
||||
) : (data.vips ?? []).length === 0 ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{t('dashboard.vipCard.noVips')}</Text>
|
||||
) : (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={0}>
|
||||
{data.vips.map((v) => (
|
||||
{(data.vips ?? []).map((v) => (
|
||||
<div key={v.address} style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
padding: '5px 0', borderBottom: '1px solid #F1F5F9',
|
||||
@@ -715,7 +715,7 @@ function HAProxyFullCard({ stats, resolveHAName }: HAProxyFullCardProps) {
|
||||
className="h-100"
|
||||
title={<><DatabaseOutlined style={{ color: '#0EA5E9' }} /> {t('dashboard.haproxyCard.title')}</>}
|
||||
extra={
|
||||
stats.frontends.length > 0 && (
|
||||
(stats.frontends ?? []).length > 0 && (
|
||||
<Space size={8}>
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>{totalSessions} sess</Text>
|
||||
{totalReqRate > 0 && <Text type="secondary" style={{ fontSize: 11 }}>{totalReqRate}/s</Text>}
|
||||
@@ -731,7 +731,7 @@ function HAProxyFullCard({ stats, resolveHAName }: HAProxyFullCardProps) {
|
||||
)}
|
||||
|
||||
{/* Listeners */}
|
||||
{stats.frontends.length > 0 && (
|
||||
{(stats.frontends ?? []).length > 0 && (
|
||||
<>
|
||||
<Text type="secondary" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{t('dashboard.haproxyCard.frontends')}
|
||||
@@ -752,7 +752,7 @@ function HAProxyFullCard({ stats, resolveHAName }: HAProxyFullCardProps) {
|
||||
)}
|
||||
|
||||
{/* Backends */}
|
||||
{stats.backends.length === 0 && !stats.error ? (
|
||||
{(stats.backends ?? []).length === 0 && !stats.error ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{t('dashboard.haproxyCard.empty')}</Text>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -101,12 +101,28 @@ async function listCerts(): Promise<TLSCertLite[]> {
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { tls_certs?: TLSCertLite[] }).tls_certs ?? []
|
||||
}
|
||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||
// Der Cache-Eintrag ['haproxy','stats'] wird mit dem Dashboard geteilt,
|
||||
// das aus derselben Antwort zusaetzlich `frontends` liest. Deshalb hier
|
||||
// IMMER die vollstaendige Antwort cachen und erst per `select` auf die
|
||||
// Backends reduzieren, die diese Seite braucht.
|
||||
//
|
||||
// Befund 2026-09-11: Lieferte diese Funktion nur das Backend-Array, hing
|
||||
// es vom zuletzt besuchten Screen ab, welche Form unter dem Key lag —
|
||||
// nach einem Wechsel hierher und zurueck riss das Dashboard mit
|
||||
// "Cannot read properties of undefined (reading 'length')" die ganze
|
||||
// Oberflaeche in die ErrorBoundary.
|
||||
interface HAProxyStatsPayload {
|
||||
backends: HAProxyStat[]
|
||||
frontends: unknown[]
|
||||
error?: string
|
||||
}
|
||||
async function fetchHAProxyStats(): Promise<HAProxyStatsPayload> {
|
||||
try {
|
||||
const r = await apiClient.get('/haproxy/stats')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { backends?: HAProxyStat[] }).backends ?? []
|
||||
} catch { return [] }
|
||||
if (!isEnvelope(r.data)) return { backends: [], frontends: [] }
|
||||
const d = r.data.data as Partial<HAProxyStatsPayload>
|
||||
return { backends: d.backends ?? [], frontends: d.frontends ?? [], error: d.error }
|
||||
} catch { return { backends: [], frontends: [] } }
|
||||
}
|
||||
|
||||
export default function DomainDetailPage() {
|
||||
@@ -126,7 +142,8 @@ export default function DomainDetailPage() {
|
||||
const { data: certs } = useQuery({ queryKey: ['tls-certs'], queryFn: listCerts })
|
||||
const { data: haproxyStats } = useQuery({
|
||||
queryKey: ['haproxy', 'stats'],
|
||||
queryFn: listHAProxyStats,
|
||||
queryFn: fetchHAProxyStats,
|
||||
select: (d: HAProxyStatsPayload) => d.backends,
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
|
||||
|
||||
@@ -83,12 +83,28 @@ async function listCerts(): Promise<TLSCertLite[]> {
|
||||
}
|
||||
|
||||
interface HAProxyStat { backend: string; server: string; status: string }
|
||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||
// Der Cache-Eintrag ['haproxy','stats'] wird mit dem Dashboard geteilt,
|
||||
// das aus derselben Antwort zusaetzlich `frontends` liest. Deshalb hier
|
||||
// IMMER die vollstaendige Antwort cachen und erst per `select` auf die
|
||||
// Backends reduzieren, die diese Seite braucht.
|
||||
//
|
||||
// Befund 2026-09-11: Lieferte diese Funktion nur das Backend-Array, hing
|
||||
// es vom zuletzt besuchten Screen ab, welche Form unter dem Key lag —
|
||||
// nach einem Wechsel hierher und zurueck riss das Dashboard mit
|
||||
// "Cannot read properties of undefined (reading 'length')" die ganze
|
||||
// Oberflaeche in die ErrorBoundary.
|
||||
interface HAProxyStatsPayload {
|
||||
backends: HAProxyStat[]
|
||||
frontends: unknown[]
|
||||
error?: string
|
||||
}
|
||||
async function fetchHAProxyStats(): Promise<HAProxyStatsPayload> {
|
||||
try {
|
||||
const r = await apiClient.get('/haproxy/stats')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { backends?: HAProxyStat[] }).backends ?? []
|
||||
} catch { return [] }
|
||||
if (!isEnvelope(r.data)) return { backends: [], frontends: [] }
|
||||
const d = r.data.data as Partial<HAProxyStatsPayload>
|
||||
return { backends: d.backends ?? [], frontends: d.frontends ?? [], error: d.error }
|
||||
} catch { return { backends: [], frontends: [] } }
|
||||
}
|
||||
|
||||
export default function DomainsPage() {
|
||||
@@ -109,7 +125,8 @@ export default function DomainsPage() {
|
||||
const { data: certs } = useQuery({ queryKey: ['tls-certs'], queryFn: listCerts })
|
||||
const { data: haproxyStats } = useQuery({
|
||||
queryKey: ['haproxy', 'stats'],
|
||||
queryFn: listHAProxyStats,
|
||||
queryFn: fetchHAProxyStats,
|
||||
select: (d: HAProxyStatsPayload) => d.backends,
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
const certByDomain = new Map((certs ?? []).map(c => [c.domain, c]))
|
||||
|
||||
@@ -58,12 +58,28 @@ function fmtBytes(n: number): string {
|
||||
if (n >= 1_024) return (n / 1_024).toFixed(0) + ' KB'
|
||||
return n + ' B'
|
||||
}
|
||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||
// Der Cache-Eintrag ['haproxy','stats'] wird mit dem Dashboard geteilt,
|
||||
// das aus derselben Antwort zusaetzlich `frontends` liest. Deshalb hier
|
||||
// IMMER die vollstaendige Antwort cachen und erst per `select` auf die
|
||||
// Backends reduzieren, die diese Seite braucht.
|
||||
//
|
||||
// Befund 2026-09-11: Lieferte diese Funktion nur das Backend-Array, hing
|
||||
// es vom zuletzt besuchten Screen ab, welche Form unter dem Key lag —
|
||||
// nach einem Wechsel hierher und zurueck riss das Dashboard mit
|
||||
// "Cannot read properties of undefined (reading 'length')" die ganze
|
||||
// Oberflaeche in die ErrorBoundary.
|
||||
interface HAProxyStatsPayload {
|
||||
backends: HAProxyStat[]
|
||||
frontends: unknown[]
|
||||
error?: string
|
||||
}
|
||||
async function fetchHAProxyStats(): Promise<HAProxyStatsPayload> {
|
||||
try {
|
||||
const r = await apiClient.get('/haproxy/stats')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { backends?: HAProxyStat[] }).backends ?? []
|
||||
} catch { return [] }
|
||||
if (!isEnvelope(r.data)) return { backends: [], frontends: [] }
|
||||
const d = r.data.data as Partial<HAProxyStatsPayload>
|
||||
return { backends: d.backends ?? [], frontends: d.frontends ?? [], error: d.error }
|
||||
} catch { return { backends: [], frontends: [] } }
|
||||
}
|
||||
|
||||
export default function RoutingRulesPage() {
|
||||
@@ -76,7 +92,8 @@ export default function RoutingRulesPage() {
|
||||
const { data: backends } = useQuery({ queryKey: ['backends'], queryFn: listBackends })
|
||||
const { data: haproxyStats } = useQuery({
|
||||
queryKey: ['haproxy', 'stats'],
|
||||
queryFn: listHAProxyStats,
|
||||
queryFn: fetchHAProxyStats,
|
||||
select: (d: HAProxyStatsPayload) => d.backends,
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user