Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions frontend/src/components/node/Charts.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2025 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { parseDiskSpace } from '../../lib/units';
import { TestContext } from '../../test';
import { StorageBarChart } from './Charts';

vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key.replace(/^.*\|/, ''),
i18n: { language: 'en' },
}),
}));

const capturedProps: any[] = [];
vi.mock('../common/Chart', () => ({
PercentageBar: (props: any) => {
capturedProps.push(props);
return <div data-testid="percentage-bar" />;
},
}));

function makeNode(capacity: string, allocatable: string): any {
return {
status: {
capacity: { 'ephemeral-storage': capacity },
allocatable: { 'ephemeral-storage': allocatable },
},
};
}

describe('StorageBarChart', () => {
it('passes correct allocatable value and capacity total to PercentageBar', () => {
capturedProps.length = 0;
const node = makeNode('100Gi', '90Gi');
render(
<TestContext>
<StorageBarChart node={node} />
</TestContext>
);
expect(capturedProps).toHaveLength(1);
expect(capturedProps[0].data[0].value).toBe(parseDiskSpace('90Gi'));
expect(capturedProps[0].total).toBe(parseDiskSpace('100Gi'));
});

it('skips rendering PercentageBar when capacity is zero', () => {
capturedProps.length = 0;
const node = makeNode('0', '0');
render(
<TestContext>
<StorageBarChart node={node} />
</TestContext>
);
expect(capturedProps).toHaveLength(0);
});

it('falls back to camelCase ephemeralStorage key', () => {
capturedProps.length = 0;
const node = {
status: {
capacity: { ephemeralStorage: '50Gi' },
allocatable: { ephemeralStorage: '45Gi' },
},
} as any;
render(
<TestContext>
<StorageBarChart node={node} />
</TestContext>
);
expect(capturedProps).toHaveLength(1);
expect(capturedProps[0].data[0].value).toBe(parseDiskSpace('45Gi'));
expect(capturedProps[0].total).toBe(parseDiskSpace('50Gi'));
});

it('tooltip shows allocatable, capacity and percentage', () => {
capturedProps.length = 0;
const node = makeNode('100Gi', '90Gi');
render(
<TestContext>
<StorageBarChart node={node} />
</TestContext>
);
expect(capturedProps).toHaveLength(1);
const tooltip = render(<TestContext>{capturedProps[0].tooltipFunc()}</TestContext>);
const text = tooltip.container.textContent ?? '';
expect(text).toContain('Allocatable');
expect(text).toContain('Capacity');
expect(text).toContain('%');
});
});
49 changes: 48 additions & 1 deletion frontend/src/components/node/Charts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { KubeMetrics } from '../../lib/k8s/cluster';
import Node from '../../lib/k8s/node';
import { parseDiskSpace, unparseRam } from '../../lib/units';
import { getPercentStr, getResourceMetrics, getResourceStr } from '../../lib/util';
import { PercentageBar } from '../common/Chart';
import { TooltipIcon } from '../common/Tooltip';
Expand All @@ -41,7 +42,7 @@ export function UsageBarChart(props: UsageBarChartProps) {

const data = [
{
name: t('used'),
name: t('translation|used'),
value: used,
},
];
Expand All @@ -64,3 +65,49 @@ export function UsageBarChart(props: UsageBarChartProps) {
<PercentageBar data={data} total={capacity} tooltipFunc={tooltipFunc} />
);
}

interface StorageBarChartProps {
node: Node;
}

export function StorageBarChart(props: StorageBarChartProps) {
const { node } = props;
const { t } = useTranslation(['glossary', 'translation']);

const capacityRaw =
(node.status?.capacity as any)?.['ephemeral-storage'] ??
(node.status?.capacity as any)?.ephemeralStorage ??
'0';
const allocatableRaw =
(node.status?.allocatable as any)?.['ephemeral-storage'] ??
(node.status?.allocatable as any)?.ephemeralStorage ??
'0';
const capacity = parseDiskSpace(capacityRaw);
const allocatable = parseDiskSpace(allocatableRaw);

if (capacity === 0) {
return <></>;
}

const capacityInfo = unparseRam(capacity);
const allocatableInfo = unparseRam(allocatable);

const data = [
{
name: t('translation|Allocatable'),
value: allocatable,
},
];

function tooltipFunc() {
return (
<Typography>
{t('translation|Allocatable')}: {allocatableInfo.value}
{allocatableInfo.unit} / {t('glossary|Capacity')}: {capacityInfo.value}
{capacityInfo.unit} ({getPercentStr(allocatable, capacity)})
</Typography>
Comment thread
aabhinavvvvvvv marked this conversation as resolved.
);
}

return <PercentageBar data={data} total={capacity} tooltipFunc={tooltipFunc} />;
}
16 changes: 15 additions & 1 deletion frontend/src/components/node/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

import { useTranslation } from 'react-i18next';
import Node from '../../lib/k8s/node';
import { parseDiskSpace } from '../../lib/units';
import { getResourceMetrics } from '../../lib/util';
import { HoverInfoLabel } from '../common/Label';
import ResourceListView from '../common/Resource/ResourceListView';
import { UsageBarChart } from './Charts';
import { StorageBarChart, UsageBarChart } from './Charts';
import { NodeReadyLabel } from './Details';
import { formatTaint, NodeTaintsLabel } from './utils';

Expand Down Expand Up @@ -74,6 +75,19 @@ export default function NodeList() {
/>
),
},
{
id: 'disk',
label: t('translation|Disk'),
disableFiltering: true,
getValue: node => {
const raw =
(node.status?.allocatable as any)?.['ephemeral-storage'] ??
(node.status?.allocatable as any)?.ephemeralStorage ??
'0';
return parseDiskSpace(raw);
},
render: node => <StorageBarChart node={node} />,
Comment thread
aabhinavvvvvvv marked this conversation as resolved.
},
{
id: 'ready',
label: t('translation|Ready'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
</div>
</div>
<table
class="MuiTable-root css-1drzxj3-MuiTable-root"
class="MuiTable-root css-zr5bi5-MuiTable-root"
>
<thead
class="MuiTableHead-root css-1tmrira-MuiTableHead-root"
Expand Down Expand Up @@ -388,6 +388,61 @@
/>
</div>
</th>
<th
aria-sort="none"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignLeft MuiTableCell-sizeMedium css-1s50ib7-MuiTableCell-root"
colspan="1"
data-can-sort="true"
data-index="-1"
scope="col"
>
<div
class="Mui-TableHeadCell-Content MuiBox-root css-1w86f15"
>
<div
class="Mui-TableHeadCell-Content-Labels MuiBox-root css-68rqdf"
>
<div
class="Mui-TableHeadCell-Content-Wrapper MuiBox-root css-lapokc"
>
Disk
</div>
<span
aria-label="Sort by Disk descending"
class="MuiBadge-root css-1c32n2y-MuiBadge-root"
data-mui-internal-clone-element="true"
>
<span
aria-label="Sort by Disk descending"
class="MuiButtonBase-root MuiTableSortLabel-root Mui-active css-542clt-MuiButtonBase-root-MuiTableSortLabel-root"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc css-1vweko9-MuiSvgIcon-root-MuiTableSortLabel-icon"
data-testid="SyncAltIcon"
focusable="false"
style="transform: rotate(-90deg) scaleX(0.9) translateX(-1px);"
viewBox="0 0 24 24"
>
<path
d="m18 12 4-4-4-4v3H3v2h15zM6 12l-4 4 4 4v-3h15v-2H6z"
/>
</svg>
</span>
<span
class="MuiBadge-badge MuiBadge-standard MuiBadge-invisible MuiBadge-anchorOriginTopRight MuiBadge-anchorOriginTopRightCircular MuiBadge-overlapCircular css-dniquu-MuiBadge-badge"
>
0
</span>
</span>
</div>
<div
class="Mui-TableHeadCell-Content-Actions MuiBox-root css-epvm6"
/>
</div>
</th>
<th
aria-sort="none"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignLeft MuiTableCell-sizeMedium css-fn1srg-MuiTableCell-root"
Expand Down Expand Up @@ -862,6 +917,9 @@
style="width: 95%; height: 20px; min-width: 0;"
/>
</td>
<td
class="MuiTableCell-root MuiTableCell-alignLeft MuiTableCell-sizeMedium css-zfcpaf-MuiTableCell-root"
/>
<td
class="MuiTableCell-root MuiTableCell-alignLeft MuiTableCell-sizeMedium css-x4ro7m-MuiTableCell-root"
>
Expand Down Expand Up @@ -979,6 +1037,9 @@
style="width: 95%; height: 20px; min-width: 0;"
/>
</td>
<td
class="MuiTableCell-root MuiTableCell-alignLeft MuiTableCell-sizeMedium css-zfcpaf-MuiTableCell-root"
/>
<td
class="MuiTableCell-root MuiTableCell-alignLeft MuiTableCell-sizeMedium css-x4ro7m-MuiTableCell-root"
>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/i18n/locales/de/glossary.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"except: {{ cidrExceptions }}": "außer: {{ cidrExceptions }}",
"Pod Selector": "Pod-Auswahl",
"Network Policies": "Netzwerkrichtlinien",
"Capacity": "Kapazität",
"Uncordoning node {{name}}…": "Aufhebung der Planungssperre für Node {{name}}…",
"Cordoning node {{name}}…": "Node {{name}} wird für die Planung gesperrt…",
"Uncordoned node {{name}}.": "Planungssperre für Node {{name}} wurde aufgehoben.",
Expand Down Expand Up @@ -234,7 +235,6 @@
"Git Tree State": "Git-Tree-Status",
"Go Version": "Go Version",
"Platform": "Plattform",
"Capacity": "Kapazität",
"Access Modes": "Zugriffsmodi",
"Volume Mode": "Speichervolume-Modus",
"Storage Class": "Speicher-Klasse",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -656,10 +656,12 @@
"Create Namespace": "",
"To": "An",
"used": "benutzt",
"Allocatable": "",
"Scheduling Disabled": "Planung deaktiviert",
"Scheduling Enabled": "Planung aktiviert",
"Taints": "",
"Not ready yet!": "Noch nicht fertig!",
"Disk": "",
"Internal IP": "Interne IP",
"None": "",
"Software": "Software",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/i18n/locales/en/glossary.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"except: {{ cidrExceptions }}": "except: {{ cidrExceptions }}",
"Pod Selector": "Pod Selector",
"Network Policies": "Network Policies",
"Capacity": "Capacity",
"Uncordoning node {{name}}…": "Uncordoning node {{name}}…",
"Cordoning node {{name}}…": "Cordoning node {{name}}…",
"Uncordoned node {{name}}.": "Uncordoned node {{name}}.",
Expand Down Expand Up @@ -234,7 +235,6 @@
"Git Tree State": "Git Tree State",
"Go Version": "Go Version",
"Platform": "Platform",
"Capacity": "Capacity",
"Access Modes": "Access Modes",
"Volume Mode": "Volume Mode",
"Storage Class": "Storage Class",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -656,10 +656,12 @@
"Create Namespace": "Create Namespace",
"To": "To",
"used": "used",
"Allocatable": "Allocatable",
"Scheduling Disabled": "Scheduling Disabled",
"Scheduling Enabled": "Scheduling Enabled",
"Taints": "Taints",
"Not ready yet!": "Not ready yet!",
"Disk": "Disk",
"Internal IP": "Internal IP",
"None": "None",
"Software": "Software",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/i18n/locales/es/glossary.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"except: {{ cidrExceptions }}": "excepto: {{ cidrExceptions }}",
"Pod Selector": "Selector de Pod",
"Network Policies": "Políticas de Red",
"Capacity": "Capacidad",
"Uncordoning node {{name}}…": "Desbloqueando el nodo {{name}}…",
"Cordoning node {{name}}…": "Bloqueando nodo {{name}}…",
"Uncordoned node {{name}}.": "Nodo {{name}} desbloqueado.",
Expand Down Expand Up @@ -234,7 +235,6 @@
"Git Tree State": "Estado del Árbol de Git",
"Go Version": "Versión de Go",
"Platform": "Plataforma",
"Capacity": "Capacidad",
"Access Modes": "Modos de Acceso",
"Volume Mode": "Modo de Volume",
"Storage Class": "Storage Class",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -661,10 +661,12 @@
"Create Namespace": "Crear Espacio de Nombre",
"To": "A",
"used": "usado",
"Allocatable": "",
Comment thread
aabhinavvvvvvv marked this conversation as resolved.
"Scheduling Disabled": "Agendamiento deshabilitado",
"Scheduling Enabled": "Agendamiento habilitado",
"Taints": "Taints",
"Not ready yet!": "¡No listo todavía!",
"Disk": "",
"Internal IP": "IP interna",
"None": "Ninguno",
"Software": "Software",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/i18n/locales/fr/glossary.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"except: {{ cidrExceptions }}": "sauf: {{ cidrExceptions }}",
"Pod Selector": "Sélecteur de pod",
"Network Policies": "Politiques de réseau",
"Capacity": "Capacité",
"Uncordoning node {{name}}…": "Débloquant le nœud {{name}}…",
"Cordoning node {{name}}…": "Bloquant le nœud {{name}}…",
"Uncordoned node {{name}}.": "Nœud {{name}} débloqué.",
Expand Down Expand Up @@ -234,7 +235,6 @@
"Git Tree State": "État de l'arbre Git",
"Go Version": "Version de Go",
"Platform": "Plateforme",
"Capacity": "Capacité",
"Access Modes": "Modes d'accès",
"Volume Mode": "Mode Volume",
"Storage Class": "Classe de stockage",
Expand Down
Loading
Loading