Skip to content

Commit 62658ff

Browse files
authored
feat: blabsy and pictqiue filter (#953)
1 parent a4be956 commit 62658ff

3 files changed

Lines changed: 241 additions & 4 deletions

File tree

infrastructure/dev-sandbox/src/routes/+page.svelte

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const config = $derived({
3636
let ontologies: { id: string; title: string }[] = $state([]);
3737
let selectedOntologyId: string | null = $state(null);
3838
let inspectorEName: string = $state("");
39+
const ontologyTitleMap = $derived(
40+
new Map(ontologies.map((o) => [o.id, o.title || o.id])),
41+
);
42+
3943
let schemasLoading = $state(false);
4044
let schemasError: string | null = $state(null);
4145
@@ -258,6 +262,88 @@ let beforeCursor: string | null = $state(null);
258262
let pageOffset: number = $state(0);
259263
let 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
262348
let expandedIds = $state(new Set<string>());
263349
function 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>

platforms/blabsy/client/src/components/chat/chat-list.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ export function ChatList(): JSX.Element {
6666
return <Loading className='mt-5' />;
6767
}
6868

69-
if (!chats?.length) {
69+
const visibleChats =
70+
chats?.filter((chat) => chat.participants.length >= 2) ?? [];
71+
72+
if (!visibleChats.length) {
7073
console.log('ChatList: No chats found');
7174
return (
7275
<div className='flex h-full flex-col gap-4'>
@@ -94,7 +97,7 @@ export function ChatList(): JSX.Element {
9497
return (
9598
<div className='flex h-full flex-col'>
9699
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-1'>
97-
{chats.map((chat) => {
100+
{visibleChats.map((chat) => {
98101
const otherParticipant = chat.participants.find(
99102
(p) => p !== user?.id
100103
);

platforms/pictique/client/src/routes/(protected)/messages/+page.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@
4949
totalChats = data.total;
5050
hasMorePages = data.page < data.totalPages;
5151
52-
// Transform chats to messages
53-
const newMessages = data.chats.map((c) => {
52+
// Transform chats to messages, hiding chats that only contain a single user
53+
const newMessages = data.chats
54+
.filter((c) => c.participants.length >= 2)
55+
.map((c) => {
5456
const members = c.participants.filter((u) => u.id !== userData.id);
5557
const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename);
5658
const isGroup = members.length > 1;

0 commit comments

Comments
 (0)