-
+
diff --git a/src/pages/DomainDetailsPage.tsx b/src/pages/DomainDetailsPage.tsx
new file mode 100644
index 0000000..7b362d7
--- /dev/null
+++ b/src/pages/DomainDetailsPage.tsx
@@ -0,0 +1,139 @@
+import { useQuery } from "@tanstack/react-query";
+import {
+ Badge,
+ Button,
+ makeStyles,
+ MessageBar,
+ MessageBarBody,
+ shorthands,
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableHeaderCell,
+ TableRow,
+ Text,
+ Title3,
+ tokens,
+} from "@fluentui/react-components";
+import { ArrowLeftRegular } from "@fluentui/react-icons";
+import { Link, useParams } from "react-router-dom";
+import { portalApi } from "../api/portalApi";
+import { DataState } from "../components/DataState";
+import { PageHeader } from "../components/PageHeader";
+
+const useStyles = makeStyles({
+ backLink: {
+ display: "inline-flex",
+ marginBottom: "18px",
+ textDecorationLine: "none",
+ },
+ details: {
+ display: "grid",
+ gap: "14px",
+ gridTemplateColumns: "repeat(4, minmax(160px, 1fr))",
+ marginBottom: "24px",
+ maxWidth: "980px",
+ ...shorthands.border("1px", "solid", tokens.colorNeutralStroke2),
+ ...shorthands.borderRadius("8px"),
+ ...shorthands.padding("18px"),
+ },
+ field: {
+ display: "grid",
+ gap: "4px",
+ minWidth: 0,
+ },
+ value: {
+ overflowWrap: "anywhere",
+ },
+ sectionTitle: {
+ marginBottom: "12px",
+ },
+});
+
+export function DomainDetailsPage() {
+ const styles = useStyles();
+ const { id } = useParams();
+ const { data, error, isLoading } = useQuery({
+ enabled: Boolean(id),
+ queryKey: ["domain", id, "environments"],
+ queryFn: ({ signal }) => portalApi.getDomainEnvironments(id!, signal),
+ });
+ const links = data?.environmentDomains?.filter((link) => link.environment) ?? [];
+
+ return (
+ <>
+
+
}>
+ Domains
+
+
+
+
+ {data && (
+ <>
+
+
+ Name
+
+ {data.name}
+
+
+
+ FQDN
+
+ {data.fqdn}
+
+
+
+ NetBIOS
+
+ {data.netBIOS}
+
+
+
+ Id
+
+ {data.id}
+
+
+
+
+
Linked Environments
+ {links.length === 0 ? (
+
+ Diese Domain ist noch mit keinem Environment verknuepft.
+
+ ) : (
+
+
+
+ Name
+ Id
+ Status
+ Details
+
+
+
+ {links.map((link) => (
+
+ {link.environment!.name}
+ {link.environment!.id}
+
+
+ Linked
+
+
+
+ Oeffnen
+
+
+ ))}
+
+
+ )}
+ >
+ )}
+ >
+ );
+}
diff --git a/src/pages/DomainsPage.tsx b/src/pages/DomainsPage.tsx
index 6427391..cac46c1 100644
--- a/src/pages/DomainsPage.tsx
+++ b/src/pages/DomainsPage.tsx
@@ -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
(null);
+ const [selectedDomain, setSelectedDomain] = useState(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) => {
+ 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 (
<>
- {
- event.preventDefault();
- addDomain.mutate({ fqdn, name, netBIOS });
- }}
- >
-
-
- setName(data.value)} />
-
-
- setFqdn(data.value)} />
-
-
- setNetBIOS(data.value)} />
-
-
-
-
-
-
-
+
+ } onClick={openAddDialog}>
+ Domain hinzufuegen
+
+
+
+
+
+
{data && (
@@ -74,6 +169,7 @@ export function DomainsPage() {
FQDN
NetBIOS
Id
+ Aktionen
@@ -83,6 +179,36 @@ export function DomainsPage() {
{domain.fqdn}
{domain.netBIOS}
{domain.id}
+
+
+
+
+ } />
+
+
+
+ }
+ onClick={() => openEditDialog(domain)}
+ />
+
+
+ }
+ onClick={() => {
+ if (window.confirm(`Domain "${domain.name}" wirklich loeschen?`)) {
+ deleteDomain.mutate(domain.id);
+ }
+ }}
+ />
+
+
+
))}
diff --git a/src/pages/EnvironmentDetailsPage.tsx b/src/pages/EnvironmentDetailsPage.tsx
new file mode 100644
index 0000000..6af8640
--- /dev/null
+++ b/src/pages/EnvironmentDetailsPage.tsx
@@ -0,0 +1,129 @@
+import { useQuery } from "@tanstack/react-query";
+import {
+ Badge,
+ Button,
+ makeStyles,
+ MessageBar,
+ MessageBarBody,
+ shorthands,
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableHeaderCell,
+ TableRow,
+ Text,
+ Title3,
+ tokens,
+} from "@fluentui/react-components";
+import { ArrowLeftRegular } from "@fluentui/react-icons";
+import { Link, useParams } from "react-router-dom";
+import { portalApi } from "../api/portalApi";
+import { DataState } from "../components/DataState";
+import { PageHeader } from "../components/PageHeader";
+
+const useStyles = makeStyles({
+ backLink: {
+ display: "inline-flex",
+ marginBottom: "18px",
+ textDecorationLine: "none",
+ },
+ details: {
+ display: "grid",
+ gap: "14px",
+ gridTemplateColumns: "repeat(2, minmax(180px, 1fr))",
+ marginBottom: "24px",
+ maxWidth: "720px",
+ ...shorthands.border("1px", "solid", tokens.colorNeutralStroke2),
+ ...shorthands.borderRadius("8px"),
+ ...shorthands.padding("18px"),
+ },
+ field: {
+ display: "grid",
+ gap: "4px",
+ minWidth: 0,
+ },
+ value: {
+ overflowWrap: "anywhere",
+ },
+ sectionTitle: {
+ marginBottom: "12px",
+ },
+});
+
+export function EnvironmentDetailsPage() {
+ const styles = useStyles();
+ const { id } = useParams();
+ const { data, error, isLoading } = useQuery({
+ enabled: Boolean(id),
+ queryKey: ["environment", id, "domains"],
+ queryFn: ({ signal }) => portalApi.getEnvironmentDomains(id!, signal),
+ });
+ const links = data?.environmentDomains?.filter((link) => link.domain) ?? [];
+
+ return (
+ <>
+
+ }>
+ Environments
+
+
+
+
+ {data && (
+ <>
+
+
+ Name
+
+ {data.name}
+
+
+
+ Id
+
+ {data.id}
+
+
+
+
+ Linked Domains
+ {links.length === 0 ? (
+
+ Dieses Environment ist noch mit keiner Domain verknuepft.
+
+ ) : (
+
+
+
+ Name
+ FQDN
+ NetBIOS
+ Status
+ Details
+
+
+
+ {links.map((link) => (
+
+ {link.domain!.name}
+ {link.domain!.fqdn}
+ {link.domain!.netBIOS}
+
+
+ Linked
+
+
+
+ Oeffnen
+
+
+ ))}
+
+
+ )}
+ >
+ )}
+ >
+ );
+}
diff --git a/src/pages/EnvironmentDomainsPage.tsx b/src/pages/EnvironmentDomainsPage.tsx
new file mode 100644
index 0000000..a144720
--- /dev/null
+++ b/src/pages/EnvironmentDomainsPage.tsx
@@ -0,0 +1,104 @@
+import { useMutation, useQuery } from "@tanstack/react-query";
+import {
+ Button,
+ Combobox,
+ Field,
+ MessageBar,
+ MessageBarBody,
+ Option,
+} from "@fluentui/react-components";
+import { useMemo, useState } from "react";
+import { portalApi } from "../api/portalApi";
+import { DataState } from "../components/DataState";
+import { FormActions, FormGrid, FormSection } from "../components/FormSection";
+import { PageHeader } from "../components/PageHeader";
+
+export function EnvironmentDomainsPage() {
+ const [domainId, setDomainId] = useState("");
+ const [environmentId, setEnvironmentId] = useState("");
+
+ const domains = useQuery({
+ queryKey: ["domains"],
+ queryFn: ({ signal }) => portalApi.getDomains(signal),
+ });
+ const environments = useQuery({
+ queryKey: ["environments"],
+ queryFn: ({ signal }) => portalApi.getEnvironments(signal),
+ });
+ const linkMutation = useMutation({
+ mutationFn: () => portalApi.linkDomainToEnvironment(domainId, environmentId),
+ });
+
+ const selectedDomain = useMemo(
+ () => domains.data?.find((domain) => domain.id === domainId),
+ [domainId, domains.data],
+ );
+ const selectedEnvironment = useMemo(
+ () => environments.data?.find((environment) => environment.id === environmentId),
+ [environmentId, environments.data],
+ );
+
+ const isLoading = domains.isLoading || environments.isLoading;
+ const error = domains.error ?? environments.error;
+
+ return (
+ <>
+
+ {
+ event.preventDefault();
+ linkMutation.mutate();
+ }}
+ >
+
+
+ setEnvironmentId(data.optionValue ?? "")}
+ placeholder="Select environment"
+ value={selectedEnvironment?.name ?? ""}
+ >
+ {environments.data?.map((environment) => (
+
+ ))}
+
+
+
+ setDomainId(data.optionValue ?? "")}
+ placeholder="Select domain"
+ value={selectedDomain?.name ?? ""}
+ >
+ {domains.data?.map((domain) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ {linkMutation.isSuccess && (
+
+ Domain wurde mit dem Environment verknuepft.
+
+ )}
+ >
+ );
+}
diff --git a/src/pages/EnvironmentsPage.tsx b/src/pages/EnvironmentsPage.tsx
index 2440890..86e20d4 100644
--- a/src/pages/EnvironmentsPage.tsx
+++ b/src/pages/EnvironmentsPage.tsx
@@ -1,71 +1,198 @@
-import { useQuery } from "@tanstack/react-query";
+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 { useMutation, useQueryClient } from "@tanstack/react-query";
+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 { EnvironmentItem } 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 EnvironmentsPage() {
+ const styles = useStyles();
const queryClient = useQueryClient();
+ const [dialogMode, setDialogMode] = useState(null);
+ const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [name, setName] = useState("");
const { data, error, isLoading } = useQuery({
queryKey: ["environments"],
queryFn: ({ signal }) => portalApi.getEnvironments(signal),
});
+
+ const closeDialog = () => {
+ setDialogMode(null);
+ setSelectedEnvironment(null);
+ setName("");
+ };
+
+ const openAddDialog = () => {
+ setSelectedEnvironment(null);
+ setName("");
+ setDialogMode("add");
+ };
+
+ const openEditDialog = (environment: EnvironmentItem) => {
+ setSelectedEnvironment(environment);
+ setName(environment.name);
+ setDialogMode("edit");
+ };
+
const addEnvironment = useMutation({
mutationFn: portalApi.addEnvironment,
onSuccess: async () => {
- setName("");
+ closeDialog();
await queryClient.invalidateQueries({ queryKey: ["environments"] });
},
});
+ const updateEnvironment = useMutation({
+ mutationFn: ({ id, environment }: { id: string; environment: { name: string } }) =>
+ portalApi.updateEnvironment(id, environment),
+ onSuccess: async () => {
+ closeDialog();
+ await queryClient.invalidateQueries({ queryKey: ["environments"] });
+ },
+ });
+
+ const deleteEnvironment = useMutation({
+ mutationFn: portalApi.deleteEnvironment,
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({ queryKey: ["environments"] });
+ },
+ });
+
+ const submitEnvironment = (event: React.FormEvent) => {
+ event.preventDefault();
+ const environment = { name };
+
+ if (dialogMode === "edit" && selectedEnvironment) {
+ updateEnvironment.mutate({ id: selectedEnvironment.id, environment });
+ return;
+ }
+
+ addEnvironment.mutate(environment);
+ };
+
+ const formError = addEnvironment.error?.message ?? updateEnvironment.error?.message;
+ const isSaving = addEnvironment.isPending || updateEnvironment.isPending;
+
return (
<>
- {
- event.preventDefault();
- addEnvironment.mutate({ name });
- }}
- >
-
-
- setName(data.value)} />
-
-
-
-
-
-
-
+
+ } onClick={openAddDialog}>
+ Environment hinzufuegen
+
+
+
+
+
+
{data && (
-
- Name
- Id
-
+
+ Name
+ Id
+ Aktionen
+
{data.map((environment) => (
{environment.name}
{environment.id}
+
+
+
+
+ } />
+
+
+
+ }
+ onClick={() => openEditDialog(environment)}
+ />
+
+
+ }
+ onClick={() => {
+ if (window.confirm(`Environment "${environment.name}" wirklich loeschen?`)) {
+ deleteEnvironment.mutate(environment.id);
+ }
+ }}
+ />
+
+
+
))}
diff --git a/src/pages/ServicesPage.tsx b/src/pages/ServicesPage.tsx
index 0e56b70..3bd70af 100644
--- a/src/pages/ServicesPage.tsx
+++ b/src/pages/ServicesPage.tsx
@@ -1,39 +1,230 @@
-import { useQuery } from "@tanstack/react-query";
+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,
+ Textarea,
+ Tooltip,
} from "@fluentui/react-components";
+import { AddRegular, DeleteRegular, EditRegular, OpenRegular } from "@fluentui/react-icons";
+import { useState } from "react";
import { portalApi } from "../api/portalApi";
import { DataState } from "../components/DataState";
import { PageHeader } from "../components/PageHeader";
+import type { ServiceItem } 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"),
+ },
+ value: {
+ overflowWrap: "anywhere",
+ },
+});
+
+type DialogMode = "add" | "edit" | "details" | null;
export function ServicesPage() {
+ const styles = useStyles();
+ const queryClient = useQueryClient();
+ const [dialogMode, setDialogMode] = useState(null);
+ const [selectedService, setSelectedService] = useState(null);
+ const [name, setName] = useState("");
+ const [description, setDescription] = useState("");
const { data, error, isLoading } = useQuery({
queryKey: ["services"],
queryFn: ({ signal }) => portalApi.getServices(signal),
});
+ const closeDialog = () => {
+ setDialogMode(null);
+ setSelectedService(null);
+ setName("");
+ setDescription("");
+ };
+
+ const openAddDialog = () => {
+ setName("");
+ setDescription("");
+ setSelectedService(null);
+ setDialogMode("add");
+ };
+
+ const openServiceDialog = (mode: "edit" | "details", service: ServiceItem) => {
+ setSelectedService(service);
+ setName(service.name);
+ setDescription(service.description ?? "");
+ setDialogMode(mode);
+ };
+
+ const addService = useMutation({
+ mutationFn: portalApi.addService,
+ onSuccess: async () => {
+ closeDialog();
+ await queryClient.invalidateQueries({ queryKey: ["services"] });
+ },
+ });
+
+ const updateService = useMutation({
+ mutationFn: ({ id, service }: { id: string; service: { name: string; description: string } }) =>
+ portalApi.updateService(id, service),
+ onSuccess: async () => {
+ closeDialog();
+ await queryClient.invalidateQueries({ queryKey: ["services"] });
+ },
+ });
+
+ const deleteService = useMutation({
+ mutationFn: portalApi.deleteService,
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({ queryKey: ["services"] });
+ },
+ });
+
+ const submitService = (event: React.FormEvent) => {
+ event.preventDefault();
+ const service = { description, name };
+
+ if (dialogMode === "edit" && selectedService) {
+ updateService.mutate({ id: selectedService.id, service });
+ return;
+ }
+
+ addService.mutate(service);
+ };
+
+ const formError = addService.error?.message ?? updateService.error?.message;
+ const isSaving = addService.isPending || updateService.isPending;
+ const isDetails = dialogMode === "details";
+
return (
<>
-
+
+ } onClick={openAddDialog}>
+ Service hinzufuegen
+
+
+
+
+
+
{data && (
-
- Name
- Id
-
+
+ Name
+ Description
+ Id
+ Aktionen
+
{data.map((service) => (
{service.name}
+ {service.description}
{service.id}
+
+
+
+ }
+ onClick={() => openServiceDialog("details", service)}
+ />
+
+
+ }
+ onClick={() => openServiceDialog("edit", service)}
+ />
+
+
+ }
+ onClick={() => {
+ if (window.confirm(`Service "${service.name}" wirklich loeschen?`)) {
+ deleteService.mutate(service.id);
+ }
+ }}
+ />
+
+
+
))}
diff --git a/src/pages/TemplatesPage.tsx b/src/pages/TemplatesPage.tsx
index 73ac397..69c0d77 100644
--- a/src/pages/TemplatesPage.tsx
+++ b/src/pages/TemplatesPage.tsx
@@ -1,39 +1,305 @@
-import { useQuery } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
+ Button,
+ Checkbox,
+ Dialog,
+ DialogActions,
+ DialogBody,
+ DialogContent,
+ DialogSurface,
+ DialogTitle,
+ Field,
+ Input,
+ makeStyles,
+ shorthands,
Table,
TableBody,
TableCell,
TableHeader,
TableHeaderCell,
TableRow,
+ Textarea,
+ Tooltip,
} from "@fluentui/react-components";
+import { AddRegular, DeleteRegular, EditRegular, OpenRegular } from "@fluentui/react-icons";
+import { useState } from "react";
import { portalApi } from "../api/portalApi";
import { DataState } from "../components/DataState";
import { PageHeader } from "../components/PageHeader";
+import type { Template } from "../types/portal";
+
+const useStyles = makeStyles({
+ toolbar: {
+ display: "flex",
+ justifyContent: "flex-start",
+ marginBottom: "18px",
+ },
+ form: {
+ display: "grid",
+ gap: "14px",
+ },
+ grid: {
+ display: "grid",
+ gap: "14px",
+ gridTemplateColumns: "repeat(2, minmax(180px, 1fr))",
+ },
+ wide: {
+ gridColumn: "1 / -1",
+ },
+ actions: {
+ display: "flex",
+ gap: "4px",
+ ...shorthands.padding("2px", "0"),
+ },
+ value: {
+ overflowWrap: "anywhere",
+ },
+});
+
+type DialogMode = "add" | "edit" | "details" | null;
+
+function getTemplateJsonData(template: Template) {
+ return template.jsonData ?? template.jSONData ?? "";
+}
export function TemplatesPage() {
+ const styles = useStyles();
+ const queryClient = useQueryClient();
+ const [dialogMode, setDialogMode] = useState(null);
+ const [selectedTemplate, setSelectedTemplate] = useState(null);
+ const [templateCategoryId, setTemplateCategoryId] = useState("");
+ const [name, setName] = useState("");
+ const [cloudTemplate, setCloudTemplate] = useState(false);
+ const [version, setVersion] = useState("");
+ const [description, setDescription] = useState("");
+ const [jsonData, setJsonData] = useState("");
const { data, error, isLoading } = useQuery({
queryKey: ["templates"],
queryFn: ({ signal }) => portalApi.getTemplates(signal),
});
+ const closeDialog = () => {
+ setDialogMode(null);
+ setSelectedTemplate(null);
+ setTemplateCategoryId("");
+ setName("");
+ setCloudTemplate(false);
+ setVersion("");
+ setDescription("");
+ setJsonData("");
+ };
+
+ const openAddDialog = () => {
+ closeDialog();
+ setDialogMode("add");
+ };
+
+ const openTemplateDialog = (mode: "edit" | "details", template: Template) => {
+ setSelectedTemplate(template);
+ setTemplateCategoryId(template.templateCategoryId ?? "");
+ setName(template.name);
+ setCloudTemplate(Boolean(template.cloudTemplate));
+ setVersion(template.version ?? "");
+ setDescription(template.description ?? "");
+ setJsonData(getTemplateJsonData(template));
+ setDialogMode(mode);
+ };
+
+ const addTemplate = useMutation({
+ mutationFn: portalApi.addTemplate,
+ onSuccess: async () => {
+ closeDialog();
+ await queryClient.invalidateQueries({ queryKey: ["templates"] });
+ },
+ });
+
+ const updateTemplate = useMutation({
+ mutationFn: ({
+ id,
+ template,
+ }: {
+ id: string;
+ template: {
+ templateCategoryId: string;
+ name: string;
+ cloudTemplate: boolean;
+ version: string;
+ description: string;
+ jsonData: string;
+ };
+ }) => portalApi.updateTemplate(id, template),
+ onSuccess: async () => {
+ closeDialog();
+ await queryClient.invalidateQueries({ queryKey: ["templates"] });
+ },
+ });
+
+ const deleteTemplate = useMutation({
+ mutationFn: portalApi.deleteTemplate,
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({ queryKey: ["templates"] });
+ },
+ });
+
+ const submitTemplate = (event: React.FormEvent) => {
+ event.preventDefault();
+ const template = { cloudTemplate, description, jsonData, name, templateCategoryId, version };
+
+ if (dialogMode === "edit" && selectedTemplate) {
+ updateTemplate.mutate({ id: selectedTemplate.id, template });
+ return;
+ }
+
+ addTemplate.mutate(template);
+ };
+
+ const formError = addTemplate.error?.message ?? updateTemplate.error?.message;
+ const isSaving = addTemplate.isPending || updateTemplate.isPending;
+ const isDetails = dialogMode === "details";
+
return (
<>
-
+
+ } onClick={openAddDialog}>
+ Template hinzufuegen
+
+
+
+
+
+
{data && (
-
- Name
- Id
-
+
+ Name
+ Version
+ Cloud
+ TemplateCategoryId
+ Id
+ Aktionen
+
{data.map((template) => (
{template.name}
+ {template.version}
+ {template.cloudTemplate ? "Ja" : "Nein"}
+ {template.templateCategoryId}
{template.id}
+
+
+
+ }
+ onClick={() => openTemplateDialog("details", template)}
+ />
+
+
+ }
+ onClick={() => openTemplateDialog("edit", template)}
+ />
+
+
+ }
+ onClick={() => {
+ if (window.confirm(`Template "${template.name}" wirklich loeschen?`)) {
+ deleteTemplate.mutate(template.id);
+ }
+ }}
+ />
+
+
+
))}
diff --git a/src/types/portal.ts b/src/types/portal.ts
index df78bb3..64cabb0 100644
--- a/src/types/portal.ts
+++ b/src/types/portal.ts
@@ -27,6 +27,12 @@ export type Domain = {
netBIOS: string;
};
+export type DomainWithEnvironments = Domain & {
+ environmentDomains?: Array<{
+ environment?: EnvironmentItem;
+ }>;
+};
+
export type AddDomain = {
name: string;
fqdn: string;
@@ -38,6 +44,12 @@ export type EnvironmentItem = {
name: string;
};
+export type EnvironmentWithDomains = EnvironmentItem & {
+ environmentDomains?: Array<{
+ domain?: Domain;
+ }>;
+};
+
export type AddEnvironment = {
name: string;
};
@@ -56,9 +68,30 @@ export type AddRunbook = {
export type Template = {
id: string;
name: string;
+ templateCategoryId?: string;
+ cloudTemplate?: boolean;
+ version?: string;
+ description?: string;
+ jsonData?: string;
+ jSONData?: string;
+};
+
+export type AddTemplate = {
+ templateCategoryId: string;
+ name: string;
+ cloudTemplate: boolean;
+ version: string;
+ description: string;
+ jsonData: string;
};
export type ServiceItem = {
id: string;
name: string;
+ description?: string;
+};
+
+export type AddService = {
+ name: string;
+ description: string;
};