Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 280 additions & 49 deletions data/schema.graphql

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions packages/backend.ai-ui/src/components/ResourceStatistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ import { useTranslation } from 'react-i18next';

interface ResourceData {
cpu: {
using: { current: number; total?: number };
remaining: { current: number; total?: number };
used: { current: number; total?: number };
free: { current: number; total?: number };
metadata: { title: string; displayUnit: string };
} | null;
memory: {
using: { current: number; total?: number };
remaining: { current: number; total?: number };
used: { current: number; total?: number };
free: { current: number; total?: number };
metadata: { title: string; displayUnit: string };
} | null;
accelerators: Array<{
key: string;
using: { current: number; total?: number };
remaining: { current: number; total?: number };
used: { current: number; total?: number };
free: { current: number; total?: number };
metadata: { title: string; displayUnit: string };
}>;
}

interface ResourceStatisticsProps {
resourceData: ResourceData;
displayType: 'using' | 'remaining';
displayType: 'used' | 'free';
showProgress?: boolean;
precision?: number;
progressSteps?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const BAIBucketSelect = ({
edges {
node {
id
bucket
namespace
}
}
}
Expand Down Expand Up @@ -95,7 +95,7 @@ const BAIBucketSelect = ({
);

const selectedOptions = _.map(paginationData, (item) => ({
label: item.node.bucket,
label: item.node.namespace,
value: item.node.id,
}));

Expand Down
4 changes: 3 additions & 1 deletion react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import MainLayout from './components/MainLayout/MainLayout';
import WebUINavigate from './components/WebUINavigate';
import { useSuspendedBackendaiClient } from './hooks';
import { useBAISettingUserState } from './hooks/useBAISetting';
import AdminDashboardPage from './pages/AdminDashboardPage';
// High priority to import the component
import ComputeSessionListPage from './pages/ComputeSessionListPage';
import ModelStoreListPage from './pages/ModelStoreListPage';
Expand All @@ -35,6 +34,9 @@ const EndpointDetailPage = React.lazy(
);
const StartPage = React.lazy(() => import('./pages/StartPage'));
const DashboardPage = React.lazy(() => import('./pages/DashboardPage'));
const AdminDashboardPage = React.lazy(
() => import('./pages/AdminDashboardPage'),
);
const EnvironmentPage = React.lazy(() => import('./pages/EnvironmentPage'));
const MyEnvironmentPage = React.lazy(() => import('./pages/MyEnvironmentPage'));
const StorageHostSettingPage = React.lazy(
Expand Down
225 changes: 225 additions & 0 deletions react/src/components/AgentStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { useResourceSlotsDetails } from '../hooks/backendai';
import BAIFetchKeyButton from './BAIFetchKeyButton';
import { useControllableValue } from 'ahooks';
import { Segmented, Skeleton, theme, Typography } from 'antd';
import {
BAIFlex,
BAIBoardItemTitle,
ResourceStatistics,
convertToNumber,
processMemoryValue,
BAIFlexProps,
} from 'backend.ai-ui';
import _ from 'lodash';
import { useTransition, ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useRefetchableFragment } from 'react-relay';
import { AgentStatsFragment$key } from 'src/__generated__/AgentStatsFragment.graphql';

interface AgentStatsProps extends BAIFlexProps {
queryRef: AgentStatsFragment$key;
isRefetching?: boolean;
displayType?: 'used' | 'free';
onDisplayTypeChange?: (type: 'used' | 'free') => void;
extra?: ReactNode;
}

const AgentStats: React.FC<AgentStatsProps> = ({
queryRef,
isRefetching,
extra,
...props
}) => {
const { t } = useTranslation();
const { token } = theme.useToken();

const [isPendingRefetch, startRefetchTransition] = useTransition();

const [displayType, setDisplayType] = useControllableValue<
Exclude<AgentStatsProps['displayType'], undefined>
>(props, {
defaultValue: 'used',
trigger: 'onDisplayTypeChange',
defaultValuePropName: 'defaultDisplayType',
});

const [data, refetch] = useRefetchableFragment(
graphql`
fragment AgentStatsFragment on Query
@refetchable(queryName: "AgentStatsRefetchQuery") {
agentStats @since(version: "25.15.0") {
totalResource {
free
used
capacity
}
}
}
`,
queryRef,
);

const resourceSlotsDetails = useResourceSlotsDetails();

const agentStatsData = (() => {
const totalResource = data.agentStats.totalResource;
if (!totalResource) {
return { cpu: null, memory: null, accelerators: [] };
}

const free = totalResource.free as Record<string, number>;
const used = totalResource.used as Record<string, number>;
const capacity = totalResource.capacity as Record<string, number>;

const cpuSlot = resourceSlotsDetails?.resourceSlotsInRG?.['cpu'];
const memSlot = resourceSlotsDetails?.resourceSlotsInRG?.['mem'];

const cpuData = cpuSlot
? {
used: {
current: convertToNumber(used['cpu'] || 0),
total: convertToNumber(capacity['cpu'] || 0),
},
free: {
current: convertToNumber(free['cpu'] || 0),
total: convertToNumber(capacity['cpu'] || 0),
},
metadata: {
title: cpuSlot.human_readable_name,
displayUnit: cpuSlot.display_unit,
},
}
: null;

const memoryData = memSlot
? {
used: {
current: processMemoryValue(used['mem'] || 0, memSlot.display_unit),
total: processMemoryValue(
capacity['mem'] || 0,
memSlot.display_unit,
),
},
free: {
current: processMemoryValue(free['mem'] || 0, memSlot.display_unit),
total: processMemoryValue(
capacity['mem'] || 0,
memSlot.display_unit,
),
},
metadata: {
title: memSlot.human_readable_name,
displayUnit: memSlot.display_unit,
},
}
: null;

const accelerators = _.chain(resourceSlotsDetails?.resourceSlotsInRG)
.omit(['cpu', 'mem'])
.map((resourceSlot, key) => {
if (!resourceSlot) return null;

const freeValue = free[key] || 0;
const usedValue = used[key] || 0;
const capacityValue = capacity[key] || 0;

return {
key,
used: {
current: convertToNumber(usedValue),
total: convertToNumber(capacityValue),
},
free: {
current: convertToNumber(freeValue),
total: convertToNumber(capacityValue),
},
metadata: {
title: resourceSlot.human_readable_name,
displayUnit: resourceSlot.display_unit,
},
};
})
.compact()
.filter((item) => !!(item.used.current || item.used.total))
.value();

return { cpu: cpuData, memory: memoryData, accelerators };
})();

return (
<BAIFlex
direction="column"
align="stretch"
style={{
paddingInline: token.paddingXL,
paddingBottom: token.padding,
...props.style,
}}
{..._.omit(props, ['style'])}
>
<BAIBoardItemTitle
title={
<Typography.Text
style={{
fontSize: token.fontSizeHeading5,
fontWeight: token.fontWeightStrong,
}}
>
{t('agentStats.AgentStats')}
</Typography.Text>
}
tooltip={t('agentStats.AgentStatsDescription')}
extra={
<BAIFlex gap={'xs'} wrap="wrap">
<Segmented<Exclude<AgentStatsProps['displayType'], undefined>>
size="small"
options={[
{
label: t('dashboard.Used'),
value: 'used',
},
{
value: 'free',
label: t('dashboard.Free'),
},
]}
value={displayType}
onChange={(v) => setDisplayType(v)}
/>
<BAIFetchKeyButton
size="small"
loading={isPendingRefetch || isRefetching}
value=""
onChange={() => {
startRefetchTransition(() => {
refetch(
{},
{
fetchPolicy: 'network-only',
},
);
});
}}
type="text"
style={{
backgroundColor: 'transparent',
}}
/>
{extra}
</BAIFlex>
}
/>
{resourceSlotsDetails.isLoading ? (
<Skeleton active />
) : (
<ResourceStatistics
resourceData={agentStatsData}
displayType={displayType === 'used' ? 'used' : 'free'}
showProgress={true}
/>
)}
</BAIFlex>
);
};

export default AgentStats;
2 changes: 1 addition & 1 deletion react/src/components/ConfigurableResourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const ConfigurableResourceCard: React.FC<ConfigurableResourceCardProps> = ({
titleStyle: {
paddingLeft: 0,
},
..._.omit(props, ['style']),
..._.omit(props, ['style', 'title']),
};

switch (currentPanelType) {
Expand Down
5 changes: 3 additions & 2 deletions react/src/components/MainLayout/WebUISider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type MenuKeys =
| 'resource-policy'
| 'reservoir'
// superAdminMenu keys
| 'admin-dashboard'
| 'agent'
| 'settings'
| 'maintenance'
Expand Down Expand Up @@ -307,7 +308,7 @@ const WebUISider: React.FC<WebUISiderProps> = (props) => {
},
]);

const superAdminMenu: MenuProps['items'] = [
const superAdminMenu: MenuProps['items'] = filterOutEmpty([
{
label: <WebUILink to="/agent">{t('webui.menu.Resources')}</WebUILink>,
icon: <HddOutlined style={{ color: token.colorInfo }} />,
Expand All @@ -334,7 +335,7 @@ const WebUISider: React.FC<WebUISiderProps> = (props) => {
icon: <InfoCircleOutlined style={{ color: token.colorInfo }} />,
key: 'information',
},
];
]);

const pluginMap: Record<string, MenuProps['items']> = {
'menuitem-user': generalMenu,
Expand Down
Loading
Loading