diff --git a/client/src/apps/Containers/backend/api.ts b/client/src/apps/Containers/backend/api.ts index bc3b3cff..8441497f 100644 --- a/client/src/apps/Containers/backend/api.ts +++ b/client/src/apps/Containers/backend/api.ts @@ -4,6 +4,7 @@ import { ContainerFilters, Containers, EnvVariables, + Port, Tags, } from "./models"; import { DockerContainerInfo } from "../../../models/docker"; @@ -82,6 +83,10 @@ const getContainerPorts = async (id: string) => { return data; }; +const saveContainerPorts = (id: string, ports: Port[]) => { + return server.patch(`/containers/${id}/ports`, { ports }); +}; + const getDocker = async (id: string) => { const { data } = await server.get( `/containers/${id}/docker` @@ -117,6 +122,7 @@ export const API = { getContainerEnvironment, saveEnv, getContainerPorts, + saveContainerPorts, getDockerInfo: getDocker, recreateDocker, updateService, diff --git a/client/src/apps/Containers/backend/models.ts b/client/src/apps/Containers/backend/models.ts index 943ed373..394dd154 100644 --- a/client/src/apps/Containers/backend/models.ts +++ b/client/src/apps/Containers/backend/models.ts @@ -32,6 +32,13 @@ export type EnvVariable = { secret: boolean; }; +export type Port = { + id: string; + container_id: string; + in: string; + out: string; +}; + export type Containers = Container[]; export type Container = { id: string; diff --git a/client/src/apps/Containers/hooks/useContainer.tsx b/client/src/apps/Containers/hooks/useContainer.tsx index 542285b6..8ef70dae 100644 --- a/client/src/apps/Containers/hooks/useContainer.tsx +++ b/client/src/apps/Containers/hooks/useContainer.tsx @@ -1,5 +1,11 @@ -import { useQuery } from "@tanstack/react-query"; +import { + useMutation, + UseMutationOptions, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import { API } from "../backend/api"; +import { Port } from "../backend/models"; export default function useContainer(id?: string) { const queryContainer = useQuery({ @@ -48,3 +54,21 @@ export function useContainerPorts(id?: string) { errorPorts: queryPorts.error, }; } + +export function useSaveContainerPorts( + id?: string, + options?: UseMutationOptions +) { + const queryClient = useQueryClient(); + const { mutate: savePorts, ...rest } = useMutation({ + ...options, + mutationFn: (ports: Port[]) => API.saveContainerPorts(id, ports), + onSettled: async (...args) => { + await queryClient.invalidateQueries({ + queryKey: ["container_ports", id], + }); + options?.onSettled?.(...args); + }, + }); + return { savePorts, ...rest }; +} diff --git a/client/src/apps/Containers/pages/ContainerPorts/ContainerPorts.tsx b/client/src/apps/Containers/pages/ContainerPorts/ContainerPorts.tsx index ff3c74a5..c646d699 100644 --- a/client/src/apps/Containers/pages/ContainerPorts/ContainerPorts.tsx +++ b/client/src/apps/Containers/pages/ContainerPorts/ContainerPorts.tsx @@ -11,46 +11,130 @@ import { Vertical, } from "@vertex-center/components"; import { useParams } from "react-router-dom"; -import { useQueryClient } from "@tanstack/react-query"; -import { useContainerPorts } from "../../hooks/useContainer"; import { ProgressOverlay } from "../../../../components/Progress/Progress"; import { APIError } from "../../../../components/Error/APIError"; import { Horizontal } from "../../../../components/Layouts/Layouts"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { ArrowUUpLeft, FloppyDiskBack } from "@phosphor-icons/react"; +import { API } from "../../backend/api"; +import { useSaveContainerPorts } from "../../hooks/useContainer"; export default function ContainerPorts() { const { uuid } = useParams(); - // const queryClient = useQueryClient(); + const { control, handleSubmit, reset } = useForm({ + defaultValues: async () => { + const ports = await API.getContainerPorts(uuid); + return { ports }; + }, + }); - const { ports, isLoadingPorts, errorPorts } = useContainerPorts(uuid); + const { fields } = useFieldArray({ + control, + name: "ports", + }); - const error = errorPorts; - const isLoading = isLoadingPorts; + const { savePorts, isPending, error } = useSaveContainerPorts(uuid, { + onSuccess: () => { + reset({}, { keepValues: true }); + }, + }); + + const onSubmit = handleSubmit((data) => { + savePorts(data.ports); + }); + + const isLoading = isPending; return ( Ports - - - - Port inside container - Port outside container - - - - {ports?.map((port, i) => ( - - - - - - - - - ))} - -
+
+ + + + + + Port inside container + + + Port outside container + + + + + {fields?.map((port, i) => ( + + + ( + + )} + /> + + + ( + + )} + /> + + + ))} + +
+ + + + +
+
); } diff --git a/server/apps/containers/adapter/port_db.go b/server/apps/containers/adapter/port_db.go index f68d6928..af0af5a7 100644 --- a/server/apps/containers/adapter/port_db.go +++ b/server/apps/containers/adapter/port_db.go @@ -46,3 +46,12 @@ func (a *portDBAdapter) DeleteContainerPorts(ctx context.Context, id uuid.UUID) `, id) return err } + +func (a *portDBAdapter) UpdateContainerPortByID(ctx context.Context, port types.Port) error { + _, err := a.db.NamedExec(` + UPDATE ports + SET internal_port = :internal_port, external_port = :external_port + WHERE id = :id + `, port) + return err +} diff --git a/server/apps/containers/app.go b/server/apps/containers/app.go index b6b71504..bd869fe6 100644 --- a/server/apps/containers/app.go +++ b/server/apps/containers/app.go @@ -177,6 +177,13 @@ func (a *App) InitializeRouter(r *fizz.RouterGroup) error { fizz.Response("404", "Container not found", nil, nil, map[string]interface{}{"error": "container not found"}), }, containersHandler.GetContainerPorts()) + containers.PATCH("/:container_id/ports", []fizz.OperationOption{ + fizz.ID("patchContainerPorts"), + fizz.Summary("Patch container ports"), + fizz.Response("404", "Container not found", nil, nil, map[string]interface{}{"error": "container not found"}), + fizz.Response("500", "", nil, nil, map[string]interface{}{"error": "failed to patch container ports"}), + }, containersHandler.PatchContainerPorts()) + containers.GET("/:container_id/events", []fizz.OperationOption{ fizz.ID("eventsContainer"), fizz.Summary("Get container events"), diff --git a/server/apps/containers/core/port/adapters.go b/server/apps/containers/core/port/adapters.go index 28b2afb9..c424a36c 100644 --- a/server/apps/containers/core/port/adapters.go +++ b/server/apps/containers/core/port/adapters.go @@ -28,6 +28,7 @@ type ( GetContainerPorts(ctx context.Context, id uuid.UUID) (types.Ports, error) CreatePort(ctx context.Context, port types.Port) error DeleteContainerPorts(ctx context.Context, id uuid.UUID) error + UpdateContainerPortByID(ctx context.Context, port types.Port) error } VolumeAdapter interface { diff --git a/server/apps/containers/core/port/handlers.go b/server/apps/containers/core/port/handlers.go index 56af6b88..b0de20f4 100644 --- a/server/apps/containers/core/port/handlers.go +++ b/server/apps/containers/core/port/handlers.go @@ -17,6 +17,7 @@ type ( GetContainerEnv() gin.HandlerFunc PatchEnvironment() gin.HandlerFunc GetContainerPorts() gin.HandlerFunc + PatchContainerPorts() gin.HandlerFunc GetDocker() gin.HandlerFunc RecreateDocker() gin.HandlerFunc GetLogs() gin.HandlerFunc diff --git a/server/apps/containers/core/port/services.go b/server/apps/containers/core/port/services.go index 519f48f6..0ba8e332 100644 --- a/server/apps/containers/core/port/services.go +++ b/server/apps/containers/core/port/services.go @@ -31,6 +31,7 @@ type ( GetContainerEnv(ctx context.Context, id uuid.UUID) (types.EnvVariables, error) SaveEnv(ctx context.Context, id uuid.UUID, env types.EnvVariables) error GetContainerPorts(ctx context.Context, id uuid.UUID) (types.Ports, error) + SaveContainerPorts(ctx context.Context, id uuid.UUID, ports []types.Port) error GetAllVersions(ctx context.Context, id uuid.UUID, useCache bool) ([]string, error) GetContainerInfo(ctx context.Context, id uuid.UUID) (map[string]any, error) WaitStatus(ctx context.Context, id uuid.UUID, status string) error diff --git a/server/apps/containers/core/port/services_mock.go b/server/apps/containers/core/port/services_mock.go index e92d078f..7522ecbb 100644 --- a/server/apps/containers/core/port/services_mock.go +++ b/server/apps/containers/core/port/services_mock.go @@ -99,6 +99,11 @@ func (m *MockContainerService) GetContainerPorts(ctx context.Context, id uuid.UU return args.Get(0).(types.Ports), args.Error(1) } +func (m *MockContainerService) SaveContainerPorts(ctx context.Context, id uuid.UUID, ports []types.Port) error { + args := m.Called(ctx, id, ports) + return args.Error(0) +} + func (m *MockContainerService) RecreateContainer(ctx context.Context, uuid uuid.UUID) error { args := m.Called(ctx, uuid) return args.Error(0) diff --git a/server/apps/containers/core/service/container.go b/server/apps/containers/core/service/container.go index 9933dfe7..4b267626 100644 --- a/server/apps/containers/core/service/container.go +++ b/server/apps/containers/core/service/container.go @@ -623,10 +623,6 @@ func (s *containerService) GetContainerEnv(ctx context.Context, id uuid.UUID) (t return s.vars.GetContainerVariables(ctx, id) } -func (s *containerService) GetContainerPorts(ctx context.Context, id uuid.UUID) (types.Ports, error) { - return s.ports.GetContainerPorts(ctx, id) -} - // remapDatabaseEnv remaps the environment variables of a container. func (s *containerService) remapDatabaseEnv(ctx context.Context, c *types.Container, options map[string]*types.SetDatabasesOptions) error { for databaseID, databaseContainerID := range c.Databases { @@ -736,6 +732,20 @@ func (s *containerService) SaveEnv(ctx context.Context, id uuid.UUID, env types. return s.RecreateContainer(ctx, id) } +func (s *containerService) GetContainerPorts(ctx context.Context, id uuid.UUID) (types.Ports, error) { + return s.ports.GetContainerPorts(ctx, id) +} + +func (s *containerService) SaveContainerPorts(ctx context.Context, id uuid.UUID, ports []types.Port) error { + for _, p := range ports { + err := s.ports.UpdateContainerPortByID(ctx, p) + if err != nil { + return err + } + } + return s.RecreateContainer(ctx, id) +} + func (s *containerService) GetAllVersions(ctx context.Context, id uuid.UUID, useCache bool) ([]string, error) { c, err := s.containers.GetContainer(ctx, id) if err != nil { diff --git a/server/apps/containers/handler/container.go b/server/apps/containers/handler/container.go index 470eebef..2d891c31 100644 --- a/server/apps/containers/handler/container.go +++ b/server/apps/containers/handler/container.go @@ -175,7 +175,17 @@ func (h *containerHandler) GetContainerPorts() gin.HandlerFunc { return tonic.Handler(func(ctx *gin.Context, params *GetContainerPortsParams) (types.Ports, error) { return h.containerService.GetContainerPorts(ctx, params.ContainerID.UUID) }, http.StatusOK) +} +type PatchContainerPortsParams struct { + ContainerID uuid.NullUUID `path:"container_id"` + Ports []types.Port `body:"ports"` +} + +func (h *containerHandler) PatchContainerPorts() gin.HandlerFunc { + return tonic.Handler(func(ctx *gin.Context, params *PatchContainerPortsParams) error { + return h.containerService.SaveContainerPorts(ctx, params.ContainerID.UUID, params.Ports) + }, http.StatusOK) } func (h *containerHandler) GetDocker() gin.HandlerFunc {