@@ -59,6 +59,19 @@ const K8sClient = (() => {
5959 // Tab B: Fehler-Metriken
6060 // =========================================================================
6161
62+ // Nur Metriken, deren Name auf ein tatsächliches Problem hindeutet (Timeouts, Fehlschläge,
63+ // 4xx/5xx, offene Circuit Breaker etc.) UND einen aktiven (nonzero) Wert haben, gelten als Fehler.
64+ const ERROR_METRIC_PATTERN = / f a i l | t i m e o u t | o v e r f l o w | r e j e c t | n o n e _ h e a l t h y | d e s t r o y _ r e m o t e | d e s t r o y _ l o c a l | r e s e t | a b o r t | _ 5 x x | _ 4 x x | r e t r y _ l i m i t _ e x c e e d e d | c i r c u i t _ b r e a k e r / i;
65+
66+ function isProblemMetric ( key , value ) {
67+ const num = Number ( value ) ;
68+ return ! Number . isNaN ( num ) && num > 0 && ERROR_METRIC_PATTERN . test ( key ) ;
69+ }
70+
71+ function metricValueClass ( key , value ) {
72+ return isProblemMetric ( key , value ) ? 'fw-bold text-danger' : 'text-body-secondary' ;
73+ }
74+
6275 function renderTabB ( healthDiagnostics , errorDiv , targetUrl = '' ) {
6376 const errorEntries = Object . entries ( healthDiagnostics ?. activeErrorMetrics ?? { } ) ;
6477 const errorCount = healthDiagnostics ?. errorCount ?? errorEntries . length ;
@@ -81,7 +94,7 @@ const K8sClient = (() => {
8194 <tbody>${ targetMetrics . map ( ( [ k , v ] ) => `
8295 <tr>
8396 <td class="font-monospace text-truncate" style="max-width:280px;" title="${ k } ">${ k } </td>
84- <td class="fw-bold text-danger ">${ v } </td>
97+ <td class="${ metricValueClass ( k , v ) } ">${ v } </td>
8598 </tr>` ) . join ( '' ) }
8699 </tbody>
87100 </table>
@@ -120,7 +133,7 @@ const K8sClient = (() => {
120133 <tbody>${ errorEntries . map ( ( [ k , v ] ) => `
121134 <tr>
122135 <td class="font-monospace text-truncate" style="max-width:300px;" title="${ k } ">${ k } </td>
123- <td class="fw-bold text-danger ">${ v } </td>
136+ <td class="${ metricValueClass ( k , v ) } ">${ v } </td>
124137 </tr>` ) . join ( '' ) }
125138 </tbody>
126139 </table>` ;
@@ -451,6 +464,11 @@ const K8sClient = (() => {
451464 catch ( err ) { return { error : err . message } ; }
452465 } ,
453466
467+ loadServices : async ( ) => {
468+ try { return await apiFetch ( '/api/k8s/services' ) ; }
469+ catch ( err ) { return [ ] ; }
470+ } ,
471+
454472 runFullDiagnostics : async ( ) => {
455473 const configDiv = document . getElementById ( 'configDisplay' ) ;
456474 const errorDiv = document . getElementById ( 'errorDisplay' ) ;
@@ -492,9 +510,32 @@ const K8sClient = (() => {
492510// Navbar & Event Listener
493511// =============================================================================
494512
513+ async function refreshEnvoyServiceSelect ( ) {
514+ const select = document . getElementById ( 'envoyServiceSelect' ) ;
515+ if ( ! select ) return ;
516+
517+ select . innerHTML = `<option value="">Lade Services...</option>` ;
518+ const services = await K8sClient . loadServices ( ) ;
519+
520+ if ( ! services . length ) {
521+ select . innerHTML = `<option value="">-- keine Envoy-Services gefunden --</option>` ;
522+ return ;
523+ }
524+
525+ const options = services . map ( s => {
526+ const url = `http://${ s . host } :${ s . port } ` ;
527+ const label = s . subset ? `${ s . shortName } (${ s . subset } ) — ${ url } ` : `${ s . shortName } — ${ url } ` ;
528+ return `<option value="${ url } ">${ label } </option>` ;
529+ } ) . join ( '' ) ;
530+
531+ select . innerHTML = `<option value="">-- über Envoy erreichbaren Service wählen --</option>${ options } ` ;
532+ }
533+
495534document . addEventListener ( 'DOMContentLoaded' , async ( ) => {
496535 const contextEl = document . getElementById ( 'k8sContext' ) ;
497536 const diagnoseBtn = document . getElementById ( 'k8sDiagnoseBtn' ) ;
537+ const serviceSelect = document . getElementById ( 'envoyServiceSelect' ) ;
538+ const refreshServicesBtn = document . getElementById ( 'refreshServicesBtn' ) ;
498539
499540 const ctx = await K8sClient . loadContext ( ) ;
500541 if ( contextEl && ! ctx . error ) {
@@ -504,6 +545,20 @@ document.addEventListener('DOMContentLoaded', async () => {
504545 <span class="badge ${ ctx . istioSidecar ? 'bg-success' : 'bg-warning text-dark' } ">Istio: ${ ctx . istioSidecar ? 'ON' : 'OFF' } </span>` ;
505546 }
506547
548+ refreshEnvoyServiceSelect ( ) ;
549+
550+ if ( serviceSelect ) {
551+ serviceSelect . addEventListener ( 'change' , ( ) => {
552+ if ( serviceSelect . value ) {
553+ document . getElementById ( 'url' ) . value = serviceSelect . value ;
554+ }
555+ } ) ;
556+ }
557+
558+ if ( refreshServicesBtn ) {
559+ refreshServicesBtn . addEventListener ( 'click' , refreshEnvoyServiceSelect ) ;
560+ }
561+
507562 if ( diagnoseBtn ) {
508563 diagnoseBtn . addEventListener ( 'click' , ( ) => {
509564 const currentUrl = document . getElementById ( 'url' ) . value ;
0 commit comments