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

Node overview #4911

Merged
merged 1 commit into from Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -72,10 +72,9 @@ export const StatusCard: React.FC<DashboardItemProps> = ({
const resiliencyProgressError = prometheusResults.getIn([resiliencyProgressQuery, 'loadError']);

const cephHealthState = getCephHealthState({ ceph: { data, loaded, loadError } });
const dataResiliencyState = getDataResiliencyState(
[resiliencyProgress],
[resiliencyProgressError],
);
const dataResiliencyState = getDataResiliencyState([
{ response: resiliencyProgress, error: resiliencyProgressError },
]);

return (
<DashboardCard gradient>
Expand Down
Expand Up @@ -31,12 +31,12 @@ export const getCephHealthState: ResourceHealthHandler<WatchCephResource> = ({ c
return CephHealthStatus[status] || { state: HealthState.UNKNOWN };
};

export const getDataResiliencyState: PrometheusHealthHandler = (responses = [], errors = []) => {
const progress: number = getResiliencyProgress(responses[0]);
if (errors[0]) {
export const getDataResiliencyState: PrometheusHealthHandler = (responses) => {
const progress: number = getResiliencyProgress(responses[0].response);
if (responses[0].error) {
return { state: HealthState.NOT_AVAILABLE };
}
if (!responses[0]) {
if (!responses[0].response) {
return { state: HealthState.LOADING };
}
if (Number.isNaN(progress)) {
Expand Down
Expand Up @@ -3,62 +3,33 @@ import {
HealthState,
healthStateMapping,
} from '@console/shared/src/components/dashboard/status-card/states';
import { PrometheusResponse } from '@console/internal/components/graphs';
import { getControlPlaneComponentHealth } from './status';
import Status, {
StatusPopupSection,
} from '@console/shared/src/components/dashboard/status-card/StatusPopup';
import { PrometheusHealthPopupProps } from '@console/plugin-sdk';

const ResponseRate: React.FC<ResponseRateProps> = ({ response, children, error }) => {
const health = getControlPlaneComponentHealth(response, error);
let icon: React.ReactNode;
if (health.state === HealthState.LOADING) {
icon = <div className="skeleton-health" />;
} else if (health.state !== HealthState.NOT_AVAILABLE) {
icon = healthStateMapping[health.state].icon;
}
return (
<div className="co-overview-status__row">
<div>{children}</div>
<div className="co-overview-status__response-rate">
<div className="text-secondary">{health.message}</div>
{icon && <div className="co-overview-status__response-rate-icon">{icon}</div>}
</div>
</div>
);
};
const titles = ['API Servers', 'Controller Managers', 'Schedulers', 'API Request Success Rate'];

const ControlPlanePopup: React.FC<ControlPlanePopupProps> = ({ results, errors }) => (
const ControlPlanePopup: React.FC<PrometheusHealthPopupProps> = ({ responses }) => (
<>
<div className="co-overview-status__control-plane-description">
Components of the Control Plane are responsible for maintaining and reconciling the state of
the cluster.
</div>
<div className="co-overview-status__row">
<div className="co-overview-status__text--bold">Components</div>
<div className="text-secondary">Response rate</div>
</div>
<ResponseRate response={results[0]} error={errors[0]}>
API Servers
</ResponseRate>
<ResponseRate response={results[1]} error={errors[1]}>
Controller Managers
</ResponseRate>
<ResponseRate response={results[2]} error={errors[2]}>
Schedulers
</ResponseRate>
<ResponseRate response={results[3]} error={errors[3]}>
API Request Success Rate
</ResponseRate>
Components of the Control Plane are responsible for maintaining and reconciling the state of the
cluster.
<StatusPopupSection firstColumn="Components" secondColumn="Response rate">
{responses.map(({ response, error }, index) => {
const health = getControlPlaneComponentHealth(response, error);
let icon: React.ReactNode;
if (health.state === HealthState.LOADING) {
icon = <div className="skeleton-health" />;
} else if (health.state !== HealthState.NOT_AVAILABLE) {
icon = healthStateMapping[health.state].icon;
}
return (
<Status key={titles[index]} title={titles[index]} value={health.message} icon={icon} />
);
})}
</StatusPopupSection>
</>
);

export default ControlPlanePopup;

type ControlPlanePopupProps = {
results: PrometheusResponse[];
errors: any[];
};

type ResponseRateProps = {
response: PrometheusResponse;
children: React.ReactNode;
error: any;
};
Expand Up @@ -3,21 +3,19 @@ import { referenceForModel, ClusterOperator } from '@console/internal/module/k8s
import { ClusterOperatorModel } from '@console/internal/models';
import { ResourceLink } from '@console/internal/components/utils/resource-link';
import { OperatorRowProps } from '@console/plugin-sdk';
import { OperatorStatusRow } from '@console/shared/src/components/dashboard/status-card/OperatorStatusBody';

import './operator-status.scss';
import Status from '@console/shared/src/components/dashboard/status-card/StatusPopup';

const ClusterOperatorStatusRow: React.FC<OperatorRowProps<ClusterOperator>> = ({
operatorStatus,
}) => (
<OperatorStatusRow title={operatorStatus.status.title} icon={operatorStatus.status.icon}>
<Status value={operatorStatus.status.title} icon={operatorStatus.status.icon}>
<ResourceLink
kind={referenceForModel(ClusterOperatorModel)}
name={operatorStatus.operators[0].metadata.name}
hideIcon
className="co-operator-status__title"
className="co-status-popup__title"
/>
</OperatorStatusRow>
</Status>
);

export default ClusterOperatorStatusRow;

This file was deleted.

Expand Up @@ -71,9 +71,9 @@ export const getControlPlaneComponentHealth = (

const errorStates = [HealthState.WARNING, HealthState.ERROR, HealthState.NOT_AVAILABLE];

export const getControlPlaneHealth: PrometheusHealthHandler = (responses = [], errors = []) => {
const componentsHealth = responses.map((r, index) =>
getControlPlaneComponentHealth(r, errors[index]),
export const getControlPlaneHealth: PrometheusHealthHandler = (responses) => {
const componentsHealth = responses.map(({ response, error }) =>
getControlPlaneComponentHealth(response, error),
);
if (componentsHealth.some((c) => c.state === HealthState.LOADING)) {
return { state: HealthState.LOADING };
Expand Down
Expand Up @@ -7,11 +7,21 @@ import { nodeStatus } from '../../status/node';
import NodeDetails from './NodeDetails';
import NodeTerminal from './NodeTerminal';
import { menuActions } from './menu-actions';
import NodeDashboard from './node-dashboard/NodeDashboard';

const { details, editYaml, events, pods } = navFactory;
const { editYaml, events, pods } = navFactory;

const pages = [
details(NodeDetails),
{
href: '',
name: 'Overview',
component: NodeDashboard,
},
{
href: 'details',
name: 'Details',
component: NodeDetails,
},
editYaml(),
pods(({ obj }) => (
<PodsPage showTitle={false} fieldSelector={`spec.nodeName=${obj.metadata.name}`} />
Expand Down
Expand Up @@ -28,11 +28,11 @@ const getDegradedStates = (node: NodeKind): Condition[] => {
.map(({ type }) => type as Condition);
};

const NodeStatus: React.FC<NodeStatusProps> = ({ node, showPopovers = false }) => {
const NodeStatus: React.FC<NodeStatusProps> = ({ node, showPopovers = false, className }) => {
const status = showPopovers ? getDegradedStates(node) : [];
return (
<>
<Status status={nodeStatus(node)} />
<Status status={nodeStatus(node)} className={className} />
<SecondaryStatus status={getNodeSecondaryStatus(node)} />
{status.length > 0 &&
status.map((item) => (
Expand All @@ -53,6 +53,7 @@ const NodeStatus: React.FC<NodeStatusProps> = ({ node, showPopovers = false }) =
type NodeStatusProps = {
node: NodeKind;
showPopovers?: boolean;
className?: string;
};

export default NodeStatus;
@@ -0,0 +1,61 @@
import * as React from 'react';
import DashboardCard from '@console/shared/src/components/dashboard/dashboard-card/DashboardCard';
import DashboardCardHeader from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardHeader';
import DashboardCardTitle from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardTitle';
import DashboardCardLink from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardLink';
import DashboardCardBody from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardBody';
import ActivityBody, {
RecentEventsBody,
OngoingActivityBody,
} from '@console/shared/src/components/dashboard/activity-card/ActivityBody';
import { EventModel, NodeModel } from '@console/internal/models';
import { EventKind, NodeKind } from '@console/internal/module/k8s';
import { resourcePathFromModel } from '@console/internal/components/utils';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';

import { NodeDashboardContext } from './NodeDashboardContext';

const eventsResource = {
isList: true,
kind: EventModel.kind,
};

const nodeEventsFilter = (event: EventKind, uid: string, kind: string, name: string): boolean => {
const { uid: objectUID, kind: objectKind, name: objectName } = event?.involvedObject || {};
return objectUID === uid && objectKind === kind && objectName === name;
};

const RecentEvent: React.FC<RecentEventProps> = ({ node }) => {
const [data, loaded, loadError] = useK8sWatchResource<EventKind[]>(eventsResource);
const { uid, name } = node.metadata;
const eventsFilter = React.useCallback(
(event) => nodeEventsFilter(event, uid, NodeModel.kind, name),
[uid, name],
);
return <RecentEventsBody events={{ data, loaded, loadError }} filter={eventsFilter} />;
};

const ActivityCard: React.FC = () => {
const { obj } = React.useContext(NodeDashboardContext);
const eventsLink = `${resourcePathFromModel(NodeModel, obj.metadata.name)}/events`;
return (
<DashboardCard gradient data-test-id="activity-card">
<DashboardCardHeader>
<DashboardCardTitle>Activity</DashboardCardTitle>
<DashboardCardLink to={eventsLink}>View events</DashboardCardLink>
</DashboardCardHeader>
<DashboardCardBody>
<ActivityBody className="co-project-dashboard__activity-body">
<OngoingActivityBody loaded />
<RecentEvent node={obj} />
</ActivityBody>
</DashboardCardBody>
</DashboardCard>
);
};

type RecentEventProps = {
node: NodeKind;
};

export default ActivityCard;
@@ -0,0 +1,51 @@
import * as React from 'react';
import DashboardCard from '@console/shared/src/components/dashboard/dashboard-card/DashboardCard';
import DashboardCardHeader from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardHeader';
import DashboardCardTitle from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardTitle';
import DashboardCardBody from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardBody';
import DetailsBody from '@console/shared/src/components/dashboard/details-card/DetailsBody';
import DetailItem from '@console/shared/src/components/dashboard/details-card/DetailItem';
import DashboardCardLink from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardLink';
import { getNodeAddresses } from '@console/shared/src/selectors/node';
import { resourcePathFromModel } from '@console/internal/components/utils';
import { NodeModel } from '@console/internal/models';

import NodeIPList from '../NodeIPList';
import NodeRoles from '../NodeRoles';
import { NodeDashboardContext } from './NodeDashboardContext';

const DetailsCard: React.FC = () => {
const { obj } = React.useContext(NodeDashboardContext);
const detailsLink = `${resourcePathFromModel(NodeModel, obj.metadata.name)}/details`;
const instanceType = obj.metadata.labels?.['beta.kubernetes.io/instance-type'];
const zone = obj.metadata.labels?.['topology.kubernetes.io/region'];
return (
<DashboardCard data-test-id="details-card">
<DashboardCardHeader>
<DashboardCardTitle>Details</DashboardCardTitle>
<DashboardCardLink to={detailsLink}>View all</DashboardCardLink>
</DashboardCardHeader>
<DashboardCardBody>
<DetailsBody>
<DetailItem isLoading={!obj} title="Node Name">
{obj.metadata.name}
</DetailItem>
<DetailItem isLoading={!obj} title="Role">
<NodeRoles node={obj} />
</DetailItem>
<DetailItem isLoading={!obj} title="Instance Type" error={!instanceType}>
{instanceType}
</DetailItem>
<DetailItem isLoading={!obj} title="Zone" error={!zone}>
{zone}
</DetailItem>
<DetailItem isLoading={!obj} title="Node Addresses">
<NodeIPList ips={getNodeAddresses(obj)} expand />
</DetailItem>
</DetailsBody>
</DashboardCardBody>
</DashboardCard>
);
};

export default DetailsCard;
@@ -0,0 +1,74 @@
import * as React from 'react';
import DashboardCard from '@console/shared/src/components/dashboard/dashboard-card/DashboardCard';
import DashboardCardHeader from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardHeader';
import DashboardCardTitle from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardTitle';
import DashboardCardBody from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardBody';
import InventoryItem, {
ResourceInventoryItem,
StatusGroupMapper,
} from '@console/shared/src/components/dashboard/inventory-card/InventoryItem';
import { getPodStatusGroups } from '@console/shared/src/components/dashboard/inventory-card/utils';
import { referenceForModel, K8sResourceCommon, K8sKind } from '@console/internal/module/k8s';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { PodModel, NodeModel } from '@console/internal/models';
import { resourcePathFromModel } from '@console/internal/components/utils';

import { NodeDashboardContext } from './NodeDashboardContext';

const NodeInventoryItem: React.FC<NodeInventoryItemProps> = ({ nodeName, model, mapper }) => {
const resource = React.useMemo(
() => ({
kind: model.crd ? referenceForModel(model) : model.kind,
fieldSelector: `spec.nodeName=${nodeName}`,
isList: true,
}),
[nodeName, model],
);
const [data, loaded, loadError] = useK8sWatchResource<K8sResourceCommon[]>(resource);
const basePath = `${resourcePathFromModel(NodeModel, nodeName)}/pods`;

return (
<ResourceInventoryItem
kind={model}
isLoading={!loaded}
error={!!loadError}
resources={data}
mapper={mapper}
basePath={basePath}
/>
);
};

const InventoryCard: React.FC = () => {
const { obj } = React.useContext(NodeDashboardContext);

return (
<DashboardCard data-test-id="inventory-card">
<DashboardCardHeader>
<DashboardCardTitle>Inventory</DashboardCardTitle>
</DashboardCardHeader>
<DashboardCardBody>
<NodeInventoryItem
nodeName={obj.metadata.name}
model={PodModel}
mapper={getPodStatusGroups}
/>
<InventoryItem
isLoading={!obj}
title="Image"
titlePlural="Images"
count={obj.status?.images?.length}
error={!obj.status?.images}
/>
</DashboardCardBody>
</DashboardCard>
);
};

type NodeInventoryItemProps = {
nodeName: string;
model: K8sKind;
mapper?: StatusGroupMapper;
};

export default InventoryCard;