Skip to content

Commit

Permalink
Merge pull request #932 from yaacov/logs-in-overview
Browse files Browse the repository at this point in the history
🐾 Add pods tab to overview page
  • Loading branch information
yaacov authored Feb 18, 2024
2 parents 2647b1c + bd355d1 commit 0c6232e
Show file tree
Hide file tree
Showing 14 changed files with 346 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"Conditions": "Conditions",
"Confirm": "Confirm",
"Connection Failed": "Connection Failed",
"Controller": "Controller",
"Controller CPU limit": "Controller CPU limit",
"Controller Memory limit": "Controller Memory limit",
"Controls the interval at which a new snapshot is requested prior to initiating a warm migration. The default value is 60 minutes.": "Controls the interval at which a new snapshot is requested prior to initiating a warm migration. The default value is 60 minutes.",
Expand Down Expand Up @@ -291,7 +292,9 @@
"Please enter the limit for memory usage by the controller in Mi, if empty default value will be used.": "Please enter the limit for memory usage by the controller in Mi, if empty default value will be used.",
"Please enter the maximum age in hours for must gather cleanup, if empty default value will be used.": "Please enter the maximum age in hours for must gather cleanup, if empty default value will be used.",
"Please enter the maximum number of concurrent VM migrations, if empty default value will be used.": "Please enter the maximum number of concurrent VM migrations, if empty default value will be used.",
"Pod": "Pod",
"Pod network": "Pod network",
"Pods": "Pods",
"Power state": "Power state",
"Powered off": "Powered off",
"Powered on": "Powered on",
Expand Down
104 changes: 104 additions & 0 deletions packages/forklift-console-plugin/src/components/status/StatusIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';

import {
BlueInfoCircleIcon,
GreenCheckCircleIcon,
RedExclamationCircleIcon,
YellowExclamationTriangleIcon,
} from '@openshift-console/dynamic-plugin-sdk';
import { Spinner } from '@patternfly/react-core';
import { BanIcon } from '@patternfly/react-icons/dist/esm/icons/ban-icon';
import { ClipboardListIcon } from '@patternfly/react-icons/dist/esm/icons/clipboard-list-icon';
import { HourglassHalfIcon } from '@patternfly/react-icons/dist/esm/icons/hourglass-half-icon';
import { HourglassStartIcon } from '@patternfly/react-icons/dist/esm/icons/hourglass-start-icon';
import { NotStartedIcon } from '@patternfly/react-icons/dist/esm/icons/not-started-icon';
import { SyncAltIcon } from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
import { UnknownIcon } from '@patternfly/react-icons/dist/esm/icons/unknown-icon';

export const StatusIcon: React.FC<{ phase: string }> = ({ phase }) => {
switch (phase) {
case 'New':
return <HourglassStartIcon />;

case 'Pending':
return <HourglassHalfIcon />;

case 'Planning':
return <ClipboardListIcon />;

case 'ContainerCreating':
case 'UpgradePending':
case 'PendingUpgrade':
case 'PendingRollback':
return <Spinner size="sm" />;

case 'In Progress':
case 'Installing':
case 'InstallReady':
case 'Replacing':
case 'Running':
case 'Updating':
case 'Upgrading':
case 'PendingInstall':
return <SyncAltIcon />;

case 'Cancelled':
case 'Deleting':
case 'Expired':
case 'Not Ready':
case 'Cancelling':
case 'Terminating':
case 'Superseded':
case 'Uninstalling':
return <BanIcon />;

case 'Warning':
case 'RequiresApproval':
return <YellowExclamationTriangleIcon />;

case 'ContainerCannotRun':
case 'CrashLoopBackOff':
case 'Critical':
case 'ErrImagePull':
case 'Error':
case 'Failed':
case 'Failure':
case 'ImagePullBackOff':
case 'InstallCheckFailed':
case 'Lost':
case 'Rejected':
case 'UpgradeFailed':
return <RedExclamationCircleIcon />;

case 'Accepted':
case 'Active':
case 'Bound':
case 'Complete':
case 'Completed':
case 'Created':
case 'Enabled':
case 'Succeeded':
case 'Ready':
case 'Up to date':
case 'Loaded':
case 'Provisioned as node':
case 'Preferred':
case 'Connected':
case 'Deployed':
return <GreenCheckCircleIcon />;

case 'Info':
return <BlueInfoCircleIcon />;

case 'Unknown':
return <UnknownIcon />;

case 'PipelineNotStarted':
return <NotStartedIcon />;

default:
return <>-</>;
}
};

export default StatusIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@
.forklift-status-migration-chart {
height: 100%;
width: 100%;
}

.forklift-overview__controller-card__status-icon {
padding-right: var(--pf-global--spacer--sm);
}

.forklift-overview__pods-tab {
background: var(--pf-c-drawer__section--BackgroundColor);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getOperatorPhase } from '../../utils/helpers/getOperatorPhase';

import OperatorStatus from './components/OperatorStatus';
import { ShowWelcomeCardButton } from './components/ShowWelcomeCardButton';
import ForkliftControllerPodsTab from './tabs/Pods/ForkliftControllerPodsTab';
import { HeaderTitle } from './components';
import {
ForkliftControllerDetailsTab,
Expand Down Expand Up @@ -38,6 +39,11 @@ export const OverviewPage: React.FC<OverviewPageProps> = () => {
name: t('Metrics'),
component: ForkliftControllerMetricsTabWrapper,
},
{
href: 'pods',
name: t('Pods'),
component: ForkliftControllerPodsTabWrapper,
},
];

return (
Expand Down Expand Up @@ -105,4 +111,12 @@ const ForkliftControllerMetricsTabWrapper: React.FC = () => {
);
};

const ForkliftControllerPodsTabWrapper: React.FC = () => {
const [forkliftController, loaded, loadError] = useK8sWatchForkliftController();

return (
<ForkliftControllerPodsTab obj={forkliftController} loaded={loaded} loadError={loadError} />
);
};

export default OverviewPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import StatusIcon from 'src/components/status/StatusIcon';
import { useForkliftTranslation } from 'src/utils/i18n';

import { IoK8sApiCoreV1Pod } from '@kubev2v/types';
import { ResourceLink, Timestamp } from '@openshift-console/dynamic-plugin-sdk';
import { Split, SplitItem } from '@patternfly/react-core';
import { TableComposable, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';

export const PodsTable: React.FC<PodsTableProps> = ({ pods, showOwner }) => {
const { t } = useForkliftTranslation();

if (!pods) {
return <></>;
}

const getStatusLabel = (phase: string) => {
return (
<Split>
<SplitItem className="forklift-overview__controller-card__status-icon">
<StatusIcon phase={phase} />
</SplitItem>
<SplitItem>{phase}</SplitItem>
</Split>
);
};

return (
<TableComposable aria-label="Expandable table" variant="compact">
<Thead>
<Tr>
<Th width={20}>{t('Pod')}</Th>
{showOwner && <Th width={20}>{t('Owner')}</Th>}
<Th width={10}>{t('Status')}</Th>
<Th width={10}>{t('Created at')}</Th>
</Tr>
</Thead>
<Tbody>
{pods.map((pod) => (
<Tr key={pod?.metadata?.uid}>
<Td>
<ResourceLink
kind={pod?.kind}
name={pod?.metadata?.name}
namespace={pod?.metadata?.namespace}
/>
</Td>
{showOwner && (
<Td>
{pod?.metadata?.ownerReferences?.[0] ? (
<ResourceLink
kind={pod?.metadata?.ownerReferences?.[0]?.kind}
name={pod?.metadata?.ownerReferences?.[0]?.name}
namespace={pod?.metadata?.namespace}
/>
) : (
''
)}
</Td>
)}
<Td>{getStatusLabel(pod?.status?.phase)}</Td>
<Td>
<Timestamp timestamp={pod?.metadata?.creationTimestamp} />
</Td>
</Tr>
))}
</Tbody>
</TableComposable>
);
};

export type PodsTableProps = {
pods: IoK8sApiCoreV1Pod[];
showOwner?: boolean;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './HeaderTitle';
export * from './OperatorStatus';
export * from './PodsTable';
export * from './ShowWelcomeCardButton';
// @endindex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { RouteComponentProps } from 'react-router-dom';
import { useCreateOverviewContext } from 'src/modules/Overview/hooks/OverviewContextProvider';

import { V1beta1ForkliftController } from '@kubev2v/types';
import { Flex, FlexItem } from '@patternfly/react-core';
import { Flex, FlexItem, Stack, StackItem } from '@patternfly/react-core';

import { ConditionsCard, OperatorCard, OverviewCard, SettingsCard } from './cards';
import { ConditionsCard, ControllerCard, OperatorCard, OverviewCard, SettingsCard } from './cards';

interface ForkliftControllerDetailsTabProps extends RouteComponentProps {
obj: V1beta1ForkliftController;
Expand All @@ -25,36 +25,44 @@ export const ForkliftControllerDetailsTab: React.FC<ForkliftControllerDetailsTab
return (
<div className="co-dashboard-body">
<Flex direction={{ default: 'column' }}>
<Flex>
{!hideWelcomeCardByContext ? (
<FlexItem>
<OverviewCard
obj={obj}
onHide={() => {
setData({ hideWelcomeCardByContext: true });
}}
/>
</FlexItem>
) : null}
</Flex>
<Flex
direction={{ default: 'column' }}
flex={{ default: 'flex_1' }}
alignItems={{ default: 'alignItemsStretch' }}
alignContent={{ default: 'alignContentStretch' }}
>
{!hideWelcomeCardByContext ? (
<FlexItem>
<OperatorCard obj={obj} />
</FlexItem>
</Flex>
<Flex>
<FlexItem flex={{ default: 'flex_1' }} alignSelf={{ default: 'alignSelfStretch' }}>
<ConditionsCard obj={obj} />
<OverviewCard
obj={obj}
onHide={() => {
setData({ hideWelcomeCardByContext: true });
}}
/>
</FlexItem>
<FlexItem flex={{ default: 'flex_1' }}>
<SettingsCard obj={obj} />
</FlexItem>
</Flex>
) : null}

<FlexItem>
<OperatorCard obj={obj} />
</FlexItem>

<FlexItem>
<Flex direction={{ default: 'row' }}>
<FlexItem flex={{ default: 'flex_1' }}>
<Stack hasGutter>
<StackItem>
<ControllerCard obj={obj} />
</StackItem>

<StackItem>
<ConditionsCard obj={obj} />
</StackItem>
</Stack>
</FlexItem>

<FlexItem>
<Stack hasGutter>
<StackItem>
<SettingsCard obj={obj} />
</StackItem>
</Stack>
</FlexItem>
</Flex>
</FlexItem>
</Flex>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { FC } from 'react';
import { Suspend } from 'src/modules/Plans/views/details/components';
import { useForkliftTranslation } from 'src/utils/i18n';

import { IoK8sApiCoreV1Pod, V1beta1ForkliftController } from '@kubev2v/types';
import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
import { Card, CardBody, CardTitle } from '@patternfly/react-core';

import { PodsTable } from '../../../components';

type ControllerCardProps = {
obj?: V1beta1ForkliftController;
};

export const ControllerCard: FC<ControllerCardProps> = ({ obj }) => {
const { t } = useForkliftTranslation();

const [pods, loaded, loadError] = useK8sWatchResource<IoK8sApiCoreV1Pod[]>({
kind: 'Pod',
namespaced: true,
isList: true,
namespace: obj?.metadata?.namespace,
selector: { matchLabels: { app: 'forklift', 'control-plane': 'controller-manager' } },
});

return (
<Card>
<CardTitle className="forklift-title">{t('Controller')}</CardTitle>
<Suspend obj={pods} loaded={loaded} loadError={loadError}>
<CardBody>
<PodsTable pods={pods} />
</CardBody>
</Suspend>
</Card>
);
};

export default ControllerCard;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './ConditionsCard';
export * from './ControllerCard';
export * from './OperatorCard';
export * from './SettingsCard';
export * from './WelcomeCard';
Expand Down
Loading

0 comments on commit 0c6232e

Please sign in to comment.