From a8636de1c3f9ec962e18fa6d13b04c9f069f3ca5 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:04:48 -0400 Subject: [PATCH 01/24] fix(nav): hide nav groups if all items are feature flagged --- src/app/AppLayout/AppLayout.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index ced7da5f8..77be03bfd 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -592,6 +592,8 @@ export const AppLayout: React.FC = ({ children }) => { .map((route, idx) => renderable(route, idx)); if (!k) { items = renderables; + } else if (!renderables.length) { + items = []; } else { const anyActive = rs.some((r) => isActiveRoute(r)); items = [ @@ -612,11 +614,13 @@ export const AppLayout: React.FC = ({ children }) => { aria-label={t('AppLayout.TOOLBAR.ARIA_LABELS.GLOBAL_NAVIGATION')} > - {Array.from(groups.entries()).map(([groupTitle, items]) => ( - - {items} - - ))} + {Array.from(groups.entries()) + .filter(([_, items]) => items.length) + .map(([groupTitle, items]) => ( + + {items} + + ))} ); From 8e0d8e7920be1a310cd3a72238f2cc7a36da36f0 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:05:06 -0400 Subject: [PATCH 02/24] diagnostics > thread dump set to beta feature level --- src/app/routes.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/routes.tsx b/src/app/routes.tsx index 474408d6c..2a9cb80ce 100644 --- a/src/app/routes.tsx +++ b/src/app/routes.tsx @@ -177,6 +177,7 @@ const diagnosticsRoutes: IAppRoute[] = [ description: 'Create and view thread dumps on single target JVMs.', navGroup: DIAGNOSTICS, navSubgroup: ANALYZE, + featureLevel: FeatureLevel.BETA, }, ]; From bc4ec946bc5c7a8eb4a8753315a9229037b8916a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:08:19 -0400 Subject: [PATCH 03/24] remove tabbing from Thread Dumps view, correct page title --- locales/en/public.json | 3 -- src/app/Diagnostics/Diagnostics.tsx | 44 ++++------------------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/locales/en/public.json b/locales/en/public.json index f20e6fb3b..32bb65184 100644 --- a/locales/en/public.json +++ b/locales/en/public.json @@ -364,9 +364,6 @@ }, "DATETIME": "Date and Time" }, - "Diagnostics": { - "THREAD_DUMPS_TAB_TITLE": "Thread Dumps" - }, "DiagnosticsCard": { "DIAGNOSTICS_ACTION_FAILURE": "Diagnostics Failure: {{kind}}", "DIAGNOSTICS_CARD_DESCRIPTION": "Perform diagnostic operations on the target.", diff --git a/src/app/Diagnostics/Diagnostics.tsx b/src/app/Diagnostics/Diagnostics.tsx index 7aaf3e374..b308efdd6 100644 --- a/src/app/Diagnostics/Diagnostics.tsx +++ b/src/app/Diagnostics/Diagnostics.tsx @@ -14,53 +14,19 @@ * limitations under the License. */ import { TargetView } from '@app/TargetView/TargetView'; -import { getActiveTab, switchTab } from '@app/utils/utils'; -import { Card, CardBody, Tab, Tabs, TabTitleText } from '@patternfly/react-core'; -import { t } from 'i18next'; +import { Card, CardBody } from '@patternfly/react-core'; import * as React from 'react'; -import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import { ThreadDumpsTable } from './ThreadDumpsTable'; -enum DiagnosticsTab { - THREAD_DUMPS = 'thread-dumps', -} - export interface DiagnosticsProps {} export const Diagnostics: React.FC = ({ ...props }) => { - const { search, pathname } = useLocation(); - const navigate = useNavigate(); - - const activeTab = React.useMemo(() => { - return getActiveTab(search, 'tab', Object.values(DiagnosticsTab), DiagnosticsTab.THREAD_DUMPS); - }, [search]); - - const onTabSelect = React.useCallback( - (_: React.MouseEvent, key: string | number) => - switchTab(navigate, pathname, search, { tabKey: 'tab', tabValue: `${key}` }), - [navigate, pathname, search], - ); - - const cardBody = React.useMemo( - () => ( - - {t('Diagnostics.THREAD_DUMPS_TAB_TITLE')}} - data-quickstart-id="thread-dumps-tab" - > - - - - ), - [activeTab, onTabSelect], - ); - return ( - + - {cardBody} + + + ); From c146b36bed34c1498b76dc5c2480d960cb1e6176 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:09:56 -0400 Subject: [PATCH 04/24] rename files and components --- src/app/Diagnostics/{Diagnostics.tsx => ThreadDumps.tsx} | 6 +++--- src/app/routes.tsx | 4 ++-- .../{Diagnostics.test.tsx => ThreadDumps.test.tsx} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename src/app/Diagnostics/{Diagnostics.tsx => ThreadDumps.tsx} (88%) rename src/test/Diagnostics/{Diagnostics.test.tsx => ThreadDumps.test.tsx} (100%) diff --git a/src/app/Diagnostics/Diagnostics.tsx b/src/app/Diagnostics/ThreadDumps.tsx similarity index 88% rename from src/app/Diagnostics/Diagnostics.tsx rename to src/app/Diagnostics/ThreadDumps.tsx index b308efdd6..05bc281ac 100644 --- a/src/app/Diagnostics/Diagnostics.tsx +++ b/src/app/Diagnostics/ThreadDumps.tsx @@ -18,9 +18,9 @@ import { Card, CardBody } from '@patternfly/react-core'; import * as React from 'react'; import { ThreadDumpsTable } from './ThreadDumpsTable'; -export interface DiagnosticsProps {} +export interface ThreadDumpsProps {} -export const Diagnostics: React.FC = ({ ...props }) => { +export const ThreadDumps: React.FC = ({ ...props }) => { return ( @@ -32,4 +32,4 @@ export const Diagnostics: React.FC = ({ ...props }) => { ); }; -export default Diagnostics; +export default ThreadDumps; diff --git a/src/app/routes.tsx b/src/app/routes.tsx index 2a9cb80ce..7d579d0a3 100644 --- a/src/app/routes.tsx +++ b/src/app/routes.tsx @@ -21,7 +21,7 @@ import Archives from './Archives/Archives'; import CreateRecording from './CreateRecording/CreateRecording'; import Dashboard from './Dashboard/Dashboard'; import DashboardSolo from './Dashboard/DashboardSolo'; -import Diagnostics from './Diagnostics/Diagnostics'; +import ThreadDumps from './Diagnostics/ThreadDumps'; import Events from './Events/Events'; import JMCAgent from './JMCAgent/JMCAgent'; import NotFound from './NotFound/NotFound'; @@ -170,7 +170,7 @@ const flightRecorderRoutes: IAppRoute[] = [ const diagnosticsRoutes: IAppRoute[] = [ { - component: Diagnostics, + component: ThreadDumps, label: 'Thread Dumps', path: toPath('/diagnostics'), title: 'Thread Dumps', diff --git a/src/test/Diagnostics/Diagnostics.test.tsx b/src/test/Diagnostics/ThreadDumps.test.tsx similarity index 100% rename from src/test/Diagnostics/Diagnostics.test.tsx rename to src/test/Diagnostics/ThreadDumps.test.tsx From dfd3fe5d4c0ee0bd781fae14ef1cb11660dd503f Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:18:10 -0400 Subject: [PATCH 05/24] Revert "remove tabbing from Thread Dumps view, correct page title" This reverts commit 8d49fa5abe6aa97a11a209606b6f5199af6b0d21. --- locales/en/public.json | 3 +++ src/app/Diagnostics/ThreadDumps.tsx | 42 ++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/locales/en/public.json b/locales/en/public.json index 32bb65184..0df989cd3 100644 --- a/locales/en/public.json +++ b/locales/en/public.json @@ -364,6 +364,9 @@ }, "DATETIME": "Date and Time" }, + "Diagnostics": { + "TARGET_THREAD_DUMPS_TAB_TITLE": "Targets" + }, "DiagnosticsCard": { "DIAGNOSTICS_ACTION_FAILURE": "Diagnostics Failure: {{kind}}", "DIAGNOSTICS_CARD_DESCRIPTION": "Perform diagnostic operations on the target.", diff --git a/src/app/Diagnostics/ThreadDumps.tsx b/src/app/Diagnostics/ThreadDumps.tsx index 05bc281ac..9061a3f5a 100644 --- a/src/app/Diagnostics/ThreadDumps.tsx +++ b/src/app/Diagnostics/ThreadDumps.tsx @@ -14,19 +14,53 @@ * limitations under the License. */ import { TargetView } from '@app/TargetView/TargetView'; -import { Card, CardBody } from '@patternfly/react-core'; +import { getActiveTab, switchTab } from '@app/utils/utils'; +import { Card, CardBody, Tab, Tabs, TabTitleText } from '@patternfly/react-core'; +import { t } from 'i18next'; import * as React from 'react'; +import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import { ThreadDumpsTable } from './ThreadDumpsTable'; export interface ThreadDumpsProps {} +enum ThreadDumpsTab { + THREAD_DUMPS = 'target-thread-dumps', +} + export const ThreadDumps: React.FC = ({ ...props }) => { + const { search, pathname } = useLocation(); + const navigate = useNavigate(); + + const activeTab = React.useMemo(() => { + return getActiveTab(search, 'tab', Object.values(ThreadDumpsTab), ThreadDumpsTab.THREAD_DUMPS); + }, [search]); + + const onTabSelect = React.useCallback( + (_: React.MouseEvent, key: string | number) => + switchTab(navigate, pathname, search, { tabKey: 'tab', tabValue: `${key}` }), + [navigate, pathname, search], + ); + + const cardBody = React.useMemo( + () => ( + + {t('Diagnostics.TARGET_THREAD_DUMPS_TAB_TITLE')}} + data-quickstart-id="thread-dumps-tab" + > + + + + ), + [activeTab, onTabSelect], + ); + return ( - - - + {cardBody} ); From 1049430404207f20ab4dbce9d18246745a6c6e8a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:20:17 -0400 Subject: [PATCH 06/24] remove empty component wrapper --- src/app/Archives/Archives.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/Archives/Archives.tsx b/src/app/Archives/Archives.tsx index a323b8ec7..1e54f2d20 100644 --- a/src/app/Archives/Archives.tsx +++ b/src/app/Archives/Archives.tsx @@ -172,7 +172,6 @@ export const Archives: React.FC = ({ ...props }) => { {cardBody} - <> ); }; From bd1581401e51ca1f73b483ba3301d604daa91d4d Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 10:26:46 -0400 Subject: [PATCH 07/24] feature flag dashboard card button for thread dumps --- .../Dashboard/Diagnostics/DiagnosticsCard.tsx | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx b/src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx index 607d4e599..af565878f 100644 --- a/src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx +++ b/src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx @@ -46,6 +46,7 @@ import { ListIcon, WrenchIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { concatMap, filter, first } from 'rxjs/operators'; import { DashboardCard } from '../DashboardCard'; +import {FeatureFlag} from '@app/Shared/Components/FeatureFlag'; export interface DiagnosticsCardProps extends DashboardCardTypeProps {} @@ -152,25 +153,27 @@ export const DiagnosticsCard: DashboardCardFC = (props) => {t('DiagnosticsCard.DIAGNOSTICS_GC_BUTTON')} - - - + + + + - - - - - + + + + + + + + + + + + + + + + + + - - - {deleteThreadDumpModal} - - - {threadDumpRows.length ? ( - - - - {tableColumns.map(({ title, sortable }, index) => ( - - ))} - - - {threadDumpRows} -
- {title} -
- ) : ( - - } - headingLevel="h4" - /> - - )} -
-
- + + + + + + + + + + + + + + + + {deleteThreadDumpModal} + + + {threadDumpRows.length ? ( + + + + {tableColumns.map(({ title, sortable }, index) => ( + + ))} + + + {threadDumpRows} +
+ {title} +
+ ) : ( + + } + headingLevel="h4" + /> + + )} +
+
); } }; - -export interface ThreadDumpActionProps { - threadDump: ThreadDump; - onDownload: (threadDump: ThreadDump) => void; - onDelete: (threadDump: ThreadDump) => void; -} - -export const ThreadDumpAction: React.FC = ({ threadDump, onDelete, onDownload }) => { - const { t } = useCryostatTranslation(); - const [isOpen, setIsOpen] = React.useState(false); - - const actionItems = React.useMemo(() => { - return [ - { - title: 'Download Thread Dump', - key: 'download-threaddump', - onClick: () => onDownload(threadDump), - }, - { - isSeparator: true, - }, - { - key: 'delete-threaddump', - title: 'Delete', - isDanger: true, - onClick: () => onDelete(threadDump), - }, - ]; - }, [onDelete, onDownload, threadDump]); - - const handleToggle = React.useCallback((_, opened: boolean) => setIsOpen(opened), [setIsOpen]); - - const dropdownItems = React.useMemo( - () => - actionItems.map((action) => ( - { - setIsOpen(false); - action.onClick && action.onClick(); - }} - isDanger={action.isDanger} - > - {action.title} - - )), - [actionItems, setIsOpen], - ); - - return ( - ) => ( - handleToggle(event, !isOpen)} - > - - - )} - onOpenChange={setIsOpen} - onOpenChangeKeys={['Escape']} - isOpen={isOpen} - popperProps={{ - position: 'right', - enableFlip: true, - }} - > - {dropdownItems} - - ); -}; From 39652e99be90c31ce82a2526e97a3dac9bf1d80c Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:13:39 -0400 Subject: [PATCH 18/24] remove 'upload' button --- src/app/Diagnostics/ThreadDumpsTable.tsx | 40 +----------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/src/app/Diagnostics/ThreadDumpsTable.tsx b/src/app/Diagnostics/ThreadDumpsTable.tsx index ba4a6199a..7efdb0059 100644 --- a/src/app/Diagnostics/ThreadDumpsTable.tsx +++ b/src/app/Diagnostics/ThreadDumpsTable.tsx @@ -25,7 +25,6 @@ import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { TableColumn, portalRoot, sortResources } from '@app/utils/utils'; import { useCryostatTranslation } from '@i18n/i18nextUtil'; import { - Button, EmptyState, EmptyStateIcon, Stack, @@ -36,11 +35,10 @@ import { ToolbarItem, EmptyStateHeader, SearchInput, - Tooltip, Timestamp, TimestampTooltipVariant, } from '@patternfly/react-core'; -import { SearchIcon, UploadIcon } from '@patternfly/react-icons'; +import { SearchIcon } from '@patternfly/react-icons'; import { ActionsColumn, IAction, @@ -57,7 +55,6 @@ import { } from '@patternfly/react-table'; import _ from 'lodash'; import * as React from 'react'; -import { first } from 'rxjs/operators'; const tableColumns: TableColumn[] = [ { @@ -157,26 +154,6 @@ export const ThreadDumpsTable: React.FC = ({}) => { setWarningModalOpen(false); }, [setWarningModalOpen]); - const handleThreadDump = React.useCallback(() => { - addSubscription( - context.api - .runThreadDump(true) - .pipe(first()) - .subscribe({ - next: (jobId) => { - addSubscription( - context.notificationChannel.messages(NotificationCategory.ThreadDumpSuccess).subscribe((notification) => { - if (jobId == notification.message.jobId) { - refreshThreadDumps(); - } - }), - ); - }, - error: () => refreshThreadDumps(), - }), - ); - }, [addSubscription, context.api, context.notificationChannel, refreshThreadDumps]); - const handleFilterTextChange = React.useCallback((_, value: string) => setFilterText(value), [setFilterText]); React.useEffect(() => { @@ -333,21 +310,6 @@ export const ThreadDumpsTable: React.FC = ({}) => { /> - - - - - - {deleteThreadDumpModal} From cd2bfb1a77b508d8bd7800d3f010bf7c70d92a57 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:26:05 -0400 Subject: [PATCH 19/24] fixup! remove 'upload' button --- ...Dumps.test.tsx => ThreadDumpsTable.test.tsx} | 17 ----------------- 1 file changed, 17 deletions(-) rename src/test/Diagnostics/{ThreadDumps.test.tsx => ThreadDumpsTable.test.tsx} (89%) diff --git a/src/test/Diagnostics/ThreadDumps.test.tsx b/src/test/Diagnostics/ThreadDumpsTable.test.tsx similarity index 89% rename from src/test/Diagnostics/ThreadDumps.test.tsx rename to src/test/Diagnostics/ThreadDumpsTable.test.tsx index ad611ac92..ca0158c2c 100644 --- a/src/test/Diagnostics/ThreadDumps.test.tsx +++ b/src/test/Diagnostics/ThreadDumpsTable.test.tsx @@ -70,8 +70,6 @@ jest .mockReturnValueOnce(of(mockThreadDumpNotification)) .mockReturnValue(of()); -const dumpThreadsSpy = jest.spyOn(defaultServices.api, 'runThreadDump').mockReturnValue(of('someJobId')); - describe('', () => { afterEach(cleanup); @@ -95,21 +93,6 @@ describe('', () => { expect(xmlHeader).toBeVisible(); }); - it('should upload a Thread Dump when button is clicked', async () => { - const { user } = render({ - routerConfigs: { routes: [{ path: '/diagnostics', element: }] }, - }); - - await act(async () => { - const uploadButton = screen.getByRole('button', { name: 'dump-threads' }); - expect(uploadButton).toBeInTheDocument(); - expect(uploadButton).toBeVisible(); - - await user.click(uploadButton); - expect(dumpThreadsSpy).toHaveBeenCalledTimes(1); - }); - }); - it('should show warning modal and delete a Thread Dump when confirmed', async () => { const deleteRequestSpy = jest.spyOn(defaultServices.api, 'deleteThreadDump').mockReturnValue(of(true)); const { user } = render({ From 09f1787d6d3a5adbe9636055f22bcd3be000dc24 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:26:20 -0400 Subject: [PATCH 20/24] remove nested card styling --- src/app/JMCAgent/AboutAgentCard.tsx | 33 ------ src/app/JMCAgent/AgentLiveProbes.tsx | 126 +++++++++++------------ src/app/JMCAgent/AgentProbeTemplates.tsx | 15 ++- 3 files changed, 71 insertions(+), 103 deletions(-) delete mode 100644 src/app/JMCAgent/AboutAgentCard.tsx diff --git a/src/app/JMCAgent/AboutAgentCard.tsx b/src/app/JMCAgent/AboutAgentCard.tsx deleted file mode 100644 index afa67237a..000000000 --- a/src/app/JMCAgent/AboutAgentCard.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright The Cryostat Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Card, CardBody, CardTitle } from '@patternfly/react-core'; -import * as React from 'react'; - -export interface AboutAgentCardProps {} - -export const AboutAgentCard: React.FC = (_) => { - return ( - - About the JMC Agent - - The JMC Agent allows users to dynamically inject custom JFR events into running JVMs. In order to make use of - the JMC Agent, the agent jar must be present in the same container as the target, and the target must be started - with the agent (-javaagent:/path/to/agent.jar). Once these pre-requisites are met, the user can upload Probe - Templates to Cryostat and insert them to the target, as well as view or remove currently active probes. - - - ); -}; diff --git a/src/app/JMCAgent/AgentLiveProbes.tsx b/src/app/JMCAgent/AgentLiveProbes.tsx index 16f4537de..6105ca630 100644 --- a/src/app/JMCAgent/AgentLiveProbes.tsx +++ b/src/app/JMCAgent/AgentLiveProbes.tsx @@ -30,8 +30,6 @@ import { ToolbarContent, ToolbarGroup, ToolbarItem, - Stack, - StackItem, EmptyState, EmptyStateIcon, EmptyStateHeader, @@ -53,7 +51,6 @@ import { import _ from 'lodash'; import * as React from 'react'; import { combineLatest } from 'rxjs'; -import { AboutAgentCard } from './AboutAgentCard'; export type LiveProbeActions = 'REMOVE'; @@ -319,72 +316,65 @@ export const AgentLiveProbes: React.FC = () => { } else { return ( <> - - - - - - - - - - - - - - - - - - - - - - {probeRows.length ? ( - - - - {tableColumns.map(({ title, sortable }, index) => ( - - ))} - - - {probeRows} -
- {title} -
- ) : ( - - } - headingLevel="h4" + + + + + - - )} -
-
+
+ + + + + + + + + + + {probeRows.length ? ( + + + + {tableColumns.map(({ title, sortable }, index) => ( + + ))} + + + {probeRows} +
+ {title} +
+ ) : ( + + } + headingLevel="h4" + /> + + )} ); } diff --git a/src/app/JMCAgent/AgentProbeTemplates.tsx b/src/app/JMCAgent/AgentProbeTemplates.tsx index c3d1681ff..29f6194b7 100644 --- a/src/app/JMCAgent/AgentProbeTemplates.tsx +++ b/src/app/JMCAgent/AgentProbeTemplates.tsx @@ -35,6 +35,8 @@ import { ModalVariant, Stack, StackItem, + Text, + TextVariants, Toolbar, ToolbarContent, ToolbarGroup, @@ -47,6 +49,7 @@ import { MenuToggle, SearchInput, Divider, + TextContent, } from '@patternfly/react-core'; import { SearchIcon, EllipsisVIcon, UploadIcon } from '@patternfly/react-icons'; import { @@ -65,7 +68,6 @@ import _ from 'lodash'; import * as React from 'react'; import { forkJoin, Observable, of } from 'rxjs'; import { catchError, defaultIfEmpty, first, tap } from 'rxjs/operators'; -import { AboutAgentCard } from './AboutAgentCard'; const tableColumns: TableColumn[] = [ { @@ -294,7 +296,16 @@ export const AgentProbeTemplates: React.FC = ({ agentD <> - + + About the JMC Agent + + The JMC Agent allows users to dynamically inject custom JFR events into running JVMs. In order to make + use of the JMC Agent, the agent jar must be present in the same container as the target, and the target + must be started with the agent (-javaagent:/path/to/agent.jar). Once these pre-requisites are met, the + user can upload Probe Templates to Cryostat and insert them to the target, as well as view or remove + currently active probes. + + From 15ed4d98cb5f6ebaac3b9a0a84a0fc576d87026c Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:27:15 -0400 Subject: [PATCH 21/24] fixup! remove nested card styling --- .../AgentLiveProbes.test.tsx.snap | 751 +++++++++--------- 1 file changed, 357 insertions(+), 394 deletions(-) diff --git a/src/test/JMCAgent/__snapshots__/AgentLiveProbes.test.tsx.snap b/src/test/JMCAgent/__snapshots__/AgentLiveProbes.test.tsx.snap index 90e4d495c..2d173b292 100644 --- a/src/test/JMCAgent/__snapshots__/AgentLiveProbes.test.tsx.snap +++ b/src/test/JMCAgent/__snapshots__/AgentLiveProbes.test.tsx.snap @@ -1,463 +1,426 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[` renders correctly 1`] = ` -
+[
- About the JMC Agent -
-
-
- The JMC Agent allows users to dynamically inject custom JFR events into running JVMs. In order to make use of the JMC Agent, the agent jar must be present in the same container as the target, and the target must be started with the agent (-javaagent:/path/to/agent.jar). Once these pre-requisites are met, the user can upload Probe Templates to Cryostat and insert them to the target, as well as view or remove currently active probes. -
-
-
-
-
-
-
-
- - - - - - + + -
+ +
-
+
+
+
-
- -
+ Remove + all probes +
+
+ -
+ some_id + + + some_name + + + some_clazz + + + some_desc + + + a_method + + + + , +] `; From 193e7d9d437a85ff254371de037d968124fa6229 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:35:12 -0400 Subject: [PATCH 22/24] Revert "clean up table actions" This reverts commit eb69001373f2e7d9a39f911e211fab5a295fb087. --- src/app/Diagnostics/ThreadDumpsTable.tsx | 120 +++++++++++++++++------ 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/src/app/Diagnostics/ThreadDumpsTable.tsx b/src/app/Diagnostics/ThreadDumpsTable.tsx index 7efdb0059..3981c4821 100644 --- a/src/app/Diagnostics/ThreadDumpsTable.tsx +++ b/src/app/Diagnostics/ThreadDumpsTable.tsx @@ -22,7 +22,7 @@ import { NotificationsContext } from '@app/Shared/Services/Notifications.service import { ServiceContext } from '@app/Shared/Services/Services'; import useDayjs from '@app/utils/hooks/useDayjs'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; -import { TableColumn, portalRoot, sortResources } from '@app/utils/utils'; +import { TableColumn, sortResources } from '@app/utils/utils'; import { useCryostatTranslation } from '@i18n/i18nextUtil'; import { EmptyState, @@ -34,14 +34,18 @@ import { ToolbarGroup, ToolbarItem, EmptyStateHeader, + Dropdown, + DropdownItem, + DropdownList, + MenuToggleElement, + MenuToggle, SearchInput, Timestamp, TimestampTooltipVariant, + Divider, } from '@patternfly/react-core'; -import { SearchIcon } from '@patternfly/react-icons'; +import { SearchIcon, EllipsisVIcon, UploadIcon } from '@patternfly/react-icons'; import { - ActionsColumn, - IAction, ISortBy, SortByDirection, Table, @@ -235,26 +239,6 @@ export const ThreadDumpsTable: React.FC = ({}) => { ); }, [warningModalOpen, handleWarningModalAccept, handleWarningModalClose]); - const actionResolver = React.useCallback( - (threadDump: ThreadDump): IAction[] => { - return [ - { - title: t('DOWNLOAD'), - onClick: () => handleDownloadThreadDump(threadDump), - }, - { - isSeparator: true, - }, - { - title: t('DELETE'), - onClick: () => handleDeleteAction(threadDump), - isDanger: true, - }, - ]; - }, - [handleDownloadThreadDump, handleDeleteAction, t], - ); - const threadDumpRows = React.useMemo( () => filteredThreadDumps.map((t: ThreadDump, index) => { @@ -272,18 +256,12 @@ export const ThreadDumpsTable: React.FC = ({}) => { - + ); }), - [actionResolver, datetimeContext.timeZone.full, dayjs, filteredThreadDumps], + [datetimeContext.timeZone.full, dayjs, filteredThreadDumps, handleDeleteAction, handleDownloadThreadDump], ); if (errorMessage != '') { @@ -340,3 +318,81 @@ export const ThreadDumpsTable: React.FC = ({}) => { ); } }; + +export interface ThreadDumpActionProps { + threadDump: ThreadDump; + onDownload: (threadDump: ThreadDump) => void; + onDelete: (threadDump: ThreadDump) => void; +} + +export const ThreadDumpAction: React.FC = ({ threadDump, onDelete, onDownload }) => { + const { t } = useCryostatTranslation(); + const [isOpen, setIsOpen] = React.useState(false); + + const actionItems = React.useMemo(() => { + return [ + { + title: 'Download Thread Dump', + key: 'download-threaddump', + onClick: () => onDownload(threadDump), + }, + { + isSeparator: true, + }, + { + key: 'delete-threaddump', + title: 'Delete', + isDanger: true, + onClick: () => onDelete(threadDump), + }, + ]; + }, [onDelete, onDownload, threadDump]); + + const handleToggle = React.useCallback((_, opened: boolean) => setIsOpen(opened), [setIsOpen]); + + const dropdownItems = React.useMemo( + () => + actionItems.map((action, idx) => + action.isSeparator ? ( + + ) : ( + { + setIsOpen(false); + action.onClick && action.onClick(); + }} + isDanger={action.isDanger} + > + {action.title} + + ), + ), + [actionItems, setIsOpen], + ); + + return ( + ) => ( + handleToggle(event, !isOpen)} + > + + + )} + onOpenChange={setIsOpen} + onOpenChangeKeys={['Escape']} + isOpen={isOpen} + popperProps={{ + position: 'right', + enableFlip: true, + }} + > + {dropdownItems} + + ); +}; From 08c3c765f4478448f00af43f0c107227ba5ed3de Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:35:48 -0400 Subject: [PATCH 23/24] shorten download action label --- src/app/Diagnostics/ThreadDumpsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Diagnostics/ThreadDumpsTable.tsx b/src/app/Diagnostics/ThreadDumpsTable.tsx index 3981c4821..60cbfb094 100644 --- a/src/app/Diagnostics/ThreadDumpsTable.tsx +++ b/src/app/Diagnostics/ThreadDumpsTable.tsx @@ -332,7 +332,7 @@ export const ThreadDumpAction: React.FC = ({ threadDump, const actionItems = React.useMemo(() => { return [ { - title: 'Download Thread Dump', + title: 'Download', key: 'download-threaddump', onClick: () => onDownload(threadDump), }, From 02f83c19c3913ceadc7f2e80ba52361648e8faa5 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 29 Aug 2025 11:46:13 -0400 Subject: [PATCH 24/24] include file size in thread dump table --- src/app/Diagnostics/ThreadDumpsTable.tsx | 12 ++++++++++-- src/app/Shared/Services/api.types.ts | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/Diagnostics/ThreadDumpsTable.tsx b/src/app/Diagnostics/ThreadDumpsTable.tsx index 60cbfb094..dd065b944 100644 --- a/src/app/Diagnostics/ThreadDumpsTable.tsx +++ b/src/app/Diagnostics/ThreadDumpsTable.tsx @@ -22,7 +22,7 @@ import { NotificationsContext } from '@app/Shared/Services/Notifications.service import { ServiceContext } from '@app/Shared/Services/Services'; import useDayjs from '@app/utils/hooks/useDayjs'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; -import { TableColumn, sortResources } from '@app/utils/utils'; +import { TableColumn, formatBytes, sortResources } from '@app/utils/utils'; import { useCryostatTranslation } from '@i18n/i18nextUtil'; import { EmptyState, @@ -44,7 +44,7 @@ import { TimestampTooltipVariant, Divider, } from '@patternfly/react-core'; -import { SearchIcon, EllipsisVIcon, UploadIcon } from '@patternfly/react-icons'; +import { SearchIcon, EllipsisVIcon } from '@patternfly/react-icons'; import { ISortBy, SortByDirection, @@ -71,6 +71,11 @@ const tableColumns: TableColumn[] = [ keyPaths: ['lastModified'], sortable: true, }, + { + title: 'Size', + keyPaths: ['size'], + sortable: true, + }, ]; export interface ThreadDumpsProps {} @@ -255,6 +260,9 @@ export const ThreadDumpsTable: React.FC = ({}) => { {dayjs(t.lastModified).tz(datetimeContext.timeZone.full).format('L LTS z')} + + {formatBytes(t.size ?? 0)} + diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index 34338176b..3941e8e9a 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -246,6 +246,7 @@ export interface ThreadDump { uuid: string; jvmId?: string; lastModified?: number; + size?: number; } export interface ArchivedRecording extends Recording {