@@ -14,6 +14,7 @@ import { SET_DOCKER_FOLDER_CHILDREN } from '@/components/Docker/docker-set-folde
1414import { START_DOCKER_CONTAINER } from ' @/components/Docker/docker-start-container.mutation' ;
1515import { STOP_DOCKER_CONTAINER } from ' @/components/Docker/docker-stop-container.mutation' ;
1616import { UNPAUSE_DOCKER_CONTAINER } from ' @/components/Docker/docker-unpause-container.mutation' ;
17+ import { UPDATE_DOCKER_CONTAINER } from ' @/components/Docker/docker-update-container.mutation' ;
1718import { ContainerState } from ' @/composables/gql/graphql' ;
1819import { useContainerActions } from ' @/composables/useContainerActions' ;
1920import { useDockerEditNavigation } from ' @/composables/useDockerEditNavigation' ;
@@ -52,6 +53,7 @@ const UDropdownMenu = resolveComponent('UDropdownMenu');
5253const UModal = resolveComponent (' UModal' );
5354const USkeleton = resolveComponent (' USkeleton' ) as Component ;
5455const UIcon = resolveComponent (' UIcon' );
56+ const UPopover = resolveComponent (' UPopover' );
5557const rowActionDropdownUi = {
5658 content: ' overflow-x-hidden z-50' ,
5759 item: ' bg-transparent hover:bg-transparent focus:bg-transparent border-0 ring-0 outline-none shadow-none data-[state=checked]:bg-transparent' ,
@@ -288,10 +290,79 @@ const columns = computed<TableColumn<TreeRow<DockerContainer>>[]>(() => {
288290 class: ' w-5 h-5 mr-2 flex-shrink-0 text-gray-500' ,
289291 });
290292
293+ const hasUpdate =
294+ row .original .type === ' container' &&
295+ (row .original .meta ?.isUpdateAvailable || row .original .meta ?.isRebuildReady );
296+
297+ const updateBadge = hasUpdate
298+ ? h (
299+ UPopover ,
300+ {
301+ ' data-stop-row-click' : ' true' ,
302+ },
303+ {
304+ default : () =>
305+ h (
306+ UBadge ,
307+ {
308+ color: ' warning' ,
309+ variant: ' subtle' ,
310+ size: ' sm' ,
311+ class: ' ml-2 cursor-pointer' ,
312+ ' data-stop-row-click' : ' true' ,
313+ },
314+ () => ' Update'
315+ ),
316+ content : () =>
317+ h (' div' , { class: ' min-w-[280px] max-w-sm p-4' }, [
318+ h (' div' , { class: ' space-y-3' }, [
319+ h (' div' , { class: ' space-y-1.5' }, [
320+ h (' h4' , { class: ' font-semibold text-sm' }, ' Update Container' ),
321+ h (' p' , { class: ' text-sm text-gray-600 dark:text-gray-400' }, row .original .name ),
322+ ]),
323+ h (
324+ ' p' ,
325+ { class: ' text-sm text-gray-700 dark:text-gray-300' },
326+ row .original .meta ?.isUpdateAvailable
327+ ? ' A new image version is available. Would you like to update this container?'
328+ : ' The container configuration has changed. Would you like to rebuild this container?'
329+ ),
330+ h (' div' , { class: ' flex gap-2 justify-end pt-1' }, [
331+ h (
332+ UButton ,
333+ {
334+ color: ' neutral' ,
335+ variant: ' outline' ,
336+ size: ' sm' ,
337+ onClick : (e : Event ) => {
338+ e .stopPropagation ();
339+ },
340+ },
341+ () => ' Cancel'
342+ ),
343+ h (
344+ UButton ,
345+ {
346+ size: ' sm' ,
347+ onClick : async (e : Event ) => {
348+ e .stopPropagation ();
349+ await handleUpdateContainer (row .original as TreeRow <DockerContainer >);
350+ },
351+ },
352+ () => ' Update'
353+ ),
354+ ]),
355+ ]),
356+ ]),
357+ }
358+ )
359+ : null ;
360+
291361 return h (' div' , { class: ' truncate flex items-center' , ' data-row-id' : row .original .id }, [
292362 indent ,
293363 iconElement ,
294364 h (' span' , { class: ' max-w-[40ch] truncate font-medium' }, row .original .name ),
365+ updateBadge ,
295366 ]);
296367 },
297368 meta: { class: { td: ' w-[40ch] truncate' , th: ' w-[45ch]' } },
@@ -359,12 +430,6 @@ const columns = computed<TableColumn<TreeRow<DockerContainer>>[]>(() => {
359430 cell : ({ row }) =>
360431 row .original .type === ' folder' ? ' ' : h (' span' , null , String (row .getValue (' autoStart' ) || ' ' )),
361432 },
362- {
363- accessorKey: ' updates' ,
364- header: ' Updates' ,
365- cell : ({ row }) =>
366- row .original .type === ' folder' ? ' ' : h (' span' , null , String (row .getValue (' updates' ) || ' ' )),
367- },
368433 {
369434 accessorKey: ' uptime' ,
370435 header: ' Uptime' ,
@@ -419,7 +484,6 @@ function applyDefaultColumnVisibility(isCompact: boolean) {
419484 lanPort: false ,
420485 volumes: false ,
421486 autoStart: false ,
422- updates: false ,
423487 uptime: false ,
424488 actions: false ,
425489 };
@@ -433,7 +497,6 @@ function applyDefaultColumnVisibility(isCompact: boolean) {
433497 lanPort: true ,
434498 volumes: false ,
435499 autoStart: true ,
436- updates: true ,
437500 uptime: false ,
438501 };
439502 }
@@ -511,6 +574,7 @@ const { mutate: startContainerMutation } = useMutation(START_DOCKER_CONTAINER);
511574const { mutate : stopContainerMutation } = useMutation (STOP_DOCKER_CONTAINER );
512575const { mutate : pauseContainerMutation } = useMutation (PAUSE_DOCKER_CONTAINER );
513576const { mutate : unpauseContainerMutation } = useMutation (UNPAUSE_DOCKER_CONTAINER );
577+ const { mutate : updateContainerMutation } = useMutation (UPDATE_DOCKER_CONTAINER );
514578
515579declare global {
516580 interface Window {
@@ -525,6 +589,29 @@ function showToast(message: string) {
525589 window .toast ?.success (message );
526590}
527591
592+ async function handleUpdateContainer(row : TreeRow <DockerContainer >) {
593+ if (! row .containerId ) return ;
594+
595+ setRowsBusy ([row .id ], true );
596+
597+ try {
598+ await updateContainerMutation (
599+ { id: row .containerId },
600+ {
601+ refetchQueries: [{ query: GET_DOCKER_CONTAINERS , variables: { skipCache: true } }],
602+ awaitRefetchQueries: true ,
603+ }
604+ );
605+ showToast (` Successfully updated ${row .name } ` );
606+ } catch (error ) {
607+ window .toast ?.error ?.(` Failed to update ${row .name } ` , {
608+ description: error instanceof Error ? error .message : ' Unknown error' ,
609+ });
610+ } finally {
611+ setRowsBusy ([row .id ], false );
612+ }
613+ }
614+
528615const folderOps = useFolderOperations ({
529616 rootFolderId ,
530617 folderChildrenIds ,
0 commit comments