- Implement folder account options helper for filtering access items based on person ID and date. - Create FolderAccessAccountField component for selecting accounts with profile integration. - Develop AssetLocalIdentities component for managing local identities associated with assets. - Introduce LocalIdentityOwner component to display and manage local identity ownership. - Add LocalIdentityEditor for creating, associating, and retiring local identities. - Implement local identity API for fetching and executing local identity commands. - Create tests for local identity UI components to ensure functionality and correctness. - Add NetworkShareOptions class for structured and paginated SMB selection. - Introduce NetworkShareAddress component for displaying and copying network share addresses. - Create RemoteOptionList and RemoteOptionPicker components for remote option selection with pagination and search. - Add tests for RemoteOptionList to validate rendering and behavior under various states.
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
|
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
|
|
|
|
import { IntegrationSummary } from "./integration-summary";
|
|
import { NetworkShareAddress } from "./network-share-address";
|
|
|
|
export function ResourceDetail({
|
|
resource,
|
|
references = [],
|
|
compact = false,
|
|
}: {
|
|
resource: ResourceDTO;
|
|
compact?: boolean;
|
|
references?: readonly ResourceReferenceDTO[];
|
|
}) {
|
|
const { t, i18n } = useTranslation("resources");
|
|
|
|
const display = (value: string | number | boolean | undefined) =>
|
|
value === undefined
|
|
? t("noValue")
|
|
: typeof value === "boolean"
|
|
? t(value ? "yes" : "no")
|
|
: String(value);
|
|
|
|
return (
|
|
<div className="@container space-y-5">
|
|
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
|
{!compact && (
|
|
<dl className="grid gap-4 @min-[560px]:grid-cols-2">
|
|
<div>
|
|
<dt className="text-sm text-muted-foreground">{t("template")}</dt>
|
|
<dd>{resource.specification.templateName}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-muted-foreground">{t("status")}</dt>
|
|
<dd>{t(resource.values.active ? "active" : "inactive")}</dd>
|
|
</div>
|
|
</dl>
|
|
)}
|
|
<FieldSections
|
|
items={resource.specification.fields}
|
|
layout={compact ? "rows" : "fields"}
|
|
renderItem={(field) => (
|
|
<DetailField
|
|
key={field.fieldId}
|
|
label={
|
|
<>
|
|
{field.name}
|
|
{field.unit ? ` (${field.unit})` : ""}
|
|
</>
|
|
}
|
|
>
|
|
{resource.networkShare?.capability === "network:smb-share" &&
|
|
resource.values.characteristics[field.fieldId] === resource.networkShare.address ? (
|
|
<NetworkShareAddress address={resource.networkShare.address} />
|
|
) : (
|
|
display(resource.values.characteristics[field.fieldId])
|
|
)}
|
|
</DetailField>
|
|
)}
|
|
sections={resource.specification.sections}
|
|
ungroupedLabel={t("sections.ungrouped")}
|
|
values
|
|
/>
|
|
<IntegrationSummary integration={resource.values.integration} references={references} />
|
|
{resource.values.notes && (
|
|
<p className="whitespace-pre-wrap break-words">{resource.values.notes}</p>
|
|
)}
|
|
{!compact && (
|
|
<>
|
|
<FormSectionTitle>
|
|
{t("history")} ({resource.history.length})
|
|
</FormSectionTitle>
|
|
<ol className="space-y-4">
|
|
{[...resource.history].reverse().map((revision) => (
|
|
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
|
<p className="font-medium">
|
|
{new Intl.DateTimeFormat(i18n.language, {
|
|
dateStyle: "medium",
|
|
timeStyle: "short",
|
|
}).format(new Date(revision.recordedAt))}{" "}
|
|
· {t("recordedBy", { name: revision.actorName })}
|
|
</p>
|
|
<p>
|
|
{revision.values.name} · {t(revision.values.active ? "active" : "inactive")}
|
|
</p>
|
|
<FieldSections
|
|
items={(revision.specification ?? resource.specification).fields}
|
|
renderItem={(field) => (
|
|
<div key={field.fieldId}>
|
|
<dt className="text-muted-foreground">{field.name}</dt>
|
|
<dd className="whitespace-pre-wrap break-words">
|
|
{display(revision.values.characteristics[field.fieldId])}
|
|
</dd>
|
|
</div>
|
|
)}
|
|
sections={(revision.specification ?? resource.specification).sections}
|
|
ungroupedLabel={t("sections.ungrouped")}
|
|
values
|
|
/>
|
|
<IntegrationSummary
|
|
integration={revision.values.integration}
|
|
references={references}
|
|
/>
|
|
{revision.values.notes && (
|
|
<p className="whitespace-pre-wrap break-words">{revision.values.notes}</p>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|