11/**
22 * k8s-client.js
3- * Zuständig für Kubernetes Kontext-Informationen und Istio-Netzwerkdiagnose .
3+ * Verwaltet Kubernetes Kontext und tiefe Istio/Envoy Diagnose-Daten .
44 */
55const K8sClient = ( ( ) => {
66
7- // Private Hilfsfunktion für API-Anfragen an den DiagnosticController
7+ /**
8+ * Private Hilfsfunktion für API-Calls
9+ */
810 async function apiFetch ( endpoint ) {
911 const response = await fetch ( endpoint ) ;
1012 if ( ! response . ok ) {
11- throw new Error ( `Fehler beim Abrufen der K8s-Daten (Status : ${ response . status } ) ` ) ;
13+ throw new Error ( `Server-Fehler : ${ response . status } ` ) ;
1214 }
1315 return await response . json ( ) ;
1416 }
1517
16- /**
17- * Erstellt das HTML für die Anzeige der Istio-Ressourcen
18- */
19- function renderDiagnostics ( container , data ) {
20- let html = `
21- <div class="row g-3">
22- <div class="col-md-6">
23- <div class="p-2 border rounded bg-light h-100">
24- <h6 class="fw-bold border-bottom pb-2">
25- <i class="bi bi-shuffle me-2"></i>Virtual Services
26- <span class="badge bg-secondary float-end">${ data . virtualServices . length } </span>
27- </h6>
28- <div class="list-group list-group-flush shadow-sm mt-2">
29- ${ data . virtualServices . map ( v => `
30- <div class="list-group-item p-2 small">
31- <span class="text-primary fw-bold">${ v . metadata . name } </span>
32- </div>
33- ` ) . join ( '' ) || '<div class="text-muted small p-2 italic">Keine VirtualServices gefunden</div>' }
34- </div>
35- </div>
36- </div>
37- <div class="col-md-6">
38- <div class="p-2 border rounded bg-light h-100">
39- <h6 class="fw-bold border-bottom pb-2">
40- <i class="bi bi-shield-check me-2"></i>Destination Rules
41- <span class="badge bg-secondary float-end">${ data . destinationRules . length } </span>
42- </h6>
43- <div class="list-group list-group-flush shadow-sm mt-2">
44- ${ data . destinationRules . map ( d => `
45- <div class="list-group-item p-2 small">
46- <span class="text-success fw-bold">${ d . metadata . name } </span>
47- </div>
48- ` ) . join ( '' ) || '<div class="text-muted small p-2 italic">Keine DestinationRules gefunden</div>' }
49- </div>
50- </div>
51- </div>
52- </div>
53- <div class="mt-3 p-2 bg-dark text-white rounded x-small">
54- <i class="bi bi-info-circle me-2"></i>Gefiltert nach Host: <strong>${ data . host } </strong> im Namespace: <strong>${ data . namespace } </strong>
55- </div>
56- ` ;
57- container . innerHTML = html ;
58- }
59-
6018 return {
61- // Lädt die Pod- und Namespace-Informationen für die Navbar
19+ /**
20+ * Lädt initialen Kontext für die Navbar (Namespace & Istio Status)
21+ */
6222 loadContext : async ( ) => {
6323 try {
6424 return await apiFetch ( '/api/k8s/context' ) ;
@@ -68,89 +28,144 @@ const K8sClient = (() => {
6828 }
6929 } ,
7030
71- // Führt eine Tiefen-Diagnose für eine Ziel-URL durch
31+ /**
32+ * Hauptfunktion für die Deep-Dive Diagnose
33+ */
7234 runFullDiagnostics : async ( targetUrl ) => {
73- const contentDiv = document . getElementById ( 'istioContent' ) ;
74- if ( ! contentDiv ) return ;
35+ const configDiv = document . getElementById ( 'configDisplay' ) ;
36+ const errorDiv = document . getElementById ( 'errorDisplay' ) ;
37+ const resourceDiv = document . getElementById ( 'resourceDisplay' ) ;
7538
76- contentDiv . innerHTML = `
77- <div class="text-center p-4">
78- <div class="spinner-border text-info" role="status"></div>
79- <div class="mt-2">Analysiere Routing-Regeln im Cluster...</div>
80- </div>` ;
39+ // 1. Loading State für alle Tabs setzen
40+ const spinner = ' <div class="text-center p-3"><div class="spinner-border spinner-border-sm text-info"></div><div class="mt-2 x-small text-muted">Abfrage läuft...</div></div>' ;
41+ if ( configDiv ) configDiv . innerHTML = spinner ;
42+ if ( errorDiv ) errorDiv . innerHTML = spinner ;
43+ if ( resourceDiv ) resourceDiv . innerHTML = spinner ;
8144
8245 try {
83- // Namespace aus URL extrahieren (Erwartet Format: http://service.namespace.svc...)
46+ // 2. Daten parallel abfragen
47+ // A) Full Report vom Envoy Proxy (Backend getFullReport)
48+ // B) K8s Ressourcen (Backend getIstioResources)
49+
8450 const urlObj = new URL ( targetUrl ) ;
85- const host = urlObj . hostname ;
86- const parts = host . split ( '.' ) ;
87-
88- // Logik: service.namespace.svc -> namespace ist an Index 1
89- // Bei "localhost" oder einfachen Namen nehmen wir "default"
90- const namespace = ( parts . length > 1 && parts [ 1 ] !== 'svc' ) ? parts [ 1 ] : 'default' ;
51+ const hostParts = urlObj . hostname . split ( '.' ) ;
52+ // Extrahiere Namespace aus URL (z.B. service.namespace.svc.cluster.local)
53+ const targetNamespace = ( hostParts . length > 1 && hostParts [ 1 ] !== 'svc' ) ? hostParts [ 1 ] : 'default' ;
9154
92- const [ vs , dr ] = await Promise . all ( [
93- apiFetch ( ` /api/k8s/istio/virtualservice?namespace= ${ namespace } ` ) ,
94- apiFetch ( `/api/k8s/istio/destinationrule ?namespace=${ namespace } ` )
55+ const [ report , vsData ] = await Promise . all ( [
56+ apiFetch ( ' /api/k8s/istio/full-report' ) ,
57+ apiFetch ( `/api/k8s/istio/virtualservice ?namespace=${ targetNamespace } ` )
9558 ] ) ;
9659
97- // Filtern der Ressourcen, die den Host-Namen im Spec oder Metadaten enthalten
98- const virtualServices = vs . filter ( item => JSON . stringify ( item ) . includes ( parts [ 0 ] ) ) ;
99- const destinationRules = dr . filter ( item => JSON . stringify ( item ) . includes ( parts [ 0 ] ) ) ;
60+ if ( report . error ) throw new Error ( report . error ) ;
10061
101- renderDiagnostics ( contentDiv , { host, namespace, virtualServices, destinationRules } ) ;
102- } catch ( err ) {
103- contentDiv . innerHTML = `
104- <div class="alert alert-warning">
105- <i class="bi bi-exclamation-triangle me-2"></i>
106- <strong>Diagnose eingeschränkt:</strong><br>
107- ${ err . message } . Stellen Sie sicher, dass es sich um eine interne Cluster-URL handelt.
62+ // --- RENDERING TAB A: CONFIG & REACHABILITY ---
63+ configDiv . innerHTML = `
64+ <div class="mb-3">
65+ <label class="form-label fw-bold small text-uppercase text-muted">Aktive Upstream Endpunkte (Envoy Clusters):</label>
66+ <pre class="console x-small" style="max-height: 250px; overflow:auto; background: #1e1e1e; color: #4af626; padding: 12px; border-radius: 4px; border: 1px solid #333;">${ report . reachability . activeEndpoints || 'Keine Daten verfügbar' } </pre>
67+ <div class="badge bg-primary mt-1">${ report . reachability . summary } </div>
68+ </div>
69+ <div class="mt-3 border-top pt-2">
70+ <button class="btn btn-sm btn-outline-secondary" onclick="this.nextElementSibling.classList.toggle('d-none')">
71+ <i class="bi bi-code-slash me-1"></i>Raw Envoy Config Dump (JSON)
72+ </button>
73+ <pre class="console x-small d-none mt-2" style="max-height: 400px; overflow:auto; background: #f8f9fa; color: #333; border: 1px solid #ddd; padding: 10px;">${ JSON . stringify ( report . reachability . envoyConfig , null , 2 ) } </pre>
74+ </div>` ;
75+
76+ // --- RENDERING TAB B: ACTIVE ERRORS ---
77+ const errorEntries = Object . entries ( report . healthDiagnostics . activeErrorMetrics ) ;
78+ if ( errorEntries . length === 0 ) {
79+ errorDiv . innerHTML = `
80+ <div class="alert alert-success border-0 shadow-sm d-flex align-items-center mt-2">
81+ <i class="bi bi-check-circle-fill fs-4 me-3 text-success"></i>
82+ <div><strong>Alles okay!</strong> Keine aktiven Fehlermetriken im Sidecar Proxy für diesen Pod gefunden.</div>
83+ </div>` ;
84+ } else {
85+ errorDiv . innerHTML = `
86+ <div class="table-responsive mt-2">
87+ <table class="table table-sm table-hover border small shadow-sm">
88+ <thead class="table-dark">
89+ <tr><th>Envoy Metrik Pfad</th><th class="text-end">Zählerstand</th></tr>
90+ </thead>
91+ <tbody>
92+ ${ errorEntries . map ( ( [ k , v ] ) => `
93+ <tr>
94+ <td class="font-monospace x-small text-muted">${ k } </td>
95+ <td class="text-end fw-bold text-danger">${ v } </td>
96+ </tr>
97+ ` ) . join ( '' ) }
98+ </tbody>
99+ </table>
100+ <div class="p-2 x-small bg-light border rounded"><i class="bi bi-info-circle me-1"></i> Es werden nur Counter mit Werten > 0 angezeigt.</div>
101+ </div>` ;
102+ }
103+
104+ // --- RENDERING TAB C: K8S RESOURCES ---
105+ const vsFiltered = vsData . filter ( item => JSON . stringify ( item ) . includes ( hostParts [ 0 ] ) ) ;
106+ resourceDiv . innerHTML = `
107+ <div class="p-2">
108+ <h6 class="small fw-bold text-uppercase text-muted mb-3 border-bottom pb-2">Gefundene VirtualServices in '${ targetNamespace } '</h6>
109+ <div class="list-group list-group-flush shadow-sm">
110+ ${ vsFiltered . map ( v => `
111+ <div class="list-group-item p-2 small border-start border-4 border-info mb-2 bg-light d-flex justify-content-between">
112+ <span><i class="bi bi-shuffle me-2 text-primary"></i>${ v . metadata . name } </span>
113+ <span class="badge bg-white text-dark border">v1alpha3</span>
114+ </div>
115+ ` ) . join ( '' ) || '<div class="alert alert-light small text-center">Keine spezifischen Istio-Regeln für diesen Host gefunden.</div>' }
116+ </div>
108117 </div>` ;
118+
119+ } catch ( err ) {
120+ console . error ( "Diagnostic Error:" , err ) ;
121+ const errorMsg = `<div class="alert alert-danger m-3 small"><i class="bi bi-exclamation-triangle-fill me-2"></i>${ err . message } </div>` ;
122+ configDiv . innerHTML = errorMsg ;
123+ errorDiv . innerHTML = errorMsg ;
124+ resourceDiv . innerHTML = errorMsg ;
109125 }
110126 }
111127 } ;
112128} ) ( ) ;
113129
114- // Initialisierung bei DOM-Ready
130+ /**
131+ * Event Listener Initialisierung
132+ */
115133document . addEventListener ( 'DOMContentLoaded' , async ( ) => {
116134 const contextEl = document . getElementById ( 'k8sContext' ) ;
117135 const diagnoseBtn = document . getElementById ( 'k8sDiagnoseBtn' ) ;
118- const istioPanel = document . getElementById ( 'istioPanel' ) ;
119136
120- // 1. Kontext laden (Navbar)
137+ // 1. Initialer Context-Check für die UI
121138 const ctx = await K8sClient . loadContext ( ) ;
122- if ( contextEl ) {
123- if ( ctx . error ) {
124- contextEl . innerHTML = `<span class="badge bg-danger">K8s Offline</span>` ;
125- } else {
126- contextEl . innerHTML = `
127- <span class="badge bg-opacity-25 bg-light border me-2" title="Aktueller Namespace">
128- <i class="bi bi-tags me-1"></i>${ ctx . namespace }
129- </span>
130- <span class="badge ${ ctx . istioSidecar ? 'bg-success' : 'bg-warning text-dark' } " title="Istio Sidecar Status">
131- <i class="bi bi-shield-check me-1"></i>Istio: ${ ctx . istioSidecar ? 'ON' : 'OFF' }
132- </span>
133- ` ;
134- }
139+ if ( contextEl && ! ctx . error ) {
140+ contextEl . innerHTML = `
141+ <span class="badge bg-dark border me-2" title="Namespace"><i class="bi bi-tags me-1"></i>${ ctx . namespace } </span>
142+ <span class="badge ${ ctx . istioSidecar ? 'bg-success' : 'bg-warning text-dark' } ">
143+ <i class="bi bi-shield-check me-1"></i>Istio: ${ ctx . istioSidecar ? 'ON' : 'OFF' }
144+ </span>` ;
135145 }
136146
137- // 2. Event Listener für den Diagnose- Button
147+ // 2. Diagnose Button Logik
138148 if ( diagnoseBtn ) {
139149 diagnoseBtn . addEventListener ( 'click' , ( ) => {
140- const urlInput = document . getElementById ( 'url' ) . value ;
141- if ( ! urlInput ) {
142- alert ( "Bitte geben Sie erst eine Ziel-URL ein, die analysiert werden soll. " ) ;
150+ const urlValue = document . getElementById ( 'url' ) . value ;
151+ if ( ! urlValue ) {
152+ alert ( "Bitte eine Ziel-URL eingeben! " ) ;
143153 return ;
144154 }
145155
146- // WICHTIG: Die ResultArea und das Panel sichtbar machen
147- const resultArea = document . getElementById ( 'resultArea' ) ;
148- const istioPanel = document . getElementById ( 'istioPanel' ) ;
156+ // UI-Bereiche einblenden
157+ document . getElementById ( 'resultArea' ) . style . display = 'block' ;
158+ document . getElementById ( 'istioPanel' ) . style . display = 'block' ;
149159
150- if ( resultArea ) resultArea . style . display = 'block' ;
151- if ( istioPanel ) istioPanel . style . display = 'block' ;
160+ // Automatisch zum ersten Tab wechseln (falls man im Fehler-Tab war)
161+ const firstTabEl = document . querySelector ( '#config-tab' ) ;
162+ if ( firstTabEl ) {
163+ const tabTrigger = new bootstrap . Tab ( firstTabEl ) ;
164+ tabTrigger . show ( ) ;
165+ }
152166
153- K8sClient . runFullDiagnostics ( urlInput ) ;
167+ // Diagnose starten
168+ K8sClient . runFullDiagnostics ( urlValue ) ;
154169 } ) ;
155170 }
156171} ) ;
0 commit comments