@dexteel/mesf-core 7.22.0 → 7.22.2

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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "7.22.0"
2
+ ".": "7.22.2"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.22.2](https://github.com/dexteel/mesf-core-frontend/compare/@dexteel/mesf-core-v7.22.1...@dexteel/mesf-core-v7.22.2) (2026-05-13)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **realtime:** share SignalR connection across subscribers ([023f7cf](https://github.com/dexteel/mesf-core-frontend/commit/023f7cf2805d1d9d1419c000a13480fd14d607f0))
9
+
10
+ ## [7.22.1](https://github.com/dexteel/mesf-core-frontend/compare/@dexteel/mesf-core-v7.22.0...@dexteel/mesf-core-v7.22.1) (2026-05-11)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **logs:** improve large log filtering performance ([80eaa0a](https://github.com/dexteel/mesf-core-frontend/commit/80eaa0a0f0535e0e3f67397d85b97efaa7b8659b))
16
+
3
17
  ## [7.22.0](https://github.com/dexteel/mesf-core-frontend/compare/@dexteel/mesf-core-v7.21.0...@dexteel/mesf-core-v7.22.0) (2026-05-09)
4
18
 
5
19
 
@@ -1,4 +1,4 @@
1
1
  import { ColDef } from "ag-grid-community";
2
- export declare const useLogTableData: () => {
2
+ export declare const useLogTableData: (timezone: "UTC" | "Server") => {
3
3
  columnDefs: ColDef<any, any>[];
4
4
  };
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
1
+ import { HubConnectionState, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
2
2
  export * from '@microsoft/signalr';
3
3
  export { LicenseManager } from 'ag-grid-enterprise';
4
4
  import { styled, DialogTitle as DialogTitle$1, DialogContent as DialogContent$1, DialogActions as DialogActions$1, Grid2, Button, Box, MenuItem, ListItemIcon, createTheme, TextField, Alert as Alert$2, useTheme, InputAdornment, Popover, MenuList, ListItemText, alpha, Dialog as Dialog$1, Paper, List, ListItem, Chip, SvgIcon, Typography as Typography$1, Checkbox, IconButton as IconButton$1, CircularProgress, FormControl, FormHelperText, FormControlLabel, Snackbar, DialogContentText, Badge, InputLabel, Select, Input, Divider, Card, CardContent, CardActions, Collapse, Tooltip, CssBaseline, AppBar, Toolbar, Container, Menu, Switch, Autocomplete as Autocomplete$1, useMediaQuery, Drawer, Grid, Accordion, AccordionSummary, AccordionDetails, Tabs, Tab, ListSubheader, ListItemButton, StyledEngineProvider, ThemeProvider, ListItemSecondaryAction } from '@mui/material';
@@ -4341,34 +4341,63 @@ const DeleteUser = ({ userId, show, onHide, suffixTitle }) => {
4341
4341
  React__default.createElement(ErrorModal, { error: profileError, onHide: () => setprofileError(""), title: "Error Choosing Profile" })));
4342
4342
  };
4343
4343
 
4344
+ const receiveMessageHandlers = new Set();
4345
+ let connection = null;
4346
+ let startPromise = null;
4347
+ const getConnection = () => {
4348
+ if (connection)
4349
+ return connection;
4350
+ connection = new HubConnectionBuilder()
4351
+ .withUrl("/ws")
4352
+ .withAutomaticReconnect()
4353
+ .configureLogging(LogLevel.Information)
4354
+ .build();
4355
+ connection.on("ReceiveMessage", (author, message) => {
4356
+ receiveMessageHandlers.forEach((handler) => {
4357
+ handler(author, message);
4358
+ });
4359
+ });
4360
+ connection.onclose((error) => {
4361
+ if (error)
4362
+ console.log("SignalR connection closed", error);
4363
+ });
4364
+ return connection;
4365
+ };
4366
+ const startConnection = () => {
4367
+ const currentConnection = getConnection();
4368
+ if (currentConnection.state !== HubConnectionState.Disconnected) {
4369
+ return Promise.resolve();
4370
+ }
4371
+ if (startPromise)
4372
+ return startPromise;
4373
+ startPromise = currentConnection
4374
+ .start()
4375
+ .then(() => {
4376
+ console.log("SignalR connection started");
4377
+ })
4378
+ .catch((err) => {
4379
+ console.log("Error while starting SignalR connection: " + err);
4380
+ })
4381
+ .finally(() => {
4382
+ startPromise = null;
4383
+ });
4384
+ return startPromise;
4385
+ };
4386
+ const subscribeMesfRealtime = (handler) => {
4387
+ receiveMessageHandlers.add(handler);
4388
+ void startConnection();
4389
+ return () => {
4390
+ receiveMessageHandlers.delete(handler);
4391
+ };
4392
+ };
4393
+
4344
4394
  const useMesfRealtime = ({ onReceiveMessage }) => {
4395
+ const onReceiveMessageRef = useRef(onReceiveMessage);
4396
+ onReceiveMessageRef.current = onReceiveMessage;
4345
4397
  useEffect(() => {
4346
- const connection = new HubConnectionBuilder()
4347
- .withUrl("/ws")
4348
- .withAutomaticReconnect()
4349
- .configureLogging(LogLevel.Information)
4350
- .build();
4351
- connection
4352
- .start()
4353
- .then(() => {
4354
- console.log("Connection started");
4355
- })
4356
- .catch((err) => {
4357
- console.log("Error while starting connection: " + err);
4358
- });
4359
- connection.on("ReceiveMessage", (author, message) => {
4360
- onReceiveMessage(author, message);
4398
+ return subscribeMesfRealtime((author, message) => {
4399
+ onReceiveMessageRef.current(author, message);
4361
4400
  });
4362
- return () => {
4363
- connection
4364
- .stop()
4365
- .then(() => {
4366
- console.log("Connection stopped");
4367
- })
4368
- .catch((err) => {
4369
- console.log("Error while stopping connection: " + err);
4370
- });
4371
- };
4372
4401
  }, []);
4373
4402
  };
4374
4403
 
@@ -7853,14 +7882,14 @@ const TagFilter = ({ tagFilters, setTagFilters, filterContext, }) => {
7853
7882
  startDate: filterContext.startDate,
7854
7883
  endDate: filterContext.endDate,
7855
7884
  logTypeCode: filterContext.logTypeCode,
7856
- tagFilters,
7885
+ tagFilters: filterContext.tagFilters,
7857
7886
  });
7858
7887
  const { data: tagValues, isLoading: isLoadingValues, error: valuesError, isError: isValuesError, } = useSearchLogTagValues({
7859
7888
  tagName: selectedTag ? selectedTag.TagName : "",
7860
7889
  startDate: filterContext.startDate,
7861
7890
  endDate: filterContext.endDate,
7862
7891
  logTypeCode: filterContext.logTypeCode,
7863
- tagFilters,
7892
+ tagFilters: filterContext.tagFilters,
7864
7893
  });
7865
7894
  const handleOpenPopover = (event) => {
7866
7895
  setAnchorEl(event.currentTarget);
@@ -8056,17 +8085,24 @@ const SearchFilter = ({ search, setSearch }) => {
8056
8085
  }, label: "Search", variant: "outlined", size: "small", value: search, onChange: (e) => setSearch(e.target.value), autoComplete: "off" })));
8057
8086
  };
8058
8087
 
8059
- const useLogTableData = () => {
8088
+ const useLogTableData = (timezone) => {
8060
8089
  const backgroundColor = (logTypeCode) => {
8061
8090
  return get(LOG_TYPE_CODES, `${logTypeCode[0]}.color`, "#BBBBBB");
8062
8091
  };
8063
- const [columnDefs] = useState([
8092
+ const columnDefs = useMemo(() => [
8064
8093
  {
8065
8094
  field: "Timestamp",
8066
8095
  headerName: "Time",
8067
8096
  minWidth: 220,
8068
8097
  flex: 1,
8069
8098
  headerClass: "ag-header-cell-centered",
8099
+ valueFormatter: ({ value }) => {
8100
+ if (!value)
8101
+ return "";
8102
+ return timezone === "UTC"
8103
+ ? moment$g(value).utc().format("YYYY-MM-DD HH:mm:ss z")
8104
+ : dxtToLocalServerTime(value, "yyyy-MM-dd HH:mm:ss z");
8105
+ },
8070
8106
  },
8071
8107
  {
8072
8108
  field: "Source",
@@ -8126,7 +8162,7 @@ const useLogTableData = () => {
8126
8162
  valueFormatter: ({ value }) => value || " ",
8127
8163
  headerClass: "ag-header-cell-centered",
8128
8164
  },
8129
- ]);
8165
+ ], [timezone]);
8130
8166
  return { columnDefs };
8131
8167
  };
8132
8168
 
@@ -8148,7 +8184,7 @@ const getLogs = (params, signal) => __awaiter(void 0, void 0, void 0, function*
8148
8184
  },
8149
8185
  { name: "TagFilters", value: tagFiltersJson },
8150
8186
  ];
8151
- const resp = yield apiService.callV2("[MES].[GetLogs]", parameters);
8187
+ const resp = yield apiService.callV2("[MES].[GetLogs]", parameters, signal);
8152
8188
  if (resp.ok) {
8153
8189
  let rows = get(resp, "data.tables[0].rows", []);
8154
8190
  rows = rows.map((log) => (Object.assign(Object.assign({}, log), { Timestamp: log.Timestamp ? moment$g.utc(log["Timestamp"]).toDate() : null })));
@@ -8271,48 +8307,55 @@ const useSearchLogs = ({ startDate, endDate, logTypeCode, autoRefresh, tagFilter
8271
8307
  endDate,
8272
8308
  logTypeCode,
8273
8309
  tagFilters,
8274
- }),
8310
+ }, signal),
8275
8311
  refetchInterval: autoRefresh ? 5000 : false,
8276
8312
  enabled: !!startDate && (!!endDate || autoRefresh),
8313
+ keepPreviousData: true,
8277
8314
  staleTime: 10000,
8278
8315
  });
8279
8316
  };
8280
8317
  const getLogTypeByCodeId = (logTypeCodeId) => {
8281
8318
  return get(LOG_TYPE_CODES, `${logTypeCodeId}.description`, " -");
8282
8319
  };
8320
+ const getDefaultFilters = () => ({
8321
+ startDate: moment$g().add(-5, "days").hour(0).minute(0).second(0).toDate(),
8322
+ endDate: moment$g().hour(23).minute(59).second(59).toDate(),
8323
+ logTypeCode: ["I", "W", "E"],
8324
+ autoRefresh: false,
8325
+ tagFilters: [],
8326
+ });
8327
+ const copyFilters = (filters) => (Object.assign(Object.assign({}, filters), { logTypeCode: [...filters.logTypeCode], tagFilters: [...filters.tagFilters] }));
8283
8328
  const TableLogs = () => {
8284
- const [startDate, setStartDate] = useState(moment$g().add(-5, "days").hour(0).minute(0).second(0).toDate());
8285
- const [endDate, setEndDate] = useState(moment$g().hour(23).minute(59).second(59).toDate());
8329
+ var _a;
8330
+ const [draftFilters, setDraftFilters] = useState(() => getDefaultFilters());
8331
+ const [appliedFilters, setAppliedFilters] = useState(() => getDefaultFilters());
8332
+ const [isApplyingFilters, setIsApplyingFilters] = useState(false);
8333
+ const hasMountedRef = useRef(false);
8286
8334
  const [timezone, setTimezone] = useState("UTC");
8287
8335
  const [search, setSearch] = useState("");
8288
- const [logTypeCode, setLogTypeCode] = useState(["I", "W", "E"]);
8289
- const [autoRefresh, setAutoRefresh] = useState(false);
8290
- const [tagFilters, setTagFilters] = useState([]);
8291
8336
  const [gridAPI, setGridAPI] = useState(null);
8292
8337
  const [error, setError] = useState("");
8293
8338
  const [showLogModal, setShowLogModal] = useState(false);
8294
8339
  const [selectedLog, setSelectedLog] = useState(undefined);
8295
- const { data: rows, isLoading, refetch, isError, error: e, } = useSearchLogs({
8296
- startDate,
8297
- endDate,
8298
- logTypeCode: logTypeCode.join(","),
8299
- autoRefresh,
8300
- tagFilters,
8340
+ const { data: rows, isLoading, isFetching, isError, error: e, } = useSearchLogs({
8341
+ startDate: appliedFilters.startDate,
8342
+ endDate: appliedFilters.endDate,
8343
+ logTypeCode: appliedFilters.logTypeCode.join(","),
8344
+ autoRefresh: appliedFilters.autoRefresh,
8345
+ tagFilters: appliedFilters.tagFilters,
8301
8346
  });
8302
8347
  const onGridReady = (params) => {
8303
8348
  setGridAPI(params.api);
8304
8349
  };
8305
- const formattedRows = rows === null || rows === void 0 ? void 0 : rows.map(({ LogId, Timestamp, Source, Message, LogTypeCode, User }) => ({
8350
+ const formattedRows = useMemo(() => rows === null || rows === void 0 ? void 0 : rows.map(({ LogId, Timestamp, Source, Message, LogTypeCode, User }) => ({
8306
8351
  id: LogId,
8307
- Timestamp: timezone === "UTC"
8308
- ? moment$g(Timestamp).utc().format("YYYY-MM-DD HH:mm:ss z")
8309
- : dxtToLocalServerTime(Timestamp, "yyyy-MM-dd HH:mm:ss z"),
8352
+ Timestamp,
8310
8353
  Source,
8311
8354
  Message: isNil(Message) ? "" : Message.replaceAll("Added", " Added"),
8312
8355
  LogTypeCode: getLogTypeByCodeId(LogTypeCode),
8313
8356
  User,
8314
- }));
8315
- const { columnDefs } = useLogTableData();
8357
+ })), [rows]);
8358
+ const { columnDefs } = useLogTableData(timezone);
8316
8359
  const defaultColDef = useMemo(() => {
8317
8360
  return {
8318
8361
  sortable: true,
@@ -8328,16 +8371,17 @@ const TableLogs = () => {
8328
8371
  };
8329
8372
  }, []);
8330
8373
  const { showContextMenu, registerConfig } = useContextMenuMESF();
8374
+ const handleAutoRefreshChange = (checked) => {
8375
+ const nextFilters = checked
8376
+ ? Object.assign(Object.assign({}, draftFilters), { startDate: moment$g().add(-15, "minutes").toDate(), endDate: null, autoRefresh: true }) : Object.assign(Object.assign({}, draftFilters), { endDate: moment$g().hour(23).minute(59).second(59).toDate(), autoRefresh: false });
8377
+ setDraftFilters(copyFilters(nextFilters));
8378
+ };
8331
8379
  const handleResetButtonClick = () => {
8332
- setStartDate(moment$g().add(-5, "days").hour(0).minute(0).second(0).toDate());
8333
- setEndDate(moment$g().hour(23).minute(59).second(59).toDate());
8380
+ const filters = getDefaultFilters();
8381
+ setDraftFilters(copyFilters(filters));
8334
8382
  setSearch("");
8335
- gridAPI === null || gridAPI === void 0 ? void 0 : gridAPI.setGridOption("quickFilterText", "");
8336
- setLogTypeCode(["I", "W", "E"]);
8337
- setTagFilters([]);
8338
8383
  setSelectedLog(undefined);
8339
8384
  setError("");
8340
- refetch();
8341
8385
  };
8342
8386
  const rowClicked = (rowClickedEvent) => {
8343
8387
  let data = rowClickedEvent.data;
@@ -8360,11 +8404,11 @@ const TableLogs = () => {
8360
8404
  handleResetButtonClick,
8361
8405
  });
8362
8406
  const filterContext = useMemo(() => ({
8363
- startDate,
8364
- endDate,
8365
- logTypeCode: logTypeCode.join(","),
8366
- tagFilters,
8367
- }), [startDate, endDate, logTypeCode, tagFilters]);
8407
+ startDate: appliedFilters.startDate,
8408
+ endDate: appliedFilters.endDate,
8409
+ logTypeCode: appliedFilters.logTypeCode.join(","),
8410
+ tagFilters: appliedFilters.tagFilters,
8411
+ }), [appliedFilters]);
8368
8412
  useEffect(() => {
8369
8413
  registerConfig({
8370
8414
  id: "TableLogs",
@@ -8376,6 +8420,24 @@ const TableLogs = () => {
8376
8420
  setError(e.message);
8377
8421
  }
8378
8422
  }, [isError, e]);
8423
+ useEffect(() => {
8424
+ if (!hasMountedRef.current) {
8425
+ hasMountedRef.current = true;
8426
+ return;
8427
+ }
8428
+ setIsApplyingFilters(true);
8429
+ const timeout = window.setTimeout(() => {
8430
+ setAppliedFilters(copyFilters(draftFilters));
8431
+ setIsApplyingFilters(false);
8432
+ }, 100);
8433
+ return () => window.clearTimeout(timeout);
8434
+ }, [draftFilters]);
8435
+ useEffect(() => {
8436
+ const timeout = window.setTimeout(() => {
8437
+ gridAPI === null || gridAPI === void 0 ? void 0 : gridAPI.setGridOption("quickFilterText", search);
8438
+ }, 350);
8439
+ return () => window.clearTimeout(timeout);
8440
+ }, [gridAPI, search]);
8379
8441
  return (React.createElement(React.Fragment, null,
8380
8442
  React.createElement(Grid2, { container: true, justifyContent: "flex-start", p: 1, spacing: 1 },
8381
8443
  React.createElement(Grid2, { size: { md: 12, xs: 12 } },
@@ -8402,43 +8464,32 @@ const TableLogs = () => {
8402
8464
  React.createElement(CardContent, { sx: { width: "100%" } },
8403
8465
  React.createElement(Grid2, { container: true, alignItems: "center", direction: "row", spacing: 2 },
8404
8466
  React.createElement(Grid2, { size: { md: 1.5 } },
8405
- React.createElement(DateFilter, { label: "From", date: startDate, setDate: (date) => setStartDate(date || new Date()), maxDate: endDate !== null && endDate !== void 0 ? endDate : undefined })),
8467
+ React.createElement(DateFilter, { label: "From", date: draftFilters.startDate, setDate: (date) => setDraftFilters(Object.assign(Object.assign({}, draftFilters), { startDate: date || new Date() })), maxDate: (_a = draftFilters.endDate) !== null && _a !== void 0 ? _a : undefined })),
8406
8468
  React.createElement(Grid2, { size: { md: 1.5 } },
8407
- React.createElement(DateFilter, { label: "To", date: endDate, setDate: (date) => {
8469
+ React.createElement(DateFilter, { label: "To", date: draftFilters.endDate, setDate: (date) => {
8408
8470
  if (date) {
8409
- setEndDate(date);
8410
- setAutoRefresh(false);
8471
+ setDraftFilters(Object.assign(Object.assign({}, draftFilters), { endDate: date, autoRefresh: false }));
8411
8472
  }
8412
- }, minDate: startDate })),
8473
+ }, minDate: draftFilters.startDate })),
8413
8474
  React.createElement(Grid2, { size: { md: 2, xs: 12 } },
8414
- React.createElement(CodeFilter, { LogTypeCode: logTypeCode, setLogTypeCodeFilter: (logTypeCodes) => setLogTypeCode(logTypeCodes) })),
8475
+ React.createElement(CodeFilter, { LogTypeCode: draftFilters.logTypeCode, setLogTypeCodeFilter: (logTypeCodes) => setDraftFilters(Object.assign(Object.assign({}, draftFilters), { logTypeCode: logTypeCodes })) })),
8415
8476
  React.createElement(Grid2, { size: { md: 1, xs: 12 } },
8416
8477
  React.createElement(TimezoneSelector, { value: timezone, onChange: setTimezone })),
8417
8478
  React.createElement(Grid2, { size: { md: 1.5, xs: 12 } },
8418
- React.createElement(FormControlLabel, { checked: autoRefresh, control: React.createElement(Switch, { color: "primary" }), label: "Auto Refresh", onChange: (e, checked) => {
8419
- if (checked) {
8420
- setEndDate(null);
8421
- }
8422
- else {
8423
- setEndDate(moment$g().hour(23).minute(59).second(59).toDate());
8424
- }
8425
- setAutoRefresh(checked);
8426
- } })),
8479
+ React.createElement(FormControlLabel, { checked: draftFilters.autoRefresh, control: React.createElement(Switch, { color: "primary" }), label: "Auto Refresh", onChange: (e, checked) => handleAutoRefreshChange(checked) })),
8427
8480
  React.createElement(Grid2, { size: { md: 3.5, xs: 12 } },
8428
- React.createElement(SearchFilter, { search: search, setSearch: (search) => {
8429
- setSearch(search);
8430
- gridAPI === null || gridAPI === void 0 ? void 0 : gridAPI.setGridOption("quickFilterText", search);
8431
- } })),
8481
+ React.createElement(SearchFilter, { search: search, setSearch: setSearch })),
8432
8482
  React.createElement(Grid2, { size: { md: 1, xs: 12 }, style: { paddingTop: 8 } },
8433
8483
  React.createElement(Button, { variant: "contained", color: "inherit", onClick: handleResetButtonClick, fullWidth: true }, "Reset")),
8434
8484
  React.createElement(Grid2, { size: { md: 12, xs: 12 }, sx: { mt: 1 } },
8435
- React.createElement(TagFilter, { tagFilters: tagFilters, setTagFilters: setTagFilters, filterContext: filterContext })))))),
8485
+ React.createElement(TagFilter, { tagFilters: draftFilters.tagFilters, setTagFilters: (tagFilters) => setDraftFilters(Object.assign(Object.assign({}, draftFilters), { tagFilters })), filterContext: filterContext })))))),
8436
8486
  React.createElement(Grid2, { size: { md: 12, xs: 12 }, style: {
8437
8487
  height: "70vh",
8438
8488
  } },
8439
8489
  React.createElement(Paper, { style: { height: "100%", width: "100%" } },
8440
- React.createElement(AgGridReact, { loading: isLoading, gridOptions: {
8490
+ React.createElement(AgGridReact, { loading: isLoading || isApplyingFilters || isFetching, gridOptions: {
8441
8491
  theme: themeDXT,
8492
+ cacheQuickFilter: true,
8442
8493
  }, rowData: formattedRows, columnDefs: columnDefs, defaultColDef: defaultColDef, onGridReady: onGridReady, getRowId: (params) => params.data.id, rowHeight: 34, headerHeight: 40, animateRows: true, loadingOverlayComponent: CenteredLazyLoading, getContextMenuItems: (e) => getContextMenuItems(e), rowSelection: "single", onRowDoubleClicked: (e) => {
8443
8494
  rowClicked(e);
8444
8495
  } })))),
@@ -10243,6 +10294,7 @@ const ChatComponent = () => {
10243
10294
  const [user, setUser] = useState("");
10244
10295
  const [message, setMessage] = useState("");
10245
10296
  const messageEndRef = useRef(null);
10297
+ const connectionRef = useRef(null);
10246
10298
  const latestMessages = useRef([]);
10247
10299
  latestMessages.current = messages;
10248
10300
  const scrollToBottom = () => {
@@ -10259,6 +10311,7 @@ const ChatComponent = () => {
10259
10311
  .withAutomaticReconnect()
10260
10312
  .configureLogging(LogLevel.Information)
10261
10313
  .build();
10314
+ connectionRef.current = newConnection;
10262
10315
  newConnection.on("ReceiveMessage", (user, message) => {
10263
10316
  const newMessage = {
10264
10317
  user,
@@ -10289,7 +10342,9 @@ const ChatComponent = () => {
10289
10342
  useEffect(() => {
10290
10343
  startConnection();
10291
10344
  return () => {
10292
- connection === null || connection === void 0 ? void 0 : connection.stop();
10345
+ var _a;
10346
+ void ((_a = connectionRef.current) === null || _a === void 0 ? void 0 : _a.stop());
10347
+ connectionRef.current = null;
10293
10348
  };
10294
10349
  }, [startConnection]);
10295
10350
  const sendMessage = (e) => __awaiter(void 0, void 0, void 0, function* () {