Files
Microsoft.SelfService.Porta…/src/pages/EnvironmentDetailsPage.tsx
Torsten Brendgen 3083e81452 feat: Refactor deployment types and add new deployment rules management
- Updated DeploymentExecution and related types to use targetId instead of virtualMachineId.
- Introduced new types for DeploymentRule, DeploymentRuleStep, and related entities.
- Added DeploymentRulesPage for managing deployment rules with CRUD operations.
- Created TargetDetailsPage and TargetsPage for managing targets with linking and unlinking functionality.
- Enhanced UI components with Fluent UI for better user experience.
- Implemented data fetching and state management using React Query.
2026-07-07 23:30:08 +02:00

208 lines
7.2 KiB
TypeScript

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
Badge,
Button,
makeStyles,
MessageBar,
MessageBarBody,
shorthands,
Table,
TableBody,
TableCell,
TableHeader,
TableHeaderCell,
TableRow,
Text,
Title3,
tokens,
Tooltip,
} from "@fluentui/react-components";
import { ArrowLeftRegular, DeleteRegular, OpenRegular } 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",
},
actions: {
display: "flex",
gap: "4px",
...shorthands.padding("2px", "0"),
},
});
export function EnvironmentDetailsPage() {
const styles = useStyles();
const queryClient = useQueryClient();
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) ?? [];
const unlinkDomainFromEnvironment = useMutation({
mutationFn: ({ domainId, environmentId }: { domainId: string; environmentId: string }) =>
portalApi.unlinkDomainFromEnvironment(domainId, environmentId),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["environment", id, "domains"] });
await queryClient.invalidateQueries({ queryKey: ["domains"] });
await queryClient.invalidateQueries({ queryKey: ["environments"] });
},
});
return (
<>
<Link className={styles.backLink} to="/environments">
<Button appearance="subtle" icon={<ArrowLeftRegular />}>
Environments
</Button>
</Link>
<PageHeader title={data?.name ?? "Environment"} description="Details und verknuepfte Domains." />
<DataState isLoading={isLoading} error={error} />
{data && (
<>
<section className={styles.details} aria-label="Environment details">
<div className={styles.field}>
<Text size={200}>Name</Text>
<Text className={styles.value} weight="semibold">
{data.name}
</Text>
</div>
<div className={styles.field}>
<Text size={200}>Id</Text>
<Text className={styles.value} weight="semibold">
{data.id}
</Text>
</div>
<div className={styles.field}>
<Text size={200}>Environment Stage</Text>
<Text className={styles.value} weight="semibold">
{data.environmentType}
</Text>
</div>
<div className={styles.field}>
<Text size={200}>Hosting Type</Text>
<Text className={styles.value} weight="semibold">
{data.hostingType}
</Text>
</div>
<div className={styles.field}>
<Text size={200}>Cloud Enabled</Text>
<Text className={styles.value} weight="semibold">
{data.hostingType === "OnPrem" ? "Nein" : "Ja"}
</Text>
</div>
<div className={styles.field}>
<Text size={200}>Provider Type</Text>
<Text className={styles.value} weight="semibold">
{data.hostingType === "OnPrem" ? data.providerType : ""}
</Text>
</div>
{data.hostingType !== "OnPrem" && (
<div className={styles.field}>
<Text size={200}>Tenant Id</Text>
<Text className={styles.value} weight="semibold">
{data.tenantId}
</Text>
</div>
)}
{data.hostingType === "AzureTenant" && (
<div className={styles.field}>
<Text size={200}>Subscription Id</Text>
<Text className={styles.value} weight="semibold">
{data.subscriptionId}
</Text>
</div>
)}
</section>
<Title3 className={styles.sectionTitle}>Linked Domains</Title3>
{links.length === 0 ? (
<MessageBar>
<MessageBarBody>Dieses Environment ist noch mit keiner Domain verknuepft.</MessageBarBody>
</MessageBar>
) : (
<Table aria-label="Linked domains">
<TableHeader>
<TableRow>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>FQDN</TableHeaderCell>
<TableHeaderCell>NetBIOS</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
<TableHeaderCell>Actions</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{links.map((link) => (
<TableRow key={link.domain!.id}>
<TableCell>{link.domain!.name}</TableCell>
<TableCell>{link.domain!.fqdn}</TableCell>
<TableCell>{link.domain!.netBIOS}</TableCell>
<TableCell>
<Badge appearance="filled" color="success">
Linked
</Badge>
</TableCell>
<TableCell>
<div className={styles.actions}>
<Tooltip content="Details" relationship="label">
<Link to={`/domains/${link.domain!.id}`}>
<Button appearance="subtle" aria-label="Details" icon={<OpenRegular />} />
</Link>
</Tooltip>
<Tooltip content="Unlink" relationship="label">
<Button
appearance="subtle"
aria-label="Unlink"
disabled={unlinkDomainFromEnvironment.isPending}
icon={<DeleteRegular />}
onClick={() => {
if (!id) return;
unlinkDomainFromEnvironment.mutate({
domainId: link.domain!.id,
environmentId: id,
});
}}
/>
</Tooltip>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</>
)}
</>
);
}