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
4 changes: 3 additions & 1 deletion packages/backend.ai-ui/src/components/BAIBoardItemTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export interface BAIBoardItemTitleProps {
style?: React.CSSProperties;
}

const Z_INDEX_IN_BAI_BOARD_ITEM_TITLE = 5;

const BAIBoardItemTitle: React.FC<BAIBoardItemTitleProps> = ({
title,
tooltip,
Expand All @@ -28,7 +30,7 @@ const BAIBoardItemTitle: React.FC<BAIBoardItemTitleProps> = ({
position: 'sticky',
top: 0,
backgroundColor: token.colorBgContainer,
zIndex: 1,
zIndex: Z_INDEX_IN_BAI_BOARD_ITEM_TITLE,
...style,
}}
gap="xs"
Expand Down
14 changes: 14 additions & 0 deletions react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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 Down Expand Up @@ -493,6 +494,19 @@ const router = createBrowserRouter([
handle: { labelKey: 'webui.menu.Settings&Logs' },
Component: UserSettingsPage,
},
{
path: '/admin-dashboard',
handle: { labelKey: 'webui.menu.AdminDashboard' },
Component: () => {
return (
<BAIErrorBoundary>
<Suspense fallback={<Skeleton active />}>
<AdminDashboardPage />
</Suspense>
</BAIErrorBoundary>
);
},
},
{
path: '/credential',
handle: { labelKey: 'webui.menu.UserCredentials&Policies' },
Expand Down
84 changes: 84 additions & 0 deletions react/src/components/ActiveAgents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import AgentList from './AgentList';
import BAIFetchKeyButton from './BAIFetchKeyButton';
import { theme } from 'antd';
import { BAIFlex, BAIBoardItemTitle } from 'backend.ai-ui';
import { useTransition } from 'react';
import { useTranslation } from 'react-i18next';

interface ActiveAgentsProps {
fetchKey?: string;
onChangeFetchKey?: (key: string) => void;
}

// TODO: Refactor this component with agent_nodes.
// ref: https://lablup.atlassian.net/browse/FR-1533
const ActiveAgents: React.FC<ActiveAgentsProps> = ({
fetchKey,
onChangeFetchKey,
}) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const [isPendingRefetch, startRefetchTransition] = useTransition();

return (
<BAIFlex
direction="column"
align="stretch"
style={{
paddingLeft: token.paddingXL,
paddingRight: token.paddingXL,
height: '100%',
}}
>
<BAIBoardItemTitle
title={t('activeAgent.ActiveAgents')}
tooltip={t('activeAgent.ActiveAgentsTooltip', {
count: 5,
})}
extra={
<BAIFetchKeyButton
size="small"
loading={isPendingRefetch}
value=""
onChange={(newFetchKey) => {
startRefetchTransition(() => {
onChangeFetchKey?.(newFetchKey);
});
}}
type="text"
style={{
backgroundColor: 'transparent',
}}
/>
}
/>

{/* Scrollable Content Section */}
<BAIFlex
direction="column"
align="stretch"
style={{
flex: 1,
overflowY: 'auto',
overflowX: 'hidden',
}}
>
<AgentList
fetchKey={fetchKey}
onChangeFetchKey={onChangeFetchKey}
headerProps={{
style: { display: 'none' },
}}
tableProps={{
pagination: {
pageSize: 3,
showSizeChanger: false,
},
}}
/>
</BAIFlex>
</BAIFlex>
);
};

export default ActiveAgents;
31 changes: 21 additions & 10 deletions react/src/components/AgentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import {
convertUnitValue,
toFixedFloorWithoutTrailingZeros,
} from '../helper';
import {
INITIAL_FETCH_KEY,
useFetchKey,
useSuspendedBackendaiClient,
} from '../hooks';
import { INITIAL_FETCH_KEY, useSuspendedBackendaiClient } from '../hooks';
import { ResourceSlotName, useResourceSlotsDetails } from '../hooks/backendai';
import { useBAIPaginationOptionStateOnSearchParam } from '../hooks/reactPaginationQueryOptions';
import { useHiddenColumnKeysSetting } from '../hooks/useHiddenColumnKeysSetting';
Expand All @@ -34,7 +30,7 @@ import {
ReloadOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { useToggle } from 'ahooks';
import { useControllableValue, useToggle } from 'ahooks';
import { Button, TableProps, Tag, theme, Tooltip, Typography } from 'antd';
import { AnyObject } from 'antd/es/_util/type';
import { ColumnsType, ColumnType } from 'antd/es/table';
Expand All @@ -43,6 +39,7 @@ import {
BAITable,
BAIFlex,
BAIPropertyFilter,
BAIFlexProps,
} from 'backend.ai-ui';
import dayjs from 'dayjs';
import _ from 'lodash';
Expand All @@ -55,11 +52,17 @@ type Agent = NonNullable<AgentListQuery$data['agent_list']>['items'][number];

interface AgentListProps {
tableProps?: Omit<TableProps, 'dataSource'>;
headerProps?: BAIFlexProps;
fetchKey?: string;
onChangeFetchKey?: (key: string) => void;
}

const AgentList: React.FC<AgentListProps> = ({ tableProps }) => {
const AgentList: React.FC<AgentListProps> = ({
tableProps,
headerProps,
...otherProps
}) => {
'use memo';

const { t } = useTranslation();
const { token } = theme.useToken();
const { isDarkMode } = useThemeMode();
Expand All @@ -86,7 +89,11 @@ const AgentList: React.FC<AgentListProps> = ({ tableProps }) => {
pageSize: 10,
});

const [fetchKey, updateFetchKey] = useFetchKey();
const [fetchKey, setFetchKey] = useControllableValue(otherProps, {
valuePropName: 'fetchKey',
trigger: 'onChangeFetchKey',
defaultValue: INITIAL_FETCH_KEY,
});

const queryVariables = {
limit: baiPaginationOption.limit,
Expand All @@ -99,6 +106,10 @@ const AgentList: React.FC<AgentListProps> = ({ tableProps }) => {
const deferredQueryVariables = useDeferredValue(queryVariables);
const deferredFetchKey = useDeferredValue(fetchKey);

const updateFetchKey = () => {
setFetchKey(() => new Date().toISOString());
};

const { agent_list } = useLazyLoadQuery<AgentListQuery>(
graphql`
query AgentListQuery(
Expand Down Expand Up @@ -762,7 +773,7 @@ const AgentList: React.FC<AgentListProps> = ({ tableProps }) => {

return (
<BAIFlex direction="column" align="stretch" gap="sm">
<BAIFlex justify="between" align="start" wrap="wrap">
<BAIFlex justify="between" align="start" wrap="wrap" {...headerProps}>
<BAIFlex
direction="row"
gap={'sm'}
Expand Down
2 changes: 1 addition & 1 deletion react/src/components/MainLayout/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from 'react';
import { useNavigate, Outlet, useMatches, useLocation } from 'react-router-dom';

export const HEADER_Z_INDEX_IN_MAIN_LAYOUT = 5;
export const HEADER_Z_INDEX_IN_MAIN_LAYOUT = 6;
export type PluginPage = {
name: string;
url: string;
Expand Down
12 changes: 12 additions & 0 deletions react/src/components/MainLayout/WebUISider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,18 @@ const WebUISider: React.FC<WebUISiderProps> = (props) => {
]);

const adminMenu: MenuProps['items'] = filterOutEmpty([
// WARN: Currently only superadmins can access AdminDashboardPage.
// To place the Admin Dashboard menu item at the top of adminMenu,
// add it to adminMenu instead of superAdminMenu:
currentUserRole === 'superadmin' && {
label: (
<WebUILink to="/admin-dashboard">
{t('webui.menu.AdminDashboard')}
</WebUILink>
),
icon: <DashboardOutlined style={{ color: token.colorInfo }} />,
key: 'admin-dashboard',
},
{
label: <WebUILink to="/credential">{t('webui.menu.Users')}</WebUILink>,
icon: <UserOutlined style={{ color: token.colorInfo }} />,
Expand Down
4 changes: 2 additions & 2 deletions react/src/components/RecentlyCreatedSession.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ const RecentlyCreatedSession: React.FC<RecentlyCreatedSessionProps> = ({
graphql`
fragment RecentlyCreatedSessionFragment on Query
@argumentDefinitions(
projectId: { type: "UUID!" }
scopeId: { type: "ScopeField" }
)
@refetchable(queryName: "RecentlyCreatedSessionRefetchQuery") {
compute_session_nodes(
first: 5
order: "-created_at"
filter: "status == \"running\""
project_id: $projectId
scope_id: $scopeId
) {
edges {
node {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { MySessionQueryFragment$key } from '../__generated__/MySessionQueryFragment.graphql';
import BAIFetchKeyButton from './BAIFetchKeyButton';
import BAIPanelItem from './BAIPanelItem';
import { theme } from 'antd';
Expand All @@ -10,49 +9,55 @@ import {
import { useTransition } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useRefetchableFragment } from 'react-relay';
import { SessionCountDashboardItemFragment$key } from 'src/__generated__/SessionCountDashboardItemFragment.graphql';

interface MySessionProps {
queryRef: MySessionQueryFragment$key;
interface SessionCountDashboardItemProps {
queryRef: SessionCountDashboardItemFragment$key;
isRefetching?: boolean;
title?: string;
}

const MySession: React.FC<MySessionProps> = ({ queryRef, isRefetching }) => {
const SessionCountDashboardItem: React.FC<SessionCountDashboardItemProps> = ({
queryRef,
isRefetching,
title,
}) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const [isPendingRefetch, startRefetchTransition] = useTransition();

const [data, refetch] = useRefetchableFragment(
graphql`
fragment MySessionQueryFragment on Query
fragment SessionCountDashboardItemFragment on Query
@argumentDefinitions(
projectId: { type: "UUID!" }
scopeId: { type: "ScopeField" }
)
@refetchable(queryName: "MySessionQueryFragmentRefetchQuery") {
@refetchable(queryName: "SessionCountDashboardItemRefetchQuery") {
myInteractive: compute_session_nodes(
first: 0
filter: "status != \"TERMINATED\" & status != \"CANCELLED\" & type == \"interactive\""
project_id: $projectId
scope_id: $scopeId
) {
count
}
myBatch: compute_session_nodes(
first: 0
filter: "status != \"TERMINATED\" & status != \"CANCELLED\" & type == \"batch\""
project_id: $projectId
scope_id: $scopeId
) {
count
}
myInference: compute_session_nodes(
first: 0
filter: "status != \"TERMINATED\" & status != \"CANCELLED\" & type == \"inference\""
project_id: $projectId
scope_id: $scopeId
) {
count
}
myUpload: compute_session_nodes(
first: 0
filter: "status != \"TERMINATED\" & status != \"CANCELLED\" & type == \"system\""
project_id: $projectId
scope_id: $scopeId
) {
count
}
Expand All @@ -79,7 +84,7 @@ const MySession: React.FC<MySessionProps> = ({ queryRef, isRefetching }) => {
>
{/* Fixed Title Section */}
<BAIBoardItemTitle
title={t('session.MySessions')}
title={title}
extra={
<BAIFetchKeyButton
loading={isPendingRefetch || isRefetching}
Expand Down Expand Up @@ -135,4 +140,4 @@ const MySession: React.FC<MySessionProps> = ({ queryRef, isRefetching }) => {
);
};

export default MySession;
export default SessionCountDashboardItem;
1 change: 1 addition & 0 deletions react/src/hooks/useBAISetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface UserSettings {
experimental_dashboard?: boolean;
session_metrics_board_items?: Array<Omit<BAIBoardItem, 'data'>>;
dashboard_board_items?: Array<Omit<BAIBoardItem, 'data'>>;
admin_dashboard_board_items?: Array<Omit<BAIBoardItem, 'data'>>;
resource_panel_type?:
| 'MyResource'
| 'MyResourceWithinResourceGroup'
Expand Down
Loading
Loading