@ocelescope/core 0.0.1 → 0.1.1

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 { ActionIcon, Anchor, 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 { BookOpenIcon, BugIcon, ChevronDownIcon, ChevronRightIcon, ContainerIcon, DownloadIcon, FilterIcon, GlobeIcon, HelpCircleIcon, LightbulbIcon, LogOutIcon, MailIcon, PackageIcon, PackagePlusIcon, PuzzleIcon, TriangleAlertIcon, 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 z from "zod";
13
+ import { Notifications, showNotification } from "@mantine/notifications";
16
14
  import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
17
15
  import { useDisclosure } from "@mantine/hooks";
16
+ import { env as env$1, useSessionStore } from "@ocelescope/api-client";
18
17
  import { create } from "zustand";
19
18
  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);
19
+ import Link from "next/link.js";
20
+ import { useRouter } from "next/router.js";
21
+ import { Head, Html, Main, NextScript } from "next/document.js";
22
+ import { parse } from "content-disposition-attachment";
23
+ import FileSaver 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,280 @@ 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
+ const env = z.object({
460
+ NEXT_PUBLIC_APP_VERSION: z.string().default("v0.0.0"),
461
+ NEXT_PUBLIC_PROJECT_PAGE: z.url().default("https://www.ocelescope.org/"),
462
+ NEXT_PUBLIC_DOCS_URL: z.url().default("https://www.ocelescope.org/getting-started/download/"),
463
+ NEXT_PUBLIC_PLUGIN_DEV_GUIDE_URL: z.url().default("https://www.ocelescope.org/plugin-development/"),
464
+ NEXT_PUBLIC_PLUGIN_LIBRARY_URL: z.url().default("https://www.ocelescope.org/community/plugin-library/"),
465
+ NEXT_PUBLIC_REPORT_BUG_URL: z.url().default("https://www.ocelescope.org/community/report-bug/"),
466
+ NEXT_PUBLIC_TROUBLESHOOTING_URL: z.url().default("https://www.ocelescope.org/community/troubleshooting/"),
467
+ NEXT_PUBLIC_REQUEST_FEATURE_URL: z.url().default("https://www.ocelescope.org/community/request-feature/"),
468
+ NEXT_PUBLIC_SUBMIT_PLUGIN_URL: z.url().default("https://www.ocelescope.org/community/submit-plugin/"),
469
+ NEXT_PUBLIC_REQUEST_ENVIRONMENT_URL: z.url().default("https://www.ocelescope.org/plugin-development/request-package/"),
470
+ NEXT_PUBLIC_CONTACT_EMAIL: z.string().default("ocelescope@pads.rwth-aachen.de")
471
+ }).transform((env) => ({
472
+ appVersion: env.NEXT_PUBLIC_APP_VERSION,
473
+ projectPage: env.NEXT_PUBLIC_PROJECT_PAGE,
474
+ docsUrl: env.NEXT_PUBLIC_DOCS_URL,
475
+ pluginDevGuideUrl: env.NEXT_PUBLIC_PLUGIN_DEV_GUIDE_URL,
476
+ pluginLibraryUrl: env.NEXT_PUBLIC_PLUGIN_LIBRARY_URL,
477
+ reportBugUrl: env.NEXT_PUBLIC_REPORT_BUG_URL,
478
+ requestFeatureUrl: env.NEXT_PUBLIC_REQUEST_FEATURE_URL,
479
+ submitPluginUrl: env.NEXT_PUBLIC_SUBMIT_PLUGIN_URL,
480
+ troubleshootingUrl: env.NEXT_PUBLIC_TROUBLESHOOTING_URL,
481
+ requestEnvironmentUrl: env.NEXT_PUBLIC_REQUEST_ENVIRONMENT_URL,
482
+ contactEmail: env.NEXT_PUBLIC_CONTACT_EMAIL
483
+ })).parse({
484
+ NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION,
485
+ NEXT_PUBLIC_PROJECT_PAGE: process.env.NEXT_PUBLIC_PROJECT_PAGE,
486
+ NEXT_PUBLIC_DOCS_URL: process.env.NEXT_PUBLIC_DOCS_URL,
487
+ NEXT_PUBLIC_PLUGIN_DEV_GUIDE_URL: process.env.NEXT_PUBLIC_PLUGIN_DEV_GUIDE_URL,
488
+ NEXT_PUBLIC_PLUGIN_LIBRARY_URL: process.env.NEXT_PUBLIC_PLUGIN_LIBRARY_URL,
489
+ NEXT_PUBLIC_REPORT_BUG_URL: process.env.NEXT_PUBLIC_REPORT_BUG_URL,
490
+ NEXT_PUBLIC_TROUBLESHOOTING_URL: process.env.NEXT_PUBLIC_TROUBLESHOOTING_URL,
491
+ NEXT_PUBLIC_REQUEST_FEATURE_URL: process.env.NEXT_PUBLIC_REQUEST_FEATURE_URL,
492
+ NEXT_PUBLIC_SUBMIT_PLUGIN_URL: process.env.NEXT_PUBLIC_SUBMIT_PLUGIN_URL,
493
+ NEXT_PUBLIC_REQUEST_ENVIRONMENT_URL: process.env.NEXT_PUBLIC_REQUEST_ENVIRONMENT_URL,
494
+ NEXT_PUBLIC_CONTACT_EMAIL: process.env.NEXT_PUBLIC_CONTACT_EMAIL
495
+ });
496
+ //#endregion
497
+ //#region src/components/HelpModal/HelpModal.tsx
498
+ const HelpLink = ({ icon: Icon, label, href }) => /* @__PURE__ */ jsx(Anchor, {
499
+ href,
500
+ target: "_blank",
501
+ rel: "noopener noreferrer",
502
+ underline: "never",
503
+ c: "inherit",
504
+ children: /* @__PURE__ */ jsxs(Group, {
505
+ gap: "sm",
506
+ children: [/* @__PURE__ */ jsx(Icon, { size: 18 }), /* @__PURE__ */ jsx(Text, {
507
+ size: "sm",
508
+ children: label
509
+ })]
510
+ })
511
+ });
512
+ const HelpModal = ({ visible, onClose }) => {
513
+ return /* @__PURE__ */ jsx(Modal, {
514
+ title: /* @__PURE__ */ jsx(Text, {
515
+ size: "h3",
516
+ children: "Help"
517
+ }),
518
+ opened: visible,
519
+ onClose,
520
+ children: /* @__PURE__ */ jsxs(Stack, {
521
+ gap: "sm",
522
+ children: [
523
+ /* @__PURE__ */ jsx(HelpLink, {
524
+ icon: GlobeIcon,
525
+ label: "Website",
526
+ href: env.projectPage
527
+ }),
528
+ /* @__PURE__ */ jsx(HelpLink, {
529
+ icon: BookOpenIcon,
530
+ label: "Documentation",
531
+ href: env.docsUrl
532
+ }),
533
+ /* @__PURE__ */ jsx(HelpLink, {
534
+ icon: PuzzleIcon,
535
+ label: "Plugin development guide",
536
+ href: env.pluginDevGuideUrl
537
+ }),
538
+ /* @__PURE__ */ jsx(HelpLink, {
539
+ icon: TriangleAlertIcon,
540
+ label: "Known issues",
541
+ href: env.troubleshootingUrl
542
+ }),
543
+ /* @__PURE__ */ jsx(HelpLink, {
544
+ icon: BugIcon,
545
+ label: "Report a bug",
546
+ href: env.reportBugUrl
547
+ }),
548
+ /* @__PURE__ */ jsx(HelpLink, {
549
+ icon: LightbulbIcon,
550
+ label: "Request a feature",
551
+ href: env.requestFeatureUrl
552
+ }),
553
+ /* @__PURE__ */ jsx(HelpLink, {
554
+ icon: UploadIcon,
555
+ label: "Submit a plugin",
556
+ href: env.submitPluginUrl
557
+ }),
558
+ /* @__PURE__ */ jsx(HelpLink, {
559
+ icon: PackagePlusIcon,
560
+ label: "Request environment addition",
561
+ href: env.requestEnvironmentUrl
562
+ }),
563
+ /* @__PURE__ */ jsx(HelpLink, {
564
+ icon: MailIcon,
565
+ label: env.contactEmail,
566
+ href: `mailto:${env.contactEmail}`
567
+ })
568
+ ]
569
+ })
570
+ });
571
+ };
572
+ //#endregion
573
+ //#region src/components/ModulePage.tsx
574
+ const createModulesPage = (config) => {
575
+ const { modules = [] } = config;
576
+ const getStaticPaths = async () => {
577
+ return {
578
+ paths: [...modules.flatMap(({ name: moduleName, routes }) => [...routes.map(({ name: routeName }) => ({ params: { slug: [moduleName, routeName] } })), { params: { slug: [moduleName] } }]), { params: { slug: [] } }],
579
+ fallback: false
580
+ };
581
+ };
582
+ const getStaticProps = async ({ params }) => {
583
+ const slugs = params?.slug ?? [];
584
+ const moduleDef = slugs[0] ? modules.find(({ name }) => name === slugs[0]) : modules[0];
585
+ const routeDef = slugs[1] ? moduleDef?.routes.find(({ name }) => name === slugs[1]) : moduleDef?.routes[0];
586
+ if (!moduleDef || !routeDef) return { notFound: true };
587
+ return { props: {
588
+ moduleName: moduleDef.name,
589
+ routeName: routeDef.name
590
+ } };
591
+ };
592
+ const ModulePage = ({ moduleName, routeName }) => {
593
+ const RouteComponent = modules.find(({ name }) => name === moduleName)?.routes.find(({ name }) => name === routeName)?.component;
594
+ return RouteComponent ? /* @__PURE__ */ jsx(RouteComponent, {}) : null;
595
+ };
596
+ return {
597
+ getStaticPaths,
598
+ getStaticProps,
599
+ ModulePage
600
+ };
601
+ };
602
+ //#endregion
603
+ //#region src/store/currentOcelStore.ts
604
+ const useCurrentOcelStore = create()(persist((set) => ({
605
+ ocelId: null,
606
+ setOcel: (id) => set({ ocelId: id }),
607
+ clearOcel: () => set({ ocelId: null })
608
+ }), { name: env$1.current_ocel_id }));
609
+ //#endregion
610
+ //#region src/hooks/useCurrentOCEL.ts
611
+ const useCurrentOcel = () => {
612
+ const { data: ocels = [] } = useGetOcels();
613
+ const { ocelId, setOcel, clearOcel } = useCurrentOcelStore();
614
+ useEffect(() => {
615
+ if (ocelId && !ocels?.some(({ id }) => id === ocelId)) clearOcel();
616
+ if (!ocelId && ocels[0]) setOcel(ocels[0].id);
617
+ }, [
618
+ clearOcel,
619
+ setOcel,
620
+ ocels,
621
+ ocelId
622
+ ]);
623
+ return {
624
+ id: ocelId,
625
+ setCurrentOcel: setOcel
626
+ };
627
+ };
628
+ //#endregion
629
+ //#region src/components/OcelSelect/OcelSelect.tsx
630
+ const FilteredIndicator = () => /* @__PURE__ */ jsx(Tooltip, {
631
+ label: "This log has been filtered",
632
+ children: /* @__PURE__ */ jsx(FilterIcon, {
633
+ size: 14,
634
+ color: "var(--mantine-color-blue-6)"
635
+ })
636
+ });
637
+ const OcelSelect = ({ extension, value, onChange, placeholder = "Select an OCEL", ...inputProps }) => {
638
+ const { data } = useGetOcels(extension ? { extension_name: extension } : {});
639
+ const ocels = useMemo(() => data ?? [], [data]);
640
+ const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption() });
641
+ const selected = useMemo(() => ocels.find((ocel) => ocel.id === value), [ocels, value]);
642
+ return /* @__PURE__ */ jsxs(Combobox, {
643
+ store: combobox,
644
+ onOptionSubmit: (val) => {
645
+ onChange?.(val);
646
+ combobox.closeDropdown();
647
+ },
648
+ children: [/* @__PURE__ */ jsx(Combobox.Target, { children: /* @__PURE__ */ jsx(InputBase, {
649
+ ...inputProps,
650
+ component: "button",
651
+ type: "button",
652
+ pointer: true,
653
+ rightSection: /* @__PURE__ */ jsx(Combobox.Chevron, {}),
654
+ rightSectionPointerEvents: "none",
655
+ onClick: () => combobox.toggleDropdown(),
656
+ children: selected ? /* @__PURE__ */ jsxs(Group, {
657
+ gap: 6,
658
+ wrap: "nowrap",
659
+ style: { overflow: "hidden" },
660
+ children: [/* @__PURE__ */ jsx(Text, {
661
+ span: true,
662
+ size: "sm",
663
+ truncate: true,
664
+ children: selected.name
665
+ }), selected.filter_applied && /* @__PURE__ */ jsx(FilteredIndicator, {})]
666
+ }) : /* @__PURE__ */ jsx(Input.Placeholder, { children: placeholder })
667
+ }) }), /* @__PURE__ */ jsx(Combobox.Dropdown, { children: /* @__PURE__ */ jsx(Combobox.Options, { children: ocels.map((ocel) => /* @__PURE__ */ jsx(Combobox.Option, {
668
+ value: ocel.id,
669
+ children: /* @__PURE__ */ jsxs(Group, {
670
+ gap: 6,
671
+ wrap: "nowrap",
672
+ children: [ocel.name, ocel.filter_applied && /* @__PURE__ */ jsx(FilteredIndicator, {})]
673
+ })
674
+ }, ocel.id)) }) })]
675
+ });
676
+ };
677
+ const CurrentOcelSelect = () => {
678
+ const { id, setCurrentOcel } = useCurrentOcel();
679
+ return /* @__PURE__ */ jsx(OcelSelect, {
680
+ value: id,
681
+ w: 250,
682
+ onChange: (id) => {
683
+ if (id) setCurrentOcel(id);
684
+ }
685
+ });
686
+ };
687
+ //#endregion
688
+ //#region src/hooks/useInvalidate.ts
689
+ const useInvalidate = () => {
690
+ const queryClient = useQueryClient();
691
+ return async (routeNames) => await queryClient.invalidateQueries({ predicate: (query) => typeof query.queryKey[0] === "string" && routeNames.some((route) => query.queryKey[0].includes(`/${route}`)) });
692
+ };
136
693
  //#endregion
137
694
  //#region src/components/UploadSection/UploadSection.tsx
138
695
  const DefaultOcels = ({ onSuccess, setIsPending }) => {
139
696
  const { data: ocels = [] } = useGetDefaultOcel({});
140
- const invalidate = useInvalidate_default();
697
+ const invalidate = useInvalidate();
141
698
  const { mutate: uploadDefault } = useImportDefaultOcel({ mutation: {
142
699
  onSuccess: async () => {
143
700
  await invalidate(["ocels"]);
@@ -178,7 +735,7 @@ const FileUploadZone = ({ onSuccess, setIsPending }) => {
178
735
  onMutate: () => setIsPending(true),
179
736
  onSettled: () => setIsPending(false)
180
737
  } });
181
- return /* @__PURE__ */ jsx(Dropzone_default, {
738
+ return /* @__PURE__ */ jsx(FileDropzone, {
182
739
  content: { description: /* @__PURE__ */ jsx("span", { children: `Drag'n'drop your ${[
183
740
  "OCELs",
184
741
  "Plugins",
@@ -191,8 +748,10 @@ const FileUploadZone = ({ onSuccess, setIsPending }) => {
191
748
  };
192
749
  const UploadSection = ({ onSuccess }) => {
193
750
  const [isPending, setIsPending] = useState(false);
194
- const onUploadSuccess = useCallback(() => {
751
+ const invalidate = useInvalidate();
752
+ const onUploadSuccess = useCallback(async () => {
195
753
  setIsPending(false);
754
+ await invalidate(["tasks"]);
196
755
  onSuccess?.();
197
756
  }, [onSuccess]);
198
757
  return /* @__PURE__ */ jsxs(Stack, {
@@ -217,298 +776,6 @@ const UploadSection = ({ onSuccess }) => {
217
776
  ]
218
777
  });
219
778
  };
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
779
  //#endregion
513
780
  //#region src/components/UploadModal/UploadModal.tsx
514
781
  const UploadModal = ({ visible, onClose }) => {
@@ -520,46 +787,9 @@ const UploadModal = ({ visible, onClose }) => {
520
787
  opened: visible,
521
788
  onClose,
522
789
  size: "xl",
523
- children: /* @__PURE__ */ jsx(UploadSection_default, { onSuccess: onClose })
790
+ children: /* @__PURE__ */ jsx(UploadSection, { onSuccess: onClose })
524
791
  });
525
792
  };
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
793
  //#endregion
564
794
  //#region src/hooks/useModulePath.ts
565
795
  const useModulePath = (config) => {
@@ -574,77 +804,151 @@ const useModulePath = (config) => {
574
804
  };
575
805
  }, [moduleName, routeName]);
576
806
  };
577
- var useModulePath_default = useModulePath;
578
-
579
807
  //#endregion
580
- //#region src/components/AppShell/components/Header.tsx
808
+ //#region src/lib/getModuleRoute.ts
809
+ const getModuleRoute = ({ moduleName, routeName, query }) => {
810
+ return {
811
+ pathname: `/${moduleName}${routeName ? `/${routeName}` : ""}`,
812
+ ...query && { query: { ...query } }
813
+ };
814
+ };
815
+ //#endregion
816
+ //#region src/components/AppShell/AppShell.module.css
817
+ var AppShell_module_default = {
818
+ "button": "rZS5FG_button",
819
+ "buttonIcon": "rZS5FG_buttonIcon",
820
+ "chevron": "rZS5FG_chevron",
821
+ "control": "rZS5FG_control",
822
+ "footer": "rZS5FG_footer",
823
+ "header": "rZS5FG_header",
824
+ "link": "rZS5FG_link",
825
+ "linkIcon": "rZS5FG_linkIcon",
826
+ "links": "rZS5FG_links",
827
+ "navbar": "rZS5FG_navbar"
828
+ };
829
+ //#endregion
830
+ //#region src/components/AppShell/components/NavBar.tsx
831
+ const LogoutButton = () => {
832
+ const [isModalOpen, setIsModalOpen] = useState(false);
833
+ const queryClient = useQueryClient();
834
+ const { push } = useRouter();
835
+ const { mutate: logout } = useLogout({ mutation: { onSuccess: () => {
836
+ queryClient.clear();
837
+ setIsModalOpen(false);
838
+ push("/");
839
+ } } });
840
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Modal, {
841
+ opened: isModalOpen,
842
+ onClose: () => setIsModalOpen(false),
843
+ title: "Are you sure?",
844
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
845
+ "If you leave now, all your data and progress will be",
846
+ " ",
847
+ /* @__PURE__ */ jsx("strong", { children: "deleted permanently" }),
848
+ ". This action cannot be undone."
849
+ ] }), /* @__PURE__ */ jsx(Button, {
850
+ color: "red",
851
+ mt: "md",
852
+ onClick: () => {
853
+ logout();
854
+ },
855
+ fullWidth: true,
856
+ children: "Accept"
857
+ })]
858
+ }), /* @__PURE__ */ jsx(UnstyledButton, {
859
+ className: AppShell_module_default.button ?? "",
860
+ onClick: () => setIsModalOpen(true),
861
+ children: /* @__PURE__ */ jsxs(Group, {
862
+ justify: "space-between",
863
+ w: "100%",
864
+ children: [/* @__PURE__ */ jsx(Text, {
865
+ style: { lineHeight: 1 },
866
+ children: "Logout"
867
+ }), /* @__PURE__ */ jsx(LogOutIcon, { className: AppShell_module_default.buttonIcon })]
868
+ })
869
+ })] });
870
+ };
871
+ const NavbarGroup = ({ modules, modulePath, isOcelAvailable }) => {
872
+ return modules.map(({ label, name, icon: Icon = PackageIcon, routes }) => {
873
+ const isModuleDisabled = !Object.values(routes).some(({ requiresOcel }) => !requiresOcel) && !isOcelAvailable;
874
+ return /* @__PURE__ */ jsx(NavLink, {
875
+ leftSection: /* @__PURE__ */ jsx(Icon, {
876
+ width: 18,
877
+ height: 18,
878
+ size: 18
879
+ }),
880
+ label,
881
+ defaultOpened: name === modulePath?.moduleName,
882
+ component: Link,
883
+ href: getModuleRoute({ moduleName: name }),
884
+ disabled: isModuleDisabled,
885
+ ...isModuleDisabled ? { opened: false } : {},
886
+ active: Object.keys(routes).length === 1 && name === modulePath?.moduleName,
887
+ children: Object.keys(routes).length > 1 && Object.values(routes).map(({ label: routeLabel, name: routeName, requiresOcel }) => /* @__PURE__ */ jsx(NavLink, {
888
+ label: routeLabel,
889
+ href: getModuleRoute({
890
+ moduleName: name,
891
+ routeName
892
+ }),
893
+ disabled: !!requiresOcel && !isOcelAvailable,
894
+ component: Link,
895
+ active: name === modulePath?.moduleName && routeName === modulePath.routeName
896
+ }, routeName))
897
+ }, name);
898
+ });
899
+ };
581
900
  const NavBar = ({ config }) => {
582
- const { modules = [] } = config;
583
- const router = useRouter();
584
- const modulePath = useModulePath_default(config);
901
+ const { modules = [], navbarGroups } = config;
902
+ const modulePath = useModulePath(config);
585
903
  const { data: ocels } = useGetOcels();
586
904
  const isOcelAvailable = ocels?.length !== 0;
905
+ const moduleGroups = useMemo(() => {
906
+ if (!navbarGroups) return [{
907
+ title: void 0,
908
+ modules
909
+ }];
910
+ const groupedModuleNames = navbarGroups.flatMap(({ modulesNames }) => modulesNames);
911
+ const ungroupedModules = modules.filter(({ name }) => !groupedModuleNames.includes(name));
912
+ return [...navbarGroups.map(({ modulesNames, title }) => ({
913
+ title,
914
+ modules: modulesNames.map((moduleName) => modules.find(({ name }) => name === moduleName)).filter((module) => !!module)
915
+ })), ...ungroupedModules.length > 0 ? [{
916
+ title: void 0,
917
+ modules: ungroupedModules
918
+ }] : []];
919
+ }, [modules, navbarGroups]);
587
920
  return /* @__PURE__ */ jsx(AppShell.Navbar, {
588
- className: AppShell_module_default["navbar"] ?? "",
921
+ className: AppShell_module_default.navbar ?? "",
589
922
  children: /* @__PURE__ */ jsxs(Stack, {
590
923
  justify: "space-between",
591
924
  h: "100%",
592
925
  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, {})]
926
+ children: [
927
+ /* @__PURE__ */ jsx(Stack, {
928
+ gap: 0,
929
+ flex: 1,
930
+ children: moduleGroups.map(({ modules, title }, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && /* @__PURE__ */ jsx(Divider, {
931
+ label: title,
932
+ my: !title ? 9 : void 0
933
+ }), /* @__PURE__ */ jsx(NavbarGroup, {
934
+ modules,
935
+ isOcelAvailable,
936
+ modulePath
937
+ })] }, index))
938
+ }),
939
+ /* @__PURE__ */ jsx(Divider, {}),
940
+ /* @__PURE__ */ jsx(LogoutButton, {})
941
+ ]
637
942
  })
638
943
  });
639
944
  };
640
- var Header_default = NavBar;
641
-
642
945
  //#endregion
643
946
  //#region src/components/AppShell/AppShell.tsx
644
947
  const AppShell$1 = ({ config, children }) => {
645
948
  const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(true);
646
949
  const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
647
950
  const [isUploadModalVisible, setIsUploadModalVisible] = useState(false);
951
+ const [isHelpModalVisible, setIsHelpModalVisible] = useState(false);
648
952
  return /* @__PURE__ */ jsxs(AppShell, {
649
953
  header: { height: 60 },
650
954
  navbar: {
@@ -663,17 +967,25 @@ const AppShell$1 = ({ config, children }) => {
663
967
  justify: "space-between",
664
968
  children: [/* @__PURE__ */ jsxs(Group, {
665
969
  align: "center",
666
- children: [/* @__PURE__ */ jsx(Burger, {
667
- opened: mobileOpened,
668
- onClick: toggleMobile,
669
- hiddenFrom: "sm",
670
- size: "sm"
671
- }), /* @__PURE__ */ jsx(Burger, {
672
- opened: desktopOpened,
673
- onClick: toggleDesktop,
674
- visibleFrom: "sm",
675
- size: "sm"
676
- })]
970
+ children: [
971
+ /* @__PURE__ */ jsx(Burger, {
972
+ opened: mobileOpened,
973
+ onClick: toggleMobile,
974
+ hiddenFrom: "sm",
975
+ size: "sm"
976
+ }),
977
+ /* @__PURE__ */ jsx(Burger, {
978
+ opened: desktopOpened,
979
+ onClick: toggleDesktop,
980
+ visibleFrom: "sm",
981
+ size: "sm"
982
+ }),
983
+ /* @__PURE__ */ jsx(Text, {
984
+ size: "xs",
985
+ c: "dimmed",
986
+ children: env.appVersion
987
+ })
988
+ ]
677
989
  }), /* @__PURE__ */ jsxs(Group, { children: [
678
990
  /* @__PURE__ */ jsx(CurrentOcelSelect, {}),
679
991
  /* @__PURE__ */ jsx(Button, {
@@ -681,20 +993,30 @@ const AppShell$1 = ({ config, children }) => {
681
993
  onClick: () => setIsUploadModalVisible(true),
682
994
  children: "Upload"
683
995
  }),
684
- /* @__PURE__ */ jsx(UploadModal_default, {
996
+ /* @__PURE__ */ jsx(UploadModal, {
685
997
  visible: isUploadModalVisible,
686
998
  onClose: () => setIsUploadModalVisible(false)
999
+ }),
1000
+ /* @__PURE__ */ jsx(ActionIcon, {
1001
+ variant: "subtle",
1002
+ "aria-label": "Help",
1003
+ onClick: () => setIsHelpModalVisible(true),
1004
+ children: /* @__PURE__ */ jsx(HelpCircleIcon, { size: 20 })
1005
+ }),
1006
+ /* @__PURE__ */ jsx(HelpModal, {
1007
+ visible: isHelpModalVisible,
1008
+ onClose: () => setIsHelpModalVisible(false)
687
1009
  })
688
1010
  ] })]
689
1011
  }) }),
690
- /* @__PURE__ */ jsx(Header_default, { config }),
1012
+ /* @__PURE__ */ jsx(NavBar, { config }),
691
1013
  /* @__PURE__ */ jsx(AppShell.Main, {
692
1014
  style: { overflow: "hidden" },
693
1015
  h: "calc(100dvh - var(--app-shell-header-offset, 0rem) - var(--app-shell-footer-height, 0px) + var(--app-shell-padding, 0))",
694
1016
  children: /* @__PURE__ */ jsx(Box, {
695
1017
  h: "100%",
696
1018
  style: {
697
- overflow: "scroll",
1019
+ overflow: "auto",
698
1020
  position: "relative"
699
1021
  },
700
1022
  children
@@ -703,23 +1025,146 @@ const AppShell$1 = ({ config, children }) => {
703
1025
  ]
704
1026
  });
705
1027
  };
706
-
1028
+ //#endregion
1029
+ //#region src/lib/sse.ts
1030
+ const OCELLink = z.object({
1031
+ type: z.literal("ocel"),
1032
+ ocel_id: z.string()
1033
+ });
1034
+ const ResourceLink = z.object({
1035
+ type: z.literal("resource"),
1036
+ resource_id: z.string()
1037
+ });
1038
+ const PluginLink = z.object({
1039
+ type: z.literal("plugin"),
1040
+ id: z.string(),
1041
+ method: z.string(),
1042
+ task_id: z.string()
1043
+ });
1044
+ const SystemLink = z.discriminatedUnion("type", [
1045
+ OCELLink,
1046
+ ResourceLink,
1047
+ PluginLink
1048
+ ]);
1049
+ const SystemNotification = z.object({
1050
+ type: z.literal("notification"),
1051
+ title: z.string(),
1052
+ message: z.string(),
1053
+ notification_type: z.enum([
1054
+ "warning",
1055
+ "info",
1056
+ "error"
1057
+ ]),
1058
+ link: SystemLink.nullish()
1059
+ });
1060
+ const InvalidationRequest = z.object({
1061
+ type: z.literal("invalidation"),
1062
+ routes: z.array(z.enum([
1063
+ "ocels",
1064
+ "resources",
1065
+ "tasks",
1066
+ "plugins"
1067
+ ]))
1068
+ });
1069
+ const ErrorNotification = z.object({
1070
+ type: z.literal("error"),
1071
+ title: z.string(),
1072
+ message: z.string(),
1073
+ trace: z.string()
1074
+ });
1075
+ const ServerEventMessage = z.discriminatedUnion("type", [
1076
+ SystemNotification,
1077
+ InvalidationRequest,
1078
+ ErrorNotification
1079
+ ]);
1080
+ //#endregion
1081
+ //#region src/components/SSEWrapper.tsx
1082
+ const SSEWrapper = () => {
1083
+ const { sessionId } = useSessionStore();
1084
+ const invalidate = useInvalidate();
1085
+ useEffect(() => {
1086
+ if (!sessionId) return;
1087
+ const es = new EventSource(`${env$1.event_url}?sessionId=${sessionId}`);
1088
+ es.onmessage = async (event) => {
1089
+ const result = ServerEventMessage.safeParse(JSON.parse(event.data));
1090
+ if (!result.success) {
1091
+ console.warn("Invalid SSE message", event.data, result.error);
1092
+ return;
1093
+ }
1094
+ const message = result.data;
1095
+ switch (message.type) {
1096
+ case "notification":
1097
+ showNotification({
1098
+ title: message.title,
1099
+ message: message.message,
1100
+ color: "green"
1101
+ });
1102
+ break;
1103
+ case "error":
1104
+ showNotification({
1105
+ title: message.title,
1106
+ color: "red",
1107
+ autoClose: 6e4,
1108
+ message: /* @__PURE__ */ jsxs(Stack, {
1109
+ gap: 6,
1110
+ align: "flex-start",
1111
+ children: [/* @__PURE__ */ jsx(Text, {
1112
+ size: "sm",
1113
+ style: { flex: 1 },
1114
+ lineClamp: 2,
1115
+ children: message.message
1116
+ }), /* @__PURE__ */ jsx(CopyButton, {
1117
+ value: message.trace ?? "",
1118
+ timeout: 1500,
1119
+ children: ({ copied, copy }) => /* @__PURE__ */ jsx(Text, {
1120
+ size: "xs",
1121
+ c: copied ? "teal" : "red",
1122
+ td: "underline",
1123
+ style: {
1124
+ cursor: "pointer",
1125
+ whiteSpace: "nowrap",
1126
+ marginTop: 2
1127
+ },
1128
+ onClick: copy,
1129
+ children: copied ? "Copied" : "Copy trace"
1130
+ })
1131
+ })]
1132
+ })
1133
+ });
1134
+ break;
1135
+ case "invalidation": await invalidate(message.routes);
1136
+ }
1137
+ };
1138
+ es.onerror = (err) => {
1139
+ console.error("SSE error:", err);
1140
+ };
1141
+ return () => es.close();
1142
+ }, [sessionId, invalidate]);
1143
+ return null;
1144
+ };
707
1145
  //#endregion
708
1146
  //#region src/components/OcelescopeApp.tsx
1147
+ const theme = createTheme({ fontWeights: { medium: "500" } });
709
1148
  const OcelescopeApp = (config) => ({ Component, pageProps }) => {
710
1149
  const [client] = useState(() => new QueryClient());
711
1150
  return /* @__PURE__ */ jsxs(QueryClientProvider, {
712
1151
  client,
713
1152
  children: [/* @__PURE__ */ jsx(HydrationBoundary, {
714
1153
  state: pageProps.dehydratedState,
715
- children: /* @__PURE__ */ jsx(MantineProvider, { children: /* @__PURE__ */ jsx(AppShell$1, {
716
- config,
717
- children: /* @__PURE__ */ jsx(Component, { ...pageProps })
718
- }) })
1154
+ children: /* @__PURE__ */ jsxs(MantineProvider, {
1155
+ theme,
1156
+ children: [
1157
+ /* @__PURE__ */ jsx(SSEWrapper, {}),
1158
+ /* @__PURE__ */ jsx(Notifications, {}),
1159
+ /* @__PURE__ */ jsx(AppShell$1, {
1160
+ config,
1161
+ children: /* @__PURE__ */ jsx(Component, { ...pageProps })
1162
+ })
1163
+ ]
1164
+ })
719
1165
  }), /* @__PURE__ */ jsx(ReactQueryDevtools, {})]
720
1166
  });
721
1167
  };
722
-
723
1168
  //#endregion
724
1169
  //#region src/components/OcelescopeDocument.tsx
725
1170
  const OcelescopeDocument = () => {
@@ -729,11 +1174,298 @@ const OcelescopeDocument = () => {
729
1174
  children: [/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx(ColorSchemeScript, { defaultColorScheme: "auto" }) }), /* @__PURE__ */ jsxs("body", { children: [/* @__PURE__ */ jsx(Main, {}), /* @__PURE__ */ jsx(NextScript, {})] })]
730
1175
  });
731
1176
  };
732
-
1177
+ //#endregion
1178
+ //#region src/components/RelationTable.tsx
1179
+ const PAGE_SIZE = 10;
1180
+ const COLUMN_WIDTHS = {
1181
+ selector: 30,
1182
+ source: 220,
1183
+ target: 220,
1184
+ qualifier: 220,
1185
+ range: 160,
1186
+ total: 120
1187
+ };
1188
+ const relationId = ({ source, target }) => `${source} ${target}`;
1189
+ const SubRelationTable = ({ ocelId, relationType, direction, source, target, hideRange = false, hideTotal = false, ocelVersion, extraColumns = [] }) => {
1190
+ const { data, isFetching } = (relationType === "e2o" ? useE2o : useO2o)(ocelId, {
1191
+ direction,
1192
+ source_types: [source],
1193
+ target_types: [target],
1194
+ with_qualifier: true,
1195
+ ocel_version: ocelVersion
1196
+ }, { query: { placeholderData: keepPreviousData } });
1197
+ const columns = useMemo(() => [
1198
+ {
1199
+ accessor: "selector",
1200
+ title: "",
1201
+ width: COLUMN_WIDTHS.selector
1202
+ },
1203
+ {
1204
+ accessor: "source",
1205
+ title: "",
1206
+ width: COLUMN_WIDTHS.source,
1207
+ render: () => null
1208
+ },
1209
+ {
1210
+ accessor: "target",
1211
+ title: "",
1212
+ width: COLUMN_WIDTHS.target,
1213
+ render: () => null
1214
+ },
1215
+ {
1216
+ accessor: "qualifier",
1217
+ title: "Qualifier",
1218
+ width: COLUMN_WIDTHS.qualifier,
1219
+ render: ({ qualifier }) => qualifier || /* @__PURE__ */ jsx("i", { children: "No qualifier" })
1220
+ },
1221
+ {
1222
+ accessor: "range",
1223
+ title: "Range",
1224
+ width: COLUMN_WIDTHS.range,
1225
+ render: ({ min_count, max_count }) => `${min_count} - ${max_count}`,
1226
+ hidden: hideRange
1227
+ },
1228
+ {
1229
+ accessor: "sum",
1230
+ title: "Total",
1231
+ width: COLUMN_WIDTHS.total,
1232
+ hidden: hideTotal
1233
+ },
1234
+ ...extraColumns
1235
+ ], [
1236
+ hideRange,
1237
+ hideTotal,
1238
+ extraColumns
1239
+ ]);
1240
+ return /* @__PURE__ */ jsx(DataTable, {
1241
+ noHeader: true,
1242
+ idAccessor: "qualifier",
1243
+ records: data?.response ?? [],
1244
+ columns,
1245
+ fetching: isFetching,
1246
+ backgroundColor: {
1247
+ light: "gray.0",
1248
+ dark: "dark.6"
1249
+ }
1250
+ });
1251
+ };
1252
+ const RelationTable = ({ ocelId, relationType = "o2o", direction = "source", extraColumns = [], subTableExtraColumns, hideRange = false, hideTotal = false, ocelVersion, visibleRelations }) => {
1253
+ const isE2O = relationType === "e2o";
1254
+ const isSource = direction === "source";
1255
+ const [currentPage, setCurrentPage] = useState(1);
1256
+ const { data: combinations = [] } = (isE2O ? useE2oCombinations : useO2oCombinations)(ocelId, {
1257
+ direction,
1258
+ ocel_version: ocelVersion
1259
+ });
1260
+ const sourceOptions = useMemo(() => Array.from(new Set(combinations.map(({ source }) => source))), [combinations]);
1261
+ const targetOptions = useMemo(() => Array.from(new Set(combinations.map(({ target }) => target))), [combinations]);
1262
+ const qualifierOptions = useMemo(() => Array.from(new Set(combinations.map(({ qualifier }) => qualifier))), [combinations]);
1263
+ const sourceTitle = isE2O ? isSource ? "Activity" : "Object Type" : "Source";
1264
+ const targetTitle = isE2O ? isSource ? "Object Type" : "Activity" : "Target";
1265
+ const [filteredSources, setFilteredSources] = useState([]);
1266
+ const [filteredTargets, setFilteredTargets] = useState([]);
1267
+ const [filteredQualifiers, setFilteredQualifiers] = useState([]);
1268
+ const isConstrained = visibleRelations !== void 0;
1269
+ const visibleSources = useMemo(() => isConstrained ? Array.from(new Set(visibleRelations.map(({ source }) => source))) : [], [isConstrained, visibleRelations]);
1270
+ const visibleTargets = useMemo(() => isConstrained ? Array.from(new Set(visibleRelations.map(({ target }) => target))) : [], [isConstrained, visibleRelations]);
1271
+ const sourceSelectOptions = isConstrained ? sourceOptions.filter((option) => visibleSources.includes(option)) : sourceOptions;
1272
+ const targetSelectOptions = isConstrained ? targetOptions.filter((option) => visibleTargets.includes(option)) : targetOptions;
1273
+ const effectiveSources = useMemo(() => {
1274
+ if (!isConstrained) return filteredSources;
1275
+ return filteredSources.length > 0 ? filteredSources.filter((source) => visibleSources.includes(source)) : visibleSources;
1276
+ }, [
1277
+ isConstrained,
1278
+ filteredSources,
1279
+ visibleSources
1280
+ ]);
1281
+ const effectiveTargets = useMemo(() => {
1282
+ if (!isConstrained) return filteredTargets;
1283
+ return filteredTargets.length > 0 ? filteredTargets.filter((target) => visibleTargets.includes(target)) : visibleTargets;
1284
+ }, [
1285
+ isConstrained,
1286
+ filteredTargets,
1287
+ visibleTargets
1288
+ ]);
1289
+ const showNoRelations = isConstrained && (effectiveSources.length === 0 || effectiveTargets.length === 0);
1290
+ const { data, isFetching } = (isE2O ? useE2o : useO2o)(ocelId, {
1291
+ direction,
1292
+ page: currentPage,
1293
+ page_size: PAGE_SIZE,
1294
+ with_qualifier: false,
1295
+ ocel_version: ocelVersion,
1296
+ ...effectiveSources.length > 0 && { source_types: effectiveSources },
1297
+ ...effectiveTargets.length > 0 && { target_types: effectiveTargets },
1298
+ ...filteredQualifiers.length > 0 && { qualifiers: filteredQualifiers }
1299
+ }, { query: {
1300
+ placeholderData: keepPreviousData,
1301
+ enabled: !showNoRelations
1302
+ } });
1303
+ const [selectedRelations, setSelectedRelations] = useState([]);
1304
+ const columns = useMemo(() => [
1305
+ {
1306
+ accessor: "selector",
1307
+ title: "",
1308
+ textAlign: "center",
1309
+ width: COLUMN_WIDTHS.selector,
1310
+ cellsStyle: () => ({ padding: 5 }),
1311
+ render: (record) => {
1312
+ if ((record.qualifiers?.length ?? 0) <= 1) return null;
1313
+ return /* @__PURE__ */ jsx(ThemeIcon, {
1314
+ size: "sm",
1315
+ variant: "transparent",
1316
+ style: { display: "flex" },
1317
+ children: selectedRelations.includes(relationId(record)) ? /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) : /* @__PURE__ */ jsx(ChevronRightIcon, { size: 16 })
1318
+ });
1319
+ }
1320
+ },
1321
+ {
1322
+ accessor: "source",
1323
+ title: sourceTitle,
1324
+ width: COLUMN_WIDTHS.source,
1325
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
1326
+ data: sourceSelectOptions,
1327
+ value: filteredSources,
1328
+ onChange: setFilteredSources,
1329
+ comboboxProps: { withinPortal: false },
1330
+ clearable: true,
1331
+ searchable: true
1332
+ }),
1333
+ filtering: filteredSources.length > 0
1334
+ },
1335
+ {
1336
+ accessor: "target",
1337
+ title: targetTitle,
1338
+ width: COLUMN_WIDTHS.target,
1339
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
1340
+ data: targetSelectOptions,
1341
+ value: filteredTargets,
1342
+ onChange: setFilteredTargets,
1343
+ comboboxProps: { withinPortal: false },
1344
+ clearable: true,
1345
+ searchable: true
1346
+ }),
1347
+ filtering: filteredTargets.length > 0
1348
+ },
1349
+ {
1350
+ accessor: "qualifiers",
1351
+ title: "Qualifiers",
1352
+ width: COLUMN_WIDTHS.qualifier,
1353
+ render: ({ qualifiers = [] }) => {
1354
+ const labels = qualifiers.map((qualifier) => qualifier || "No qualifier");
1355
+ return `${labels.slice(0, 2).join(", ")}${labels.length > 2 ? `, ... (${labels.length})` : ""}`;
1356
+ },
1357
+ filter: () => /* @__PURE__ */ jsx(MultiSelect, {
1358
+ data: qualifierOptions,
1359
+ value: filteredQualifiers,
1360
+ onChange: setFilteredQualifiers,
1361
+ comboboxProps: { withinPortal: false },
1362
+ clearable: true,
1363
+ searchable: true
1364
+ }),
1365
+ filtering: filteredQualifiers.length > 0
1366
+ },
1367
+ {
1368
+ accessor: "range",
1369
+ title: "Range",
1370
+ width: COLUMN_WIDTHS.range,
1371
+ render: ({ min_count, max_count }) => `${min_count} - ${max_count}`,
1372
+ hidden: hideRange
1373
+ },
1374
+ {
1375
+ accessor: "sum",
1376
+ title: "Total",
1377
+ width: COLUMN_WIDTHS.total,
1378
+ hidden: hideTotal
1379
+ },
1380
+ ...extraColumns
1381
+ ], [
1382
+ sourceTitle,
1383
+ targetTitle,
1384
+ sourceSelectOptions,
1385
+ targetSelectOptions,
1386
+ qualifierOptions,
1387
+ filteredSources,
1388
+ filteredTargets,
1389
+ filteredQualifiers,
1390
+ selectedRelations,
1391
+ hideRange,
1392
+ hideTotal,
1393
+ extraColumns
1394
+ ]);
1395
+ return /* @__PURE__ */ jsx(DataTable, {
1396
+ idAccessor: relationId,
1397
+ records: showNoRelations ? [] : data?.response,
1398
+ columns,
1399
+ withTableBorder: true,
1400
+ fetching: isFetching,
1401
+ totalRecords: showNoRelations ? 0 : data?.total_items ?? 0,
1402
+ page: currentPage,
1403
+ recordsPerPage: PAGE_SIZE,
1404
+ onPageChange: setCurrentPage,
1405
+ noRecordsText: "No relations found",
1406
+ height: 500,
1407
+ rowExpansion: {
1408
+ allowMultiple: false,
1409
+ expandable: ({ record }) => (record.qualifiers?.length ?? 0) > 1,
1410
+ expanded: {
1411
+ recordIds: selectedRelations,
1412
+ onRecordIdsChange: setSelectedRelations
1413
+ },
1414
+ content: ({ record }) => /* @__PURE__ */ jsx(SubRelationTable, {
1415
+ ocelId,
1416
+ relationType,
1417
+ direction,
1418
+ source: record.source,
1419
+ target: record.target,
1420
+ hideRange,
1421
+ hideTotal,
1422
+ ocelVersion,
1423
+ extraColumns: subTableExtraColumns
1424
+ })
1425
+ }
1426
+ });
1427
+ };
1428
+ //#endregion
1429
+ //#region src/hooks/useDownload.ts
1430
+ const useDownloadFile = () => {
1431
+ const { sessionId } = useSessionStore();
1432
+ const downloadFile = async (path) => {
1433
+ try {
1434
+ const response = await fetch(path, {
1435
+ method: "GET",
1436
+ headers: { [env$1.session_id]: sessionId ?? "" }
1437
+ });
1438
+ if (!response.ok) throw new Error("Failed to download file");
1439
+ const blob = await response.blob();
1440
+ const contentDisposition = parse(response.headers.get("Content-Disposition") ?? "");
1441
+ const fileName = contentDisposition.attachment ? contentDisposition.filename : "fileName";
1442
+ FileSaver.saveAs(blob, fileName);
1443
+ } catch (err) {
1444
+ console.error("Download error:", err);
1445
+ }
1446
+ };
1447
+ return downloadFile;
1448
+ };
1449
+ const useDownloadFromServer = (getQueryKey) => () => {
1450
+ const downloadFile = useDownloadFile();
1451
+ return { download: (...params) => {
1452
+ const result = getQueryKey(...params);
1453
+ const path = result[0];
1454
+ const searchParams = result[1];
1455
+ const queryString = qs.stringify(searchParams, {
1456
+ skipNulls: true,
1457
+ indices: false
1458
+ });
1459
+ downloadFile(queryString ? `${path}?${queryString}` : path);
1460
+ } };
1461
+ };
1462
+ const useDownloadOCEL = useDownloadFromServer(getDownloadOCELQueryKey);
1463
+ const useDownloadFlatOCEL = useDownloadFromServer(getDownloadFlatLogQueryKey);
1464
+ const useDownloadResource = useDownloadFromServer(getDownloadResourceQueryKey);
1465
+ const useDownloadResourceAsPnml = useDownloadFromServer(getDownloadResourceAsPnmlQueryKey);
733
1466
  //#endregion
734
1467
  //#region src/lib/config.ts
735
1468
  const defineModule = (def) => def;
736
1469
  const defineModuleRoute = (def) => def;
737
-
738
1470
  //#endregion
739
- export { OcelescopeApp, OcelescopeDocument, createModulesPage, defineModule, defineModuleRoute, getModuleRoute, useCurrentOcel };
1471
+ export { AttributesTable, BarList, FileDropzone, HelpModal, OcelSelect, OcelescopeApp, OcelescopeDocument, RelationTable, UploadModal, UploadSection, createModulesPage, defineModule, defineModuleRoute, env, formatAttributeValue, getModuleRoute, sseHandler, useCurrentOcel, useDownloadFile, useDownloadFlatOCEL, useDownloadOCEL, useDownloadResource, useDownloadResourceAsPnml, useInvalidate };