@ocelescope/core 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,77 +1,367 @@
1
- import { ActionIcon, AppShell, Badge, Box, Burger, Button, ColorSchemeScript, Container, Divider, Group, LoadingOverlay, MantineProvider, Menu, MenuItem, Modal, NavLink, Select, Stack, Text, TextInput, ThemeIcon, Title, mantineHtmlProps, useMantineTheme } from "@mantine/core";
2
- import { generateColor } from "@marko19907/string-to-color";
3
- import { useDeleteOcel, useDeleteResource, useGetDefaultOcel, useGetOcels, useGetResourceMeta, useImportDefaultOcel, useRenameOcel, useRenameResource, useResources, useUpload } from "@ocelescope/api-base";
4
- import { ResourceModal } from "@ocelescope/resources";
5
- import { CheckIcon, ContainerIcon, DownloadIcon, EllipsisVerticalIcon, EyeIcon, HomeIcon, PackageIcon, PencilIcon, TrashIcon, Upload, UploadIcon, X, XIcon } from "lucide-react";
1
+ import { AppShell, Box, Burger, Button, ColorSchemeScript, Combobox, CopyButton, Divider, Group, Input, InputBase, LoadingOverlay, MantineProvider, Modal, MultiSelect, NavLink, Stack, Table, Text, ThemeIcon, Tooltip, UnstyledButton, createTheme, mantineHtmlProps, useCombobox, useMantineTheme } from "@mantine/core";
2
+ import { getDownloadFlatLogQueryKey, getDownloadOCELQueryKey, getDownloadResourceAsPnmlQueryKey, getDownloadResourceQueryKey, useActivities, useAggregatedAttributes, useAttributeNames, useE2o, useE2oCombinations, useEventAttributes, useGetDefaultOcel, useGetOcels, useImportDefaultOcel, useLogout, useO2o, useO2oCombinations, useObjectAttributes, useObjectTypes, useUpload } from "@ocelescope/api-base";
3
+ import { HydrationBoundary, QueryClient, QueryClientProvider, keepPreviousData, useQueryClient } from "@tanstack/react-query";
4
+ import { ChevronDownIcon, ChevronRightIcon, ContainerIcon, DownloadIcon, FilterIcon, LogOutIcon, PackageIcon, UploadIcon, XIcon } from "lucide-react";
6
5
  import { DataTable } from "mantine-datatable";
7
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8
- import { env, useSessionStore } from "@ocelescope/api-client";
9
- import { parse } from "content-disposition-attachment";
10
- import { saveAs } from "file-saver";
11
- import { HydrationBoundary, QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query";
6
+ import { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
12
7
  import dayjs from "dayjs";
13
- import isBetween from "dayjs/plugin/isBetween";
8
+ import isBetween from "dayjs/plugin/isBetween.js";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
10
  import { Dropzone } from "@mantine/dropzone";
15
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ import { Readable } from "node:stream";
12
+ import { Notifications, showNotification } from "@mantine/notifications";
16
13
  import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
17
14
  import { useDisclosure } from "@mantine/hooks";
15
+ import { env as env$1, useSessionStore } from "@ocelescope/api-client";
18
16
  import { create } from "zustand";
19
17
  import { persist } from "zustand/middleware";
20
- import Link from "next/link";
21
- import { useRouter } from "next/router";
22
- import { Head, Html, Main, NextScript } from "next/document";
23
-
24
- import './index.css';
25
- //#region src/hooks/useDownload.ts
26
- const useDownloadFile = () => {
27
- const { sessionId } = useSessionStore();
28
- const downloadFile = async (path) => {
29
- try {
30
- const response = await fetch(`${env.backend_url}${path}`, {
31
- method: "GET",
32
- headers: { [env.session_id]: sessionId ?? "" }
33
- });
34
- if (!response.ok) throw new Error("Failed to download file");
35
- const blob = await response.blob();
36
- const contentDisposition = parse(response.headers.get("Content-Disposition") ?? "");
37
- saveAs(blob, contentDisposition.attachment ? contentDisposition.filename : "fileName");
38
- } catch (err) {
39
- console.error("Download error:", err);
18
+ import Link from "next/link.js";
19
+ import { useRouter } from "next/router.js";
20
+ import z from "zod";
21
+ import { Head, Html, Main, NextScript } from "next/document.js";
22
+ import { parse } from "content-disposition-attachment";
23
+ import { saveAs } from "file-saver";
24
+ import qs from "qs";
25
+ //#region src/lib/dayjs.ts
26
+ dayjs.extend(isBetween);
27
+ const formatDateTime = (dateString) => dayjs(dateString).format("YYYY-MM-DD HH:mm");
28
+ //#endregion
29
+ //#region src/lib/attributes.ts
30
+ const formatAttributeValue = (type, value) => {
31
+ switch (type) {
32
+ case "int": return value.toLocaleString("en-US");
33
+ case "float": return value.toLocaleString("en-US");
34
+ case "date": return formatDateTime(value);
35
+ default: return value;
36
+ }
37
+ };
38
+ //#endregion
39
+ //#region src/components/AttributeTable.tsx
40
+ const PAGE_SIZE$1 = 10;
41
+ const COLUMN_WIDTHS$1 = {
42
+ selector: 30,
43
+ name: 200,
44
+ entityType: 200,
45
+ type: 100,
46
+ range: 200,
47
+ distinctValues: 120
48
+ };
49
+ const SubAttributeTable = ({ ocelId, entityType = "objects", attributeName, entityNames, hideRange = false, hideValues = false, ocelVersion, extraColumns = [] }) => {
50
+ const isEvent = entityType === "events";
51
+ const { data, isFetching } = (isEvent ? useEventAttributes : useObjectAttributes)(ocelId, {
52
+ attribute_names: [attributeName],
53
+ names: entityNames,
54
+ ocel_version: ocelVersion
55
+ }, { query: { placeholderData: keepPreviousData } });
56
+ const columns = useMemo(() => [
57
+ {
58
+ accessor: "selector",
59
+ title: "",
60
+ width: COLUMN_WIDTHS$1.selector
61
+ },
62
+ {
63
+ accessor: "",
64
+ title: "Attribute Name",
65
+ width: COLUMN_WIDTHS$1.name
66
+ },
67
+ {
68
+ accessor: "entity_type",
69
+ title: isEvent ? "Activity" : "Object Type",
70
+ width: COLUMN_WIDTHS$1.entityType
71
+ },
72
+ {
73
+ accessor: "type",
74
+ title: "Data Type",
75
+ width: COLUMN_WIDTHS$1.type,
76
+ noWrap: true
77
+ },
78
+ {
79
+ accessor: "range",
80
+ width: COLUMN_WIDTHS$1.range,
81
+ render: ({ type, min, max }) => `${formatAttributeValue(type, min)} - ${formatAttributeValue(type, max)}`,
82
+ hidden: hideRange
83
+ },
84
+ {
85
+ accessor: "distinct_values",
86
+ title: "Values",
87
+ width: COLUMN_WIDTHS$1.distinctValues,
88
+ hidden: hideValues
89
+ },
90
+ ...extraColumns
91
+ ], [
92
+ isEvent,
93
+ hideRange,
94
+ hideValues,
95
+ extraColumns
96
+ ]);
97
+ return /* @__PURE__ */ jsx(DataTable, {
98
+ noHeader: true,
99
+ idAccessor: "entity_type",
100
+ records: data ?? [],
101
+ columns,
102
+ fetching: isFetching,
103
+ backgroundColor: {
104
+ light: "gray.0",
105
+ dark: "dark.6"
40
106
  }
41
- };
42
- return downloadFile;
107
+ });
108
+ };
109
+ const AttributesTable = ({ ocelId, entityType = "objects", extraColumns = [], hideRange = false, hideValues = false, subTableExtraColumns, ocelVersion, alwaysExpandable = false, visibleAttributes }) => {
110
+ const isEvent = entityType === "events";
111
+ const [currentPage, setCurrentPage] = useState(1);
112
+ const { data: attributeNames } = useAttributeNames(ocelId, {
113
+ entity_type: entityType,
114
+ ocel_version: ocelVersion
115
+ });
116
+ const [filteredAttributes, setFilteredAttributes] = useState([]);
117
+ const { data: entityNames } = (isEvent ? useActivities : useObjectTypes)(ocelId, { ocel_version: ocelVersion });
118
+ const [filteredEntityNames, setFilteredEntityNames] = useState([]);
119
+ const isConstrained = visibleAttributes !== void 0;
120
+ const attributeNameOptions = useMemo(() => {
121
+ if (!isConstrained) return attributeNames ?? [];
122
+ return (attributeNames ?? []).filter((name) => visibleAttributes.includes(name));
123
+ }, [
124
+ attributeNames,
125
+ isConstrained,
126
+ visibleAttributes
127
+ ]);
128
+ const effectiveAttributeNames = useMemo(() => {
129
+ if (!isConstrained) return filteredAttributes;
130
+ return (filteredAttributes.length > 0 ? filteredAttributes : visibleAttributes).filter((name) => visibleAttributes.includes(name));
131
+ }, [
132
+ filteredAttributes,
133
+ isConstrained,
134
+ visibleAttributes
135
+ ]);
136
+ const showNoAttributes = isConstrained && effectiveAttributeNames.length === 0;
137
+ const { data, isFetching } = useAggregatedAttributes(ocelId, {
138
+ entity_type: entityType,
139
+ page: currentPage,
140
+ page_size: PAGE_SIZE$1,
141
+ ocel_version: ocelVersion,
142
+ ...effectiveAttributeNames.length > 0 && { attribute_names: effectiveAttributeNames },
143
+ ...filteredEntityNames.length > 0 && { entity_names: filteredEntityNames }
144
+ }, { query: {
145
+ placeholderData: keepPreviousData,
146
+ enabled: !showNoAttributes
147
+ } });
148
+ const [selectedAttributes, setSelectedAttributes] = useState([]);
149
+ const columns = useMemo(() => [
150
+ {
151
+ accessor: "selector",
152
+ title: "",
153
+ textAlign: "center",
154
+ render: ({ entity_type_names, name }) => {
155
+ if (!alwaysExpandable && entity_type_names.length === 1) return null;
156
+ return /* @__PURE__ */ jsx(ThemeIcon, {
157
+ size: "sm",
158
+ variant: "transparent",
159
+ style: { display: "flex" },
160
+ children: selectedAttributes.includes(name) ? /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) : /* @__PURE__ */ jsx(ChevronRightIcon, { size: 16 })
161
+ });
162
+ },
163
+ width: COLUMN_WIDTHS$1.selector,
164
+ cellsStyle: () => ({ padding: 5 })
165
+ },
166
+ {
167
+ accessor: "name",
168
+ title: "Attribute Name",
169
+ width: COLUMN_WIDTHS$1.name,
170
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
171
+ data: attributeNameOptions,
172
+ value: filteredAttributes,
173
+ onChange: (newSelection) => setFilteredAttributes(newSelection),
174
+ comboboxProps: { withinPortal: false },
175
+ clearable: true,
176
+ searchable: true
177
+ }),
178
+ filtering: filteredAttributes.length > 0
179
+ },
180
+ {
181
+ accessor: "entityTypeField",
182
+ title: isEvent ? "Activity" : "Object Type",
183
+ width: COLUMN_WIDTHS$1.entityType,
184
+ render: ({ entity_type_names }) => `${entity_type_names.slice(0, 2).join(", ")}${entity_type_names.length > 3 ? `, ... (${entity_type_names.length} total)` : ""}`,
185
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
186
+ data: entityNames,
187
+ value: filteredEntityNames,
188
+ onChange: (newSelection) => setFilteredEntityNames(newSelection),
189
+ comboboxProps: { withinPortal: false },
190
+ clearable: true,
191
+ searchable: true
192
+ }),
193
+ filtering: filteredEntityNames.length > 0
194
+ },
195
+ {
196
+ accessor: "type",
197
+ title: "Data Type",
198
+ width: COLUMN_WIDTHS$1.type
199
+ },
200
+ {
201
+ accessor: "range",
202
+ width: COLUMN_WIDTHS$1.range,
203
+ render: ({ type, min, max }) => `${formatAttributeValue(type, min)} - ${formatAttributeValue(type, max)}`,
204
+ hidden: hideRange
205
+ },
206
+ {
207
+ accessor: "distinct_values",
208
+ title: "Values",
209
+ width: COLUMN_WIDTHS$1.distinctValues,
210
+ hidden: hideValues
211
+ },
212
+ ...extraColumns
213
+ ], [
214
+ isEvent,
215
+ entityNames,
216
+ filteredEntityNames,
217
+ selectedAttributes,
218
+ filteredAttributes,
219
+ attributeNameOptions,
220
+ extraColumns,
221
+ hideRange,
222
+ hideValues,
223
+ alwaysExpandable
224
+ ]);
225
+ return /* @__PURE__ */ jsx(DataTable, {
226
+ idAccessor: "name",
227
+ records: showNoAttributes ? [] : data?.response,
228
+ columns,
229
+ withTableBorder: true,
230
+ fetching: isFetching,
231
+ totalRecords: showNoAttributes ? 0 : data?.total_items ?? 0,
232
+ page: currentPage,
233
+ recordsPerPage: PAGE_SIZE$1,
234
+ onPageChange: setCurrentPage,
235
+ noRecordsText: "No attributes found",
236
+ height: 500,
237
+ rowExpansion: {
238
+ allowMultiple: false,
239
+ expandable: ({ record: { entity_type_names } }) => alwaysExpandable || entity_type_names.length > 1,
240
+ expanded: {
241
+ recordIds: selectedAttributes,
242
+ onRecordIdsChange: setSelectedAttributes
243
+ },
244
+ content: ({ record }) => /* @__PURE__ */ jsx(SubAttributeTable, {
245
+ entityType,
246
+ attributeName: record.name,
247
+ ocelId,
248
+ extraColumns: subTableExtraColumns,
249
+ hideRange,
250
+ hideValues,
251
+ ocelVersion,
252
+ ...filteredEntityNames.length > 0 && { entityNames: filteredEntityNames }
253
+ })
254
+ }
255
+ });
43
256
  };
44
-
45
257
  //#endregion
46
- //#region src/hooks/useInvalidate.ts
47
- const useInvalidate = () => {
48
- const queryClient = useQueryClient();
49
- return async (routeNames) => await queryClient.invalidateQueries({ predicate: (query) => typeof query.queryKey[0] === "string" && routeNames.some((route) => query.queryKey[0].includes(`/${route}`)) });
258
+ //#region src/components/BarList/BarList.module.css
259
+ var BarList_module_default = {
260
+ "bar": "uf8oba_bar",
261
+ "barCell": "uf8oba_barCell",
262
+ "container": "uf8oba_container",
263
+ "head": "uf8oba_head",
264
+ "headCount": "uf8oba_headCount",
265
+ "headLabel": "uf8oba_headLabel",
266
+ "labelInside": "uf8oba_labelInside",
267
+ "labelOutside": "uf8oba_labelOutside",
268
+ "scrollArea": "uf8oba_scrollArea",
269
+ "toggleRow": "uf8oba_toggleRow"
50
270
  };
51
- var useInvalidate_default = useInvalidate;
52
-
53
271
  //#endregion
54
- //#region src/lib/dayjs.ts
55
- dayjs.extend(isBetween);
56
- var dayjs_default = dayjs;
57
- const formatDateTime = (dateString) => dayjs(dateString).format("YYYY-MM-DD HH:mm");
58
-
272
+ //#region src/components/BarList/BarList.tsx
273
+ const BarList = ({ data, labelHeader, valueHeader = "Frequency", maxVisibleItems = 8 }) => {
274
+ const [expanded, setExpanded] = useState(false);
275
+ const entries = useMemo(() => Object.entries(data).sort((a, b) => b[1] - a[1]), [data]);
276
+ const max = useMemo(() => Math.max(0, ...entries.map(([, c]) => c)), [entries]);
277
+ const typeCount = entries.length;
278
+ const totalFrequency = useMemo(() => entries.reduce((sum, [, count]) => sum + count, 0), [entries]);
279
+ const hasOverflow = typeCount > maxVisibleItems;
280
+ const visibleEntries = hasOverflow && !expanded ? entries.slice(0, maxVisibleItems) : entries;
281
+ const scrollRef = useRef(null);
282
+ const [collapsedHeight, setCollapsedHeight] = useState();
283
+ useLayoutEffect(() => {
284
+ if (!expanded && scrollRef.current) setCollapsedHeight(scrollRef.current.offsetHeight);
285
+ }, [
286
+ expanded,
287
+ typeCount,
288
+ maxVisibleItems
289
+ ]);
290
+ return /* @__PURE__ */ jsxs("div", {
291
+ className: BarList_module_default.container,
292
+ children: [/* @__PURE__ */ jsx("div", {
293
+ ref: scrollRef,
294
+ className: BarList_module_default.scrollArea,
295
+ style: expanded ? { maxHeight: collapsedHeight } : void 0,
296
+ children: /* @__PURE__ */ jsxs(Table, {
297
+ variant: "vertical",
298
+ layout: "fixed",
299
+ stickyHeader: true,
300
+ children: [/* @__PURE__ */ jsx(Table.Thead, {
301
+ className: BarList_module_default.head,
302
+ children: /* @__PURE__ */ jsxs(Table.Tr, { children: [/* @__PURE__ */ jsx(Table.Th, { children: /* @__PURE__ */ jsxs("span", {
303
+ className: BarList_module_default.headLabel,
304
+ children: [
305
+ labelHeader,
306
+ " ",
307
+ /* @__PURE__ */ jsxs("span", {
308
+ className: BarList_module_default.headCount,
309
+ children: [
310
+ "(",
311
+ typeCount.toLocaleString(),
312
+ ")"
313
+ ]
314
+ })
315
+ ]
316
+ }) }), /* @__PURE__ */ jsx(Table.Th, { children: /* @__PURE__ */ jsxs("span", {
317
+ className: BarList_module_default.headLabel,
318
+ children: [
319
+ valueHeader,
320
+ " ",
321
+ /* @__PURE__ */ jsxs("span", {
322
+ className: BarList_module_default.headCount,
323
+ children: [
324
+ "(",
325
+ totalFrequency.toLocaleString(),
326
+ ")"
327
+ ]
328
+ })
329
+ ]
330
+ }) })] })
331
+ }), /* @__PURE__ */ jsx(Table.Tbody, { children: visibleEntries.map(([name, count]) => {
332
+ const percentage = max > 0 ? Math.ceil(count / max * 100) : 0;
333
+ return /* @__PURE__ */ jsxs(Table.Tr, { children: [/* @__PURE__ */ jsx(Table.Td, { children: name }), /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs("div", {
334
+ className: BarList_module_default.barCell,
335
+ children: [/* @__PURE__ */ jsx("div", {
336
+ className: BarList_module_default.bar,
337
+ style: { width: `${percentage}%` },
338
+ children: percentage > 50 && /* @__PURE__ */ jsx("span", {
339
+ className: BarList_module_default.labelInside,
340
+ children: count
341
+ })
342
+ }), percentage <= 50 && /* @__PURE__ */ jsx("span", {
343
+ className: BarList_module_default.labelOutside,
344
+ children: count
345
+ })]
346
+ }) })] }, name);
347
+ }) })]
348
+ })
349
+ }), hasOverflow && /* @__PURE__ */ jsx(UnstyledButton, {
350
+ className: BarList_module_default.toggleRow,
351
+ onClick: () => setExpanded((prev) => !prev),
352
+ children: expanded ? "Show less" : `Show ${typeCount - maxVisibleItems} more`
353
+ })]
354
+ });
355
+ };
59
356
  //#endregion
60
- //#region src/components/Dropzone/Dropzone.module.css?css_module
61
- const classes$1 = {
62
- "icon": "_0yGNhG_icon",
63
- "wrapper": "_0yGNhG_wrapper",
64
- "dropzone": "_0yGNhG_dropzone",
65
- "control": "_0yGNhG_control",
66
- "description": "_0yGNhG_description"
67
- };
68
- var Dropzone_module_default = classes$1;
69
- const _icon0 = classes$1["icon"];
70
- const _wrapper0 = classes$1["wrapper"];
71
- const _dropzone0 = classes$1["dropzone"];
72
- const _control0$1 = classes$1["control"];
73
- const _description0 = classes$1["description"];
74
-
357
+ //#region src/components/Dropzone/Dropzone.module.css
358
+ var Dropzone_module_default = {
359
+ "control": "IgW5Wa_control",
360
+ "description": "IgW5Wa_description",
361
+ "dropzone": "IgW5Wa_dropzone",
362
+ "icon": "IgW5Wa_icon",
363
+ "wrapper": "IgW5Wa_wrapper"
364
+ };
75
365
  //#endregion
76
366
  //#region src/components/Dropzone/Dropzone.tsx
77
367
  const FileDropzone = ({ onUpload, accept, content }) => {
@@ -82,7 +372,7 @@ const FileDropzone = ({ onUpload, accept, content }) => {
82
372
  children: [/* @__PURE__ */ jsx(Dropzone, {
83
373
  openRef,
84
374
  onDrop: onUpload,
85
- className: Dropzone_module_default.dropzone,
375
+ className: Dropzone_module_default.dropzone ?? "",
86
376
  radius: "md",
87
377
  accept: accept ?? {},
88
378
  children: /* @__PURE__ */ jsxs("div", {
@@ -95,11 +385,11 @@ const FileDropzone = ({ onUpload, accept, content }) => {
95
385
  size: 50,
96
386
  color: theme.colors.blue[6]
97
387
  }) }),
98
- /* @__PURE__ */ jsx(Dropzone.Reject, { children: /* @__PURE__ */ jsx(X, {
388
+ /* @__PURE__ */ jsx(Dropzone.Reject, { children: /* @__PURE__ */ jsx(XIcon, {
99
389
  size: 50,
100
390
  color: theme.colors.red[6]
101
391
  }) }),
102
- /* @__PURE__ */ jsx(Dropzone.Idle, { children: /* @__PURE__ */ jsx(Upload, {
392
+ /* @__PURE__ */ jsx(Dropzone.Idle, { children: /* @__PURE__ */ jsx(UploadIcon, {
103
393
  size: 50,
104
394
  className: Dropzone_module_default.icon
105
395
  }) })
@@ -117,13 +407,13 @@ const FileDropzone = ({ onUpload, accept, content }) => {
117
407
  ]
118
408
  }),
119
409
  /* @__PURE__ */ jsx(Text, {
120
- className: Dropzone_module_default.description,
410
+ className: Dropzone_module_default.description ?? "",
121
411
  children: content.description || /* @__PURE__ */ jsx("span", { children: "Drag'n'drop files here to upload." })
122
412
  })
123
413
  ]
124
414
  })
125
415
  }), /* @__PURE__ */ jsx(Button, {
126
- className: Dropzone_module_default.control,
416
+ className: Dropzone_module_default.control ?? "",
127
417
  size: "sm",
128
418
  radius: "xl",
129
419
  onClick: () => openRef.current?.(),
@@ -131,13 +421,167 @@ const FileDropzone = ({ onUpload, accept, content }) => {
131
421
  })]
132
422
  });
133
423
  };
134
- var Dropzone_default = FileDropzone;
135
-
424
+ //#endregion
425
+ //#region src/components/EventHandler.ts
426
+ const apiBase = process.env.EXTERNAL_API_BASE_URL ?? "http://localhost:8000";
427
+ const sseHandler = async (req, res) => {
428
+ const session_id = req.query.sessionId;
429
+ const backendUrl = `${apiBase}/sse?session_id=${encodeURIComponent(session_id || "")}`;
430
+ try {
431
+ const controller = new AbortController();
432
+ const backendRes = await fetch(backendUrl, {
433
+ headers: { Accept: "text/event-stream" },
434
+ signal: controller.signal
435
+ });
436
+ if (!backendRes.body) {
437
+ res.status(502).end("No response body from backend");
438
+ return;
439
+ }
440
+ res.writeHead(200, {
441
+ "Content-Type": "text/event-stream",
442
+ "Cache-Control": "no-cache, no-transform",
443
+ Connection: "keep-alive"
444
+ });
445
+ const webStream = backendRes.body;
446
+ const nodeStream = Readable.fromWeb(webStream);
447
+ nodeStream.pipe(res);
448
+ res.on("close", () => {
449
+ console.log("Client disconnected");
450
+ controller.abort();
451
+ nodeStream.destroy();
452
+ res.end();
453
+ });
454
+ } catch (err) {
455
+ console.error("SSE proxy error:", err);
456
+ res.status(500).end("SSE proxy error");
457
+ }
458
+ };
459
+ //#endregion
460
+ //#region src/components/ModulePage.tsx
461
+ const createModulesPage = (config) => {
462
+ const { modules = [] } = config;
463
+ const getStaticPaths = async () => {
464
+ return {
465
+ paths: [...modules.flatMap(({ name: moduleName, routes }) => [...routes.map(({ name: routeName }) => ({ params: { slug: [moduleName, routeName] } })), { params: { slug: [moduleName] } }]), { params: { slug: [] } }],
466
+ fallback: false
467
+ };
468
+ };
469
+ const getStaticProps = async ({ params }) => {
470
+ const slugs = params?.slug ?? [];
471
+ const moduleDef = slugs[0] ? modules.find(({ name }) => name === slugs[0]) : modules[0];
472
+ const routeDef = slugs[1] ? moduleDef?.routes.find(({ name }) => name === slugs[1]) : moduleDef?.routes[0];
473
+ if (!moduleDef || !routeDef) return { notFound: true };
474
+ return { props: {
475
+ moduleName: moduleDef.name,
476
+ routeName: routeDef.name
477
+ } };
478
+ };
479
+ const ModulePage = ({ moduleName, routeName }) => {
480
+ const RouteComponent = modules.find(({ name }) => name === moduleName)?.routes.find(({ name }) => name === routeName)?.component;
481
+ return RouteComponent ? /* @__PURE__ */ jsx(RouteComponent, {}) : null;
482
+ };
483
+ return {
484
+ getStaticPaths,
485
+ getStaticProps,
486
+ ModulePage
487
+ };
488
+ };
489
+ //#endregion
490
+ //#region src/store/currentOcelStore.ts
491
+ const useCurrentOcelStore = create()(persist((set) => ({
492
+ ocelId: null,
493
+ setOcel: (id) => set({ ocelId: id }),
494
+ clearOcel: () => set({ ocelId: null })
495
+ }), { name: env$1.current_ocel_id }));
496
+ //#endregion
497
+ //#region src/hooks/useCurrentOCEL.ts
498
+ const useCurrentOcel = () => {
499
+ const { data: ocels = [] } = useGetOcels();
500
+ const { ocelId, setOcel, clearOcel } = useCurrentOcelStore();
501
+ useEffect(() => {
502
+ if (ocelId && !ocels?.some(({ id }) => id === ocelId)) clearOcel();
503
+ if (!ocelId && ocels[0]) setOcel(ocels[0].id);
504
+ }, [
505
+ clearOcel,
506
+ setOcel,
507
+ ocels,
508
+ ocelId
509
+ ]);
510
+ return {
511
+ id: ocelId,
512
+ setCurrentOcel: setOcel
513
+ };
514
+ };
515
+ //#endregion
516
+ //#region src/components/OcelSelect/OcelSelect.tsx
517
+ const FilteredIndicator = () => /* @__PURE__ */ jsx(Tooltip, {
518
+ label: "This log has been filtered",
519
+ children: /* @__PURE__ */ jsx(FilterIcon, {
520
+ size: 14,
521
+ color: "var(--mantine-color-blue-6)"
522
+ })
523
+ });
524
+ const OcelSelect = ({ extension, value, onChange, placeholder = "Select an OCEL", ...inputProps }) => {
525
+ const { data } = useGetOcels(extension ? { extension_name: extension } : {});
526
+ const ocels = useMemo(() => data ?? [], [data]);
527
+ const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption() });
528
+ const selected = useMemo(() => ocels.find((ocel) => ocel.id === value), [ocels, value]);
529
+ return /* @__PURE__ */ jsxs(Combobox, {
530
+ store: combobox,
531
+ onOptionSubmit: (val) => {
532
+ onChange?.(val);
533
+ combobox.closeDropdown();
534
+ },
535
+ children: [/* @__PURE__ */ jsx(Combobox.Target, { children: /* @__PURE__ */ jsx(InputBase, {
536
+ ...inputProps,
537
+ component: "button",
538
+ type: "button",
539
+ pointer: true,
540
+ rightSection: /* @__PURE__ */ jsx(Combobox.Chevron, {}),
541
+ rightSectionPointerEvents: "none",
542
+ onClick: () => combobox.toggleDropdown(),
543
+ children: selected ? /* @__PURE__ */ jsxs(Group, {
544
+ gap: 6,
545
+ wrap: "nowrap",
546
+ style: { overflow: "hidden" },
547
+ children: [/* @__PURE__ */ jsx(Text, {
548
+ span: true,
549
+ size: "sm",
550
+ truncate: true,
551
+ children: selected.name
552
+ }), selected.filter_applied && /* @__PURE__ */ jsx(FilteredIndicator, {})]
553
+ }) : /* @__PURE__ */ jsx(Input.Placeholder, { children: placeholder })
554
+ }) }), /* @__PURE__ */ jsx(Combobox.Dropdown, { children: /* @__PURE__ */ jsx(Combobox.Options, { children: ocels.map((ocel) => /* @__PURE__ */ jsx(Combobox.Option, {
555
+ value: ocel.id,
556
+ children: /* @__PURE__ */ jsxs(Group, {
557
+ gap: 6,
558
+ wrap: "nowrap",
559
+ children: [ocel.name, ocel.filter_applied && /* @__PURE__ */ jsx(FilteredIndicator, {})]
560
+ })
561
+ }, ocel.id)) }) })]
562
+ });
563
+ };
564
+ const CurrentOcelSelect = () => {
565
+ const { id, setCurrentOcel } = useCurrentOcel();
566
+ return /* @__PURE__ */ jsx(OcelSelect, {
567
+ value: id,
568
+ w: 250,
569
+ onChange: (id) => {
570
+ if (id) setCurrentOcel(id);
571
+ }
572
+ });
573
+ };
574
+ //#endregion
575
+ //#region src/hooks/useInvalidate.ts
576
+ const useInvalidate = () => {
577
+ const queryClient = useQueryClient();
578
+ return async (routeNames) => await queryClient.invalidateQueries({ predicate: (query) => typeof query.queryKey[0] === "string" && routeNames.some((route) => query.queryKey[0].includes(`/${route}`)) });
579
+ };
136
580
  //#endregion
137
581
  //#region src/components/UploadSection/UploadSection.tsx
138
582
  const DefaultOcels = ({ onSuccess, setIsPending }) => {
139
583
  const { data: ocels = [] } = useGetDefaultOcel({});
140
- const invalidate = useInvalidate_default();
584
+ const invalidate = useInvalidate();
141
585
  const { mutate: uploadDefault } = useImportDefaultOcel({ mutation: {
142
586
  onSuccess: async () => {
143
587
  await invalidate(["ocels"]);
@@ -178,7 +622,7 @@ const FileUploadZone = ({ onSuccess, setIsPending }) => {
178
622
  onMutate: () => setIsPending(true),
179
623
  onSettled: () => setIsPending(false)
180
624
  } });
181
- return /* @__PURE__ */ jsx(Dropzone_default, {
625
+ return /* @__PURE__ */ jsx(FileDropzone, {
182
626
  content: { description: /* @__PURE__ */ jsx("span", { children: `Drag'n'drop your ${[
183
627
  "OCELs",
184
628
  "Plugins",
@@ -191,8 +635,10 @@ const FileUploadZone = ({ onSuccess, setIsPending }) => {
191
635
  };
192
636
  const UploadSection = ({ onSuccess }) => {
193
637
  const [isPending, setIsPending] = useState(false);
194
- const onUploadSuccess = useCallback(() => {
638
+ const invalidate = useInvalidate();
639
+ const onUploadSuccess = useCallback(async () => {
195
640
  setIsPending(false);
641
+ await invalidate(["tasks"]);
196
642
  onSuccess?.();
197
643
  }, [onSuccess]);
198
644
  return /* @__PURE__ */ jsxs(Stack, {
@@ -217,298 +663,6 @@ const UploadSection = ({ onSuccess }) => {
217
663
  ]
218
664
  });
219
665
  };
220
- var UploadSection_default = UploadSection;
221
-
222
- //#endregion
223
- //#region src/components/EntityTable/EntityTable.tsx
224
- const ocelExtenisions = [
225
- ".sqlite",
226
- ".xmlocel",
227
- ".jsonocel"
228
- ];
229
- const EntityTable = () => {
230
- const { data: ocels = [] } = useGetOcels();
231
- const { data: resources = [] } = useResources();
232
- const invalidate = useInvalidate_default();
233
- const downloadFile = useDownloadFile();
234
- const { data: resourceMeta = {} } = useGetResourceMeta();
235
- const { mutate: deleteResource } = useDeleteResource({ mutation: { onSuccess: () => invalidate(["resources"]) } });
236
- const { mutate: deleteOcel } = useDeleteOcel({ mutation: { onSuccess: () => invalidate(["ocels"]) } });
237
- const { mutate: renameOcel } = useRenameOcel({ mutation: { onSuccess: () => invalidate(["ocels"]) } });
238
- const { mutate: renameResource } = useRenameResource({ mutation: { onSuccess: () => invalidate(["resources"]) } });
239
- const deleteEntity = useCallback((id, entityType) => {
240
- switch (entityType) {
241
- case "ocel":
242
- deleteOcel({ params: { ocel_id: id } });
243
- break;
244
- case "resource":
245
- deleteResource({ resourceId: id });
246
- break;
247
- }
248
- }, [deleteResource, deleteOcel]);
249
- const renameEntitiy = useCallback((id, entityType, newName) => {
250
- switch (entityType) {
251
- case "ocel":
252
- renameOcel({ params: {
253
- ocel_id: id,
254
- new_name: newName
255
- } });
256
- break;
257
- case "resource": renameResource({
258
- resourceId: id,
259
- params: { new_name: newName }
260
- });
261
- }
262
- }, [renameOcel, renameResource]);
263
- const [renamedEntitiy, setRenamedEntitiy] = useState(void 0);
264
- const [viewedResouce, setViewedResource] = useState();
265
- const entities = useMemo(() => {
266
- const ocelEntities = ocels.map(({ name, created_at, id, extensions }) => ({
267
- id,
268
- name,
269
- type: "ocel",
270
- entityTypes: extensions.map(({ label }) => label),
271
- createdAt: formatDateTime(created_at),
272
- downloadFormats: [
273
- ".xml",
274
- ".json",
275
- ".sqlite"
276
- ]
277
- }));
278
- const resourceEntities = resources.map(({ id, name, type, created_at }) => ({
279
- id,
280
- name,
281
- entityTypes: [resourceMeta[type]?.label ?? type],
282
- type: "resource",
283
- createdAt: formatDateTime(dayjs_default(created_at).toISOString())
284
- }));
285
- return [...ocelEntities, ...resourceEntities];
286
- }, [
287
- ocels,
288
- resources,
289
- resourceMeta
290
- ]);
291
- return /* @__PURE__ */ jsxs(Fragment, { children: [viewedResouce && /* @__PURE__ */ jsx(ResourceModal, {
292
- id: viewedResouce,
293
- onClose: () => setViewedResource(void 0)
294
- }), entities.length ? /* @__PURE__ */ jsx(DataTable, {
295
- withTableBorder: true,
296
- borderRadius: "md",
297
- idAccessor: "id",
298
- columns: [
299
- {
300
- accessor: "name",
301
- render: ({ id, type, name }) => /* @__PURE__ */ jsx(Fragment, { children: renamedEntitiy?.id === id ? /* @__PURE__ */ jsxs(Group, { children: [/* @__PURE__ */ jsx(TextInput, {
302
- variant: "unstyled",
303
- value: renamedEntitiy.value,
304
- style: { borderBottom: "1px solid #9ca3af" },
305
- onChange: (e) => {
306
- setRenamedEntitiy({
307
- id,
308
- value: e.target.value
309
- });
310
- }
311
- }), /* @__PURE__ */ jsxs(Group, {
312
- gap: "xs",
313
- children: [/* @__PURE__ */ jsx(ActionIcon, {
314
- color: "green",
315
- m: 0,
316
- onClick: () => {
317
- renameEntitiy(id, type, renamedEntitiy.value);
318
- setRenamedEntitiy(void 0);
319
- },
320
- children: /* @__PURE__ */ jsx(CheckIcon, { size: 16 })
321
- }), /* @__PURE__ */ jsx(ActionIcon, {
322
- color: "red",
323
- m: 0,
324
- onClick: () => {
325
- setRenamedEntitiy(void 0);
326
- },
327
- children: /* @__PURE__ */ jsx(XIcon, { size: 16 })
328
- })]
329
- })] }) : name })
330
- },
331
- { accessor: "createdAt" },
332
- {
333
- accessor: "entityTypes",
334
- title: "Type",
335
- render: ({ type, entityTypes }) => /* @__PURE__ */ jsx(Group, {
336
- gap: "xs",
337
- children: [...type === "ocel" ? ["OCEL"] : [], ...entityTypes].map((entityType) => /* @__PURE__ */ jsx(Badge, {
338
- color: generateColor(entityType),
339
- children: entityType
340
- }, entityType))
341
- })
342
- },
343
- {
344
- accessor: "",
345
- textAlign: "right",
346
- width: "0%",
347
- render: ({ type, id, name }) => /* @__PURE__ */ jsxs(Menu, {
348
- width: 200,
349
- position: "left-start",
350
- children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(ActionIcon, {
351
- variant: "subtle",
352
- onClick: (e) => e.stopPropagation(),
353
- children: /* @__PURE__ */ jsx(EllipsisVerticalIcon, { size: 20 })
354
- }) }), /* @__PURE__ */ jsxs(Menu.Dropdown, {
355
- onClick: (e) => e.stopPropagation(),
356
- children: [
357
- /* @__PURE__ */ jsx(Menu.Item, {
358
- leftSection: /* @__PURE__ */ jsx(PencilIcon, { size: 16 }),
359
- onClick: () => setRenamedEntitiy({
360
- id,
361
- value: name
362
- }),
363
- children: "Rename"
364
- }),
365
- type === "resource" && /* @__PURE__ */ jsx(MenuItem, {
366
- leftSection: /* @__PURE__ */ jsx(EyeIcon, { size: 16 }),
367
- onClick: () => setViewedResource(id),
368
- children: "Inspect"
369
- }),
370
- type === "ocel" ? /* @__PURE__ */ jsxs(Menu.Sub, {
371
- position: "right-start",
372
- children: [/* @__PURE__ */ jsx(Menu.Sub.Target, { children: /* @__PURE__ */ jsx(Menu.Sub.Item, {
373
- leftSection: /* @__PURE__ */ jsx(DownloadIcon, { size: 16 }),
374
- children: "Download"
375
- }) }), /* @__PURE__ */ jsx(Menu.Sub.Dropdown, { children: ocelExtenisions.map((extension) => /* @__PURE__ */ jsx(Menu.Item, {
376
- onClick: () => downloadFile(`/ocels/download?${new URLSearchParams({
377
- ocel_id: id,
378
- ext: extension
379
- }).toString()}`),
380
- children: extension
381
- }, extension)) })]
382
- }) : /* @__PURE__ */ jsx(Menu.Item, {
383
- onClick: () => downloadFile(`/resources/resource/${id}/download`),
384
- leftSection: /* @__PURE__ */ jsx(DownloadIcon, { size: 16 }),
385
- children: "Download"
386
- }),
387
- /* @__PURE__ */ jsx(Menu.Divider, {}),
388
- /* @__PURE__ */ jsx(Menu.Item, {
389
- leftSection: /* @__PURE__ */ jsx(TrashIcon, {
390
- size: 16,
391
- color: "red"
392
- }),
393
- color: "red",
394
- fw: "bold",
395
- onClick: () => deleteEntity(id, type),
396
- children: "Delete"
397
- })
398
- ]
399
- })]
400
- })
401
- }
402
- ],
403
- records: entities
404
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Title, {
405
- size: "h3",
406
- children: "Upload"
407
- }), /* @__PURE__ */ jsx(UploadSection_default, {})] })] });
408
- };
409
- var EntityTable_default = EntityTable;
410
-
411
- //#endregion
412
- //#region src/components/DefaultHomePage/DefaultHomePage.tsx
413
- const DefaultHomePage = ({ children }) => {
414
- return /* @__PURE__ */ jsx(Container, {
415
- h: "100%",
416
- children: /* @__PURE__ */ jsxs(Stack, {
417
- gap: "sm",
418
- h: "100%",
419
- children: [/* @__PURE__ */ jsx(EntityTable_default, {}), children]
420
- })
421
- });
422
- };
423
- var DefaultHomePage_default = DefaultHomePage;
424
-
425
- //#endregion
426
- //#region src/components/ModulePage.tsx
427
- const createModulesPage = (config) => {
428
- const { modules = [], homePage: HomePage = DefaultHomePage_default } = config;
429
- const getStaticPaths = async () => {
430
- return {
431
- paths: [...modules.flatMap(({ name: moduleName, routes }) => routes.map(({ name: routeName }) => ({ params: { slug: [
432
- "modules",
433
- moduleName,
434
- routeName
435
- ] } }))), { params: { slug: [] } }],
436
- fallback: false
437
- };
438
- };
439
- const getStaticProps = async ({ params }) => {
440
- const slugs = params?.["slug"] ?? [];
441
- const moduleConfig = modules.find(({ name }) => name === slugs[1]);
442
- const routeName = moduleConfig?.routes.find(({ name }) => name === slugs[2])?.name;
443
- return moduleConfig && routeName ? { props: { moduleProps: {
444
- moduleName: moduleConfig.name,
445
- routeName
446
- } } } : { props: {} };
447
- };
448
- const ModulePage = ({ moduleProps }) => {
449
- const RouteComponent = modules.find(({ name }) => name === moduleProps?.moduleName)?.routes.find(({ name }) => name === moduleProps?.routeName)?.component;
450
- return RouteComponent ? /* @__PURE__ */ jsx(RouteComponent, {}) : /* @__PURE__ */ jsx(HomePage, {});
451
- };
452
- return {
453
- getStaticPaths,
454
- getStaticProps,
455
- ModulePage
456
- };
457
- };
458
-
459
- //#endregion
460
- //#region src/store/currentOcelStore.ts
461
- const useCurrentOcelStore = create()(persist((set) => ({
462
- ocelId: null,
463
- setOcel: (id) => set({ ocelId: id }),
464
- clearOcel: () => set({ ocelId: null })
465
- }), { name: env.current_ocel_id }));
466
- var currentOcelStore_default = useCurrentOcelStore;
467
-
468
- //#endregion
469
- //#region src/hooks/useCurrentOCEL.ts
470
- const useCurrentOcel = () => {
471
- const { data: ocels = [] } = useGetOcels();
472
- const { ocelId, setOcel, clearOcel } = currentOcelStore_default();
473
- useEffect(() => {
474
- if (ocelId && !ocels?.some(({ id }) => id === ocelId)) clearOcel();
475
- if (!ocelId && ocels[0]) setOcel(ocels[0].id);
476
- }, [
477
- clearOcel,
478
- setOcel,
479
- ocels,
480
- ocelId
481
- ]);
482
- return {
483
- id: ocelId,
484
- setCurrentOcel: setOcel
485
- };
486
- };
487
-
488
- //#endregion
489
- //#region src/components/OcelSelect/OcelSelect.tsx
490
- const OcelSelect = ({ extension, ...props }) => {
491
- const { data } = useGetOcels(extension ? { extension_name: extension } : {});
492
- const ocels = useMemo(() => data ?? [], [data]);
493
- return /* @__PURE__ */ jsx(Select, {
494
- ...props,
495
- data: ocels.map(({ name, id }) => ({
496
- value: id,
497
- label: name
498
- }))
499
- });
500
- };
501
- const CurrentOcelSelect = () => {
502
- const { id, setCurrentOcel } = useCurrentOcel();
503
- return /* @__PURE__ */ jsx(OcelSelect, {
504
- value: id,
505
- unselectable: "off",
506
- onChange: (id$1) => {
507
- if (id$1) setCurrentOcel(id$1);
508
- }
509
- });
510
- };
511
-
512
666
  //#endregion
513
667
  //#region src/components/UploadModal/UploadModal.tsx
514
668
  const UploadModal = ({ visible, onClose }) => {
@@ -520,46 +674,9 @@ const UploadModal = ({ visible, onClose }) => {
520
674
  opened: visible,
521
675
  onClose,
522
676
  size: "xl",
523
- children: /* @__PURE__ */ jsx(UploadSection_default, { onSuccess: onClose })
677
+ children: /* @__PURE__ */ jsx(UploadSection, { onSuccess: onClose })
524
678
  });
525
679
  };
526
- var UploadModal_default = UploadModal;
527
-
528
- //#endregion
529
- //#region src/components/AppShell/AppShell.module.css?css_module
530
- const classes = {
531
- "chevron": "_4Ar51a_chevron",
532
- "linkIcon": "_4Ar51a_linkIcon",
533
- "links": "_4Ar51a_links",
534
- "navbar": "_4Ar51a_navbar",
535
- "buttonIcon": "_4Ar51a_buttonIcon",
536
- "link": "_4Ar51a_link",
537
- "control": "_4Ar51a_control",
538
- "header": "_4Ar51a_header",
539
- "footer": "_4Ar51a_footer",
540
- "button": "_4Ar51a_button"
541
- };
542
- var AppShell_module_default = classes;
543
- const _chevron0 = classes["chevron"];
544
- const _linkIcon0 = classes["linkIcon"];
545
- const _links0 = classes["links"];
546
- const _navbar0 = classes["navbar"];
547
- const _buttonIcon0 = classes["buttonIcon"];
548
- const _link0 = classes["link"];
549
- const _control0 = classes["control"];
550
- const _header0 = classes["header"];
551
- const _footer0 = classes["footer"];
552
- const _button0 = classes["button"];
553
-
554
- //#endregion
555
- //#region src/lib/getModuleRoute.ts
556
- const getModuleRoute = ({ moduleName, routeName, query }) => {
557
- return {
558
- pathname: `/modules/${moduleName}/${routeName}`,
559
- ...query && { query: { ...query } }
560
- };
561
- };
562
-
563
680
  //#endregion
564
681
  //#region src/hooks/useModulePath.ts
565
682
  const useModulePath = (config) => {
@@ -574,71 +691,144 @@ const useModulePath = (config) => {
574
691
  };
575
692
  }, [moduleName, routeName]);
576
693
  };
577
- var useModulePath_default = useModulePath;
578
-
579
694
  //#endregion
580
- //#region src/components/AppShell/components/Header.tsx
695
+ //#region src/lib/getModuleRoute.ts
696
+ const getModuleRoute = ({ moduleName, routeName, query }) => {
697
+ return {
698
+ pathname: `/${moduleName}${routeName ? `/${routeName}` : ""}`,
699
+ ...query && { query: { ...query } }
700
+ };
701
+ };
702
+ //#endregion
703
+ //#region src/components/AppShell/AppShell.module.css
704
+ var AppShell_module_default = {
705
+ "button": "rZS5FG_button",
706
+ "buttonIcon": "rZS5FG_buttonIcon",
707
+ "chevron": "rZS5FG_chevron",
708
+ "control": "rZS5FG_control",
709
+ "footer": "rZS5FG_footer",
710
+ "header": "rZS5FG_header",
711
+ "link": "rZS5FG_link",
712
+ "linkIcon": "rZS5FG_linkIcon",
713
+ "links": "rZS5FG_links",
714
+ "navbar": "rZS5FG_navbar"
715
+ };
716
+ //#endregion
717
+ //#region src/components/AppShell/components/NavBar.tsx
718
+ const LogoutButton = () => {
719
+ const [isModalOpen, setIsModalOpen] = useState(false);
720
+ const queryClient = useQueryClient();
721
+ const { push } = useRouter();
722
+ const { mutate: logout } = useLogout({ mutation: { onSuccess: () => {
723
+ queryClient.clear();
724
+ setIsModalOpen(false);
725
+ push("/");
726
+ } } });
727
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Modal, {
728
+ opened: isModalOpen,
729
+ onClose: () => setIsModalOpen(false),
730
+ title: "Are you sure?",
731
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
732
+ "If you leave now, all your data and progress will be",
733
+ " ",
734
+ /* @__PURE__ */ jsx("strong", { children: "deleted permanently" }),
735
+ ". This action cannot be undone."
736
+ ] }), /* @__PURE__ */ jsx(Button, {
737
+ color: "red",
738
+ mt: "md",
739
+ onClick: () => {
740
+ logout();
741
+ },
742
+ fullWidth: true,
743
+ children: "Accept"
744
+ })]
745
+ }), /* @__PURE__ */ jsx(UnstyledButton, {
746
+ className: AppShell_module_default.button ?? "",
747
+ onClick: () => setIsModalOpen(true),
748
+ children: /* @__PURE__ */ jsxs(Group, {
749
+ justify: "space-between",
750
+ w: "100%",
751
+ children: [/* @__PURE__ */ jsx(Text, {
752
+ style: { lineHeight: 1 },
753
+ children: "Logout"
754
+ }), /* @__PURE__ */ jsx(LogOutIcon, { className: AppShell_module_default.buttonIcon })]
755
+ })
756
+ })] });
757
+ };
758
+ const NavbarGroup = ({ modules, modulePath, isOcelAvailable }) => {
759
+ return modules.map(({ label, name, icon: Icon = PackageIcon, routes }) => {
760
+ const isModuleDisabled = !Object.values(routes).some(({ requiresOcel }) => !requiresOcel) && !isOcelAvailable;
761
+ return /* @__PURE__ */ jsx(NavLink, {
762
+ leftSection: /* @__PURE__ */ jsx(Icon, {
763
+ width: 18,
764
+ height: 18,
765
+ size: 18
766
+ }),
767
+ label,
768
+ defaultOpened: name === modulePath?.moduleName,
769
+ component: Link,
770
+ href: getModuleRoute({ moduleName: name }),
771
+ disabled: isModuleDisabled,
772
+ ...isModuleDisabled ? { opened: false } : {},
773
+ active: Object.keys(routes).length === 1 && name === modulePath?.moduleName,
774
+ children: Object.keys(routes).length > 1 && Object.values(routes).map(({ label: routeLabel, name: routeName, requiresOcel }) => /* @__PURE__ */ jsx(NavLink, {
775
+ label: routeLabel,
776
+ href: getModuleRoute({
777
+ moduleName: name,
778
+ routeName
779
+ }),
780
+ disabled: !!requiresOcel && !isOcelAvailable,
781
+ component: Link,
782
+ active: name === modulePath?.moduleName && routeName === modulePath.routeName
783
+ }, routeName))
784
+ }, name);
785
+ });
786
+ };
581
787
  const NavBar = ({ config }) => {
582
- const { modules = [] } = config;
583
- const router = useRouter();
584
- const modulePath = useModulePath_default(config);
788
+ const { modules = [], navbarGroups } = config;
789
+ const modulePath = useModulePath(config);
585
790
  const { data: ocels } = useGetOcels();
586
791
  const isOcelAvailable = ocels?.length !== 0;
792
+ const moduleGroups = useMemo(() => {
793
+ if (!navbarGroups) return [{
794
+ title: void 0,
795
+ modules
796
+ }];
797
+ const groupedModuleNames = navbarGroups.flatMap(({ modulesNames }) => modulesNames);
798
+ const ungroupedModules = modules.filter(({ name }) => !groupedModuleNames.includes(name));
799
+ return [...navbarGroups.map(({ modulesNames, title }) => ({
800
+ title,
801
+ modules: modulesNames.map((moduleName) => modules.find(({ name }) => name === moduleName)).filter((module) => !!module)
802
+ })), ...ungroupedModules.length > 0 ? [{
803
+ title: void 0,
804
+ modules: ungroupedModules
805
+ }] : []];
806
+ }, [modules, navbarGroups]);
587
807
  return /* @__PURE__ */ jsx(AppShell.Navbar, {
588
- className: AppShell_module_default["navbar"] ?? "",
808
+ className: AppShell_module_default.navbar ?? "",
589
809
  children: /* @__PURE__ */ jsxs(Stack, {
590
810
  justify: "space-between",
591
811
  h: "100%",
592
812
  gap: 0,
593
- children: [/* @__PURE__ */ jsxs(Stack, {
594
- gap: 0,
595
- flex: 1,
596
- children: [
597
- /* @__PURE__ */ jsx(NavLink, {
598
- component: Link,
599
- href: "/",
600
- label: "Home",
601
- leftSection: /* @__PURE__ */ jsx(HomeIcon, { size: 16 }),
602
- active: router.asPath === "/"
603
- }),
604
- /* @__PURE__ */ jsx(Divider, {}),
605
- modules.map(({ label, name, icon: Icon = PackageIcon, routes }) => {
606
- const isModuleDisabled = !Object.values(routes).some(({ requiresOcel }) => !requiresOcel) && !isOcelAvailable;
607
- return /* @__PURE__ */ jsx(NavLink, {
608
- leftSection: /* @__PURE__ */ jsx(Icon, {
609
- width: 18,
610
- height: 18,
611
- size: 18
612
- }),
613
- label,
614
- defaultOpened: name === modulePath?.moduleName,
615
- component: Link,
616
- href: getModuleRoute({
617
- moduleName: name,
618
- routeName: routes[0]?.name ?? ""
619
- }),
620
- disabled: isModuleDisabled,
621
- ...isModuleDisabled ? { opened: false } : {},
622
- active: Object.keys(routes).length === 1 && name === modulePath?.moduleName,
623
- children: Object.keys(routes).length > 1 && Object.values(routes).map(({ label: routeLabel, name: routeName, requiresOcel }) => /* @__PURE__ */ jsx(NavLink, {
624
- label: routeLabel,
625
- href: getModuleRoute({
626
- moduleName: name,
627
- routeName
628
- }),
629
- disabled: !!requiresOcel && !isOcelAvailable,
630
- component: Link,
631
- active: name === modulePath?.moduleName && routeName === modulePath.moduleName
632
- }, routeName))
633
- }, name);
634
- })
635
- ]
636
- }), /* @__PURE__ */ jsx(Divider, {})]
813
+ children: [
814
+ /* @__PURE__ */ jsx(Stack, {
815
+ gap: 0,
816
+ flex: 1,
817
+ children: moduleGroups.map(({ modules, title }, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && /* @__PURE__ */ jsx(Divider, {
818
+ label: title,
819
+ my: !title ? 9 : void 0
820
+ }), /* @__PURE__ */ jsx(NavbarGroup, {
821
+ modules,
822
+ isOcelAvailable,
823
+ modulePath
824
+ })] }, index))
825
+ }),
826
+ /* @__PURE__ */ jsx(Divider, {}),
827
+ /* @__PURE__ */ jsx(LogoutButton, {})
828
+ ]
637
829
  })
638
830
  });
639
831
  };
640
- var Header_default = NavBar;
641
-
642
832
  //#endregion
643
833
  //#region src/components/AppShell/AppShell.tsx
644
834
  const AppShell$1 = ({ config, children }) => {
@@ -681,13 +871,13 @@ const AppShell$1 = ({ config, children }) => {
681
871
  onClick: () => setIsUploadModalVisible(true),
682
872
  children: "Upload"
683
873
  }),
684
- /* @__PURE__ */ jsx(UploadModal_default, {
874
+ /* @__PURE__ */ jsx(UploadModal, {
685
875
  visible: isUploadModalVisible,
686
876
  onClose: () => setIsUploadModalVisible(false)
687
877
  })
688
878
  ] })]
689
879
  }) }),
690
- /* @__PURE__ */ jsx(Header_default, { config }),
880
+ /* @__PURE__ */ jsx(NavBar, { config }),
691
881
  /* @__PURE__ */ jsx(AppShell.Main, {
692
882
  style: { overflow: "hidden" },
693
883
  h: "calc(100dvh - var(--app-shell-header-offset, 0rem) - var(--app-shell-footer-height, 0px) + var(--app-shell-padding, 0))",
@@ -703,23 +893,146 @@ const AppShell$1 = ({ config, children }) => {
703
893
  ]
704
894
  });
705
895
  };
706
-
896
+ //#endregion
897
+ //#region src/lib/sse.ts
898
+ const OCELLink = z.object({
899
+ type: z.literal("ocel"),
900
+ ocel_id: z.string()
901
+ });
902
+ const ResourceLink = z.object({
903
+ type: z.literal("resource"),
904
+ resource_id: z.string()
905
+ });
906
+ const PluginLink = z.object({
907
+ type: z.literal("plugin"),
908
+ id: z.string(),
909
+ method: z.string(),
910
+ task_id: z.string()
911
+ });
912
+ const SystemLink = z.discriminatedUnion("type", [
913
+ OCELLink,
914
+ ResourceLink,
915
+ PluginLink
916
+ ]);
917
+ const SystemNotification = z.object({
918
+ type: z.literal("notification"),
919
+ title: z.string(),
920
+ message: z.string(),
921
+ notification_type: z.enum([
922
+ "warning",
923
+ "info",
924
+ "error"
925
+ ]),
926
+ link: SystemLink.nullish()
927
+ });
928
+ const InvalidationRequest = z.object({
929
+ type: z.literal("invalidation"),
930
+ routes: z.array(z.enum([
931
+ "ocels",
932
+ "resources",
933
+ "tasks",
934
+ "plugins"
935
+ ]))
936
+ });
937
+ const ErrorNotification = z.object({
938
+ type: z.literal("error"),
939
+ title: z.string(),
940
+ message: z.string(),
941
+ trace: z.string()
942
+ });
943
+ const ServerEventMessage = z.discriminatedUnion("type", [
944
+ SystemNotification,
945
+ InvalidationRequest,
946
+ ErrorNotification
947
+ ]);
948
+ //#endregion
949
+ //#region src/components/SSEWrapper.tsx
950
+ const SSEWrapper = () => {
951
+ const { sessionId } = useSessionStore();
952
+ const invalidate = useInvalidate();
953
+ useEffect(() => {
954
+ if (!sessionId) return;
955
+ const es = new EventSource(`${env$1.event_url}?sessionId=${sessionId}`);
956
+ es.onmessage = async (event) => {
957
+ const result = ServerEventMessage.safeParse(JSON.parse(event.data));
958
+ if (!result.success) {
959
+ console.warn("Invalid SSE message", event.data, result.error);
960
+ return;
961
+ }
962
+ const message = result.data;
963
+ switch (message.type) {
964
+ case "notification":
965
+ showNotification({
966
+ title: message.title,
967
+ message: message.message,
968
+ color: "green"
969
+ });
970
+ break;
971
+ case "error":
972
+ showNotification({
973
+ title: message.title,
974
+ color: "red",
975
+ autoClose: 6e4,
976
+ message: /* @__PURE__ */ jsxs(Stack, {
977
+ gap: 6,
978
+ align: "flex-start",
979
+ children: [/* @__PURE__ */ jsx(Text, {
980
+ size: "sm",
981
+ style: { flex: 1 },
982
+ lineClamp: 2,
983
+ children: message.message
984
+ }), /* @__PURE__ */ jsx(CopyButton, {
985
+ value: message.trace ?? "",
986
+ timeout: 1500,
987
+ children: ({ copied, copy }) => /* @__PURE__ */ jsx(Text, {
988
+ size: "xs",
989
+ c: copied ? "teal" : "red",
990
+ td: "underline",
991
+ style: {
992
+ cursor: "pointer",
993
+ whiteSpace: "nowrap",
994
+ marginTop: 2
995
+ },
996
+ onClick: copy,
997
+ children: copied ? "Copied" : "Copy trace"
998
+ })
999
+ })]
1000
+ })
1001
+ });
1002
+ break;
1003
+ case "invalidation": await invalidate(message.routes);
1004
+ }
1005
+ };
1006
+ es.onerror = (err) => {
1007
+ console.error("SSE error:", err);
1008
+ };
1009
+ return () => es.close();
1010
+ }, [sessionId, invalidate]);
1011
+ return null;
1012
+ };
707
1013
  //#endregion
708
1014
  //#region src/components/OcelescopeApp.tsx
1015
+ const theme = createTheme({ fontWeights: { medium: "500" } });
709
1016
  const OcelescopeApp = (config) => ({ Component, pageProps }) => {
710
1017
  const [client] = useState(() => new QueryClient());
711
1018
  return /* @__PURE__ */ jsxs(QueryClientProvider, {
712
1019
  client,
713
1020
  children: [/* @__PURE__ */ jsx(HydrationBoundary, {
714
1021
  state: pageProps.dehydratedState,
715
- children: /* @__PURE__ */ jsx(MantineProvider, { children: /* @__PURE__ */ jsx(AppShell$1, {
716
- config,
717
- children: /* @__PURE__ */ jsx(Component, { ...pageProps })
718
- }) })
1022
+ children: /* @__PURE__ */ jsxs(MantineProvider, {
1023
+ theme,
1024
+ children: [
1025
+ /* @__PURE__ */ jsx(SSEWrapper, {}),
1026
+ /* @__PURE__ */ jsx(Notifications, {}),
1027
+ /* @__PURE__ */ jsx(AppShell$1, {
1028
+ config,
1029
+ children: /* @__PURE__ */ jsx(Component, { ...pageProps })
1030
+ })
1031
+ ]
1032
+ })
719
1033
  }), /* @__PURE__ */ jsx(ReactQueryDevtools, {})]
720
1034
  });
721
1035
  };
722
-
723
1036
  //#endregion
724
1037
  //#region src/components/OcelescopeDocument.tsx
725
1038
  const OcelescopeDocument = () => {
@@ -729,11 +1042,298 @@ const OcelescopeDocument = () => {
729
1042
  children: [/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx(ColorSchemeScript, { defaultColorScheme: "auto" }) }), /* @__PURE__ */ jsxs("body", { children: [/* @__PURE__ */ jsx(Main, {}), /* @__PURE__ */ jsx(NextScript, {})] })]
730
1043
  });
731
1044
  };
732
-
1045
+ //#endregion
1046
+ //#region src/components/RelationTable.tsx
1047
+ const PAGE_SIZE = 10;
1048
+ const COLUMN_WIDTHS = {
1049
+ selector: 30,
1050
+ source: 220,
1051
+ target: 220,
1052
+ qualifier: 220,
1053
+ range: 160,
1054
+ total: 120
1055
+ };
1056
+ const relationId = ({ source, target }) => `${source} ${target}`;
1057
+ const SubRelationTable = ({ ocelId, relationType, direction, source, target, hideRange = false, hideTotal = false, ocelVersion, extraColumns = [] }) => {
1058
+ const { data, isFetching } = (relationType === "e2o" ? useE2o : useO2o)(ocelId, {
1059
+ direction,
1060
+ source_types: [source],
1061
+ target_types: [target],
1062
+ with_qualifier: true,
1063
+ ocel_version: ocelVersion
1064
+ }, { query: { placeholderData: keepPreviousData } });
1065
+ const columns = useMemo(() => [
1066
+ {
1067
+ accessor: "selector",
1068
+ title: "",
1069
+ width: COLUMN_WIDTHS.selector
1070
+ },
1071
+ {
1072
+ accessor: "source",
1073
+ title: "",
1074
+ width: COLUMN_WIDTHS.source,
1075
+ render: () => null
1076
+ },
1077
+ {
1078
+ accessor: "target",
1079
+ title: "",
1080
+ width: COLUMN_WIDTHS.target,
1081
+ render: () => null
1082
+ },
1083
+ {
1084
+ accessor: "qualifier",
1085
+ title: "Qualifier",
1086
+ width: COLUMN_WIDTHS.qualifier,
1087
+ render: ({ qualifier }) => qualifier || /* @__PURE__ */ jsx("i", { children: "No qualifier" })
1088
+ },
1089
+ {
1090
+ accessor: "range",
1091
+ title: "Range",
1092
+ width: COLUMN_WIDTHS.range,
1093
+ render: ({ min_count, max_count }) => `${min_count} - ${max_count}`,
1094
+ hidden: hideRange
1095
+ },
1096
+ {
1097
+ accessor: "sum",
1098
+ title: "Total",
1099
+ width: COLUMN_WIDTHS.total,
1100
+ hidden: hideTotal
1101
+ },
1102
+ ...extraColumns
1103
+ ], [
1104
+ hideRange,
1105
+ hideTotal,
1106
+ extraColumns
1107
+ ]);
1108
+ return /* @__PURE__ */ jsx(DataTable, {
1109
+ noHeader: true,
1110
+ idAccessor: "qualifier",
1111
+ records: data?.response ?? [],
1112
+ columns,
1113
+ fetching: isFetching,
1114
+ backgroundColor: {
1115
+ light: "gray.0",
1116
+ dark: "dark.6"
1117
+ }
1118
+ });
1119
+ };
1120
+ const RelationTable = ({ ocelId, relationType = "o2o", direction = "source", extraColumns = [], subTableExtraColumns, hideRange = false, hideTotal = false, ocelVersion, visibleRelations }) => {
1121
+ const isE2O = relationType === "e2o";
1122
+ const isSource = direction === "source";
1123
+ const [currentPage, setCurrentPage] = useState(1);
1124
+ const { data: combinations = [] } = (isE2O ? useE2oCombinations : useO2oCombinations)(ocelId, {
1125
+ direction,
1126
+ ocel_version: ocelVersion
1127
+ });
1128
+ const sourceOptions = useMemo(() => Array.from(new Set(combinations.map(({ source }) => source))), [combinations]);
1129
+ const targetOptions = useMemo(() => Array.from(new Set(combinations.map(({ target }) => target))), [combinations]);
1130
+ const qualifierOptions = useMemo(() => Array.from(new Set(combinations.map(({ qualifier }) => qualifier))), [combinations]);
1131
+ const sourceTitle = isE2O ? isSource ? "Activity" : "Object Type" : "Source";
1132
+ const targetTitle = isE2O ? isSource ? "Object Type" : "Activity" : "Target";
1133
+ const [filteredSources, setFilteredSources] = useState([]);
1134
+ const [filteredTargets, setFilteredTargets] = useState([]);
1135
+ const [filteredQualifiers, setFilteredQualifiers] = useState([]);
1136
+ const isConstrained = visibleRelations !== void 0;
1137
+ const visibleSources = useMemo(() => isConstrained ? Array.from(new Set(visibleRelations.map(({ source }) => source))) : [], [isConstrained, visibleRelations]);
1138
+ const visibleTargets = useMemo(() => isConstrained ? Array.from(new Set(visibleRelations.map(({ target }) => target))) : [], [isConstrained, visibleRelations]);
1139
+ const sourceSelectOptions = isConstrained ? sourceOptions.filter((option) => visibleSources.includes(option)) : sourceOptions;
1140
+ const targetSelectOptions = isConstrained ? targetOptions.filter((option) => visibleTargets.includes(option)) : targetOptions;
1141
+ const effectiveSources = useMemo(() => {
1142
+ if (!isConstrained) return filteredSources;
1143
+ return filteredSources.length > 0 ? filteredSources.filter((source) => visibleSources.includes(source)) : visibleSources;
1144
+ }, [
1145
+ isConstrained,
1146
+ filteredSources,
1147
+ visibleSources
1148
+ ]);
1149
+ const effectiveTargets = useMemo(() => {
1150
+ if (!isConstrained) return filteredTargets;
1151
+ return filteredTargets.length > 0 ? filteredTargets.filter((target) => visibleTargets.includes(target)) : visibleTargets;
1152
+ }, [
1153
+ isConstrained,
1154
+ filteredTargets,
1155
+ visibleTargets
1156
+ ]);
1157
+ const showNoRelations = isConstrained && (effectiveSources.length === 0 || effectiveTargets.length === 0);
1158
+ const { data, isFetching } = (isE2O ? useE2o : useO2o)(ocelId, {
1159
+ direction,
1160
+ page: currentPage,
1161
+ page_size: PAGE_SIZE,
1162
+ with_qualifier: false,
1163
+ ocel_version: ocelVersion,
1164
+ ...effectiveSources.length > 0 && { source_types: effectiveSources },
1165
+ ...effectiveTargets.length > 0 && { target_types: effectiveTargets },
1166
+ ...filteredQualifiers.length > 0 && { qualifiers: filteredQualifiers }
1167
+ }, { query: {
1168
+ placeholderData: keepPreviousData,
1169
+ enabled: !showNoRelations
1170
+ } });
1171
+ const [selectedRelations, setSelectedRelations] = useState([]);
1172
+ const columns = useMemo(() => [
1173
+ {
1174
+ accessor: "selector",
1175
+ title: "",
1176
+ textAlign: "center",
1177
+ width: COLUMN_WIDTHS.selector,
1178
+ cellsStyle: () => ({ padding: 5 }),
1179
+ render: (record) => {
1180
+ if ((record.qualifiers?.length ?? 0) <= 1) return null;
1181
+ return /* @__PURE__ */ jsx(ThemeIcon, {
1182
+ size: "sm",
1183
+ variant: "transparent",
1184
+ style: { display: "flex" },
1185
+ children: selectedRelations.includes(relationId(record)) ? /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) : /* @__PURE__ */ jsx(ChevronRightIcon, { size: 16 })
1186
+ });
1187
+ }
1188
+ },
1189
+ {
1190
+ accessor: "source",
1191
+ title: sourceTitle,
1192
+ width: COLUMN_WIDTHS.source,
1193
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
1194
+ data: sourceSelectOptions,
1195
+ value: filteredSources,
1196
+ onChange: setFilteredSources,
1197
+ comboboxProps: { withinPortal: false },
1198
+ clearable: true,
1199
+ searchable: true
1200
+ }),
1201
+ filtering: filteredSources.length > 0
1202
+ },
1203
+ {
1204
+ accessor: "target",
1205
+ title: targetTitle,
1206
+ width: COLUMN_WIDTHS.target,
1207
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
1208
+ data: targetSelectOptions,
1209
+ value: filteredTargets,
1210
+ onChange: setFilteredTargets,
1211
+ comboboxProps: { withinPortal: false },
1212
+ clearable: true,
1213
+ searchable: true
1214
+ }),
1215
+ filtering: filteredTargets.length > 0
1216
+ },
1217
+ {
1218
+ accessor: "qualifiers",
1219
+ title: "Qualifiers",
1220
+ width: COLUMN_WIDTHS.qualifier,
1221
+ render: ({ qualifiers = [] }) => {
1222
+ const labels = qualifiers.map((qualifier) => qualifier || "No qualifier");
1223
+ return `${labels.slice(0, 2).join(", ")}${labels.length > 2 ? `, ... (${labels.length})` : ""}`;
1224
+ },
1225
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
1226
+ data: qualifierOptions,
1227
+ value: filteredQualifiers,
1228
+ onChange: setFilteredQualifiers,
1229
+ comboboxProps: { withinPortal: false },
1230
+ clearable: true,
1231
+ searchable: true
1232
+ }),
1233
+ filtering: filteredQualifiers.length > 0
1234
+ },
1235
+ {
1236
+ accessor: "range",
1237
+ title: "Range",
1238
+ width: COLUMN_WIDTHS.range,
1239
+ render: ({ min_count, max_count }) => `${min_count} - ${max_count}`,
1240
+ hidden: hideRange
1241
+ },
1242
+ {
1243
+ accessor: "sum",
1244
+ title: "Total",
1245
+ width: COLUMN_WIDTHS.total,
1246
+ hidden: hideTotal
1247
+ },
1248
+ ...extraColumns
1249
+ ], [
1250
+ sourceTitle,
1251
+ targetTitle,
1252
+ sourceSelectOptions,
1253
+ targetSelectOptions,
1254
+ qualifierOptions,
1255
+ filteredSources,
1256
+ filteredTargets,
1257
+ filteredQualifiers,
1258
+ selectedRelations,
1259
+ hideRange,
1260
+ hideTotal,
1261
+ extraColumns
1262
+ ]);
1263
+ return /* @__PURE__ */ jsx(DataTable, {
1264
+ idAccessor: relationId,
1265
+ records: showNoRelations ? [] : data?.response,
1266
+ columns,
1267
+ withTableBorder: true,
1268
+ fetching: isFetching,
1269
+ totalRecords: showNoRelations ? 0 : data?.total_items ?? 0,
1270
+ page: currentPage,
1271
+ recordsPerPage: PAGE_SIZE,
1272
+ onPageChange: setCurrentPage,
1273
+ noRecordsText: "No relations found",
1274
+ height: 500,
1275
+ rowExpansion: {
1276
+ allowMultiple: false,
1277
+ expandable: ({ record }) => (record.qualifiers?.length ?? 0) > 1,
1278
+ expanded: {
1279
+ recordIds: selectedRelations,
1280
+ onRecordIdsChange: setSelectedRelations
1281
+ },
1282
+ content: ({ record }) => /* @__PURE__ */ jsx(SubRelationTable, {
1283
+ ocelId,
1284
+ relationType,
1285
+ direction,
1286
+ source: record.source,
1287
+ target: record.target,
1288
+ hideRange,
1289
+ hideTotal,
1290
+ ocelVersion,
1291
+ extraColumns: subTableExtraColumns
1292
+ })
1293
+ }
1294
+ });
1295
+ };
1296
+ //#endregion
1297
+ //#region src/hooks/useDownload.ts
1298
+ const useDownloadFile = () => {
1299
+ const { sessionId } = useSessionStore();
1300
+ const downloadFile = async (path) => {
1301
+ try {
1302
+ const response = await fetch(path, {
1303
+ method: "GET",
1304
+ headers: { [env$1.session_id]: sessionId ?? "" }
1305
+ });
1306
+ if (!response.ok) throw new Error("Failed to download file");
1307
+ const blob = await response.blob();
1308
+ const contentDisposition = parse(response.headers.get("Content-Disposition") ?? "");
1309
+ saveAs(blob, contentDisposition.attachment ? contentDisposition.filename : "fileName");
1310
+ } catch (err) {
1311
+ console.error("Download error:", err);
1312
+ }
1313
+ };
1314
+ return downloadFile;
1315
+ };
1316
+ const useDownloadFromServer = (getQueryKey) => () => {
1317
+ const downloadFile = useDownloadFile();
1318
+ return { download: (...params) => {
1319
+ const result = getQueryKey(...params);
1320
+ const path = result[0];
1321
+ const searchParams = result[1];
1322
+ const queryString = qs.stringify(searchParams, {
1323
+ skipNulls: true,
1324
+ indices: false
1325
+ });
1326
+ downloadFile(queryString ? `${path}?${queryString}` : path);
1327
+ } };
1328
+ };
1329
+ const useDownloadOCEL = useDownloadFromServer(getDownloadOCELQueryKey);
1330
+ const useDownloadFlatOCEL = useDownloadFromServer(getDownloadFlatLogQueryKey);
1331
+ const useDownloadResource = useDownloadFromServer(getDownloadResourceQueryKey);
1332
+ const useDownloadResourceAsPnml = useDownloadFromServer(getDownloadResourceAsPnmlQueryKey);
733
1333
  //#endregion
734
1334
  //#region src/lib/config.ts
735
1335
  const defineModule = (def) => def;
736
1336
  const defineModuleRoute = (def) => def;
737
-
1337
+ const env = z.object({ NEXT_PUBLIC_PROJECT_PAGE: z.url().default("https://www.ocelescope.org/") }).transform(({ NEXT_PUBLIC_PROJECT_PAGE }) => ({ projectPage: NEXT_PUBLIC_PROJECT_PAGE })).parse(process.env);
738
1338
  //#endregion
739
- export { OcelescopeApp, OcelescopeDocument, createModulesPage, defineModule, defineModuleRoute, getModuleRoute, useCurrentOcel };
1339
+ export { AttributesTable, BarList, FileDropzone, OcelSelect, OcelescopeApp, OcelescopeDocument, RelationTable, UploadModal, UploadSection, createModulesPage, defineModule, defineModuleRoute, env, formatAttributeValue, getModuleRoute, sseHandler, useCurrentOcel, useDownloadFile, useDownloadFlatOCEL, useDownloadOCEL, useDownloadResource, useDownloadResourceAsPnml, useInvalidate };