Skip to content

Commit 9a3c310

Browse files
committed
feat(audit-trail): implement summary, records, and metadata views
1 parent 61782bc commit 9a3c310

5 files changed

Lines changed: 485 additions & 0 deletions

File tree

apps/explorer/src/pages/trust-framework/audit-trail-result/AuditTrailContent.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import {
1818
useResolveOnChainAuditTrail,
1919
} from '~/hooks/useResolveAuditTrail';
2020
import { TransactionsView } from '../common/TransactionsView';
21+
import { AuditTrailSummaryView } from './views/AuditTrailSummaryView';
22+
import { MetadataView } from './views/MetadataView';
23+
import { TagsView } from './views/TagsView';
24+
import { RecordsView } from './views/RecordsView';
25+
import { SideBySidePanels } from '~/components/ui/SideBySidePanels';
2126

2227
interface AuditTrailContentProps {
2328
objectId: string;
@@ -108,6 +113,20 @@ export function AuditTrailContent({ objectId }: AuditTrailContentProps) {
108113
.addItem(getAuditTrailRecordsSize(auditTrailObject))
109114
.build()}
110115
/>
116+
<AuditTrailSummaryView
117+
auditTrailObject={auditTrailObject}
118+
objectData={objectResult.data!}
119+
/>
120+
<SideBySidePanels
121+
firstPanel={<p>Replace with LockLifecycleView</p>}
122+
secondPanel={<MetadataView auditTrail={auditTrailObject} />}
123+
/>
124+
<RecordsView objectId={objectId} auditTrail={auditTrailHandle} />
125+
<SideBySidePanels
126+
ratio="66-34"
127+
firstPanel={<p>Replace with RolesView</p>}
128+
secondPanel={<TagsView tags={auditTrailObject.tags} />}
129+
/>
111130
<TransactionsView objectId={objectId} />
112131
</div>
113132
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright (c) 2026 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { DisplayStats, TooltipPosition } from '@iota/apps-ui-kit';
5+
import { formatDate, useFormatCoin } from '@iota/core';
6+
import { type IotaObjectData } from '@iota/iota-sdk/client';
7+
import { CoinFormat, formatDigest } from '@iota/iota-sdk/utils';
8+
import clsx from 'clsx';
9+
import { ObjectLink, TransactionLink } from '~/components/ui';
10+
import { onCopySuccess } from '~/lib/utils';
11+
import { ErrorBoundary } from '~/components';
12+
// TODO: use '@iota/audit-trail/web' after published
13+
import { type OnChainAuditTrail } from '@iota/audit-trail';
14+
15+
interface AuditTrailSummaryViewProps {
16+
auditTrailObject: OnChainAuditTrail;
17+
objectData: IotaObjectData;
18+
}
19+
20+
export function AuditTrailSummaryView({
21+
auditTrailObject,
22+
objectData,
23+
}: AuditTrailSummaryViewProps): JSX.Element {
24+
const objectId = objectData?.objectId;
25+
const storageRebate = objectData?.storageRebate;
26+
const version = `v${auditTrailObject.version}`;
27+
const sequenceNumber = `${auditTrailObject.sequenceNumber}`;
28+
29+
const dateFormat = (timestamp: bigint): string =>
30+
// Convert seconds to milliseconds for Date constructor
31+
formatDate(new Date(Number(timestamp)), ['year', 'month', 'day', 'hour', 'minute']);
32+
const createdAt = dateFormat(auditTrailObject.createdAt);
33+
const lastTransactionBlockDigest = objectData?.previousTransaction;
34+
35+
return (
36+
<ErrorBoundary>
37+
<div className="flex flex-col gap-md">
38+
<div className={clsx('address-grid-container-top', 'no-image', 'no-description')}>
39+
{objectId && (
40+
<div>
41+
<ObjectIdCard objectId={objectId} />
42+
</div>
43+
)}
44+
45+
{version && (
46+
<div>
47+
<DisplayStats
48+
label="Version"
49+
value={version}
50+
tooltipPosition={TooltipPosition.Left}
51+
tooltipText="Version of object in a progressive sequence."
52+
/>
53+
</div>
54+
)}
55+
56+
{storageRebate && (
57+
<div>
58+
<StorageRebateCard storageRebate={storageRebate} />
59+
</div>
60+
)}
61+
62+
{createdAt && (
63+
<div>
64+
<DisplayStats
65+
label="Created at"
66+
value={createdAt}
67+
tooltipPosition={TooltipPosition.Left}
68+
tooltipText="Timestamp of the transaction that first published this audit trail onchain."
69+
/>
70+
</div>
71+
)}
72+
73+
{sequenceNumber && (
74+
<div>
75+
<DisplayStats
76+
label="Sequence"
77+
value={sequenceNumber}
78+
tooltipPosition={TooltipPosition.Left}
79+
tooltipText="Version of state change in a progressive sequence."
80+
/>
81+
</div>
82+
)}
83+
84+
{lastTransactionBlockDigest && (
85+
<div>
86+
<LastTxBlockCard digest={lastTransactionBlockDigest} />
87+
</div>
88+
)}
89+
</div>
90+
</div>
91+
</ErrorBoundary>
92+
);
93+
}
94+
95+
interface ObjectIdCardProps {
96+
objectId: string;
97+
}
98+
99+
function ObjectIdCard({ objectId }: ObjectIdCardProps): JSX.Element {
100+
return (
101+
<DisplayStats
102+
label="Object ID"
103+
value={
104+
<div className="flex flex-col gap-xs">
105+
<ObjectLink objectId={objectId} copyText={objectId} />
106+
</div>
107+
}
108+
tooltipPosition={TooltipPosition.Left}
109+
tooltipText="The unique onchain identifier of the Move object storing this audit trail's state."
110+
/>
111+
);
112+
}
113+
114+
interface LastTxBlockCardProps {
115+
digest: string;
116+
}
117+
118+
function LastTxBlockCard({ digest }: LastTxBlockCardProps): JSX.Element {
119+
return (
120+
<DisplayStats
121+
label="Last Transaction Block Digest"
122+
value={<TransactionLink digest={digest}>{formatDigest(digest)}</TransactionLink>}
123+
copyText={digest}
124+
onCopySuccess={onCopySuccess}
125+
tooltipPosition={TooltipPosition.Left}
126+
tooltipText="Hash of the most recent transaction that modified this audit trail. Use it to inspect transaction details on the explorer."
127+
/>
128+
);
129+
}
130+
131+
interface StorageRebateCardProps {
132+
storageRebate: string;
133+
}
134+
135+
function StorageRebateCard({ storageRebate }: StorageRebateCardProps): JSX.Element | null {
136+
const [storageRebateFormatted, symbol] = useFormatCoin({
137+
balance: storageRebate,
138+
format: CoinFormat.Full,
139+
});
140+
141+
return (
142+
<DisplayStats
143+
label="Storage Rebate"
144+
value={`-${storageRebateFormatted}`}
145+
supportingLabel={symbol}
146+
tooltipPosition={TooltipPosition.Left}
147+
tooltipText="IOTA tokens locked as a storage deposit for this object. Partially refundable when the object is deleted or reduced in size."
148+
/>
149+
);
150+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) 2026 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Title, TooltipPosition, Panel } from '@iota/apps-ui-kit';
5+
// TODO: use '@iota/audit-trail/web' after published
6+
import { type ImmutableMetadata, type OnChainAuditTrail } from '@iota/audit-trail';
7+
import { ErrorBoundary, SyntaxHighlighter } from '~/components';
8+
9+
interface MetadataViewProps {
10+
auditTrail: OnChainAuditTrail;
11+
}
12+
13+
interface LabelledSyntaxHighlighterProps {
14+
label: string;
15+
code: string;
16+
language: string;
17+
}
18+
19+
function LabelledSyntaxHighlighter({ label, code, language }: LabelledSyntaxHighlighterProps) {
20+
return (
21+
<div className="flex flex-col">
22+
<SyntaxHighlighter code={code} language={language} />
23+
<span className="mt-1 text-body-sm text-gray-500 dark:text-gray-400">{label}</span>
24+
</div>
25+
);
26+
}
27+
28+
export function MetadataView({ auditTrail }: MetadataViewProps) {
29+
const immutableMetadata = auditTrail.immutableMetadata;
30+
const updatableMetadata = auditTrail.updatableMetadata;
31+
32+
return (
33+
<ErrorBoundary>
34+
<div className="panel-bg flex w-full flex-col gap-sm--rs rounded-xl border border-transparent">
35+
<UpdatableMetadataPanel metadata={updatableMetadata} />
36+
<ImmutableMetadataPanel metadata={immutableMetadata} />
37+
</div>
38+
</ErrorBoundary>
39+
);
40+
}
41+
42+
function ImmutableMetadataPanel({ metadata }: { metadata?: ImmutableMetadata }) {
43+
return (
44+
<Panel>
45+
<div className="flex w-full flex-col gap-sm">
46+
<Title
47+
title="Immutable Metadata"
48+
tooltipPosition={TooltipPosition.Left}
49+
tooltipText="The immutable metadata of this Audit Trail. This data cannot be changed."
50+
/>
51+
<div className="flex flex-col gap-y-md">
52+
<LabelledSyntaxHighlighter
53+
label="Name (Text)"
54+
code={metadata?.name ?? ''}
55+
language="text"
56+
/>
57+
<LabelledSyntaxHighlighter
58+
label="Description (Text)"
59+
code={metadata?.description ?? ''}
60+
language="text"
61+
/>
62+
</div>
63+
</div>
64+
</Panel>
65+
);
66+
}
67+
68+
function UpdatableMetadataPanel({ metadata }: { metadata?: string }) {
69+
return (
70+
<Panel>
71+
<div className="flex w-full flex-col gap-sm">
72+
<Title
73+
title="Updatable Metadata"
74+
tooltipPosition={TooltipPosition.Left}
75+
tooltipText="The updatable metadata of this Audit Trail. This data can be changed by authorized actors."
76+
/>
77+
<LabelledSyntaxHighlighter
78+
label="Metadata (Text)"
79+
code={metadata ?? ''}
80+
language="text"
81+
/>
82+
</div>
83+
</Panel>
84+
);
85+
}

0 commit comments

Comments
 (0)