diff --git a/src/app/Dashboard/AddCard.tsx b/src/app/Dashboard/AddCard.tsx index 0c45fafd6..68583eba2 100644 --- a/src/app/Dashboard/AddCard.tsx +++ b/src/app/Dashboard/AddCard.tsx @@ -624,7 +624,7 @@ const SelectControl: React.FC = ({ handleChange, control, se appendTo: portalRoot, }} isScrollable - maxMenuHeight={'30vh'} + maxMenuHeight="40vh" onOpenChange={setSelectOpen} onOpenChangeKeys={['Escape']} > diff --git a/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisNameFilter.tsx b/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisNameFilter.tsx index 54bb55e83..1c8da6e5c 100644 --- a/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisNameFilter.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisNameFilter.tsx @@ -130,7 +130,7 @@ export const AutomatedAnalysisNameFilter: React.FC {selectOptionProps.map(({ value, children }, index) => ( diff --git a/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisTopicFilter.tsx b/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisTopicFilter.tsx index ce06251b2..b740723fb 100644 --- a/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisTopicFilter.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/Filters/AutomatedAnalysisTopicFilter.tsx @@ -119,7 +119,7 @@ export const AutomatedAnalysisTopicFilter: React.FC {selectOptionProps.map(({ value, children }, index) => ( diff --git a/src/app/Dashboard/DashboardCardActionMenu.tsx b/src/app/Dashboard/DashboardCardActionMenu.tsx index d18bfd480..4bfb80e62 100644 --- a/src/app/Dashboard/DashboardCardActionMenu.tsx +++ b/src/app/Dashboard/DashboardCardActionMenu.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { portalRoot } from '@app/utils/utils'; -import { Dropdown, DropdownItem, DropdownList, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; +import { Divider, Dropdown, DropdownItem, DropdownList, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; import { EllipsisVIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -66,12 +66,14 @@ export const DashboardCardActionMenu: React.FC = ({ on {t('VIEW', { ns: 'common' })} - - {t('REMOVE', { ns: 'common' })} - + {t('DashboardCardActionMenu.RESET_SIZE')} + + + {t('REMOVE', { ns: 'common' })} + ); diff --git a/src/app/Dashboard/DashboardLayoutToolbar.tsx b/src/app/Dashboard/DashboardLayoutToolbar.tsx index a241f4822..778e2d7c4 100644 --- a/src/app/Dashboard/DashboardLayoutToolbar.tsx +++ b/src/app/Dashboard/DashboardLayoutToolbar.tsx @@ -362,7 +362,8 @@ export const DashboardLayoutToolbar: React.FC = (_p {t('DashboardLayoutToolbar.DOWNLOAD_AS_TEMPLATE')} - + + {t('DashboardLayoutToolbar.CLEAR_LAYOUT')} @@ -499,7 +500,7 @@ export const DashboardLayoutToolbar: React.FC = (_p )} > - + {menuGroups(t('DashboardLayoutToolbar.MENU.FAVORITES'), true)} {menuGroups(t('DashboardLayoutToolbar.MENU.OTHERS'), false)} diff --git a/src/app/Dashboard/LayoutTemplateGroup.tsx b/src/app/Dashboard/LayoutTemplateGroup.tsx index 7841db170..00e80c2cc 100644 --- a/src/app/Dashboard/LayoutTemplateGroup.tsx +++ b/src/app/Dashboard/LayoutTemplateGroup.tsx @@ -29,6 +29,7 @@ import { DropdownList, MenuToggle, MenuToggleElement, + Divider, } from '@patternfly/react-core'; import { EllipsisVIcon } from '@patternfly/react-icons'; import * as React from 'react'; @@ -175,7 +176,8 @@ export const KebabCatalogTileBadge: React.FC = ({ te {t('DOWNLOAD', { ns: 'common' })} , - + , + {t('DELETE', { ns: 'common' })} , ]; diff --git a/src/app/DateTimePicker/TimezonePicker.tsx b/src/app/DateTimePicker/TimezonePicker.tsx index 6899f66b8..f92378ae5 100644 --- a/src/app/DateTimePicker/TimezonePicker.tsx +++ b/src/app/DateTimePicker/TimezonePicker.tsx @@ -145,7 +145,7 @@ export const TimezonePicker: React.FC = ({ onOpenChange={(isOpen) => setIsTimezoneOpen(isOpen)} onOpenChangeKeys={['Escape']} isScrollable - menuHeight="20vh" + maxMenuHeight="40vh" > diff --git a/src/app/Events/EventTemplates.tsx b/src/app/Events/EventTemplates.tsx index 537145b01..8af8b9e26 100644 --- a/src/app/Events/EventTemplates.tsx +++ b/src/app/Events/EventTemplates.tsx @@ -278,6 +278,7 @@ export const EventTemplates: React.FC = () => { { title: 'Delete', onClick: () => handleDeleteButton(t), + isDanger: true, }, ]); } diff --git a/src/app/Recordings/Filters/DatetimeFilter.tsx b/src/app/Recordings/Filters/DatetimeFilter.tsx index 6389c3fe6..cad41f681 100644 --- a/src/app/Recordings/Filters/DatetimeFilter.tsx +++ b/src/app/Recordings/Filters/DatetimeFilter.tsx @@ -55,7 +55,7 @@ export interface DateTimeRange { to?: Date; } -export const filterRecordingByDatetime = (recordings?: ActiveRecording[], filters?: DateTimeRange[]) => { +export const filterRecordingByDatetime = (recordings: ActiveRecording[], filters?: DateTimeRange[]) => { if (!recordings?.length || !filters?.length) { return recordings; } diff --git a/src/app/Recordings/Filters/DurationFilter.tsx b/src/app/Recordings/Filters/DurationFilter.tsx index 8c5d3a1f4..d9be69b25 100644 --- a/src/app/Recordings/Filters/DurationFilter.tsx +++ b/src/app/Recordings/Filters/DurationFilter.tsx @@ -38,7 +38,7 @@ import { DURATION_INPUT_MAXLENGTH } from './const'; export const CONTINUOUS_INDICATOR = 'continuous'; -export const filterRecordingByDuration = (recordings?: ActiveRecording[], filters?: DurationRange[]) => { +export const filterRecordingByDuration = (recordings: ActiveRecording[], filters?: DurationRange[]) => { if (!recordings?.length || !filters?.length) { return recordings; } diff --git a/src/app/Rules/Rules.tsx b/src/app/Rules/Rules.tsx index d4e0c87fb..cbe8739bd 100644 --- a/src/app/Rules/Rules.tsx +++ b/src/app/Rules/Rules.tsx @@ -21,7 +21,7 @@ import { MatchExpressionDisplay } from '@app/Shared/Components/MatchExpression/M import { Rule, NotificationCategory } from '@app/Shared/Services/api.types'; import { ServiceContext } from '@app/Shared/Services/Services'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; -import { TableColumn, formatBytes, formatDuration, sortResources } from '@app/utils/utils'; +import { TableColumn, formatBytes, formatDuration, sortResources, portalRoot } from '@app/utils/utils'; import { Button, Card, @@ -301,6 +301,7 @@ export const RulesTable: React.FC = () => { { title: t('DELETE', { ns: 'common' }), onClick: () => handleDeleteButton(rule), + isDanger: true, }, ]; }, @@ -366,7 +367,7 @@ export const RulesTable: React.FC = () => { document.getElementById('automated-rule-toolbar') || document.body, + appendTo: portalRoot, position: 'right', }} /> diff --git a/src/app/Settings/Config/DatetimeControl.tsx b/src/app/Settings/Config/DatetimeControl.tsx index 4d0551e78..bed88d964 100644 --- a/src/app/Settings/Config/DatetimeControl.tsx +++ b/src/app/Settings/Config/DatetimeControl.tsx @@ -126,24 +126,22 @@ const Component = () => { }} selected={datetimeFormat.dateLocale} onSelect={handleDateLocaleSelect} - menuHeight="20vh" + maxMenuHeight="40vh" isScrollable onOpenChange={setDateLocaleOpen} onOpenChangeKeys={['Escape']} > - - - - - - - - {dateLocaleOptions} - + + + + + + + {dateLocaleOptions} diff --git a/src/app/Shared/Components/ScrollableMenuContent.tsx b/src/app/Shared/Components/ScrollableMenuContent.tsx new file mode 100644 index 000000000..6c9ccc3f6 --- /dev/null +++ b/src/app/Shared/Components/ScrollableMenuContent.tsx @@ -0,0 +1,32 @@ +/* + * 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 { Panel, PanelMain, PanelMainBody } from '@patternfly/react-core'; +import * as React from 'react'; + +export interface ScrollableMenuContentProps { + maxHeight?: string; +} + +// Use case: Menu footer or search bar needs to be sticky. +export const ScrollableMenuContent: React.FC = ({ children, maxHeight }) => { + return ( + + + {children} + + + ); +}; diff --git a/src/app/TargetView/TargetContextSelector.tsx b/src/app/TargetView/TargetContextSelector.tsx index a576b8ecb..93c790259 100644 --- a/src/app/TargetView/TargetContextSelector.tsx +++ b/src/app/TargetView/TargetContextSelector.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ import { LinearDotSpinner } from '@app/Shared/Components/LinearDotSpinner'; +import { ScrollableMenuContent } from '@app/Shared/Components/ScrollableMenuContent'; import { Target } from '@app/Shared/Services/api.types'; import { isEqualTarget, getTargetRepresentation } from '@app/Shared/Services/api.utils'; import { ServiceContext } from '@app/Shared/Services/Services'; @@ -33,8 +34,8 @@ import { MenuToggle, MenuSearch, MenuSearchInput, - Split, - SplitItem, + ActionList, + ActionListItem, } from '@patternfly/react-core'; import _ from 'lodash'; import * as React from 'react'; @@ -206,18 +207,18 @@ export const TargetContextSelector: React.FC = ({ cl const selectFooter = React.useMemo( () => ( - - + + - - - - - + + ), [t, onClearSelection], ); @@ -230,10 +231,9 @@ export const TargetContextSelector: React.FC = ({ cl ) : ( setIsTargetOpen(isOpen)} + onOpenChange={setIsTargetOpen} onOpenChangeKeys={['Escape']} onSelect={onSelect} onActionClick={onFavoriteClick} @@ -256,17 +256,19 @@ export const TargetContextSelector: React.FC = ({ cl appendTo: portalRoot, }} > - - - setSearchTerm(v)} - /> - - - - {selectOptions} + + + + setSearchTerm(v)} + /> + + + + {selectOptions} + {selectFooter} )} diff --git a/src/app/TargetView/TargetSelect.tsx b/src/app/TargetView/TargetSelect.tsx index 786ccbc40..a012a5ef1 100644 --- a/src/app/TargetView/TargetSelect.tsx +++ b/src/app/TargetView/TargetSelect.tsx @@ -192,6 +192,7 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. Observable; + isDanger?: boolean; } export const ContextMenuItem: React.FC = ({ @@ -51,6 +52,7 @@ export const ContextMenuItem: React.FC = ({ onClick, variant, isDisabled, + isDanger, ...props }) => { const navigate = useNavigate(); @@ -102,7 +104,7 @@ export const ContextMenuItem: React.FC = ({ } return ( - + {children} ); diff --git a/src/app/Topology/Actions/types.ts b/src/app/Topology/Actions/types.ts index b8afb5356..5f45e44c2 100644 --- a/src/app/Topology/Actions/types.ts +++ b/src/app/Topology/Actions/types.ts @@ -71,6 +71,7 @@ export interface NodeAction { readonly action?: NodeActionFunction; readonly title?: React.ReactNode; readonly isSeparator?: boolean; + readonly isDanger?: boolean; readonly isDisabled?: (element: GraphElement | ListElement, actionUtils: ActionUtils) => Observable; readonly allowed?: (element: GraphElement | ListElement) => boolean; // Undefined means allowing all readonly blocked?: (element: GraphElement | ListElement) => boolean; // Undefined means blocking none diff --git a/src/app/Topology/Actions/utils.tsx b/src/app/Topology/Actions/utils.tsx index 220529ba7..b1672b88e 100644 --- a/src/app/Topology/Actions/utils.tsx +++ b/src/app/Topology/Actions/utils.tsx @@ -106,6 +106,7 @@ export const nodeActions: NodeAction[] = [ { key: '', isSeparator: true }, { key: 'DELETE_TARGET', + isDanger: true, action: (element, { services }) => { const targetNode: TargetNode = element.getData(); services.api.deleteTarget(targetNode.target).subscribe(() => undefined); @@ -173,7 +174,7 @@ export const nodeActions: NodeAction[] = [ services.api .graphql( ` - query DeleteRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) { + query ArchiveRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) { environmentNodes(filter: $groupFilter) { name descendantTargets { @@ -258,6 +259,7 @@ export const nodeActions: NodeAction[] = [ key: 'GROUP_DELETE_RECORDING', title: 'Delete Recording', isGroup: true, + isDanger: true, action: (element, { services, notifications }) => { const group: EnvironmentNode = element.getData(); services.api @@ -361,12 +363,19 @@ export const actionFactory = ( } filtered = stop >= 0 ? filtered.slice(0, stop + 1) : []; - return filtered.map(({ isSeparator, key, title, isDisabled, action }, index) => { + return filtered.map(({ isSeparator, key, title, isDisabled, action, isDanger }, index) => { if (isSeparator) { return ; } return ( - + {title} ); diff --git a/src/app/Topology/Toolbar/HelpButton.tsx b/src/app/Topology/Toolbar/HelpButton.tsx index c0101d760..70c663f36 100644 --- a/src/app/Topology/Toolbar/HelpButton.tsx +++ b/src/app/Topology/Toolbar/HelpButton.tsx @@ -45,7 +45,7 @@ export const HelpButton: React.FC = ({ visualization, ...props return ( diff --git a/src/app/Topology/styles/base.css b/src/app/Topology/styles/base.css index 68a1cd630..7e500a328 100644 --- a/src/app/Topology/styles/base.css +++ b/src/app/Topology/styles/base.css @@ -197,7 +197,7 @@ Below CSS rules only apply to Topology components } .topology__help-icon { - color: var(--pf-v5-global--palette--blue-400); + color: var(--pf-v5-c-button--m-link--Color); } .topology__help-icon-button {