@@ -6,7 +6,14 @@ const K8sClient = (() => {
66
77 async function apiFetch ( endpoint ) {
88 const response = await fetch ( endpoint ) ;
9- if ( ! response . ok ) throw new Error ( `HTTP ${ response . status } ` ) ;
9+ if ( ! response . ok ) {
10+ let errorMsg = `HTTP ${ response . status } ` ;
11+ try {
12+ const body = await response . json ( ) ;
13+ if ( body . error ) errorMsg = body . error ;
14+ } catch ( _ ) { }
15+ throw new Error ( errorMsg ) ;
16+ }
1017 return await response . json ( ) ;
1118 }
1219
@@ -25,50 +32,125 @@ const K8sClient = (() => {
2532 [ configDiv , errorDiv , resourceDiv ] . forEach ( el => { if ( el ) el . innerHTML = spinner ; } ) ;
2633
2734 try {
28- const [ report , context ] = await Promise . all ( [
35+ const [ report , context , status ] = await Promise . all ( [
2936 apiFetch ( '/api/k8s/istio/full-report' ) ,
30- K8sClient . loadContext ( )
37+ K8sClient . loadContext ( ) ,
38+ apiFetch ( '/api/k8s/status' ) . catch ( ( ) => null )
3139 ] ) ;
3240
33- // --- TAB A & B (Config & Errors) ---
34- // (Deine bestehende Logik für diese Tabs...)
35- configDiv . innerHTML = `<pre class="console x-small p-3 bg-dark text-success" style="border-radius:4px;">${ report . reachability . activeEndpoints } </pre>` ;
36-
37- const errorEntries = Object . entries ( report . healthDiagnostics . activeErrorMetrics ) ;
38- errorDiv . innerHTML = errorEntries . length === 0 ?
39- `<div class="alert alert-success small">Keine Fehler.</div>` :
40- `<table class="table table-sm small">${ errorEntries . map ( ( [ k , v ] ) => `<tr><td>${ k } </td><td>${ v } </td></tr>` ) . join ( '' ) } </table>` ;
41-
42-
43- // --- TAB C: POD KONTEXT (Dynamisch) ---
44- const contextRows = Object . entries ( context ) . map ( ( [ key , val ] ) => {
45- let displayVal = val ;
46- let badgeClass = "bg-light text-dark border" ;
47-
48- // 1. Behandlung von Booleans (true/false)
49- if ( typeof val === 'boolean' ) {
50- badgeClass = val ? "bg-success text-white" : "bg-danger text-white" ;
51- displayVal = val ? "JA" : "NEIN" ;
41+ // --- TAB A: Config & Erreichbarkeit ---
42+ if ( report . error ) {
43+ const warning = `<div class="alert alert-warning m-3 small"><i class="bi bi-exclamation-triangle me-2"></i>${ report . error } </div>` ;
44+ configDiv . innerHTML = warning ;
45+ errorDiv . innerHTML = `<div class="alert alert-warning m-3 small">Keine Daten – Sidecar nicht aktiv.</div>` ;
46+ } else {
47+ const r = report . reachability ?? { } ;
48+ const clusterLines = ( r . activeEndpoints ?? '' ) . split ( '\n' ) . filter ( l => l . trim ( ) ) ;
49+ configDiv . innerHTML = `
50+ <div class="d-flex align-items-center justify-content-between mb-2 px-1">
51+ <span class="badge bg-secondary"><i class="bi bi-diagram-3 me-1"></i>${ r . summary ?? '' } </span>
52+ <button class="btn btn-sm btn-outline-secondary x-small" onclick="this.closest('.d-flex').nextElementSibling.classList.toggle('d-none')">
53+ <i class="bi bi-code-slash me-1"></i>Envoy Config (JSON)
54+ </button>
55+ </div>
56+ <div class="d-none mb-2">
57+ <pre class="x-small p-2" style="background:#1e1e1e;color:#4af626;border-radius:4px;max-height:300px;overflow:auto;">${ JSON . stringify ( r . envoyConfig , null , 2 ) } </pre>
58+ </div>
59+ <div class="input-group input-group-sm mb-2">
60+ <span class="input-group-text bg-dark border-secondary text-secondary"><i class="bi bi-search"></i></span>
61+ <input type="text" id="clusterSearch" class="form-control form-control-sm x-small bg-dark text-success border-secondary" placeholder="Cluster filtern...">
62+ <span id="clusterCount" class="input-group-text bg-dark border-secondary text-secondary x-small">${ clusterLines . length } </span>
63+ </div>
64+ <pre id="clusterOutput" class="console x-small p-3 bg-dark text-success" style="border-radius:4px;max-height:400px;overflow:auto;">${ clusterLines . join ( '\n' ) } </pre>` ;
65+
66+ document . getElementById ( 'clusterSearch' ) . addEventListener ( 'input' , function ( ) {
67+ const term = this . value . toLowerCase ( ) ;
68+ const filtered = term ? clusterLines . filter ( l => l . toLowerCase ( ) . includes ( term ) ) : clusterLines ;
69+ document . getElementById ( 'clusterOutput' ) . textContent = filtered . join ( '\n' ) ;
70+ document . getElementById ( 'clusterCount' ) . textContent = filtered . length ;
71+ } ) ;
72+
73+ // --- TAB B: Fehler-Metriken ---
74+ const errorEntries = Object . entries ( report . healthDiagnostics ?. activeErrorMetrics ?? { } ) ;
75+ const errorCount = report . healthDiagnostics ?. errorCount ?? errorEntries . length ;
76+ errorDiv . innerHTML = errorEntries . length === 0 ?
77+ `<div class="alert alert-success small"><i class="bi bi-check-circle me-2"></i>Keine aktiven Fehler-Metriken.</div>` :
78+ `<div class="mb-2"><span class="badge bg-danger">${ errorCount } aktive Fehler</span></div>
79+ <table class="table table-sm x-small">
80+ <thead><tr><th>Metrik</th><th>Wert</th></tr></thead>
81+ <tbody>${ errorEntries . map ( ( [ k , v ] ) => `<tr><td class="font-monospace text-truncate" style="max-width:300px;" title="${ k } ">${ k } </td><td class="fw-bold text-danger">${ v } </td></tr>` ) . join ( '' ) } </tbody>
82+ </table>` ;
83+ }
84+
85+ // --- TAB C: POD KONTEXT ---
86+ const istioDetails = context . istioDetails ;
87+ const contextRows = Object . entries ( context )
88+ . filter ( ( [ key ] ) => key !== 'istioDetails' )
89+ . map ( ( [ key , val ] ) => {
90+ let displayVal = val ;
91+ let badgeClass = "bg-light text-dark border" ;
92+ if ( typeof val === 'boolean' ) {
93+ badgeClass = val ? "bg-success text-white" : "bg-danger text-white" ;
94+ displayVal = val ? "JA" : "NEIN" ;
95+ }
96+ return `
97+ <tr>
98+ <td class="bg-light fw-bold small text-muted w-25">${ key } </td>
99+ <td><span class="badge ${ badgeClass } font-monospace">${ displayVal } </span></td>
100+ </tr>` ;
101+ } ) . join ( '' ) ;
102+
103+ const istioDetailsHtml = istioDetails ? ( ( ) => {
104+ if ( istioDetails . error ) {
105+ return `<div class="alert alert-warning x-small mt-3">${ istioDetails . error } </div>` ;
52106 }
53- // 2. Behandlung von komplexen Objekten (wie deine istioDetails)
54- else if ( typeof val === 'object' && val !== null ) {
55- // Wir zeigen in der Tabelle nur einen Hinweis, die Details sind im JSON-Button
56- displayVal = `<i class="bi bi-diagram-3 me-1"></i> Objekt (siehe Details)` ;
57- badgeClass = "bg-info text-dark border" ;
58- }
59-
107+ return `
108+ <div class="border-top pt-3 mt-3">
109+ <h6 class="x-small fw-bold text-uppercase text-muted mb-2">Istio Sidecar Details</h6>
110+ <table class="table table-sm x-small border shadow-sm">
111+ <tbody>
112+ <tr>
113+ <td class="bg-light fw-bold text-muted w-25">clusterSummary</td>
114+ <td><span class="badge bg-secondary font-monospace">${ istioDetails . clusterSummary ?? '-' } </span></td>
115+ </tr>
116+ </tbody>
117+ </table>
118+ ${ istioDetails . networkStats && Object . keys ( istioDetails . networkStats ) . length > 0 ? `
119+ <label class="x-small fw-bold text-muted mb-1">Network Stats</label>
120+ <table class="table table-sm x-small border">
121+ <thead><tr><th>Metrik</th><th>Wert</th></tr></thead>
122+ <tbody>${ Object . entries ( istioDetails . networkStats ) . map ( ( [ k , v ] ) => `<tr><td class="font-monospace" title="${ k } ">${ k } </td><td>${ v } </td></tr>` ) . join ( '' ) } </tbody>
123+ </table>` : '' }
124+ ${ istioDetails . info ? `
125+ <button class="btn btn-sm btn-outline-secondary x-small mb-2" onclick="this.nextElementSibling.classList.toggle('d-none')">
126+ <i class="bi bi-info-circle me-1"></i>Server Info (JSON)
127+ </button>
128+ <div class="d-none mb-2">
129+ <pre class="x-small p-2" style="background:#1e1e1e;color:#4af626;border-radius:4px;max-height:200px;overflow:auto;">${ JSON . stringify ( istioDetails . info , null , 2 ) } </pre>
130+ </div>` : '' }
131+ </div>` ;
132+ } ) ( ) : '' ;
133+
134+ const statusRows = status ? Object . entries ( status ) . map ( ( [ key , val ] ) => {
135+ let displayVal = Array . isArray ( val ) ? val . join ( '<br>' ) : String ( val ) ;
136+ let badgeClass = key === 'initialized'
137+ ? ( val ? 'bg-success text-white' : 'bg-danger text-white' )
138+ : 'bg-light text-dark border' ;
139+ const cellContent = Array . isArray ( val )
140+ ? val . map ( v => `<span class="badge ${ badgeClass } font-monospace me-1 mb-1">${ v } </span>` ) . join ( '' )
141+ : `<span class="badge ${ badgeClass } font-monospace">${ displayVal } </span>` ;
60142 return `
61143 <tr>
62144 <td class="bg-light fw-bold small text-muted w-25">${ key } </td>
63- <td><span class="badge ${ badgeClass } font-monospace"> ${ displayVal } </span> </td>
145+ <td>${ cellContent } </td>
64146 </tr>` ;
65- } ) . join ( '' ) ;
147+ } ) . join ( '' ) : '' ;
66148
67149 resourceDiv . innerHTML = `
68150 <div class="p-2">
69151 <div class="d-flex justify-content-between align-items-center border-bottom pb-2 mb-3">
70152 <h6 class="x-small fw-bold text-uppercase text-muted mb-0">Identität & Kontext</h6>
71- <button class="btn btn-xs btn-outline-primary" onclick="this.parentElement .nextElementSibling.nextElementSibling.classList.toggle('d-none')">
153+ <button class="btn btn-sm btn-outline-primary x-small " onclick="this.closest('.d-flex') .nextElementSibling.nextElementSibling.classList.toggle('d-none')">
72154 <i class="bi bi-eye me-1"></i> Details (JSON)
73155 </button>
74156 </div>
@@ -79,10 +161,18 @@ const K8sClient = (() => {
79161
80162 <div class="d-none mt-3">
81163 <label class="x-small fw-bold text-muted mb-1">KOMPLETTER KONTEXT (RAW):</label>
82- <pre class="x-small p-3 shadow-inner"
83- style="background-color: #1e1e1e; color: #4af626; border-radius: 6px; border: 1px solid #333; overflow: auto; max-height: 500px; font-family: 'Fira Code', monospace;"
84- >${ JSON . stringify ( context , null , 4 ) } </pre>
164+ <pre class="x-small p-3" style="background-color:#1e1e1e;color:#4af626;border-radius:6px;border:1px solid #333;overflow:auto;max-height:500px;font-family:'Fira Code',monospace;">${ JSON . stringify ( context , null , 4 ) } </pre>
85165 </div>
166+
167+ ${ istioDetailsHtml }
168+
169+ ${ statusRows ? `
170+ <div class="border-top pt-3 mt-3">
171+ <h6 class="x-small fw-bold text-uppercase text-muted mb-2">K8s Client Status</h6>
172+ <table class="table table-sm border shadow-sm">
173+ <tbody>${ statusRows } </tbody>
174+ </table>
175+ </div>` : '' }
86176 </div>` ;
87177
88178 } catch ( err ) {
@@ -104,6 +194,7 @@ document.addEventListener('DOMContentLoaded', async () => {
104194 const ctx = await K8sClient . loadContext ( ) ;
105195 if ( contextEl && ! ctx . error ) {
106196 contextEl . innerHTML = `
197+ <span class="badge bg-dark border me-2"><i class="bi bi-cpu me-1"></i>${ ctx . podName } </span>
107198 <span class="badge bg-dark border me-2"><i class="bi bi-tags me-1"></i>${ ctx . namespace } </span>
108199 <span class="badge ${ ctx . istioSidecar ? 'bg-success' : 'bg-warning text-dark' } ">Istio: ${ ctx . istioSidecar ? 'ON' : 'OFF' } </span>` ;
109200 }
@@ -122,4 +213,4 @@ document.addEventListener('DOMContentLoaded', async () => {
122213 K8sClient . runFullDiagnostics ( currentUrl ) ;
123214 } ) ;
124215 }
125- } ) ;
216+ } ) ;
0 commit comments