From 75e3d5ada38ce87bd9ab869737b3ae419b0d5b13 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 7 May 2026 12:48:39 -0600 Subject: [PATCH 1/7] fix: Filter out empty tokens when splitting search text in FilterSearchText component --- src/shared/FilterSearchText.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/FilterSearchText.tsx b/src/shared/FilterSearchText.tsx index 101015e..8c6aa48 100644 --- a/src/shared/FilterSearchText.tsx +++ b/src/shared/FilterSearchText.tsx @@ -29,7 +29,7 @@ export const FilterSearchText = ({ onChangeValue(val); } else { // URL params mode (default) - const tokens = val.split(" "); + const tokens = val.split(" ").filter((t) => t.length > 0); searchParams.delete(PARAM); if (val.length) tokens.forEach((t) => searchParams.append(PARAM, t)); setSearchParams(searchParams); From ea297de02ef312fd46cb6ae42ffe0b19c152b65d Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 21 May 2026 10:49:50 -0600 Subject: [PATCH 2/7] fix: Standardize import statements and improve code formatting in DebugConsole, DebugFilters, DeviceFilterDropdown, and LoginForm components --- src/features/DebugConsole/DebugConsole.tsx | 16 +++++++------- src/features/DebugConsole/DebugFilters.tsx | 2 +- .../DebugConsole/DeviceFilterDropdown.tsx | 2 +- src/features/LoginForm.tsx | 22 +++++++++---------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/features/DebugConsole/DebugConsole.tsx b/src/features/DebugConsole/DebugConsole.tsx index 9324274..c6fd1c4 100644 --- a/src/features/DebugConsole/DebugConsole.tsx +++ b/src/features/DebugConsole/DebugConsole.tsx @@ -1,23 +1,23 @@ import { skipToken } from '@reduxjs/toolkit/query'; -import { useState } from "react"; -import { Alert, Button, Form } from "react-bootstrap"; -import ListFiltersHeader from "../../shared/ListFiltersHeader"; +import { useState } from 'react'; +import { Alert, Button, Form } from 'react-bootstrap'; +import ListFiltersHeader from '../../shared/ListFiltersHeader'; import useAppParams from '../../shared/hooks/useAppParams'; import { useGetDoNotLoadConfigOnNextBootQuery, useSetDoNotLoadConfigOnNextBootMutation, useSetLoadConfigMutation, useSetRestartMutation -} from "../../store/apiSlice"; +} from '../../store/apiSlice'; import { selectSearchText } from '../../store/debugConsole/debugConsoleSelectors'; import { debugConsoleActions } from '../../store/debugConsole/debugConsoleSlice'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import type { RootState } from '../../store/store'; -import ConsoleWindow from "./ConsoleWindow"; -import { DebugFilters } from "./DebugFilters"; +import ConsoleWindow from './ConsoleWindow'; +import { DebugFilters } from './DebugFilters'; import MinimumLogLevelDropdown from './MinimumLogLevelDropdown'; -import RestartConfirmModal from "./RestartConfirmModal"; -import { useFilteredMessages } from "./hooks/useFilteredMessages"; +import RestartConfirmModal from './RestartConfirmModal'; +import { useFilteredMessages } from './hooks/useFilteredMessages'; const DebugConsole = ({isConnected, join, stop, clear}: DebugConsoleProps) => { //* HOOKS ***********************************************************/ diff --git a/src/features/DebugConsole/DebugFilters.tsx b/src/features/DebugConsole/DebugFilters.tsx index 1ca03b1..90ede37 100644 --- a/src/features/DebugConsole/DebugFilters.tsx +++ b/src/features/DebugConsole/DebugFilters.tsx @@ -21,7 +21,7 @@ export const DebugFilters = () => { const deviceItems: IdLabel[] = devices .map((d) => ({ id: d.Key, label: d.Name || d.Key })) - .sort((a, b) => a.label.localeCompare(b.label)); + .sort((a, b) => a.id.localeCompare(b.id)); return [{ id: debugConsts.GLOBAL, label: 'Global' }, ...deviceItems]; }, [devices]); diff --git a/src/features/DebugConsole/DeviceFilterDropdown.tsx b/src/features/DebugConsole/DeviceFilterDropdown.tsx index c14e72a..bc60854 100644 --- a/src/features/DebugConsole/DeviceFilterDropdown.tsx +++ b/src/features/DebugConsole/DeviceFilterDropdown.tsx @@ -66,7 +66,7 @@ export const DeviceFilterDropdown = ({ items }: DeviceFilterDropdownProps) => { handleCheckChange(e, stringId)} className="flex-grow-1 m-0" diff --git a/src/features/LoginForm.tsx b/src/features/LoginForm.tsx index 959cea3..922a71c 100644 --- a/src/features/LoginForm.tsx +++ b/src/features/LoginForm.tsx @@ -1,14 +1,14 @@ -import { FormEvent, useState } from "react"; -import { Alert, Button, Form, Spinner } from "react-bootstrap"; -import { Navigate, useLocation, useNavigate } from "react-router-dom"; -import useAppParams from "../shared/hooks/useAppParams"; -import { useSetLoginCredentialsMutation } from "../store/apiSlice"; +import { FormEvent, useState } from 'react'; +import { Alert, Button, Form, Spinner } from 'react-bootstrap'; +import { Navigate, useLocation, useNavigate } from 'react-router-dom'; +import useAppParams from '../shared/hooks/useAppParams'; +import { useSetLoginCredentialsMutation } from '../store/apiSlice'; import { selectAvailableApps, selectIsAuthenticated, -} from "../store/auth/authSelectors"; -import { authActions } from "../store/auth/authSlice"; -import { useAppDispatch, useAppSelector } from "../store/hooks"; +} from '../store/auth/authSelectors'; +import { authActions } from '../store/auth/authSlice'; +import { useAppDispatch, useAppSelector } from '../store/hooks'; const ALL_APP_IDS = [ "app01", @@ -53,7 +53,7 @@ const LoginForm = () => { setError(null); setIsLoading(true); - // Probe all slots in parallel — credentials are valid if at least one succeeds + // Send login to all program slots in parallel const results = await Promise.allSettled( ALL_APP_IDS.map((id) => setLoginCredentials({ appId: id, username, password }).unwrap(), @@ -64,13 +64,13 @@ const LoginForm = () => { (_, i) => results[i].status === "fulfilled", ); + setIsLoading(false); + if (availableApps.length === 0) { - setIsLoading(false); setError("Invalid credentials. Please try again."); return; } - setIsLoading(false); dispatch(authActions.loginSuccess(availableApps)); const destination = from ?? `/${availableApps[0] ?? probeAppId}/versions`; From bc8b499cfaf7b9e14eab9d6c81687547b71c1fec Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 21 May 2026 16:45:23 -0600 Subject: [PATCH 3/7] feat: Add export log functionality to DebugConsole component --- src/features/DebugConsole/DebugConsole.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/features/DebugConsole/DebugConsole.tsx b/src/features/DebugConsole/DebugConsole.tsx index c6fd1c4..124b4f5 100644 --- a/src/features/DebugConsole/DebugConsole.tsx +++ b/src/features/DebugConsole/DebugConsole.tsx @@ -41,6 +41,19 @@ const DebugConsole = ({isConnected, join, stop, clear}: DebugConsoleProps) => { //* EFFECTS *********************************************************/ const filteredItems = useFilteredMessages(messages); + const exportFilteredItems = () => { + const content = filteredItems + .map((item) => `${item.Timestamp} [${item.Level}]${item.Properties?.Key ? ` [${item.Properties.Key}]` : ''} ${item.RenderedMessage}`) + .join('\n'); + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `debug-log-${new Date().toISOString().replace(/[:.]/g, '-')}.log`; + a.click(); + URL.revokeObjectURL(url); + }; + const clickRestart = () => { setShowModal(true); }; @@ -104,6 +117,15 @@ const DebugConsole = ({isConnected, join, stop, clear}: DebugConsoleProps) => { > Clear Console Trace +