@@ -28,6 +28,17 @@ import type { Account, BackupSnapshot, Guest } from './types';
2828
2929const SECTION_KEYS : Section [ ] = [ 'overview' , 'server' , 'network' , 'backup' , 'cloudflare' , 'settings' ] ;
3030
31+ // Human-readable section names for the tabpanel's aria-label.
32+ const SECTION_LABELS : Record < Section , string > = {
33+ overview : 'Übersicht' ,
34+ server : 'Server' ,
35+ network : 'Netzwerk' ,
36+ backup : 'Backup' ,
37+ cloudflare : 'Cloudflare' ,
38+ admin : 'Konten' ,
39+ settings : 'Einstellungen' ,
40+ } ;
41+
3142export function App ( ) {
3243 const auth = useAuth ( ) ;
3344
@@ -54,6 +65,7 @@ function Dashboard({ user, onLogout }: DashboardProps) {
5465 const [ selectedSvc , setSelectedSvc ] = useState < string | null > ( null ) ;
5566 const [ logsGuest , setLogsGuest ] = useState < Guest | null > ( null ) ;
5667 const [ confirmGuest , setConfirmGuest ] = useState < Guest | null > ( null ) ;
68+ const [ verifyTarget , setVerifyTarget ] = useState < BackupSnapshot | null > ( null ) ;
5769 const [ toasts , setToasts ] = useState < Toast [ ] > ( [ ] ) ;
5870 const [ paletteOpen , setPaletteOpen ] = useState ( false ) ;
5971 const [ helpOpen , setHelpOpen ] = useState ( false ) ;
@@ -191,22 +203,19 @@ function Dashboard({ user, onLogout }: DashboardProps) {
191203 }
192204 } ;
193205
194- const onVerifyBackup = useCallback (
195- async ( snap : BackupSnapshot ) => {
196- const ok = window . confirm (
197- `Verifikation für ${ snap . target } (${ new Date ( snap . backup_time * 1000 ) . toLocaleString ( ) } ) starten?` ,
198- ) ;
199- if ( ! ok ) return ;
200- try {
201- const r = await api . verifyBackup ( snap . backup_type , snap . backup_id , snap . backup_time ) ;
202- pushToast ( { level : 'ok' , title : `Verify gestartet · ${ snap . target } ` , body : `UPID: ${ r . upid . slice ( 0 , 32 ) } …` } ) ;
203- setTimeout ( bkp . refresh , 2000 ) ;
204- } catch ( e ) {
205- pushToast ( { level : 'err' , title : 'Verify fehlgeschlagen' , body : ( e as Error ) . message } ) ;
206- }
207- } ,
208- [ pushToast , bkp ] ,
209- ) ;
206+ // Backup verification routes through the ConfirmModal (no native window.confirm).
207+ const confirmVerify = useCallback ( async ( ) => {
208+ const snap = verifyTarget ;
209+ if ( ! snap ) return ;
210+ setVerifyTarget ( null ) ;
211+ try {
212+ const r = await api . verifyBackup ( snap . backup_type , snap . backup_id , snap . backup_time ) ;
213+ pushToast ( { level : 'ok' , title : `Verify gestartet · ${ snap . target } ` , body : `UPID: ${ r . upid . slice ( 0 , 32 ) } …` } ) ;
214+ setTimeout ( bkp . refresh , 2000 ) ;
215+ } catch ( e ) {
216+ pushToast ( { level : 'err' , title : 'Verify fehlgeschlagen' , body : ( e as Error ) . message } ) ;
217+ }
218+ } , [ verifyTarget , pushToast , bkp ] ) ;
210219
211220 const onSnapshot = useCallback ( async ( ) => {
212221 const snapshot = {
@@ -234,15 +243,28 @@ function Dashboard({ user, onLogout }: DashboardProps) {
234243 useEffect ( ( ) => {
235244 if ( errSig === lastErrSig . current ) return ;
236245 lastErrSig . current = errSig ;
237- const errs : { name : string ; err : Error | null } [ ] = [
246+ const failed = [
238247 { name : 'System' , err : sys . error } ,
239248 { name : 'Services' , err : svc . error } ,
240249 { name : 'Tunnel' , err : tun . error } ,
241250 { name : 'Backups' , err : bkp . error } ,
242251 { name : 'Netzwerk' , err : net . error } ,
243- ] ;
244- for ( const { name, err } of errs ) {
245- if ( err ) pushToast ( { level : 'err' , title : `${ name } -API Fehler` , body : err . message } ) ;
252+ ] . filter ( ( e ) => e . err ) ;
253+ if ( failed . length === 0 ) return ;
254+ // Bundle simultaneous failures into a single toast instead of flooding
255+ // the user with one per endpoint.
256+ if ( failed . length === 1 ) {
257+ pushToast ( {
258+ level : 'err' ,
259+ title : `${ failed [ 0 ] . name } -API Fehler` ,
260+ body : failed [ 0 ] . err ! . message ,
261+ } ) ;
262+ } else {
263+ pushToast ( {
264+ level : 'err' ,
265+ title : `${ failed . length } API-Endpunkte nicht erreichbar` ,
266+ body : failed . map ( ( e ) => e . name ) . join ( ', ' ) ,
267+ } ) ;
246268 }
247269 } , [ errSig , sys . error , svc . error , tun . error , bkp . error , net . error , pushToast ] ) ;
248270
@@ -297,22 +319,23 @@ function Dashboard({ user, onLogout }: DashboardProps) {
297319 < button className = "btn" type = "button" onClick = { ( ) => setPaused ( false ) } > Fortsetzen</ button >
298320 </ div >
299321 ) }
300- < main className = "dash-main" id = { `section-${ section } ` } role = "tabpanel" aria-label = { section } >
322+ < main className = "dash-main" id = { `section-${ section } ` } role = "tabpanel" aria-label = { SECTION_LABELS [ section ] } >
301323 { section === 'overview' && (
302324 < >
303325 < AttentionHero
304326 guests = { guests }
305327 services = { services }
306328 certs = { cer . data ?. certs ?? [ ] }
307329 backups = { bkp . data }
330+ tunnel = { tun . data }
308331 certWarnDays = { ui . certWarnDays }
309332 onInspectService = { setSelectedSvc }
310333 onInspectGuest = { onInspectGuest }
311334 />
312335 < HostPanel host = { sys . data ?. host ?? null } guests = { guests } />
313336 < div className = "quick-stats" >
314337 < KpiStrip guests = { guests } services = { services } />
315- < AuditLog />
338+ < AuditLog pollMs = { pollFast } />
316339 </ div >
317340 < ServiceGrid
318341 services = { services }
@@ -327,7 +350,7 @@ function Dashboard({ user, onLogout }: DashboardProps) {
327350 < HostPanel host = { sys . data ?. host ?? null } guests = { guests } />
328351 < div className = "quick-stats" >
329352 < KpiStrip guests = { guests } services = { services } />
330- < AuditLog />
353+ < AuditLog pollMs = { pollFast } />
331354 </ div >
332355 < VMTable guests = { guests } onLogs = { onLogs } onRestart = { onRestart } />
333356 < ServiceGrid
@@ -338,12 +361,14 @@ function Dashboard({ user, onLogout }: DashboardProps) {
338361 />
339362 </ >
340363 ) }
341- { section === 'network' && < NetworkPanel network = { net . data } tunnel = { tun . data } /> }
364+ { section === 'network' && (
365+ < NetworkPanel network = { net . data } tunnel = { tun . data } pollMs = { pollFast } />
366+ ) }
342367 { section === 'backup' && (
343368 < BackupsSection
344369 backups = { bkp . data }
345370 guests = { guests }
346- onVerify = { onVerifyBackup }
371+ onVerify = { setVerifyTarget }
347372 onOpenGuest = { onLogs }
348373 />
349374 ) }
@@ -354,6 +379,7 @@ function Dashboard({ user, onLogout }: DashboardProps) {
354379 services = { services }
355380 zoneName = "rxf-sys.de"
356381 onSelectService = { setSelectedSvc }
382+ pollMs = { pollCerts }
357383 />
358384 ) }
359385 { section === 'admin' && isAdmin && (
@@ -392,10 +418,40 @@ function Dashboard({ user, onLogout }: DashboardProps) {
392418 />
393419 < ConfirmModal
394420 open = { ! ! confirmGuest }
395- guest = { confirmGuest }
421+ title = "Container neu starten?"
422+ message = {
423+ confirmGuest && (
424+ < >
425+ < p style = { { margin : '0 0 8px' } } >
426+ < strong > { confirmGuest . name } </ strong > ({ confirmGuest . type } { confirmGuest . id } ) wird neu gestartet.
427+ </ p >
428+ < p style = { { margin : 0 , fontSize : 12 , color : 'var(--text-3)' } } >
429+ Die zugehörigen Services sind dabei ca. 30–60 Sekunden nicht erreichbar.
430+ </ p >
431+ </ >
432+ )
433+ }
434+ confirmLabel = "Restart"
435+ danger
396436 onConfirm = { confirmRestart }
397437 onCancel = { ( ) => setConfirmGuest ( null ) }
398438 />
439+ < ConfirmModal
440+ open = { ! ! verifyTarget }
441+ title = "Backup verifizieren?"
442+ message = {
443+ verifyTarget && (
444+ < p style = { { margin : 0 } } >
445+ Verifikation für < strong > { verifyTarget . target } </ strong > (
446+ { new Date ( verifyTarget . backup_time * 1000 ) . toLocaleString ( ) } ) starten? Der Job läuft
447+ asynchron auf dem PBS.
448+ </ p >
449+ )
450+ }
451+ confirmLabel = "Verify starten"
452+ onConfirm = { confirmVerify }
453+ onCancel = { ( ) => setVerifyTarget ( null ) }
454+ />
399455 < CommandPalette
400456 open = { paletteOpen }
401457 onClose = { ( ) => setPaletteOpen ( false ) }
0 commit comments