feat: implement port forward deletion
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { fetchApi, type ApiError } from "@/api";
|
import { fetchApi, type ApiError } from "@/api";
|
||||||
import type { QueryStatus } from "@/lib/query";
|
import type { QueryStatus } from "@/lib/query";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { mutate, useSWRConfig } from "swr";
|
||||||
import {
|
import {
|
||||||
type Workspace,
|
type Workspace,
|
||||||
type WorkspacePortMapping,
|
type WorkspacePortMapping,
|
||||||
@@ -200,6 +200,46 @@ function useAddWorkspacePort() {
|
|||||||
return { addWorkspacePort, status };
|
return { addWorkspacePort, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useDeleteWorkspacePort() {
|
||||||
|
const [status, setStatus] = useState<QueryStatus<ApiError>>({ type: "idle" });
|
||||||
|
|
||||||
|
const deleteWorkspacePort = useCallback(
|
||||||
|
async (workspaceName: string, portName: string) => {
|
||||||
|
setStatus({ type: "loading" });
|
||||||
|
try {
|
||||||
|
await mutate(
|
||||||
|
"/workspaces",
|
||||||
|
fetchApi(`/workspaces/${workspaceName}/forwarded-ports/${portName}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
populateCache: (_, workspaces) =>
|
||||||
|
workspaces.map(
|
||||||
|
(it: Workspace): Workspace =>
|
||||||
|
it.name === workspaceName
|
||||||
|
? {
|
||||||
|
...it,
|
||||||
|
ports: it.ports?.filter(
|
||||||
|
(port) => port.subdomain !== portName,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: it,
|
||||||
|
),
|
||||||
|
revalidate: false,
|
||||||
|
throwOnError: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setStatus({ type: "ok" });
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setStatus({ type: "error", error: error as ApiError });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { deleteWorkspacePort, status };
|
||||||
|
}
|
||||||
|
|
||||||
function useWorkspaceRuntimes() {
|
function useWorkspaceRuntimes() {
|
||||||
return useSWR(
|
return useSWR(
|
||||||
"/workspace-runtimes",
|
"/workspace-runtimes",
|
||||||
@@ -215,4 +255,5 @@ export {
|
|||||||
useDeleteWorkspace,
|
useDeleteWorkspace,
|
||||||
useAddWorkspacePort,
|
useAddWorkspacePort,
|
||||||
useWorkspaceRuntimes,
|
useWorkspaceRuntimes,
|
||||||
|
useDeleteWorkspacePort,
|
||||||
};
|
};
|
||||||
|
@@ -10,13 +10,14 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { superstructResolver } from "@hookform/resolvers/superstruct";
|
import { superstructResolver } from "@hookform/resolvers/superstruct";
|
||||||
import { Check, Trash2, X } from "lucide-react";
|
import { Check, Trash2, X } from "lucide-react";
|
||||||
import { useContext, useId } from "react";
|
import { useContext, useEffect, useId } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { type Infer, number, object, pattern, size, string } from "superstruct";
|
import { type Infer, number, object, pattern, size, string } from "superstruct";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { useAddWorkspacePort } from "./api";
|
import { useAddWorkspacePort, useDeleteWorkspacePort } from "./api";
|
||||||
import { WorkspaceTableRowContext } from "./workspace-table";
|
import { WorkspaceTableRowContext } from "./workspace-table";
|
||||||
|
|
||||||
interface PortInfoTabStore {
|
interface PortInfoTabStore {
|
||||||
@@ -57,7 +58,7 @@ function PortInfoTableBody() {
|
|||||||
<TableCell className="py-0">{subdomain}</TableCell>
|
<TableCell className="py-0">{subdomain}</TableCell>
|
||||||
<TableCell className="py-0">{port}</TableCell>
|
<TableCell className="py-0">{port}</TableCell>
|
||||||
<TableCell className="p-0 text-right">
|
<TableCell className="p-0 text-right">
|
||||||
<DeletePortMappingButton />
|
<DeletePortMappingButton subdomain={subdomain} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
@@ -183,10 +184,34 @@ function NewPortMappingRow() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeletePortMappingButton() {
|
function DeletePortMappingButton({ subdomain }: { subdomain: string }) {
|
||||||
|
const { deleteWorkspacePort, status } = useDeleteWorkspacePort();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const workspace = useContext(WorkspaceTableRowContext);
|
||||||
|
const isDeleting = status.type === "loading";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status.type === "error") {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to delete port.",
|
||||||
|
description: "Unexpected error.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [status.type, toast]);
|
||||||
|
|
||||||
|
async function _deleteWorkspacePort() {
|
||||||
|
await deleteWorkspacePort(workspace.name, subdomain);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="ghost" size="icon">
|
<Button
|
||||||
<Trash2 />
|
disabled={isDeleting}
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={_deleteWorkspacePort}
|
||||||
|
>
|
||||||
|
{isDeleting ? <LoadingSpinner /> : <Trash2 />}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user