diff --git a/apps/web/src/hooks/use-instance-actions.ts b/apps/web/src/hooks/use-instance-actions.ts index fb09730..995eb98 100644 --- a/apps/web/src/hooks/use-instance-actions.ts +++ b/apps/web/src/hooks/use-instance-actions.ts @@ -1,165 +1,187 @@ import { useState, useCallback } from "react"; import { - stopInstance, - deleteInstance, - startInstance, - recreateInstanceTunnel, + stopInstance, + deleteInstance, + startInstance, + recreateInstanceTunnel, } from "../api/sessions"; import type { Session } from "../api/sessions"; interface UseInstanceActionsOptions { - onRefresh: () => Promise; + onRefresh: () => Promise; } interface UseInstanceActionsReturn { - loadingSessionId: string | null; - dirtyDeleteSession: Session | null; - dirtyDeleteFiles: string[]; - handleOpen: (session: Session) => void; - handleStart: (session: Session) => Promise; - handleStop: (session: Session) => Promise; - handleDelete: (session: Session) => Promise; - handleForceDelete: (session: Session) => Promise; - handleRecreateTunnel: (session: Session) => Promise; - clearDirtyDelete: () => void; + loadingSessionId: string | null; + dirtyDeleteSession: Session | null; + dirtyDeleteFiles: string[]; + handleOpen: (session: Session) => void; + handleStart: (session: Session) => Promise; + handleStop: (session: Session) => Promise; + handleDelete: (session: Session) => Promise; + handleForceDelete: (session: Session) => Promise; + handleRecreateTunnel: (session: Session) => Promise; + clearDirtyDelete: () => void; } export function useInstanceActions( - options: UseInstanceActionsOptions + options: UseInstanceActionsOptions, ): UseInstanceActionsReturn { - const { onRefresh } = options; - const [loadingSessionId, setLoadingSessionId] = useState(null); - const [dirtyDeleteSession, setDirtyDeleteSession] = useState(null); - const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState([]); + const { onRefresh } = options; + const [loadingSessionId, setLoadingSessionId] = useState(null); + const [dirtyDeleteSession, setDirtyDeleteSession] = useState( + null, + ); + const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState([]); - const handleOpen = useCallback((session: Session) => { - if (session.url) { - window.open(session.url, "_blank", "noopener,noreferrer"); - return; - } - if (session.tool_type_interfaces?.includes("terminal")) { - window.location.href = `/instances/${session.id}/terminal`; - return; - } - window.location.href = `/projects/${session.project_id}`; - }, []); + const handleOpen = useCallback((session: Session) => { + if (session.url) { + window.open(session.url, "_blank", "noopener,noreferrer"); + return; + } + if (session.tool_type_interfaces?.includes("terminal")) { + window.location.href = `/instances/${session.id}/terminal`; + return; + } + window.location.href = `/projects/${session.project_id}`; + }, []); - const handleStart = useCallback( - async (session: Session) => { - if (loadingSessionId === session.id) return; - setLoadingSessionId(session.id); - try { - await startInstance(session.project_id, session.repository_id, session.id); - await onRefresh(); - } catch { - // ignore - } finally { - setLoadingSessionId(null); - } - }, - [loadingSessionId, onRefresh] - ); + const handleStart = useCallback( + async (session: Session) => { + if (loadingSessionId === session.id) return; + setLoadingSessionId(session.id); + try { + await startInstance( + session.project_id, + session.repository_id, + session.id, + ); + await onRefresh(); + } catch { + // ignore + } finally { + setLoadingSessionId(null); + } + }, + [loadingSessionId, onRefresh], + ); - const handleStop = useCallback( - async (session: Session) => { - if (loadingSessionId === session.id) return; - setLoadingSessionId(session.id); - try { - await stopInstance(session.project_id, session.repository_id, session.id); - await onRefresh(); - } catch { - // ignore - } finally { - setLoadingSessionId(null); - } - }, - [loadingSessionId, onRefresh] - ); + const handleStop = useCallback( + async (session: Session) => { + if (loadingSessionId === session.id) return; + setLoadingSessionId(session.id); + try { + await stopInstance( + session.project_id, + session.repository_id, + session.id, + ); + await onRefresh(); + } catch { + // ignore + } finally { + setLoadingSessionId(null); + } + }, + [loadingSessionId, onRefresh], + ); - const handleDelete = useCallback( - async (session: Session) => { - if (loadingSessionId === session.id) return; - setLoadingSessionId(session.id); - try { - await deleteInstance(session.project_id, session.repository_id, session.id); - setDirtyDeleteSession(null); - setDirtyDeleteFiles([]); - await onRefresh(); - } catch (error) { - const axiosError = error as { - response?: { status?: number; data?: { detail?: { changed_files?: string[] } } }; - }; - if (axiosError.response?.status === 409) { - const detail = axiosError.response.data?.detail; - if (detail?.changed_files) { - setDirtyDeleteSession(session); - setDirtyDeleteFiles(detail.changed_files); - return; - } - } - } finally { - setLoadingSessionId(null); - } - }, - [loadingSessionId, onRefresh] - ); + const handleDelete = useCallback( + async (session: Session) => { + if (loadingSessionId === session.id) return; + setLoadingSessionId(session.id); + try { + await deleteInstance( + session.project_id, + session.repository_id, + session.id, + ); + setDirtyDeleteSession(null); + setDirtyDeleteFiles([]); + await onRefresh(); + } catch (error) { + const axiosError = error as { + response?: { + status?: number; + data?: { detail?: { changed_files?: string[] } }; + }; + }; + if (axiosError.response?.status === 409) { + const detail = axiosError.response.data?.detail; + if (detail?.changed_files) { + setDirtyDeleteSession(session); + setDirtyDeleteFiles(detail.changed_files); + return; + } + } + } finally { + setLoadingSessionId(null); + } + }, + [loadingSessionId, onRefresh], + ); - const handleForceDelete = useCallback( - async (session: Session) => { - if (loadingSessionId === session.id) return; - setLoadingSessionId(session.id); - try { - await deleteInstance(session.project_id, session.repository_id, session.id, true); - setDirtyDeleteSession(null); - setDirtyDeleteFiles([]); - await onRefresh(); - } catch { - // ignore - } finally { - setLoadingSessionId(null); - } - }, - [loadingSessionId, onRefresh] - ); + const handleForceDelete = useCallback( + async (session: Session) => { + if (loadingSessionId === session.id) return; + setLoadingSessionId(session.id); + try { + await deleteInstance( + session.project_id, + session.repository_id, + session.id, + true, + ); + setDirtyDeleteSession(null); + setDirtyDeleteFiles([]); + await onRefresh(); + } catch { + // ignore + } finally { + setLoadingSessionId(null); + } + }, + [loadingSessionId, onRefresh], + ); - const handleRecreateTunnel = useCallback( - async (session: Session) => { - if (loadingSessionId === session.id) return; - setLoadingSessionId(session.id); - try { - await recreateInstanceTunnel( - session.project_id, - session.repository_id, - session.id - ); - await onRefresh(); - } catch (err) { - const message = - (err as { response?: { data?: { detail?: string } } })?.response?.data - ?.detail || "Failed to recreate tunnel"; - alert(message); - } finally { - setLoadingSessionId(null); - } - }, - [loadingSessionId, onRefresh] - ); + const handleRecreateTunnel = useCallback( + async (session: Session) => { + if (loadingSessionId === session.id) return; + setLoadingSessionId(session.id); + try { + await recreateInstanceTunnel( + session.project_id, + session.repository_id, + session.id, + ); + await onRefresh(); + } catch (err) { + const message = + (err as { response?: { data?: { detail?: string } } })?.response?.data + ?.detail || "Failed to recreate tunnel"; + alert(message); + } finally { + setLoadingSessionId(null); + } + }, + [loadingSessionId, onRefresh], + ); - const clearDirtyDelete = useCallback(() => { - setDirtyDeleteSession(null); - setDirtyDeleteFiles([]); - }, []); + const clearDirtyDelete = useCallback(() => { + setDirtyDeleteSession(null); + setDirtyDeleteFiles([]); + }, []); - return { - loadingSessionId, - dirtyDeleteSession, - dirtyDeleteFiles, - handleOpen, - handleStart, - handleStop, - handleDelete, - handleForceDelete, - handleRecreateTunnel, - clearDirtyDelete, - }; + return { + loadingSessionId, + dirtyDeleteSession, + dirtyDeleteFiles, + handleOpen, + handleStart, + handleStop, + handleDelete, + handleForceDelete, + handleRecreateTunnel, + clearDirtyDelete, + }; } diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 67e446c..f9375e1 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -92,7 +92,7 @@ services: AUTHENTIK_AUTHORIZE_URL: ${AUTHENTIK_AUTHORIZE_URL:-} AUTHENTIK_TOKEN_URL: ${AUTHENTIK_TOKEN_URL:-} volumes: - - repo_data:/data/repos + - /data/repos:/data/repos - /data/instances:/data/instances - avatar_uploads:/app/uploads - /var/run/docker.sock:/var/run/docker.sock @@ -116,7 +116,6 @@ services: volumes: postgres_data: redis_data: - repo_data: avatar_uploads: networks: diff --git a/docker-compose.yml b/docker-compose.yml index 1fe29ae..67bfff3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,7 +57,7 @@ services: REPO_BASE_PATH: /data/repos INSTANCE_BASE_PATH: /data/instances volumes: - - repo_data:/data/repos + - /data/repos:/data/repos - /data/instances:/data/instances ports: - "8000:8000" @@ -91,7 +91,6 @@ services: volumes: postgres_data: redis_data: - repo_data: networks: backend: