feat(ws): Add popup to Workspace data vol column in workspaces table (#160)
Signed-off-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com> Co-authored-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com>
This commit is contained in:
parent
57fc8b17e5
commit
249a45677d
|
|
@ -0,0 +1,74 @@
|
||||||
|
import {
|
||||||
|
ClipboardCopy,
|
||||||
|
ClipboardCopyVariant,
|
||||||
|
Content,
|
||||||
|
Flex,
|
||||||
|
FlexItem,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Stack,
|
||||||
|
StackItem,
|
||||||
|
Tooltip,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { DatabaseIcon, LockedIcon } from '@patternfly/react-icons';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Workspace } from '~/shared/types';
|
||||||
|
|
||||||
|
interface DataVolumesListProps {
|
||||||
|
workspace: Workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataVolumesList: React.FC<DataVolumesListProps> = ({ workspace }) => {
|
||||||
|
const workspaceDataVol = workspace.podTemplate.volumes.data;
|
||||||
|
|
||||||
|
const singleDataVolRenderer = (data: {
|
||||||
|
pvcName: string;
|
||||||
|
mountPath: string;
|
||||||
|
readOnly: boolean;
|
||||||
|
}) => (
|
||||||
|
<Flex
|
||||||
|
gap={{ default: 'gapSm' }}
|
||||||
|
alignItems={{ default: 'alignItemsFlexStart' }}
|
||||||
|
flexWrap={{ default: 'nowrap' }}
|
||||||
|
>
|
||||||
|
<FlexItem>
|
||||||
|
<DatabaseIcon />
|
||||||
|
</FlexItem>
|
||||||
|
<FlexItem>
|
||||||
|
<Content>
|
||||||
|
{data.pvcName}
|
||||||
|
{data.readOnly && (
|
||||||
|
<Tooltip content="Data is readonly">
|
||||||
|
<LockedIcon style={{ marginLeft: '5px' }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Content>
|
||||||
|
<Stack>
|
||||||
|
<StackItem>
|
||||||
|
<Content component="small">
|
||||||
|
Mount path:{' '}
|
||||||
|
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact}>
|
||||||
|
{data.mountPath}
|
||||||
|
</ClipboardCopy>
|
||||||
|
</Content>
|
||||||
|
</StackItem>
|
||||||
|
</Stack>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack hasGutter>
|
||||||
|
<StackItem>
|
||||||
|
<strong data-testid="notebook-storage-bar-title">Cluster storage</strong>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<List isPlain>
|
||||||
|
{workspaceDataVol.map((data, index) => (
|
||||||
|
<ListItem key={`data-vol-${index}`}>{singleDataVolRenderer(data)}</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</StackItem>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ExpandableRowContent, Td, Tr } from '@patternfly/react-table';
|
||||||
|
import { Workspace, WorkspacesColumnNames } from '~/shared/types';
|
||||||
|
import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList';
|
||||||
|
|
||||||
|
interface ExpandedWorkspaceRowProps {
|
||||||
|
workspace: Workspace;
|
||||||
|
columnNames: WorkspacesColumnNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpandedWorkspaceRow: React.FC<ExpandedWorkspaceRowProps> = ({
|
||||||
|
workspace,
|
||||||
|
columnNames,
|
||||||
|
}) => {
|
||||||
|
const renderExpandedData = () =>
|
||||||
|
Object.keys(columnNames).map((colName) => {
|
||||||
|
switch (colName) {
|
||||||
|
case 'name':
|
||||||
|
return (
|
||||||
|
<Td noPadding colSpan={1}>
|
||||||
|
<ExpandableRowContent>
|
||||||
|
<DataVolumesList workspace={workspace} />
|
||||||
|
</ExpandableRowContent>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <Td />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr>
|
||||||
|
<Td />
|
||||||
|
{renderExpandedData()}
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -35,7 +35,9 @@ import {
|
||||||
IActions,
|
IActions,
|
||||||
} from '@patternfly/react-table';
|
} from '@patternfly/react-table';
|
||||||
import { FilterIcon } from '@patternfly/react-icons';
|
import { FilterIcon } from '@patternfly/react-icons';
|
||||||
import { Workspace, WorkspaceState } from 'shared/types';
|
import { Workspace, WorkspacesColumnNames, WorkspaceState } from '~/shared/types';
|
||||||
|
|
||||||
|
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||||
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
||||||
|
|
||||||
export const Workspaces: React.FunctionComponent = () => {
|
export const Workspaces: React.FunctionComponent = () => {
|
||||||
|
|
@ -54,7 +56,12 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
home: '/home',
|
home: '/home',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
pvcName: 'data',
|
pvcName: 'Volume-1',
|
||||||
|
mountPath: '/data',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pvcName: 'Volume-2',
|
||||||
mountPath: '/data',
|
mountPath: '/data',
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
},
|
},
|
||||||
|
|
@ -95,7 +102,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
home: '/home',
|
home: '/home',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
pvcName: 'data',
|
pvcName: 'PVC-1',
|
||||||
mountPath: '/data',
|
mountPath: '/data',
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
},
|
},
|
||||||
|
|
@ -126,14 +133,13 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
// Table columns
|
// Table columns
|
||||||
const columnNames = {
|
const columnNames: WorkspacesColumnNames = {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
kind: 'Kind',
|
kind: 'Kind',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
podConfig: 'Pod Config',
|
podConfig: 'Pod Config',
|
||||||
state: 'State',
|
state: 'State',
|
||||||
homeVol: 'Home Vol',
|
homeVol: 'Home Vol',
|
||||||
dataVol: 'Data Vol',
|
|
||||||
cpu: 'CPU',
|
cpu: 'CPU',
|
||||||
ram: 'Memory',
|
ram: 'Memory',
|
||||||
lastActivity: 'Last Activity',
|
lastActivity: 'Last Activity',
|
||||||
|
|
@ -147,6 +153,18 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
const attributeContainerRef = React.useRef<HTMLDivElement | null>(null);
|
const attributeContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = React.useState('');
|
const [searchValue, setSearchValue] = React.useState('');
|
||||||
|
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
|
||||||
|
setExpandedWorkspacesNames((prevExpanded) => {
|
||||||
|
const newExpandedWorkspacesNames = prevExpanded.filter((wsName) => wsName !== workspace.name);
|
||||||
|
return isExpanding
|
||||||
|
? [...newExpandedWorkspacesNames, workspace.name]
|
||||||
|
: newExpandedWorkspacesNames;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isWorkspaceExpanded = (workspace: Workspace) =>
|
||||||
|
expandedWorkspacesNames.includes(workspace.name);
|
||||||
|
|
||||||
const searchInput = (
|
const searchInput = (
|
||||||
<SearchInput
|
<SearchInput
|
||||||
|
|
@ -322,19 +340,18 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
|
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
|
||||||
|
|
||||||
const getSortableRowValues = (workspace: Workspace): (string | number)[] => {
|
const getSortableRowValues = (workspace: Workspace): (string | number)[] => {
|
||||||
const { name, kind, image, podConfig, state, homeVol, dataVol, cpu, ram, lastActivity } = {
|
const { name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity } = {
|
||||||
name: workspace.name,
|
name: workspace.name,
|
||||||
kind: workspace.kind,
|
kind: workspace.kind,
|
||||||
image: workspace.options.imageConfig,
|
image: workspace.options.imageConfig,
|
||||||
podConfig: workspace.options.podConfig,
|
podConfig: workspace.options.podConfig,
|
||||||
state: WorkspaceState[workspace.status.state],
|
state: WorkspaceState[workspace.status.state],
|
||||||
homeVol: workspace.podTemplate.volumes.home,
|
homeVol: workspace.podTemplate.volumes.home,
|
||||||
dataVol: workspace.podTemplate.volumes.data[0].pvcName || '',
|
|
||||||
cpu: workspace.cpu,
|
cpu: workspace.cpu,
|
||||||
ram: workspace.ram,
|
ram: workspace.ram,
|
||||||
lastActivity: workspace.status.activity.lastActivity,
|
lastActivity: workspace.status.activity.lastActivity,
|
||||||
};
|
};
|
||||||
return [name, kind, image, podConfig, state, homeVol, dataVol, cpu, ram, lastActivity];
|
return [name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity];
|
||||||
};
|
};
|
||||||
|
|
||||||
let sortedWorkspaces = filteredWorkspaces;
|
let sortedWorkspaces = filteredWorkspaces;
|
||||||
|
|
@ -438,26 +455,33 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
<Table aria-label="Sortable table" ouiaId="SortableTable">
|
<Table aria-label="Sortable table" ouiaId="SortableTable">
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
|
<Th />
|
||||||
<Th sort={getSortParams(0)}>{columnNames.name}</Th>
|
<Th sort={getSortParams(0)}>{columnNames.name}</Th>
|
||||||
<Th sort={getSortParams(1)}>{columnNames.kind}</Th>
|
<Th sort={getSortParams(1)}>{columnNames.kind}</Th>
|
||||||
<Th sort={getSortParams(2)}>{columnNames.image}</Th>
|
<Th sort={getSortParams(2)}>{columnNames.image}</Th>
|
||||||
<Th sort={getSortParams(3)}>{columnNames.podConfig}</Th>
|
<Th sort={getSortParams(3)}>{columnNames.podConfig}</Th>
|
||||||
<Th sort={getSortParams(4)}>{columnNames.state}</Th>
|
<Th sort={getSortParams(4)}>{columnNames.state}</Th>
|
||||||
<Th sort={getSortParams(5)}>{columnNames.homeVol}</Th>
|
<Th sort={getSortParams(5)}>{columnNames.homeVol}</Th>
|
||||||
<Th sort={getSortParams(6)}>{columnNames.dataVol}</Th>
|
<Th sort={getSortParams(6)} info={{ tooltip: 'Workspace CPU usage' }}>
|
||||||
<Th sort={getSortParams(7)} info={{ tooltip: 'Workspace CPU usage' }}>
|
|
||||||
{columnNames.cpu}
|
{columnNames.cpu}
|
||||||
</Th>
|
</Th>
|
||||||
<Th sort={getSortParams(8)} info={{ tooltip: 'Workspace memory usage' }}>
|
<Th sort={getSortParams(7)} info={{ tooltip: 'Workspace memory usage' }}>
|
||||||
{columnNames.ram}
|
{columnNames.ram}
|
||||||
</Th>
|
</Th>
|
||||||
<Th sort={getSortParams(9)}>{columnNames.lastActivity}</Th>
|
<Th sort={getSortParams(8)}>{columnNames.lastActivity}</Th>
|
||||||
<Th screenReaderText="Primary action" />
|
<Th screenReaderText="Primary action" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
{sortedWorkspaces.map((workspace, rowIndex) => (
|
||||||
{sortedWorkspaces.map((workspace, rowIndex) => (
|
<Tbody key={rowIndex} isExpanded={isWorkspaceExpanded(workspace)}>
|
||||||
<Tr key={rowIndex}>
|
<Tr>
|
||||||
|
<Td
|
||||||
|
expand={{
|
||||||
|
rowIndex,
|
||||||
|
isExpanded: isWorkspaceExpanded(workspace),
|
||||||
|
onToggle: () => setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
|
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
|
||||||
<Td dataLabel={columnNames.kind}>{workspace.kind}</Td>
|
<Td dataLabel={columnNames.kind}>{workspace.kind}</Td>
|
||||||
<Td dataLabel={columnNames.image}>{workspace.options.imageConfig}</Td>
|
<Td dataLabel={columnNames.image}>{workspace.options.imageConfig}</Td>
|
||||||
|
|
@ -468,9 +492,6 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
</Label>
|
</Label>
|
||||||
</Td>
|
</Td>
|
||||||
<Td dataLabel={columnNames.homeVol}>{workspace.podTemplate.volumes.home}</Td>
|
<Td dataLabel={columnNames.homeVol}>{workspace.podTemplate.volumes.home}</Td>
|
||||||
<Td dataLabel={columnNames.dataVol}>
|
|
||||||
{workspace.podTemplate.volumes.data[0].pvcName || ''}
|
|
||||||
</Td>
|
|
||||||
<Td dataLabel={columnNames.cpu}>{`${workspace.cpu}%`}</Td>
|
<Td dataLabel={columnNames.cpu}>{`${workspace.cpu}%`}</Td>
|
||||||
<Td dataLabel={columnNames.ram}>{formatRam(workspace.ram)}</Td>
|
<Td dataLabel={columnNames.ram}>{formatRam(workspace.ram)}</Td>
|
||||||
<Td dataLabel={columnNames.lastActivity}>
|
<Td dataLabel={columnNames.lastActivity}>
|
||||||
|
|
@ -485,8 +506,11 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
<ActionsColumn items={defaultActions(workspace)} />
|
<ActionsColumn items={defaultActions(workspace)} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
{isWorkspaceExpanded(workspace) && (
|
||||||
</Tbody>
|
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
|
||||||
|
)}
|
||||||
|
</Tbody>
|
||||||
|
))}
|
||||||
</Table>
|
</Table>
|
||||||
<Pagination
|
<Pagination
|
||||||
itemCount={333}
|
itemCount={333}
|
||||||
|
|
|
||||||
|
|
@ -70,3 +70,15 @@ export interface Workspace {
|
||||||
};
|
};
|
||||||
status: WorkspaceStatus;
|
status: WorkspaceStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkspacesColumnNames = {
|
||||||
|
name: string;
|
||||||
|
kind: string;
|
||||||
|
image: string;
|
||||||
|
podConfig: string;
|
||||||
|
state: string;
|
||||||
|
homeVol: string;
|
||||||
|
cpu: string;
|
||||||
|
ram: string;
|
||||||
|
lastActivity: string;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue