feat: Enhance Domains, Environments, Services, and Templates management
- Implemented dialog-based forms for adding and editing Domains and Environments. - Added delete functionality for Domains and Environments with confirmation prompts. - Introduced EnvironmentDetailsPage to display details of selected environments and their linked domains. - Created EnvironmentDomainsPage for linking domains to environments. - Enhanced ServicesPage with dialog support for adding, editing, and viewing service details. - Updated TemplatesPage to manage templates with comprehensive form fields and validation. - Improved type definitions in portal.ts to support new features and ensure type safety.
This commit is contained in:
@@ -1,23 +1,56 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogSurface,
|
||||
DialogTitle,
|
||||
Field,
|
||||
Input,
|
||||
makeStyles,
|
||||
shorthands,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from "@fluentui/react-components";
|
||||
import { AddRegular, DeleteRegular, EditRegular, 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";
|
||||
import type { Domain } from "../types/portal";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
toolbar: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: "18px",
|
||||
},
|
||||
form: {
|
||||
display: "grid",
|
||||
gap: "14px",
|
||||
},
|
||||
actions: {
|
||||
display: "flex",
|
||||
gap: "4px",
|
||||
...shorthands.padding("2px", "0"),
|
||||
},
|
||||
});
|
||||
|
||||
type DialogMode = "add" | "edit" | null;
|
||||
|
||||
export function DomainsPage() {
|
||||
const styles = useStyles();
|
||||
const queryClient = useQueryClient();
|
||||
const [dialogMode, setDialogMode] = useState<DialogMode>(null);
|
||||
const [selectedDomain, setSelectedDomain] = useState<Domain | null>(null);
|
||||
const [name, setName] = useState("");
|
||||
const [fqdn, setFqdn] = useState("");
|
||||
const [netBIOS, setNetBIOS] = useState("");
|
||||
@@ -25,47 +58,109 @@ export function DomainsPage() {
|
||||
queryKey: ["domains"],
|
||||
queryFn: ({ signal }) => portalApi.getDomains(signal),
|
||||
});
|
||||
|
||||
const closeDialog = () => {
|
||||
setDialogMode(null);
|
||||
setSelectedDomain(null);
|
||||
setName("");
|
||||
setFqdn("");
|
||||
setNetBIOS("");
|
||||
};
|
||||
|
||||
const openAddDialog = () => {
|
||||
setSelectedDomain(null);
|
||||
setName("");
|
||||
setFqdn("");
|
||||
setNetBIOS("");
|
||||
setDialogMode("add");
|
||||
};
|
||||
|
||||
const openEditDialog = (domain: Domain) => {
|
||||
setSelectedDomain(domain);
|
||||
setName(domain.name);
|
||||
setFqdn(domain.fqdn);
|
||||
setNetBIOS(domain.netBIOS);
|
||||
setDialogMode("edit");
|
||||
};
|
||||
|
||||
const addDomain = useMutation({
|
||||
mutationFn: portalApi.addDomain,
|
||||
onSuccess: async () => {
|
||||
setName("");
|
||||
setFqdn("");
|
||||
setNetBIOS("");
|
||||
closeDialog();
|
||||
await queryClient.invalidateQueries({ queryKey: ["domains"] });
|
||||
},
|
||||
});
|
||||
|
||||
const updateDomain = useMutation({
|
||||
mutationFn: ({ id, domain }: { id: string; domain: { name: string; fqdn: string; netBIOS: string } }) =>
|
||||
portalApi.updateDomain(id, domain),
|
||||
onSuccess: async () => {
|
||||
closeDialog();
|
||||
await queryClient.invalidateQueries({ queryKey: ["domains"] });
|
||||
},
|
||||
});
|
||||
|
||||
const deleteDomain = useMutation({
|
||||
mutationFn: portalApi.deleteDomain,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["domains"] });
|
||||
},
|
||||
});
|
||||
|
||||
const submitDomain = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const domain = { fqdn, name, netBIOS };
|
||||
|
||||
if (dialogMode === "edit" && selectedDomain) {
|
||||
updateDomain.mutate({ id: selectedDomain.id, domain });
|
||||
return;
|
||||
}
|
||||
|
||||
addDomain.mutate(domain);
|
||||
};
|
||||
|
||||
const formError = addDomain.error?.message ?? updateDomain.error?.message;
|
||||
const isSaving = addDomain.isPending || updateDomain.isPending;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Domains" description="Verfuegbare Active Directory Domaenen." />
|
||||
<FormSection
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
addDomain.mutate({ fqdn, name, netBIOS });
|
||||
}}
|
||||
>
|
||||
<FormGrid>
|
||||
<Field label="Name" required>
|
||||
<Input value={name} onChange={(_, data) => setName(data.value)} />
|
||||
</Field>
|
||||
<Field label="FQDN" required>
|
||||
<Input value={fqdn} onChange={(_, data) => setFqdn(data.value)} />
|
||||
</Field>
|
||||
<Field label="NetBIOS" required validationMessage={addDomain.error?.message}>
|
||||
<Input value={netBIOS} onChange={(_, data) => setNetBIOS(data.value)} />
|
||||
</Field>
|
||||
</FormGrid>
|
||||
<FormActions>
|
||||
<Button
|
||||
appearance="primary"
|
||||
disabled={!name || !fqdn || !netBIOS || addDomain.isPending}
|
||||
type="submit"
|
||||
>
|
||||
Add domain
|
||||
</Button>
|
||||
</FormActions>
|
||||
</FormSection>
|
||||
<DataState isLoading={isLoading} error={error} />
|
||||
<div className={styles.toolbar}>
|
||||
<Button appearance="primary" icon={<AddRegular />} onClick={openAddDialog}>
|
||||
Domain hinzufuegen
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Dialog open={dialogMode !== null} onOpenChange={(_, data) => !data.open && closeDialog()}>
|
||||
<DialogSurface>
|
||||
<form onSubmit={submitDomain}>
|
||||
<DialogBody>
|
||||
<DialogTitle>{dialogMode === "edit" ? "Domain aendern" : "Domain hinzufuegen"}</DialogTitle>
|
||||
<DialogContent className={styles.form}>
|
||||
<Field label="Name" required>
|
||||
<Input value={name} onChange={(_, data) => setName(data.value)} />
|
||||
</Field>
|
||||
<Field label="FQDN" required>
|
||||
<Input value={fqdn} onChange={(_, data) => setFqdn(data.value)} />
|
||||
</Field>
|
||||
<Field label="NetBIOS" required validationMessage={formError}>
|
||||
<Input value={netBIOS} onChange={(_, data) => setNetBIOS(data.value)} />
|
||||
</Field>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button appearance="secondary" onClick={closeDialog}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button appearance="primary" disabled={!name || !fqdn || !netBIOS || isSaving} type="submit">
|
||||
Speichern
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</form>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
|
||||
<DataState isLoading={isLoading} error={error ?? deleteDomain.error} />
|
||||
{data && (
|
||||
<Table aria-label="Domains">
|
||||
<TableHeader>
|
||||
@@ -74,6 +169,7 @@ export function DomainsPage() {
|
||||
<TableHeaderCell>FQDN</TableHeaderCell>
|
||||
<TableHeaderCell>NetBIOS</TableHeaderCell>
|
||||
<TableHeaderCell>Id</TableHeaderCell>
|
||||
<TableHeaderCell>Aktionen</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -83,6 +179,36 @@ export function DomainsPage() {
|
||||
<TableCell>{domain.fqdn}</TableCell>
|
||||
<TableCell>{domain.netBIOS}</TableCell>
|
||||
<TableCell>{domain.id}</TableCell>
|
||||
<TableCell>
|
||||
<div className={styles.actions}>
|
||||
<Tooltip content="Details" relationship="label">
|
||||
<Link to={`/domains/${domain.id}`}>
|
||||
<Button appearance="subtle" aria-label="Details" icon={<OpenRegular />} />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Tooltip content="Aendern" relationship="label">
|
||||
<Button
|
||||
appearance="subtle"
|
||||
aria-label="Aendern"
|
||||
icon={<EditRegular />}
|
||||
onClick={() => openEditDialog(domain)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content="Loeschen" relationship="label">
|
||||
<Button
|
||||
appearance="subtle"
|
||||
aria-label="Loeschen"
|
||||
disabled={deleteDomain.isPending}
|
||||
icon={<DeleteRegular />}
|
||||
onClick={() => {
|
||||
if (window.confirm(`Domain "${domain.name}" wirklich loeschen?`)) {
|
||||
deleteDomain.mutate(domain.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
Reference in New Issue
Block a user