Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(pf5): revamp general table styles and upgrade archive/events views to PF5 #1334

Merged
merged 15 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"CLEAR_FILTERS": "Clear all filters",
"CLEAR_RECENT": "Clear recent",
"CLOSE": "Close",
"COPIED": "Copied",
"COPY": "Copy",
"CREATE": "Create",
"CREATING": "Creating",
"CRITICAL": "CRITICAL",
Expand Down
30 changes: 24 additions & 6 deletions locales/en/public.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
"OPEN_SOURCE_LICENSE": "Open source license",
"VERSION": "Version"
},
"AgentLiveProbes": {
"SEARCH_PLACEHOLDER": "Find by name, description, clazz, or method..."
tthvo marked this conversation as resolved.
Show resolved Hide resolved
},
"AgentProbeTemplates": {
"SEARCH_PLACEHOLDER": "Find by name, or XML content..."
tthvo marked this conversation as resolved.
Show resolved Hide resolved
},
"AllArchivedRecordingsTable": {
"SEARCH_PLACEHOLDER": "Find by directory, or JVM Hash ID..."
tthvo marked this conversation as resolved.
Show resolved Hide resolved
},
"AllTargetsArchivedRecordingsTable": {
"SEARCH_PLACEHOLDER": "Find by URL, or alias..."
tthvo marked this conversation as resolved.
Show resolved Hide resolved
},
"AppLayout": {
"APP_LAUNCHER": {
"ABOUT": "About",
Expand Down Expand Up @@ -274,6 +286,12 @@
"ErrorView": {
"EVENT_TEMPLATES": "Error retrieving Event Templates"
},
"EventTemplates": {
"SEARCH_PLACEHOLDER": "Find by name, description or provider..."
tthvo marked this conversation as resolved.
Show resolved Hide resolved
},
"EventTypes": {
"SEARCH_PLACEHOLDER": "Find by name, description, typeId, or description..."
},
"JvmDetailsCard": {
"CARD_DESCRIPTION": "Display details about the selected Target JVM.",
"CARD_DESCRIPTION_FULL": "View information such as the connection URL, Labels, and Annotations belonging to the selected Target JVM.",
Expand Down Expand Up @@ -329,15 +347,15 @@
"CLEAN_DESCRIPTION": "Clean will stop any Active Recordings that {{ruleName}} created."
},
"Rules": {
"ABOUT_BODY": "Automated Rules define a dynamic set of target JVMs to connect to and start <0>Active Recordings</0> using a specific <1>Event Template</1> when the Automated Rule is created and when any new matching target JVMs appear. If your Target JVM connections require JMX Credentials, you can configure these in <2>Security</2>. Automated Rules can be configured to periodically copy the contents of the Active Recording to <3>Archives</3> to ensure you always have up-to-date information about your JVMs.",
"ABOUT_TITLE": "About Automated Rules",
"ABOUT_BODY": "Automated Rules are configurations that instruct Cryostat to create JDK Flight Recordings on matching target JVM applications, using a specific <1>Event Template</1>. If your Target JVM connections require JMX Credentials, you can configure these in <2>Security</2>. Automated Rules can be configured to periodically copy the contents of the Active Recording to <3>Archives</3> to ensure you always have up-to-date information about your JVMs.",
"ARCHIVAL_PERIOD_TOOLTIP": "Period in seconds. Cryostat will connect to matching targets at this interval and copy the relevant Recording data into its archives. Values less than 1 prevent data from being repeatedly copied into archives - Recordings will be started and remain only in Target JVM memory.",
"EVENT_SPECIFIER_TOOLTIP": "The name and location of the Event Template applied by this rule.",
"INITIAL_DELAY_TOOLTIP": "Initial delay in seconds. Cryostat will wait this amount of time before first copying Recording data into its archives. Values less than 0 default to equal to the Archival period. You can set a non-zero Initial delay with a zero Archival period, which will start a Recording and copy it into archives exactly once after a set delay.",
"MATCH_EXPRESSION_TOOLTIP": "A code-snippet expression which must evaluate to a boolean when applied to a given target. If the expression evaluates to true then the rule applies to that target.",
"MAX_AGE_TOOLTIP": "The maximum age in seconds for data kept in the JFR Recordings started by this rule. Values less than 1 indicate no limit.",
"MAX_SIZE_TOOLTIP": "The maximum size in bytes for JFR Recordings started by this rule. Values less than 1 indicate no limit.",
"PRESERVED_ARCHIVES_TOOLTIP": "The number of Recording copies to be maintained in the Cryostat archives. Cryostat will continue retrieving further archived copies and trimming the oldest copies from the archive to maintain this limit. Values less than 1 prevent data from being copied into archives - Recordings will be started and remain only in Target JVM memory."
"PRESERVED_ARCHIVES_TOOLTIP": "The number of Recording copies to be maintained in the Cryostat archives. Cryostat will continue retrieving further archived copies and trimming the oldest copies from the archive to maintain this limit. Values less than 1 prevent data from being copied into archives - Recordings will be started and remain only in Target JVM memory.",
"SEARCH_PLACEHOLDER": "Find by name, or description..."
},
"RulesUploadModal": {
"DESCRIPTION": "Select an Automated Rules definition file to upload. File must be in valid JSON format.",
Expand Down Expand Up @@ -386,7 +404,7 @@
},
"DESCRIPTION": "",
"LOCALE_SELECT_DESCRIPTION": "Select current date locale.",
"SEARCH_PLACEHOLDER": "Filter by locale...",
"SEARCH_PLACEHOLDER": "Find by locale...",
"TIMEZONE_SELECT_DESCRIPTION": "Select current timezone.",
"TITLE": "Date & Time"
},
Expand Down Expand Up @@ -441,7 +459,7 @@
"CLEAR_SELECTION": "Clear selection",
"CREATE_TARGET": "Create Target",
"NO_SEARCH_MATCHES": "No Target found",
"SEARCH_PLACEHOLDER": "Filter by URL, alias, or discovery group...",
"SEARCH_PLACEHOLDER": "Find by URL, alias, or discovery group...",
"TOGGLE_LABEL": "Select Target",
"TOGGLE_PLACEHOLDER": "Select a Target"
},
Expand Down Expand Up @@ -476,7 +494,7 @@
"SELECT": "Select a timezone",
"TYPE_AHEAD": "Search a timezone"
},
"SEARCH_PLACEHOLDER": "Filter by timezone..."
"SEARCH_PLACEHOLDER": "Find by timezone..."
},
"Topology": {
"GRAPH_VIEW": "Graph view",
Expand Down
25 changes: 15 additions & 10 deletions src/app/Agent/AgentLiveProbes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ import {
ToolbarContent,
ToolbarGroup,
ToolbarItem,
TextInput,
Stack,
StackItem,
EmptyState,
EmptyStateIcon,
EmptyStateHeader,
SearchInput,
} from '@patternfly/react-core';
import { SearchIcon } from '@patternfly/react-icons';
import {
Expand All @@ -49,7 +49,9 @@ import {
Tr,
Td,
} from '@patternfly/react-table';
import _ from 'lodash';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { combineLatest } from 'rxjs';
import { AboutAgentCard } from './AboutAgentCard';

Expand Down Expand Up @@ -85,8 +87,9 @@ const tableColumns: TableColumn[] = [

export interface AgentLiveProbesProps {}

export const AgentLiveProbes: React.FC<AgentLiveProbesProps> = (_) => {
export const AgentLiveProbes: React.FC<AgentLiveProbesProps> = () => {
const context = React.useContext(ServiceContext);
const { t } = useTranslation();
const addSubscription = useSubscriptions();

const [probes, setProbes] = React.useState<EventProbe[]>([]);
Expand Down Expand Up @@ -245,14 +248,14 @@ export const AgentLiveProbes: React.FC<AgentLiveProbesProps> = (_) => {
if (!filterText) {
filtered = probes;
} else {
const ft = filterText.trim().toLowerCase();
const reg = new RegExp(_.escapeRegExp(filterText), 'i');
filtered = probes.filter(
(t: EventProbe) =>
t.name.toLowerCase().includes(ft) ||
t.description.toLowerCase().includes(ft) ||
t.clazz.toLowerCase().includes(ft) ||
t.methodDescriptor.toLowerCase().includes(ft) ||
t.methodName.toLowerCase().includes(ft),
reg.test(t.name) ||
reg.test(t.description) ||
reg.test(t.clazz) ||
reg.test(t.methodDescriptor) ||
reg.test(t.methodName),
);
}

Expand Down Expand Up @@ -325,16 +328,18 @@ export const AgentLiveProbes: React.FC<AgentLiveProbesProps> = (_) => {
<ToolbarContent>
<ToolbarGroup variant="filter-group">
<ToolbarItem>
<TextInput
<SearchInput
style={{ minWidth: '36ch' }}
name="activeProbeFilter"
id="activeProbeFilter"
type="search"
placeholder="Filter..."
placeholder={t('AgentLiveProbes.SEARCH_PLACEHOLDER')}
aria-label="Active probe filter"
tthvo marked this conversation as resolved.
Show resolved Hide resolved
onChange={handleFilterTextChange}
/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarItem variant="separator" />
<ToolbarGroup variant="icon-button-group">
<ToolbarItem>
<Button
Expand Down
13 changes: 8 additions & 5 deletions src/app/Agent/AgentProbeTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ import {
ThProps,
Tr,
} from '@patternfly/react-table';
import _ from 'lodash';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, defaultIfEmpty, first, tap } from 'rxjs/operators';
import { AboutAgentCard } from './AboutAgentCard';
Expand All @@ -83,6 +85,7 @@ export interface AgentProbeTemplatesProps {

export const AgentProbeTemplates: React.FC<AgentProbeTemplatesProps> = ({ agentDetected }) => {
const context = React.useContext(ServiceContext);
const { t } = useTranslation();
const addSubscription = useSubscriptions();

const [templates, setTemplates] = React.useState<ProbeTemplate[]>([]);
Expand Down Expand Up @@ -212,10 +215,8 @@ export const AgentProbeTemplates: React.FC<AgentProbeTemplatesProps> = ({ agentD
if (!filterText) {
filtered = templates;
} else {
const ft = filterText.trim().toLowerCase();
filtered = templates.filter(
(t: ProbeTemplate) => t.name.toLowerCase().includes(ft) || t.xml.toLowerCase().includes(ft),
);
const reg = new RegExp(_.escapeRegExp(filterText), 'i');
filtered = templates.filter((t: ProbeTemplate) => reg.test(t.name) || reg.test(t.xml));
}

setFilteredTemplates(
Expand Down Expand Up @@ -300,16 +301,18 @@ export const AgentProbeTemplates: React.FC<AgentProbeTemplatesProps> = ({ agentD
<ToolbarGroup variant="filter-group">
<ToolbarItem>
<TextInput
style={{ minWidth: '30ch' }}
name="templateFilter"
id="templateFilter"
type="search"
placeholder="Filter..."
placeholder={t('AgentProbeTemplates.SEARCH_PLACEHOLDER')}
aria-label="Probe Template filter"
onChange={handleFilterTextChange}
value={filterText}
/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarItem variant="separator" />
<ToolbarGroup variant="icon-button-group">
<ToolbarItem>
<Button key="upload" variant="secondary" aria-label="Upload" onClick={handleTemplateUpload}>
Expand Down
96 changes: 58 additions & 38 deletions src/app/Archives/AllArchivedRecordingsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,33 @@ import {
ToolbarGroup,
ToolbarItem,
SearchInput,
Badge,
EmptyState,
EmptyStateIcon,
Text,
Tooltip,
Split,
SplitItem,
EmptyStateHeader,
Button,
Icon,
Bullseye,
} from '@patternfly/react-core';
import { HelpIcon, SearchIcon } from '@patternfly/react-icons';
import { Table, Th, Thead, Tbody, Tr, Td, ExpandableRowContent, SortByDirection } from '@patternfly/react-table';
import { FileIcon, HelpIcon, SearchIcon } from '@patternfly/react-icons';
import {
Table,
Th,
Thead,
Tbody,
Tr,
Td,
ExpandableRowContent,
SortByDirection,
OuterScrollContainer,
InnerScrollContainer,
} from '@patternfly/react-table';
import _ from 'lodash';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Observable, of } from 'rxjs';
import { getTargetFromDirectory, includesDirectory, indexOfDirectory } from './utils';

Expand Down Expand Up @@ -67,6 +82,7 @@ export interface AllArchivedRecordingsTableProps {}

export const AllArchivedRecordingsTable: React.FC<AllArchivedRecordingsTableProps> = () => {
const context = React.useContext(ServiceContext);
const { t } = useTranslation();

const [directories, setDirectories] = React.useState<_RecordingDirectory[]>([]);
const [searchText, setSearchText] = React.useState('');
Expand Down Expand Up @@ -122,11 +138,9 @@ export const AllArchivedRecordingsTable: React.FC<AllArchivedRecordingsTableProp
if (!searchText) {
updatedSearchedDirectories = directories;
} else {
const formattedSearchText = searchText.trim().toLowerCase();
const reg = new RegExp(_.escape(searchText), 'i');
updatedSearchedDirectories = directories.filter(
(d: _RecordingDirectory) =>
d.jvmId.toLowerCase().includes(formattedSearchText) ||
d.connectUrl.toLowerCase().includes(formattedSearchText),
(d: _RecordingDirectory) => reg.test(d.jvmId) || reg.test(d.connectUrl),
);
}
return sortResources(
Expand Down Expand Up @@ -243,7 +257,12 @@ export const AllArchivedRecordingsTable: React.FC<AllArchivedRecordingsTableProp
</Split>
</Td>
<Td key={`directory-table-row-${idx}_3`} dataLabel={tableColumns[1].title}>
<Badge key={`${idx}_count`}>{dir.recordings.length || 0}</Badge>
<Button variant="plain" onClick={() => toggleExpanded(dir)}>
<Icon iconSize="md">
<FileIcon />
</Icon>
<span style={{ marginLeft: 'var(--pf-v5-global--spacer--sm)' }}>{dir.recordings.length || 0}</span>
</Button>
</Td>
</Tr>
);
Expand Down Expand Up @@ -306,47 +325,48 @@ export const AllArchivedRecordingsTable: React.FC<AllArchivedRecordingsTableProp
} else if (!searchedDirectories.length) {
view = (
<>
<EmptyState>
<EmptyStateHeader
titleText="No Archived Recordings"
icon={<EmptyStateIcon icon={SearchIcon} />}
headingLevel="h4"
/>
</EmptyState>
<Bullseye>
<EmptyState>
<EmptyStateHeader
titleText="No Archived Recordings"
tthvo marked this conversation as resolved.
Show resolved Hide resolved
icon={<EmptyStateIcon icon={SearchIcon} />}
headingLevel="h4"
/>
</EmptyState>
</Bullseye>
</>
);
} else {
view = (
<>
<Table aria-label="all-archives-table">
<Thead>
<Tr>
<Th key="table-header-expand" />
{tableColumns.map(({ title, width }, index) => (
<Th
key={`table-header-${title}`}
sort={getSortParams(index)}
width={width as React.ComponentProps<typeof Th>['width']}
>
{title}
</Th>
))}
</Tr>
</Thead>
<Tbody>{rowPairs}</Tbody>
</Table>
</>
<Table aria-label="all-archives-table" isStickyHeader>
<Thead>
<Tr>
<Th key="table-header-expand" />
{tableColumns.map(({ title, width }, index) => (
<Th
key={`table-header-${title}`}
sort={getSortParams(index)}
width={width as React.ComponentProps<typeof Th>['width']}
>
{title}
</Th>
))}
</Tr>
</Thead>
<Tbody>{rowPairs}</Tbody>
</Table>
);
}

return (
<>
<OuterScrollContainer className="archive-table-outer-container">
<Toolbar id="all-archives-toolbar">
<ToolbarContent>
<ToolbarGroup variant="filter-group">
<ToolbarItem>
<SearchInput
placeholder="Search"
style={{ minWidth: '30ch' }}
placeholder={t('AllArchivedRecordingsTable.SEARCH_PLACEHOLDER')}
value={searchText}
onChange={handleSearchInput}
onClear={handleSearchInputClear}
Expand All @@ -355,7 +375,7 @@ export const AllArchivedRecordingsTable: React.FC<AllArchivedRecordingsTableProp
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
{view}
</>
<InnerScrollContainer className="archive-table-inner-container">{view}</InnerScrollContainer>
</OuterScrollContainer>
);
};
Loading
Loading