-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTimeline.jsx
More file actions
112 lines (108 loc) · 5.28 KB
/
Copy pathTimeline.jsx
File metadata and controls
112 lines (108 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { useState } from 'react';
import { fmtBlock, shortHash, blocksToApproxTime } from '../data/launches.js';
const TYPE_META = {
wake: { label: 'Scheduled wake', tone: 'text-fog border-line' },
check_ok: { label: 'Audit passed', tone: 'text-status-good border-status-good/40' },
checkpoint: { label: 'Checkpoint', tone: 'text-fog border-line' },
release: { label: 'Vesting release', tone: 'text-gold border-gold/40' },
flag: { label: 'Flagged', tone: 'text-status-warn border-status-warn/40' },
freeze: { label: 'Freeze executed', tone: 'text-status-warn border-status-warn/60' },
revival: { label: 'Consensus revival', tone: 'text-status-info border-status-info/40' },
};
const ICONS = {
wake: 'M8 4v4l2.5 1.5M14 8A6 6 0 1 1 2 8a6 6 0 0 1 12 0z',
check_ok: 'M8 1.5 13.5 4v4c0 3.2-2.3 5.6-5.5 6.5C4.8 13.6 2.5 11.2 2.5 8V4L8 1.5zM5.8 8l1.6 1.6L10.5 6.4',
checkpoint: 'M3 3h8l2 2v8H3V3zM5.5 3v3h4V3M5.5 13V9h5v4',
release: 'M8 10.5V3M8 3 5 6M8 3l3 3M2.5 10.5v2a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-2',
flag: 'M4 14V2.5M4 3h8l-1.8 2.5L12 8H4',
freeze: 'M8 1.5v13M8 8l4.5-2.6M8 8 3.5 5.4M8 8l4.5 2.6M8 8l-4.5 2.6M5.5 2.8 8 4.2l2.5-1.4M5.5 13.2 8 11.8l2.5 1.4',
revival: 'M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2.5v2.6h-2.6',
};
function EventIcon({ type }) {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d={ICONS[type] ?? ICONS.wake} stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
/**
* Enforcement log timeline — every entry carries its TEE attestation hash.
* Optional `pageSize` paginates long logs; the page index is clamped every render
* because the events array can shrink or grow under us on background refetches.
*/
export default function Timeline({ events, currentBlock, pageSize }) {
const [page, setPage] = useState(0);
if (events.length === 0) {
return <p className="text-sm text-faint">No enforcement actions recorded in the recent block window.</p>;
}
const paginated = pageSize > 0 && events.length > pageSize;
const pageCount = paginated ? Math.ceil(events.length / pageSize) : 1;
const current = Math.min(page, pageCount - 1);
const visible = paginated ? events.slice(current * pageSize, (current + 1) * pageSize) : events;
return (
<div>
<ol className="relative">
{visible.map((e, i) => {
const meta = TYPE_META[e.type] ?? TYPE_META.wake;
return (
<li key={`${e.block}-${i}`} className="relative flex gap-4 pb-6 last:pb-0">
{i < visible.length - 1 && (
<span aria-hidden="true" className="absolute left-[15px] top-8 h-full w-px bg-linefaint" />
)}
<span
className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border bg-surface ${meta.tone}`}
>
<EventIcon type={e.type} />
</span>
<div className="min-w-0 pt-0.5">
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-0.5">
<span className={`text-xs font-semibold uppercase tracking-wider ${meta.tone.split(' ')[0]}`}>
{meta.label}
</span>
<span className="mono text-faint">
block {fmtBlock(e.block)} · {blocksToApproxTime(currentBlock - e.block)} ago
</span>
</div>
<p className="mt-1 text-sm leading-relaxed text-fog">{e.detail}</p>
<span
title={e.attestation}
className="mono mt-1 inline-flex items-center gap-1.5 text-ember/90"
>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M6 1.2 10.5 3v3c0 2.4-1.8 4.2-4.5 4.8C3.3 10.2 1.5 8.4 1.5 6V3L6 1.2z" stroke="currentColor" strokeWidth="1.1" />
</svg>
TEE attestation {shortHash(e.attestation)}
</span>
</div>
</li>
);
})}
</ol>
{paginated && (
<div className="mt-5 flex items-center justify-between border-t border-linefaint pt-3">
<span className="text-[11px] uppercase tracking-wider text-faint">
{current * pageSize + 1}–{Math.min((current + 1) * pageSize, events.length)} of {events.length}
</span>
<div className="flex gap-2">
<button
type="button"
disabled={current === 0}
onClick={() => setPage(current - 1)}
className="rounded-md border border-line px-2.5 py-1 text-xs font-semibold text-cream transition-colors duration-200 hover:border-fog/60 hover:bg-raise disabled:pointer-events-none disabled:opacity-40"
>
Newer
</button>
<button
type="button"
disabled={current === pageCount - 1}
onClick={() => setPage(current + 1)}
className="rounded-md border border-line px-2.5 py-1 text-xs font-semibold text-cream transition-colors duration-200 hover:border-fog/60 hover:bg-raise disabled:pointer-events-none disabled:opacity-40"
>
Older
</button>
</div>
</div>
)}
</div>
);
}