feat: add Deployment Jobs, Template Categories, Virtual Machine details and management pages

- Implemented DeploymentJobsPage for managing deployment jobs with filtering and retry functionality.
- Created TemplateCategoriesPage for managing template categories with add, edit, and delete capabilities.
- Developed VirtualMachineDetailsPage to display detailed information about a specific virtual machine.
- Added VirtualMachinesPage for listing, adding, editing, and deleting virtual machines, including linking and unlinking to domains.
This commit is contained in:
Torsten Brendgen
2026-05-16 16:44:38 +02:00
parent 7080d659ef
commit 08bcd60746
19 changed files with 3265 additions and 208 deletions

View File

@@ -1,79 +1,191 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
Button,
Checkbox,
Combobox,
Field,
Input,
makeStyles,
Option,
Table,
TableBody,
TableCell,
TableHeader,
TableHeaderCell,
TableRow,
Tooltip,
} from "@fluentui/react-components";
import { OpenRegular } from "@fluentui/react-icons";
import { useState } from "react";
import { Link } from "react-router-dom";
import { portalApi } from "../api/portalApi";
import { DataState } from "../components/DataState";
import { FormActions, FormGrid, FormSection } from "../components/FormSection";
import { PageHeader } from "../components/PageHeader";
const useStyles = makeStyles({
actions: {
display: "flex",
gap: "6px",
},
});
export function DeploymentGroupsPage() {
const styles = useStyles();
const queryClient = useQueryClient();
const [serviceId, setServiceId] = useState("");
const [templateId, setTemplateId] = useState("");
const [virtualMachineIds, setVirtualMachineIds] = useState<string[]>([]);
const [status, setStatus] = useState("New");
const { data, error, isLoading } = useQuery({
queryKey: ["deploymentGroups"],
queryFn: ({ signal }) => portalApi.getDeploymentGroups(signal),
queryKey: ["deployment-batches"],
queryFn: ({ signal }) => portalApi.getDeploymentBatches(signal),
});
const addDeploymentGroup = useMutation({
mutationFn: portalApi.addDeploymentGroup,
const { data: services } = useQuery({
queryKey: ["services"],
queryFn: ({ signal }) => portalApi.getServices(signal),
});
const { data: templates } = useQuery({
queryKey: ["templates"],
queryFn: ({ signal }) => portalApi.getTemplates(signal),
});
const { data: templateCategories } = useQuery({
queryKey: ["template-categories"],
queryFn: ({ signal }) => portalApi.getTemplateCategories(signal),
});
const { data: virtualMachines } = useQuery({
queryKey: ["virtual-machines"],
queryFn: ({ signal }) => portalApi.getVirtualMachines(signal),
});
const addDeploymentBatch = useMutation({
mutationFn: portalApi.addDeploymentBatch,
onSuccess: async () => {
setServiceId("");
setTemplateId("");
setVirtualMachineIds([]);
setStatus("New");
await queryClient.invalidateQueries({ queryKey: ["deploymentGroups"] });
await queryClient.invalidateQueries({ queryKey: ["deployment-batches"] });
await queryClient.invalidateQueries({ queryKey: ["deployments"] });
},
});
const selectedService = (services ?? []).find((service) => service.id === serviceId);
const isOnPremService = selectedService ? !selectedService.isCloudService : false;
return (
<>
<PageHeader title="Deployment Groups" description="Gruppen von Bereitstellungen je Template." />
<PageHeader title="Deployments" description="Deployment Batches und deren Ausfuehrungen." />
<FormSection
onSubmit={(event) => {
event.preventDefault();
addDeploymentGroup.mutate({ status, templateId });
addDeploymentBatch.mutate({
status,
templateId,
virtualMachineIds: isOnPremService ? virtualMachineIds : undefined,
});
}}
>
<FormGrid>
<Field label="Template Id" required>
<Input value={templateId} onChange={(_, data) => setTemplateId(data.value)} />
<Field label="Service" required>
<Combobox
placeholder="Service waehlen"
value={services?.find((service) => service.id === serviceId)?.name ?? ""}
onOptionSelect={(_, data) => {
const nextServiceId = data.optionValue ?? "";
setServiceId(nextServiceId);
setTemplateId("");
setVirtualMachineIds([]);
}}
>
{(services ?? []).map((service) => (
<Option key={service.id} text={service.name} value={service.id}>
{service.name}
</Option>
))}
</Combobox>
</Field>
<Field label="Status" required validationMessage={addDeploymentGroup.error?.message}>
<Input value={status} onChange={(_, data) => setStatus(data.value)} />
<Field label="Template" required>
<Combobox
placeholder={serviceId ? "Template waehlen" : "Erst Service waehlen"}
disabled={!serviceId}
value={templates?.find((template) => template.id === templateId)?.name ?? ""}
onOptionSelect={(_, data) => setTemplateId(data.optionValue ?? "")}
>
{(templates ?? [])
.filter((template) => {
const category = (templateCategories ?? []).find((entry) => entry.id === template.templateCategoryId);
return category?.serviceId === serviceId;
})
.map((template) => (
<Option key={template.id} text={template.name} value={template.id}>
{template.name}
</Option>
))}
</Combobox>
</Field>
<Field label="Status" required validationMessage={addDeploymentBatch.error?.message}>
<Combobox value={status} onOptionSelect={(_, data) => setStatus(data.optionValue ?? "New")}>
<Option value="New">New</Option>
<Option value="Pending">Pending</Option>
<Option value="Running">Running</Option>
<Option value="Succeeded">Succeeded</Option>
<Option value="Failed">Failed</Option>
</Combobox>
</Field>
{isOnPremService && (
<Field label="Virtual Machines" required>
<div>
{(virtualMachines ?? []).map((virtualMachine) => (
<Checkbox
key={virtualMachine.id}
label={virtualMachine.name}
checked={virtualMachineIds.includes(virtualMachine.id)}
onChange={(_, data) => {
if (data.checked) {
setVirtualMachineIds((prev) => [...prev, virtualMachine.id]);
} else {
setVirtualMachineIds((prev) => prev.filter((id) => id !== virtualMachine.id));
}
}}
/>
))}
</div>
</Field>
)}
</FormGrid>
<FormActions>
<Button
appearance="primary"
disabled={!templateId || !status || addDeploymentGroup.isPending}
disabled={!templateId || !status || (isOnPremService && virtualMachineIds.length === 0) || addDeploymentBatch.isPending}
type="submit"
>
Add deployment group
Add deployment batch
</Button>
</FormActions>
</FormSection>
<DataState isLoading={isLoading} error={error} />
{data && (
<Table aria-label="Deployment groups">
<Table aria-label="Deployment batches">
<TableHeader>
<TableRow>
<TableHeaderCell>Status</TableHeaderCell>
<TableHeaderCell>Id</TableHeaderCell>
<TableHeaderCell>Actions</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{data.map((deploymentGroup) => (
<TableRow key={deploymentGroup.id}>
<TableCell>{deploymentGroup.status ?? "Unknown"}</TableCell>
<TableCell>{deploymentGroup.id}</TableCell>
{data.map((deploymentBatch) => (
<TableRow key={deploymentBatch.id}>
<TableCell>{deploymentBatch.status ?? "Unknown"}</TableCell>
<TableCell>{deploymentBatch.id}</TableCell>
<TableCell>
<div className={styles.actions}>
<Tooltip content="Details" relationship="label">
<Link to={`/deployments/${deploymentBatch.id}`}>
<Button appearance="subtle" aria-label="Details" icon={<OpenRegular />} />
</Link>
</Tooltip>
</div>
</TableCell>
</TableRow>
))}
</TableBody>