Add create endpoints for users, roles, tenants, projects, networks, subnets, and security rules with audit logging, implement commit_or_400 helper for IntegrityError handling with 409 responses, enhance cluster sync to populate nodes, workloads, and networks from provider inventory with last_sync_at tracking, add update/delete operations for IP addresses and policies with version tracking, implement IP
43 lines
2.3 KiB
TypeScript
43 lines
2.3 KiB
TypeScript
import { FormEvent, useState } from "react";
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { Plus, SquareStack } from "lucide-react";
|
|
|
|
import { api, ServiceCatalogItem } from "../api/client";
|
|
import { DataTable } from "../components/DataTable";
|
|
import { buttonClass, Field, inputClass } from "../components/FormControls";
|
|
import { PageHeader } from "../components/PageHeader";
|
|
|
|
export function ServiceCatalog() {
|
|
const queryClient = useQueryClient();
|
|
const services = useQuery({ queryKey: ["service-catalog"], queryFn: () => api<ServiceCatalogItem[]>("/service-catalog") });
|
|
const [form, setForm] = useState({ name: "Custom API", protocol: "tcp", ports: "8443", editable: true });
|
|
const create = useMutation({
|
|
mutationFn: () => api<ServiceCatalogItem>("/service-catalog", { method: "POST", body: JSON.stringify(form) }),
|
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["service-catalog"] }),
|
|
});
|
|
|
|
async function submit(event: FormEvent) {
|
|
event.preventDefault();
|
|
await create.mutateAsync();
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<PageHeader title="Service Catalog" subtitle="Maintain reusable protocols and port ranges for policy rules." />
|
|
<div className="grid gap-4 lg:grid-cols-[340px_1fr]">
|
|
<form onSubmit={submit} className="rounded-md border border-border bg-panel p-4">
|
|
<div className="mb-4 flex items-center gap-2 font-medium"><SquareStack size={18} /> Add Service</div>
|
|
<div className="grid gap-3">
|
|
<Field label="Name"><input className={inputClass} value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} /></Field>
|
|
<Field label="Protocol"><input className={inputClass} value={form.protocol} onChange={(event) => setForm({ ...form, protocol: event.target.value })} /></Field>
|
|
<Field label="Ports"><input className={inputClass} value={form.ports} onChange={(event) => setForm({ ...form, ports: event.target.value })} /></Field>
|
|
<button className={buttonClass}><Plus size={16} /> Save Service</button>
|
|
</div>
|
|
</form>
|
|
<DataTable rows={(services.data ?? []) as unknown as Record<string, unknown>[]} columns={[{ key: "name", label: "Name" }, { key: "protocol", label: "Protocol" }, { key: "ports", label: "Ports" }]} />
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|