@@ -36,6 +36,10 @@ const config = $derived({
3636let ontologies: { id: string ; title: string }[] = $state ([]);
3737let selectedOntologyId: string | null = $state (null );
3838let inspectorEName: string = $state (" " );
39+ const ontologyTitleMap = $derived (
40+ new Map (ontologies .map ((o ) => [o .id , o .title || o .id ])),
41+ );
42+
3943let schemasLoading = $state (false );
4044let schemasError: string | null = $state (null );
4145
@@ -258,6 +262,88 @@ let beforeCursor: string | null = $state(null);
258262let pageOffset: number = $state (0 );
259263let currentTab = $state <string >(" sandbox" );
260264
265+ // eVault Logs state
266+ interface EvaultLog {
267+ id: string ;
268+ eName: string ;
269+ metaEnvelopeId: string ;
270+ envelopeHash: string ;
271+ operation: string ;
272+ platform: string ;
273+ timestamp: string ;
274+ ontology: string ;
275+ }
276+ let logsEName: string = $state (" " );
277+ let evaultLogs: EvaultLog [] = $state ([]);
278+ let logsLoading = $state (false );
279+ let logsError: string | null = $state (null );
280+ let logsNextCursor: string | null = $state (null );
281+ let logsHasMore = $state (false );
282+ let logsPageSize = 20 ;
283+
284+ async function loadEvaultLogs(cursor ? : string | null ): Promise <void > {
285+ const ename = logsEName .trim () || selectedIdentity ?.w3id ;
286+ if (! ename ) {
287+ logsError = " Enter an eName or provision an identity first." ;
288+ return ;
289+ }
290+
291+ logsLoading = true ;
292+ logsError = null ;
293+ if (ontologies .length === 0 ) await loadOntologies ();
294+ try {
295+ const token = await getPlatformToken ();
296+
297+ // Resolve eVault URI
298+ let baseUrl: string ;
299+ const lookupEName = logsEName .trim ();
300+ if (! lookupEName && selectedIdentity ) {
301+ baseUrl = selectedIdentity .uri .replace (/ \/ + $ / , " " );
302+ } else {
303+ const resolveRes = await fetch (
304+ new URL (` resolve?w3id=${encodeURIComponent (ename )} ` , registryUrl ).toString (),
305+ );
306+ if (! resolveRes .ok ) throw new Error (` Registry resolve failed: ${resolveRes .status } ` );
307+ const resolveData = await resolveRes .json ();
308+ if (! resolveData .uri ) throw new Error (" Registry returned no URI for that eName." );
309+ baseUrl = resolveData .uri .replace (/ \/ + $ / , " " );
310+ }
311+
312+ const params = new URLSearchParams ({ limit: String (logsPageSize ) });
313+ if (cursor ) params .set (" cursor" , cursor );
314+
315+ const headers: Record <string , string > = { " X-ENAME" : ename };
316+ if (token ) headers [" Authorization" ] = ` Bearer ${token } ` ;
317+
318+ const res = await fetch (` ${baseUrl }/logs?${params .toString ()} ` , { headers });
319+ if (! res .ok ) throw new Error (` Logs request failed: ${res .status } ` );
320+
321+ const data = await res .json ();
322+ if (cursor ) {
323+ evaultLogs = [... evaultLogs , ... data .logs ];
324+ } else {
325+ evaultLogs = data .logs ;
326+ }
327+ logsNextCursor = data .nextCursor ?? null ;
328+ logsHasMore = data .hasMore ?? false ;
329+ } catch (e ) {
330+ logsError = e instanceof Error ? e .message : String (e );
331+ } finally {
332+ logsLoading = false ;
333+ }
334+ }
335+
336+ function loadLogsFirstPage() {
337+ evaultLogs = [];
338+ logsNextCursor = null ;
339+ logsHasMore = false ;
340+ loadEvaultLogs ();
341+ }
342+
343+ function loadLogsNextPage() {
344+ if (logsNextCursor ) loadEvaultLogs (logsNextCursor );
345+ }
346+
261347// Expanded envelope IDs set
262348let expandedIds = $state (new Set <string >());
263349function toggleExpand(id : string ) {
@@ -786,6 +872,13 @@ async function doSign() {
786872 >
787873 eVault Inspector
788874 </button >
875+ <button
876+ class:active ={currentTab === " logs" }
877+ type =" button"
878+ onclick ={() => (currentTab = " logs" )}
879+ >
880+ eVault Logs
881+ </button >
789882 <button
790883 class:active ={currentTab === " config" }
791884 type =" button"
@@ -1065,6 +1158,72 @@ async function doSign() {
10651158 {/if }
10661159 </div >
10671160
1161+ <div class ="view view-logs" class:hidden ={currentTab !== ' logs' }>
1162+ <section class =" card" >
1163+ <h2 >eVault Operation Logs</h2 >
1164+ <p class =" config-hint" >View create, update, and delete operations logged by an eVault.</p >
1165+ <div class =" field" >
1166+ <label for =" logsEName" ><strong >eName (X-ENAME):</strong ></label >
1167+ <input
1168+ id =" logsEName"
1169+ type =" text"
1170+ bind:value ={logsEName }
1171+ placeholder =" Enter any eName"
1172+ />
1173+ </div >
1174+ <div class =" inspector-actions" >
1175+ <button disabled ={logsLoading || (! logsEName .trim () && ! selectedIdentity )} onclick ={loadLogsFirstPage }>
1176+ {logsLoading && evaultLogs .length === 0 ? " Loading..." : " Load Logs" }
1177+ </button >
1178+ </div >
1179+ </section >
1180+
1181+ {#if logsError }
1182+ <p class ="error" >{logsError }</p >
1183+ {/if }
1184+
1185+ {#if logsLoading && evaultLogs .length === 0 }
1186+ <div class =" envelope-loading" >Loading logs...</div >
1187+ {:else if evaultLogs .length === 0 && ! logsError && currentTab === ' logs' }
1188+ <div class =" envelope-empty" >No logs loaded. Enter an eName and click Load Logs.</div >
1189+ {:else if evaultLogs .length > 0 }
1190+ <div class =" logs-table-wrap" >
1191+ <table class =" logs-table" >
1192+ <thead >
1193+ <tr >
1194+ <th >Timestamp</th >
1195+ <th >Operation</th >
1196+ <th >Ontology</th >
1197+ <th >MetaEnvelope ID</th >
1198+ <th >Platform</th >
1199+ <th >Hash</th >
1200+ </tr >
1201+ </thead >
1202+ <tbody >
1203+ {#each evaultLogs as log (log .id )}
1204+ <tr >
1205+ <td class ="logs-td-ts" >{log .timestamp .slice (0 , 19 ).replace (' T' , ' ' )}</td >
1206+ <td ><span class ="logs-op-badge" class:op-create ={log .operation === ' create' } class:op-update ={log .operation === ' update' || log .operation === ' update_envelope_value' } class:op-delete ={log .operation === ' delete' }>{log .operation }</span ></td >
1207+ <td class ="logs-td-ontology" >{ontologyTitleMap .get (log .ontology ) ?? " Unknown" }</td >
1208+ <td class ="logs-td-mono" >{log .metaEnvelopeId }</td >
1209+ <td >{log .platform }</td >
1210+ <td class ="logs-td-mono logs-td-hash" >{log .envelopeHash }</td >
1211+ </tr >
1212+ {/each }
1213+ </tbody >
1214+ </table >
1215+ </div >
1216+
1217+ {#if logsHasMore }
1218+ <div class =" pagination-footer" >
1219+ <button disabled ={logsLoading } onclick ={loadLogsNextPage }>
1220+ {logsLoading ? " Loading..." : " Load More" }
1221+ </button >
1222+ </div >
1223+ {/if }
1224+ {/if }
1225+ </div >
1226+
10681227 <div class ="view view-config" class:hidden ={currentTab !== ' config' }>
10691228 <section class =" card" >
10701229 <h2 >Sandbox Config</h2 >
@@ -1707,4 +1866,77 @@ async function doSign() {
17071866 color : var (--muted , #64748b );
17081867 margin-right : 0.35rem ;
17091868 }
1869+
1870+ /* eVault Logs — table */
1871+ .logs-table-wrap {
1872+ overflow-x : auto ;
1873+ border : 1px solid var (--border , #e2e8f0 );
1874+ border-radius : 10px ;
1875+ background : var (--bg-card , #fff );
1876+ box-shadow : 0 1px 3px rgba (0 , 0 , 0 , 0.06 );
1877+ }
1878+ .logs-table {
1879+ width : 100% ;
1880+ border-collapse : collapse ;
1881+ font-size : 0.82rem ;
1882+ }
1883+ .logs-table thead th {
1884+ text-align : left ;
1885+ padding : 0.6rem 0.75rem ;
1886+ font-weight : 600 ;
1887+ font-size : 0.75rem ;
1888+ text-transform : uppercase ;
1889+ letter-spacing : 0.03em ;
1890+ color : var (--muted , #64748b );
1891+ background : var (--bg-page , #f0f2f5 );
1892+ border-bottom : 2px solid var (--border , #e2e8f0 );
1893+ white-space : nowrap ;
1894+ }
1895+ .logs-table tbody tr {
1896+ border-bottom : 1px solid var (--border , #e2e8f0 );
1897+ }
1898+ .logs-table tbody tr :last-child {
1899+ border-bottom : none ;
1900+ }
1901+ .logs-table tbody tr :hover {
1902+ background : var (--bg-page , #f8fafc );
1903+ }
1904+ .logs-table td {
1905+ padding : 0.55rem 0.75rem ;
1906+ vertical-align : top ;
1907+ }
1908+ .logs-td-ts {
1909+ white-space : nowrap ;
1910+ color : var (--muted , #64748b );
1911+ font-size : 0.78rem ;
1912+ }
1913+ .logs-td-mono {
1914+ font-family : ui-monospace , " Cascadia Code" , " SF Mono" , monospace ;
1915+ font-size : 0.75rem ;
1916+ word-break : break-all ;
1917+ }
1918+ .logs-td-hash {
1919+ color : var (--muted , #64748b );
1920+ max-width : 18ch ;
1921+ overflow : hidden ;
1922+ text-overflow : ellipsis ;
1923+ white-space : nowrap ;
1924+ }
1925+ .logs-td-ontology {
1926+ font-weight : 500 ;
1927+ }
1928+ .logs-op-badge {
1929+ display : inline-block ;
1930+ font-size : 0.72rem ;
1931+ font-weight : 600 ;
1932+ text-transform : uppercase ;
1933+ padding : 0.15em 0.45em ;
1934+ border-radius : 4px ;
1935+ background : #475569 ;
1936+ color : #fff ;
1937+ white-space : nowrap ;
1938+ }
1939+ .logs-op-badge.op-create { background : #15803d ; }
1940+ .logs-op-badge.op-update { background : #b45309 ; }
1941+ .logs-op-badge.op-delete { background : #b91c1c ; }
17101942 </style >
0 commit comments