Skip to content

Commit 55fd733

Browse files
committed
refactor(kubeflow): extract notebook and overview presentation components
Signed-off-by: alokdangre <alokdangre@gmail.com>
1 parent feaebab commit 55fd733

31 files changed

+851
-866
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {
2+
NameValueTable,
3+
SectionBox,
4+
StatusLabel,
5+
} from '@kinvolk/headlamp-plugin/lib/components/common';
6+
import Box from '@mui/material/Box';
7+
import Typography from '@mui/material/Typography';
8+
import React from 'react';
9+
import { NotebookStatusBadge } from './NotebookStatusBadge';
10+
import { NotebookTypeBadge } from './NotebookTypeBadge';
11+
12+
interface NotebookDetailContentProps {
13+
notebook: any;
14+
}
15+
16+
export function NotebookDetailContent({ notebook }: NotebookDetailContentProps) {
17+
const containers = notebook?.spec?.template?.spec?.containers || [];
18+
const container = containers[0] || {};
19+
const requests = container?.resources?.requests || {};
20+
const limits = container?.resources?.limits || {};
21+
const gpuCount =
22+
limits['nvidia.com/gpu'] ||
23+
limits['amd.com/gpu'] ||
24+
requests['nvidia.com/gpu'] ||
25+
requests['amd.com/gpu'];
26+
const volumes = notebook?.spec?.template?.spec?.volumes || [];
27+
const volumeMounts = container?.volumeMounts || [];
28+
const envVars = container?.env || [];
29+
const conditions = notebook?.status?.conditions || [];
30+
31+
return (
32+
<Box sx={{ padding: '24px 16px' }}>
33+
<Typography variant="h5" sx={{ fontWeight: 700, mb: 3 }}>
34+
{notebook?.metadata?.name}
35+
</Typography>
36+
37+
<NameValueTable
38+
rows={[
39+
{ name: 'Namespace', value: notebook?.metadata?.namespace || '-' },
40+
{
41+
name: 'Status',
42+
value: <NotebookStatusBadge jsonData={notebook} />,
43+
},
44+
{
45+
name: 'Type',
46+
value: <NotebookTypeBadge image={container?.image || ''} />,
47+
},
48+
{ name: 'Ready Replicas', value: String(notebook?.status?.readyReplicas ?? 0) },
49+
{ name: 'Container Image', value: container?.image || '-' },
50+
]}
51+
/>
52+
53+
<SectionBox title="Resource Requirements">
54+
<NameValueTable
55+
rows={[
56+
{ name: 'CPU Request', value: requests.cpu || 'Not set' },
57+
{ name: 'CPU Limit', value: limits.cpu || 'Not set' },
58+
{ name: 'Memory Request', value: requests.memory || 'Not set' },
59+
{ name: 'Memory Limit', value: limits.memory || 'Not set' },
60+
{ name: 'GPU', value: gpuCount || 'None' },
61+
]}
62+
/>
63+
</SectionBox>
64+
65+
{volumeMounts.length > 0 && (
66+
<SectionBox title="Volumes & Mounts">
67+
<NameValueTable
68+
rows={volumeMounts.map((mount: any) => {
69+
const volume = volumes.find((vol: any) => vol.name === mount.name);
70+
let volumeType = 'Unknown';
71+
72+
if (volume?.persistentVolumeClaim) {
73+
volumeType = `PVC: ${volume.persistentVolumeClaim.claimName}`;
74+
} else if (volume?.emptyDir !== undefined) {
75+
volumeType = 'EmptyDir';
76+
} else if (volume?.configMap) {
77+
volumeType = `ConfigMap: ${volume.configMap.name}`;
78+
} else if (volume?.secret) {
79+
volumeType = `Secret: ${volume.secret.secretName}`;
80+
}
81+
82+
return {
83+
name: `${mount.name}${mount.mountPath}`,
84+
value: `${volumeType}${mount.readOnly ? ' (ReadOnly)' : ''}`,
85+
};
86+
})}
87+
/>
88+
</SectionBox>
89+
)}
90+
91+
{envVars.length > 0 && (
92+
<SectionBox title="Environment Variables">
93+
<NameValueTable
94+
rows={envVars.map((env: any) => ({
95+
name: env.name,
96+
value: env.value
97+
? env.value
98+
: env.valueFrom?.secretKeyRef
99+
? `Secret ${env.valueFrom.secretKeyRef.name}/${env.valueFrom.secretKeyRef.key}`
100+
: env.valueFrom?.configMapKeyRef
101+
? `ConfigMap ${env.valueFrom.configMapKeyRef.name}/${env.valueFrom.configMapKeyRef.key}`
102+
: '-',
103+
}))}
104+
/>
105+
</SectionBox>
106+
)}
107+
108+
{conditions.length > 0 && (
109+
<SectionBox title="Conditions">
110+
<NameValueTable
111+
rows={conditions.map((cond: any) => ({
112+
name: cond.type,
113+
value: (
114+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
115+
<StatusLabel
116+
status={
117+
cond.status === 'True'
118+
? 'success'
119+
: cond.type === 'Failed' || cond.type?.includes('Error')
120+
? 'error'
121+
: ''
122+
}
123+
>
124+
{cond.status}
125+
</StatusLabel>
126+
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
127+
{cond.reason ? `${cond.reason} - ` : ''}
128+
{cond.message || ''}
129+
</Typography>
130+
</Box>
131+
),
132+
}))}
133+
/>
134+
</SectionBox>
135+
)}
136+
</Box>
137+
);
138+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Icon } from '@iconify/react';
2+
import { LightTooltip, StatusLabel } from '@kinvolk/headlamp-plugin/lib/components/common';
3+
import Box from '@mui/material/Box';
4+
import React from 'react';
5+
import { getNotebookStatus } from './notebookUtils';
6+
7+
interface NotebookStatusBadgeProps {
8+
jsonData: any;
9+
}
10+
11+
export function NotebookStatusBadge({ jsonData }: NotebookStatusBadgeProps) {
12+
const { label, status, icon, reason } = getNotebookStatus(jsonData);
13+
const statusEl = (
14+
<StatusLabel status={status}>
15+
{label}
16+
<Icon aria-hidden icon={icon} width="1.2rem" height="1.2rem" />
17+
</StatusLabel>
18+
);
19+
20+
if (reason) {
21+
return (
22+
<LightTooltip title={reason} interactive>
23+
<Box display="inline">{statusEl}</Box>
24+
</LightTooltip>
25+
);
26+
}
27+
28+
return statusEl;
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Icon } from '@iconify/react';
2+
import Box from '@mui/material/Box';
3+
import React from 'react';
4+
import { getNotebookType } from './notebookUtils';
5+
6+
interface NotebookTypeBadgeProps {
7+
image: string;
8+
}
9+
10+
export function NotebookTypeBadge({ image }: NotebookTypeBadgeProps) {
11+
const notebookType = getNotebookType(image);
12+
13+
return (
14+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
15+
<Icon icon={notebookType.icon} width="18" height="18" style={{ color: notebookType.color }} />
16+
{notebookType.label}
17+
</Box>
18+
);
19+
}

kubeflow/src/components/notebooks/NotebooksDetail.stories.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import {
55
notebookRunning,
66
notebookTerminated,
77
} from './__fixtures__/mockData';
8-
import { NotebookDetailPure } from './PureComponents';
8+
import { NotebookDetailContent } from './StoryComponents';
99

10-
const meta: Meta<typeof NotebookDetailPure> = {
10+
const meta: Meta<typeof NotebookDetailContent> = {
1111
title: 'Kubeflow/Notebooks/NotebookDetail',
12-
component: NotebookDetailPure,
12+
component: NotebookDetailContent,
1313
parameters: {
1414
docs: {
1515
description: {
@@ -21,7 +21,7 @@ const meta: Meta<typeof NotebookDetailPure> = {
2121
};
2222
export default meta;
2323

24-
type Story = StoryObj<typeof NotebookDetailPure>;
24+
type Story = StoryObj<typeof NotebookDetailContent>;
2525

2626
/**
2727
* A healthy, running Jupyter notebook with volumes, environment variables,

kubeflow/src/components/notebooks/NotebooksDetail.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function NotebooksDetail(props: { namespace?: string; name?: string }) {
1818
const { namespace = params.namespace, name = params.name } = props;
1919

2020
return (
21-
<SectionPage title="Notebook Detail" apiPath="/apis/kubeflow.org/v1/notebooks">
21+
<SectionPage title="Notebook Detail" apiPath="/apis/kubeflow.org/v1">
2222
<DetailsGrid
2323
resourceType={NotebookClass}
2424
name={name}
@@ -64,7 +64,7 @@ export function NotebooksDetail(props: { namespace?: string; name?: string }) {
6464
const statusEl = (
6565
<StatusLabel status={status}>
6666
{label}
67-
<Icon aria-label="hidden" icon={icon} width="1.2rem" height="1.2rem" />
67+
<Icon aria-hidden icon={icon} width="1.2rem" height="1.2rem" />
6868
</StatusLabel>
6969
);
7070
if (reason) {
@@ -162,7 +162,7 @@ export function NotebooksDetail(props: { namespace?: string; name?: string }) {
162162
const volumes = item?.jsonData?.spec?.template?.spec?.volumes || [];
163163
const volumeMounts =
164164
item?.jsonData?.spec?.template?.spec?.containers?.[0]?.volumeMounts || [];
165-
if (volumes.length === 0 && volumeMounts.length === 0) return null;
165+
if (volumeMounts.length === 0) return null;
166166
return (
167167
<SectionBox title="Volumes & Mounts">
168168
<NameValueTable
@@ -246,7 +246,7 @@ export function NotebooksDetail(props: { namespace?: string; name?: string }) {
246246
status={
247247
cond.status === 'True'
248248
? 'success'
249-
: cond.type === 'Failed' || cond.type.includes('Error')
249+
: cond.type === 'Failed' || cond.type?.includes('Error')
250250
? 'error'
251251
: ''
252252
}

kubeflow/src/components/notebooks/NotebooksList.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
notebookRunning,
66
notebookTerminated,
77
} from './__fixtures__/mockData';
8-
import { NotebookStatusBadge, NotebookTypeBadge } from './PureComponents';
8+
import { NotebookStatusBadge, NotebookTypeBadge } from './StoryComponents';
99

1010
// ─── NotebookStatusBadge ──────────────────────────────────────────────────────
1111

kubeflow/src/components/notebooks/NotebooksList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getNotebookStatus, getNotebookType } from './notebookUtils';
1212

1313
export function NotebooksList() {
1414
return (
15-
<SectionPage title="Notebook Servers" apiPath="/apis/kubeflow.org/v1/notebooks">
15+
<SectionPage title="Notebook Servers" apiPath="/apis/kubeflow.org/v1">
1616
<ResourceListView
1717
title="Notebook Servers"
1818
resourceClass={NotebookClass}
@@ -158,7 +158,7 @@ export function NotebooksList() {
158158
const statusEl = (
159159
<StatusLabel status={status}>
160160
{label}
161-
<Icon aria-label="hidden" icon={icon} width="1.2rem" height="1.2rem" />
161+
<Icon aria-hidden icon={icon} width="1.2rem" height="1.2rem" />
162162
</StatusLabel>
163163
);
164164
if (reason) {

kubeflow/src/components/notebooks/NotebooksOverview.stories.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Meta, StoryObj } from '@storybook/react';
22
import { allNotebooks, allPodDefaults, allProfiles } from './__fixtures__/mockData';
3-
import { NotebooksOverviewPure } from './PureComponents';
3+
import { NotebooksOverviewContent } from './StoryComponents';
44

5-
const meta: Meta<typeof NotebooksOverviewPure> = {
5+
const meta: Meta<typeof NotebooksOverviewContent> = {
66
title: 'Kubeflow/Notebooks/Overview',
7-
component: NotebooksOverviewPure,
7+
component: NotebooksOverviewContent,
88
parameters: {
99
docs: {
1010
description: {
@@ -16,7 +16,7 @@ const meta: Meta<typeof NotebooksOverviewPure> = {
1616
};
1717
export default meta;
1818

19-
type Story = StoryObj<typeof NotebooksOverviewPure>;
19+
type Story = StoryObj<typeof NotebooksOverviewContent>;
2020

2121
/**
2222
* Full dashboard with all resource types populated.

kubeflow/src/components/notebooks/NotebooksOverview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function NotebooksOverview() {
133133
];
134134

135135
return (
136-
<SectionPage title="Notebooks" apiPath="/apis/kubeflow.org/v1/notebooks">
136+
<SectionPage title="Notebooks" apiPath="/apis/kubeflow.org/v1">
137137
<Box sx={{ padding: '24px 16px', pt: '32px' }}>
138138
<Typography variant="h1" sx={{ fontSize: '1.87rem', fontWeight: 700, mb: 1 }}>
139139
Notebooks Dashboard
@@ -234,7 +234,7 @@ export function NotebooksOverview() {
234234
const statusEl = (
235235
<StatusLabel status={status}>
236236
{label}
237-
<Icon aria-label="hidden" icon={icon} width="1.2rem" height="1.2rem" />
237+
<Icon aria-hidden icon={icon} width="1.2rem" height="1.2rem" />
238238
</StatusLabel>
239239
);
240240
if (reason) {

0 commit comments

Comments
 (0)