Skip to content

Commit

Permalink
fix(loading): ensure loading spinners are displayed (#412) (#413)
Browse files Browse the repository at this point in the history
* fix(security): jmx credentials view displays loading spinner

* fix(recordings): table toolbars remain visible while loading

allows actions such as creation, upload to be triggered while content view is still loading

* fix(rules): display loading spinner

* fix(templates): table toolbar remains visible while loading

allows event templates to be uploaded while content view is still loading

(cherry picked from commit a83c895)

Co-authored-by: Andrew Azores <aazores@redhat.com>
  • Loading branch information
mergify[bot] and andrewazores authored Apr 21, 2022
1 parent 62f3677 commit 6edda33
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 73 deletions.
38 changes: 22 additions & 16 deletions src/app/Events/EventTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,26 +249,32 @@ export const EventTemplates = () => {
setSortBy({ index, direction });
};

const toolbar: JSX.Element = (<>
<Toolbar id="event-templates-toolbar">
<ToolbarContent>
<ToolbarGroup variant="filter-group">
<ToolbarItem>
<TextInput name="templateFilter" id="templateFilter" type="search" placeholder="Filter..." aria-label="Event template filter" onChange={setFilterText}/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup variant="icon-button-group">
<ToolbarItem>
<Button key="create" variant="primary" onClick={handleModalToggle}>Upload</Button>
</ToolbarItem>
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
</>);
if (errorMessage != '') {
return (<ErrorView message={errorMessage}/>)
return (<ErrorView message={errorMessage}/>);
} else if (isLoading) {
return (<LoadingView/>)
return (<>
{ toolbar }
<LoadingView/>
</>);
} else {
return (<>
<Toolbar id="event-templates-toolbar">
<ToolbarContent>
<ToolbarGroup variant="filter-group">
<ToolbarItem>
<TextInput name="templateFilter" id="templateFilter" type="search" placeholder="Filter..." aria-label="Event template filter" onChange={setFilterText}/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup variant="icon-button-group">
<ToolbarItem>
<Button key="create" variant="primary" onClick={handleModalToggle}>Upload</Button>
</ToolbarItem>
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
{ toolbar }
<Table aria-label="Event Templates table"
variant={TableVariant.compact}
cells={tableColumns}
Expand Down
31 changes: 17 additions & 14 deletions src/app/Recordings/RecordingsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
/*
* Copyright The Cryostat Authors
*
*
* The Universal Permissive License (UPL), Version 1.0
*
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Expand Down Expand Up @@ -54,24 +54,22 @@ export interface RecordingsTableProps {
}

export const RecordingsTable: React.FunctionComponent<RecordingsTableProps> = (props) => {
let view: JSX.Element;
if (props.errorMessage != '') {
return (<ErrorView message={props.errorMessage}/>)
view = (<ErrorView message={props.errorMessage}/>)
} else if (props.isLoading) {
return (<LoadingView/>)
view = (<LoadingView/>)
} else if (props.isEmpty) {
return (
<>
{ props.toolbar }
view = (<>
<EmptyState>
<EmptyStateIcon icon={SearchIcon}/>
<Title headingLevel="h4" size="lg">
No {props.tableTitle}
</Title>
</EmptyState>
</>)
</>);
} else {
return (<>
{ props.toolbar }
view = (<>
<TableComposable aria-label={props.tableTitle}>
<Thead>
<Tr>
Expand All @@ -93,4 +91,9 @@ export const RecordingsTable: React.FunctionComponent<RecordingsTableProps> = (p
</TableComposable>
</>);
}

return (<>
{ props.toolbar }
{ view }
</>);
};
6 changes: 3 additions & 3 deletions src/app/Rules/Rules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ export const Rules = () => {
};

const viewContent = () => {
if (rules.length === 0) {
if (isLoading) {
return <LoadingView />;
} else if (rules.length === 0) {
return (<>
<EmptyState>
<EmptyStateIcon icon={SearchIcon}/>
Expand All @@ -209,8 +211,6 @@ export const Rules = () => {
</Title>
</EmptyState>
</>);
} else if (isLoading) {
return <LoadingView />;
} else {
return (<>
<Table aria-label="Automated Rules table"
Expand Down
90 changes: 50 additions & 40 deletions src/app/SecurityPanel/StoreJmxCredentials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { TableComposable, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-tab
import { CreateJmxCredentialModal } from './CreateJmxCredentialModal';
import { SecurityCard } from './SecurityPanel';
import { NotificationCategory } from '@app/Shared/Services/NotificationChannel.service';
import { LoadingView } from '@app/LoadingView/LoadingView';

const Component = () => {
const context = React.useContext(ServiceContext);
Expand All @@ -66,12 +67,17 @@ const Component = () => {
const [headerChecked, setHeaderChecked] = React.useState(false);
const [checkedIndices, setCheckedIndices] = React.useState([] as number[]);
const [showAuthModal, setShowAuthModal] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);

const tableColumns: string[] = ['Target Alias', 'Connect URL'];
const tableTitle = 'Stored Credentials';

const refreshStoredTargetsList = React.useCallback(() => {
addSubscription(context.api.getTargetsWithStoredJmxCredentials().subscribe(t => setStoredTargets(t)));
setIsLoading(true);
addSubscription(context.api.getTargetsWithStoredJmxCredentials().subscribe((t: Target[]) => {
setStoredTargets(t);
setIsLoading(false);
}));
}, [context, context.api, context.targets, setStoredTargets]);

React.useEffect(() => {
Expand Down Expand Up @@ -194,49 +200,53 @@ const Component = () => {
return storedTargets.map((t, idx) => <TargetCredentialsTableRow key={idx} target={t} index={idx} />);
}, [storedTargets, checkedIndices]);

return (
<>
{storedTargets.length === 0 ? (
<>
<TargetCredentialsToolbar />
<EmptyState>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h4" size="lg">
No {tableTitle}
</Title>
</EmptyState>
</>
) : (
<>
<TargetCredentialsToolbar />
<TableComposable aria-label={tableTitle}>
<Thead>
<Tr>
<Th
key="table-header-check-all"
select={{
onSelect: handleHeaderCheck,
isSelected: headerChecked,
}}
/>
{tableColumns.map((key, idx) => (
<Th key={`table-header-${key}`}>{key}</Th>
))}
</Tr>
</Thead>
{targetRows}
</TableComposable>
</>
)}
<CreateJmxCredentialModal visible={showAuthModal} onClose={handleModalClose} />
</>
);
let content: JSX.Element;
if (isLoading) {
content = (<>
<LoadingView />
</>);
} else if (storedTargets.length === 0) {
content = (<>
<EmptyState>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h4" size="lg">
No {tableTitle}
</Title>
</EmptyState>
</>);
} else {
content = (<>
<TableComposable aria-label={tableTitle}>
<Thead>
<Tr>
<Th
key="table-header-check-all"
select={{
onSelect: handleHeaderCheck,
isSelected: headerChecked,
}}
/>
{tableColumns.map((key, idx) => (
<Th key={`table-header-${key}`}>{key}</Th>
))}
</Tr>
</Thead>
{targetRows}
</TableComposable>
</>);
}

return (<>
<TargetCredentialsToolbar />
{ content }
<CreateJmxCredentialModal visible={showAuthModal} onClose={handleModalClose} />
</>);
};

export const StoreJmxCredentials: SecurityCard = {
title: 'Store JMX Credentials',
description: `Targets for which Cryostat has stored JMX credentials are listed here.
If a Target JVM requires JMX authentication, Cryostat will use stored credentials
description: `Targets for which Cryostat has stored JMX credentials are listed here.
If a Target JVM requires JMX authentication, Cryostat will use stored credentials
when attempting to open JMX connections to the target.`,
content: Component,
};

0 comments on commit 6edda33

Please sign in to comment.