1- import {
2- Api ,
3- Config ,
4- Dom ,
5- Id ,
6- Text ,
7- View
8- } from './constants.js' ;
9- import {
10- el ,
11- status ,
12- write
13- } from './dom.js' ;
14- import {
15- hasSelectedResource ,
16- initializeBrowsing ,
17- loadData ,
18- loadResources ,
19- resetBrowsing ,
20- viewChanged
21- } from './browsing.js' ;
22- import {
23- clearObservations ,
24- physicalFiles ,
25- renderSnapshot ,
26- selectView
27- } from './rendering.js' ;
28- import {
29- resetMetrics
30- } from './metrics.js' ;
1+ import { Api , BrowseViews , Config , Css , Dom , Id , View } from './constants.js' ;
2+ import { Text } from './text.js' ;
3+ import { el , status , write } from './dom.js' ;
4+ import { initializeBrowsing , loadData , loadResources , resetBrowsing , viewChanged } from './browsing.js' ;
5+ import { clearCatalog , initializeCatalog , loadCatalog } from './catalog.js' ;
6+ import { clearErrors , errorsVisible , logEvent , renderErrors } from './errors.js' ;
7+ import { date , shortId } from './format.js' ;
8+ import { measure , resetMetrics } from './metrics.js' ;
9+ import { initializeNavigation , selectView } from './navigation.js' ;
10+ import { clearNodes , renderNodes } from './nodes.js' ;
11+ import { clearOverview , renderOverview } from './overview.js' ;
12+ import { clearPerformance , renderPerformance } from './performance.js' ;
13+ import { clearStorage , renderStorage } from './storage.js' ;
14+
3115let credential = Text . empty ;
3216let epoch = Config . zero ;
3317let controller = null ;
3418let inFlight = false ;
3519let poll = null ;
3620let view = View . overview ;
21+ let observed = null ;
22+
3723function connection ( active ) {
3824 document . body . classList . toggle ( Dom . connected , active ) ;
39- write ( Id . state , active ?Text . connected :Text . disconnected ) ;
25+ write ( Id . state , active ? Text . connected : Text . disconnected ) ;
26+ write ( Id . liveText , active ? Text . live : Text . offline ) ;
4027 el ( Id . disconnect ) . disabled = ! active ;
4128 el ( Id . refresh ) . disabled = ! active ;
4229}
30+
4331function stopPolling ( ) {
44- if ( poll !== null ) clearTimeout ( poll ) ;
32+ if ( poll !== null ) clearTimeout ( poll ) ;
4533 poll = null ;
4634}
35+
4736function schedule ( ) {
4837 stopPolling ( ) ;
49- if ( credential && ! document . hidden ) poll = setTimeout ( refresh , Config . pollMs ) ;
38+ if ( credential && ! document . hidden ) poll = setTimeout ( refresh , Config . pollMs ) ;
5039}
40+
5141function cancelPending ( ) {
52- epoch += Config . one ;
42+ epoch += Config . one ;
5343 controller ?. abort ( ) ;
5444 controller = null ;
5545 inFlight = false ;
46+ el ( Id . refresh ) . classList . remove ( Dom . spinning ) ;
5647 schedule ( ) ;
5748}
49+
50+ function clearObservations ( ) {
51+ observed = null ;
52+ resetMetrics ( ) ;
53+ clearOverview ( ) ;
54+ clearPerformance ( ) ;
55+ clearErrors ( ) ;
56+ clearNodes ( ) ;
57+ clearStorage ( ) ;
58+ clearCatalog ( ) ;
59+ write ( Id . captured , Text . awaiting ) ;
60+ el ( Id . dialog ) . close ( ) ;
61+ el ( Id . dialog ) . querySelector ( Dom . pre ) . textContent = Text . empty ;
62+ }
63+
5864function disconnect ( message = Text . disconnectedHint , resetScope = true ) {
5965 cancelPending ( ) ;
6066 credential = Text . empty ;
@@ -70,100 +76,144 @@ function disconnect(message = Text.disconnectedHint, resetScope = true) {
7076 resetBrowsing ( ) ;
7177 status ( message , message === Text . unauthorized ) ;
7278}
79+
80+ function observationFailed ( ) {
81+ resetMetrics ( ) ;
82+ clearOverview ( ) ;
83+ clearPerformance ( ) ;
84+ write ( Id . state , Text . observationUnavailable ) ;
85+ write ( Id . liveText , Text . paused ) ;
86+ write ( Id . captured , Text . stale ) ;
87+ el ( Id . refresh ) . disabled = false ;
88+ status ( Text . failed , true ) ;
89+ logEvent ( Css . bad , Text . eventFailed ) ;
90+ }
91+
92+ async function send ( path , body , signal ) {
93+ const headers = { [ Api . authorization ] : Api . bearer + credential } ;
94+ if ( body !== null ) headers [ Api . contentType ] = Api . json ;
95+ return fetch ( path , {
96+ method : body === null ? Api . get : Api . post ,
97+ headers,
98+ body : body === null ? undefined : JSON . stringify ( body ) ,
99+ credentials : Api . sameOrigin ,
100+ cache : Api . noStore ,
101+ signal
102+ } ) ;
103+ }
104+
73105async function request ( path , body = null ) {
74- if ( ! credential || inFlight ) return null ;
106+ if ( ! credential || inFlight ) return null ;
75107 inFlight = true ;
76108 const started = epoch ;
77109 const current = new AbortController ( ) ;
78110 controller = current ;
79111 stopPolling ( ) ;
112+ el ( Id . refresh ) . classList . add ( Dom . spinning ) ;
80113 try {
81- const headers = {
82- [ Api . authorization ] :Api . bearer + credential
83- } ;
84- if ( body !== null ) headers [ Api . contentType ] = Api . json ;
85- const response = await fetch ( path , {
86- method :body === null ?Api . get :Api . post ,
87- headers,
88- body :body === null ?undefined :JSON . stringify ( body ) ,
89- credentials :Api . sameOrigin ,
90- cache :Api . noStore ,
91- signal :current . signal
92- } ) ;
93- if ( started !== epoch ) return null ;
114+ const response = await send ( path , body , current . signal ) ;
115+ if ( started !== epoch ) return null ;
94116 if ( response . status === Config . unauthenticated || response . status === Config . forbidden ) {
95117 disconnect ( Text . unauthorized ) ;
96118 return null ;
97119 }
98- if ( ! response . ok ) throw new Error ( Text . httpError + response . status + Text . closeParen ) ;
120+ if ( ! response . ok ) throw new Error ( Text . httpError + response . status + Text . closeParen ) ;
99121 const result = await response . json ( ) ;
100- return started === epoch ? result : null ;
122+ return started === epoch ? result : null ;
101123 } catch ( error ) {
102- if ( started !== epoch || error . name === Api . abort ) return null ;
103- resetMetrics ( ) ;
104- write ( Id . state , Text . observationUnavailable ) ;
105- write ( Id . captured , Text . stale ) ;
106- el ( Id . refresh ) . disabled = false ;
107- status ( Text . failed , true ) ;
124+ if ( started !== epoch || error . name === Api . abort ) return null ;
125+ observationFailed ( ) ;
108126 return null ;
109127 } finally {
110128 if ( started === epoch ) {
111129 inFlight = false ;
112130 controller = null ;
131+ el ( Id . refresh ) . classList . remove ( Dom . spinning ) ;
113132 schedule ( ) ;
114133 }
115134 }
116135}
117- async function refresh ( ) {
118- if ( ! credential || inFlight || document . hidden ) return ;
136+
137+ function noteTransitions ( snapshot ) {
138+ if ( ! observed ) logEvent ( Css . good , Text . eventConnected + shortId ( snapshot . node . nodeId ) ) ;
139+ if ( ! snapshot . node . routingReady && observed ?. node . routingReady !== false ) logEvent ( Css . warn , Text . eventRouting ) ;
140+ if ( ! snapshot . storage . complete && observed ?. storage . complete !== false )
141+ logEvent ( Css . warn , Text . eventPartial + ( snapshot . storage . notice ?? Text . partial ) ) ;
142+ observed = snapshot ;
143+ }
144+
145+ function render ( snapshot ) {
146+ if ( measure ( snapshot ) ) logEvent ( Css . warn , Text . eventReset ) ;
147+ noteTransitions ( snapshot ) ;
148+ renderOverview ( snapshot ) ;
149+ renderPerformance ( snapshot ) ;
150+ renderErrors ( snapshot ) ;
151+ renderNodes ( snapshot ) ;
152+ renderStorage ( snapshot ) ;
153+ write ( Id . captured , Text . captured + date ( snapshot . capturedAt ) ) ;
154+ }
155+
156+ /** Automatic polls pause in background tabs; explicit operator actions always refresh. */
157+ async function refresh ( manual = false ) {
158+ if ( ! credential || inFlight || ( ! manual && document . hidden ) ) return ;
119159 status ( Text . loading ) ;
120160 const result = await request ( Api . snapshot ) ;
121- if ( ! result ) return ;
122- renderSnapshot ( result ) ;
161+ if ( ! result ) return ;
123162 connection ( true ) ;
163+ render ( result ) ;
124164 status ( Text . ready ) ;
125- if ( view === View . files && ! hasSelectedResource ( ) ) physicalFiles ( ) ;
126165}
166+
127167async function connect ( event ) {
128168 event . preventDefault ( ) ;
129169 const key = el ( Id . key ) . value . trim ( ) ;
130- if ( ! key ) return ;
170+ if ( ! key ) return ;
131171 disconnect ( Text . disconnectedHint , false ) ;
132172 credential = key ;
133173 el ( Id . key ) . value = Text . empty ;
134174 write ( Id . state , Text . connecting ) ;
135175 el ( Id . disconnect ) . disabled = false ;
136- await refresh ( ) ;
176+ await refresh ( true ) ;
137177}
138- async function navigate ( next ) {
178+
179+ async function navigate ( next , openName = null ) {
139180 cancelPending ( ) ;
140181 view = next ;
141182 selectView ( view ) ;
142- viewChanged ( view ) ;
183+ viewChanged ( view , openName ) ;
184+ errorsVisible ( view === View . errors ) ;
143185 schedule ( ) ;
144- if ( credential && view !== View . overview && view !== View . cluster ) await loadResources ( ) ;
186+ if ( ! credential ) return ;
187+ if ( view === View . catalog ) await loadCatalog ( ) ;
188+ else if ( BrowseViews . includes ( view ) ) await loadResources ( ) ;
145189}
190+
146191function wireBrowsing ( ) {
147192 el ( Id . scope ) . addEventListener ( Dom . submit , event => {
148193 event . preventDefault ( ) ;
149- loadResources ( ) ;
194+ if ( view === View . catalog ) loadCatalog ( ) ;
195+ else loadResources ( ) ;
150196 } ) ;
151197 el ( Id . resourcesFirst ) . addEventListener ( Dom . click , ( ) => loadResources ( ) ) ;
152198 el ( Id . resourcesNext ) . addEventListener ( Dom . click , ( ) => loadResources ( true ) ) ;
153199 el ( Id . dataFirst ) . addEventListener ( Dom . click , ( ) => loadData ( ) ) ;
154200 el ( Id . dataNext ) . addEventListener ( Dom . click , ( ) => loadData ( true ) ) ;
155201}
202+
156203function start ( ) {
157204 initializeBrowsing ( request , cancelPending ) ;
205+ initializeCatalog ( request , cancelPending , navigate ) ;
206+ initializeNavigation ( navigate ) ;
207+ clearObservations ( ) ;
158208 el ( Id . form ) . addEventListener ( Dom . submit , connect ) ;
159209 el ( Id . disconnect ) . addEventListener ( Dom . click , ( ) => disconnect ( ) ) ;
160- el ( Id . refresh ) . addEventListener ( Dom . click , refresh ) ;
161- document . querySelectorAll ( Dom . nav ) . forEach ( button => button . addEventListener ( Dom . click , ( ) => navigate ( button . dataset . view ) ) ) ;
210+ el ( Id . refresh ) . addEventListener ( Dom . click , ( ) => refresh ( true ) ) ;
162211 el ( Id . closeDetails ) . addEventListener ( Dom . click , ( ) => el ( Id . dialog ) . close ( ) ) ;
163212 document . addEventListener ( Dom . visibility , ( ) => {
164- if ( document . hidden ) stopPolling ( ) ;
213+ if ( document . hidden ) stopPolling ( ) ;
165214 else refresh ( ) ;
166215 } ) ;
167216 wireBrowsing ( ) ;
168217}
218+
169219start ( ) ;
0 commit comments