1818. group-row + . child-row td { padding-top : 2px ; }
1919. child-row td : first-child { padding-left : 28px ; }
2020. child-row : last-child td { border-bottom : 1px solid # 334155; }
21- . badge { display : inline-block ; padding : 2 px 8 px ; border-radius : 999px ; font-size : .75 rem ; font-weight : 600 ; }
21+ . badge { display : inline-flex ; align-items : center ; gap : 4 px ; padding : 4 px 12 px ; border-radius : 999px ; font-size : .85 rem ; font-weight : 600 ; white-space : nowrap ; }
2222. badge-uptodate { background : # 064e3b; color : # 6ee7b7; }
2323. badge-outdated { background : # 7c2d12; color : # fdba74; }
2424. badge-partial { background : # 713f12; color : # fcd34d; }
6161</ style >
6262</ head >
6363< body >
64- < h1 > 🐳 < span id ="appTitle "> Docker Image Watcher</ span > < small id ="status "> </ small > < span style ="margin-left:auto;display:flex;align-items:center;gap:8px "> < a href ="# " id ="langSwitch " style ="color:#64748b;text-decoration:none;font-size:.75rem " onclick ="switchLang();return false "> IT</ a > < a href ="# " id ="logoutLink " style ="color:#ef4444;text-decoration:none;font-size:.75rem " onclick ="doLogout();return false "> Logout</ a > < label style ="font-size:.8rem;font-weight:400;display:flex;align-items:center;gap:6px;cursor:pointer "> < span id ="hideLabel "> Hide up to date</ span > < div class ="toggle on " id ="hideToggle "> </ div > </ label > </ span > </ h1 >
64+ < h1 > 🐳 < span id ="appTitle "> Docker Image Watcher</ span > < small id ="status "> </ small > < span style ="margin-left:auto;display:flex;align-items:center;gap:8px "> < button id =" updateAllBtn " class =" btn btn-group " onclick =" doGlobalUpdate() " hidden > </ button > < a href ="# " id ="langSwitch " style ="color:#64748b;text-decoration:none;font-size:.75rem " onclick ="switchLang();return false "> IT</ a > < a href ="# " id ="logoutLink " style ="color:#ef4444;text-decoration:none;font-size:.75rem " onclick ="doLogout();return false "> Logout</ a > < label style ="font-size:.8rem;font-weight:400;display:flex;align-items:center;gap:6px;cursor:pointer "> < span id ="hideLabel "> Hide up to date</ span > < div class ="toggle on " id ="hideToggle "> </ div > </ label > </ span > </ h1 >
6565< div class ="warning-banner " id ="rateLimitBanner " hidden >
6666 < div >
6767 < strong id ="rateLimitTitle "> </ strong >
@@ -134,7 +134,7 @@ <h1>🐳 <span id="appTitle">Docker Image Watcher</span> <small id="status"></sm
134134 empty : 'Nessun container in esecuzione' ,
135135 emptyHint : 'Assicurati che il socket Docker sia montato e che i container siano attivi' ,
136136 uptodate : '✅ Aggiornato' ,
137- outdated : '🔄 Aggiornamento disp. ' ,
137+ outdated : '🔄 Da aggiornare ' ,
138138 partial : '⚠️ Parziale' ,
139139 unknown : '❓ Sconosciuto' ,
140140 update : 'Aggiorna' ,
@@ -180,7 +180,8 @@ <h1>🐳 <span id="appTitle">Docker Image Watcher</span> <small id="status"></sm
180180}
181181
182182function renderRateLimit ( ) {
183- document . getElementById ( 'rateLimitBanner' ) . hidden = ! rateLimited || rateLimitDismissed ;
183+ const banner = document . getElementById ( 'rateLimitBanner' ) ;
184+ banner . style . display = ( ! rateLimited || rateLimitDismissed ) ? 'none' : 'flex' ;
184185}
185186
186187async function checkRateLimit ( ) {
@@ -220,6 +221,10 @@ <h1>🐳 <span id="appTitle">Docker Image Watcher</span> <small id="status"></sm
220221function render ( groups ) {
221222 const tbody = document . getElementById ( 'rows' ) ;
222223 const counts = { uptodate : 0 , outdated : 0 , unknown : 0 , partial : 0 } ;
224+ const totalOutdated = groups . reduce ( ( sum , g ) => sum + g . containers . filter ( c => c . status === 'outdated' ) . length , 0 ) ;
225+ const updateAllBtn = document . getElementById ( 'updateAllBtn' ) ;
226+ updateAllBtn . hidden = totalOutdated === 0 ;
227+ updateAllBtn . textContent = t . updateAll ( totalOutdated ) ;
223228 let html = '' ;
224229 if ( groups . length === 0 ) {
225230 html = '<tr><td colspan="5"><div class="empty">' + esc ( t . empty ) + '<br><p>' + esc ( t . emptyHint ) + '</p></div></td></tr>' ;
@@ -238,7 +243,7 @@ <h1>🐳 <span id="appTitle">Docker Image Watcher</span> <small id="status"></sm
238243 <td class="digest">${ esc ( g . remote_digest ) } </td>
239244 <td><span class="badge ${ gBadgeClass } ">${ gBadgeLabel } </span></td>
240245 <td></td>
241- <td>${ outdated > 0 ? `<button class="btn btn-group" data-image="${ esc ( g . image ) } " data-cids="${ esc ( outdatedCids . join ( ',' ) ) } " onclick="doGroupUpdate(this)">${ esc ( t . updateAll ( total ) ) } </button>` : '' } </td>
246+ <td>${ outdated > 0 && total > 1 ? `<button class="btn btn-group" data-image="${ esc ( g . image ) } " data-cids="${ esc ( outdatedCids . join ( ',' ) ) } " onclick="doGroupUpdate(this)">${ esc ( t . updateAll ( total ) ) } </button>` : '' } </td>
242247 </tr>` ;
243248 for ( const c of g . containers ) {
244249 if ( ! showAll && c . status === 'uptodate' ) continue ;
@@ -311,6 +316,28 @@ <h1>🐳 <span id="appTitle">Docker Image Watcher</span> <small id="status"></sm
311316 }
312317}
313318
319+ async function doGlobalUpdate ( ) {
320+ const btn = document . getElementById ( 'updateAllBtn' ) ;
321+ btn . disabled = true ;
322+ btn . textContent = t . updatingLabel ;
323+ try {
324+ const res = await fetch ( '/api/images' ) ;
325+ const groups = await res . json ( ) ;
326+ for ( const g of groups ) {
327+ const outdated = g . containers . filter ( c => c . status === 'outdated' ) ;
328+ if ( outdated . length === 0 ) continue ;
329+ await fetch ( '/api/groups/update' , {
330+ method : 'POST' ,
331+ headers : { 'Content-Type' : 'application/json' } ,
332+ body : JSON . stringify ( { image : g . image } )
333+ } ) ;
334+ }
335+ setTimeout ( load , 2000 ) ;
336+ } catch ( e ) {
337+ btn . textContent = t . errorLabel ;
338+ }
339+ }
340+
314341async function pollProgress ( cid ) {
315342 const cell = document . getElementById ( 'progress-' + cid ) ;
316343 if ( ! cell ) return ;
0 commit comments