feat: add resource for first login

This commit is contained in:
jialin
2025-12-11 17:50:39 +08:00
parent b38bc8a207
commit 3dbb0de683
11 changed files with 227 additions and 82 deletions
+7
View File
@@ -27,4 +27,11 @@ export const clusterListAtom = atom<
}[] }[]
>([]); >([]);
export const workerListAtom = atom<
{
label: string;
value: number;
}[]
>([]);
export const backendOptionsAtom = atom<BackendOption[]>([]); export const backendOptionsAtom = atom<BackendOption[]>([]);
@@ -0,0 +1,92 @@
import { clusterListAtom, workerListAtom } from '@/atoms/models';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { queryWorkersList } from '@/pages/resources/apis';
import { useAtom } from 'jotai';
import { useState } from 'react';
/**
* Currently fetch the cluster list and worker list for checking resource existence.
* If there is no cluster or no worker, introduct the use to create/add them.
* @returns
*/
export default function useClusterList() {
const [clusterList, setClusterList] = useState<
{
label: string;
value: number;
id: number;
state: string;
provider: string;
}[]
>([]);
const [workerList, setWorkerList] = useState<
{
cluster_id: number;
state: string;
label: string;
value: number;
id: number;
labels: { [key: string]: string };
name: string;
}[]
>([]);
const [, setClusterListAtom] = useAtom(clusterListAtom);
const [, setWorkerListAtom] = useAtom(workerListAtom);
const fetchClusterList = async () => {
try {
const res = await queryClusterList({ page: -1 });
const list = res?.items?.map((item: any) => ({
label: item.name,
value: item.id,
id: item.id,
state: item.state,
provider: item.provider
}));
return list;
} catch (error) {
return [];
}
};
const fetchWorkerList = async () => {
try {
const res = await queryWorkersList({ page: -1 });
const list = res?.items?.map((item: any) => ({
cluster_id: item.cluster_id,
state: item.state,
label: item.name,
value: item.id,
id: item.id,
labels: item.labels || {},
name: item.name
}));
return list;
} catch (error) {
return [];
}
};
const fetchAll = async () => {
const [clusters, workers] = await Promise.all([
fetchClusterList(),
fetchWorkerList()
]);
setClusterList(clusters);
setWorkerList(workers);
setClusterListAtom(clusters);
setWorkerListAtom(workers);
return {
hasClusters: clusters.length > 0,
hasWorkers: workers.length > 0
};
};
return {
clusterList,
workerList,
fetchAll,
fetchWorkerList,
fetchClusterList
};
}
+1 -21
View File
@@ -2,7 +2,6 @@ import CardWrapper from '@/components/card-wrapper';
import GaugeChart from '@/components/echarts/gauge'; import GaugeChart from '@/components/echarts/gauge';
import PageTools from '@/components/page-tools'; import PageTools from '@/components/page-tools';
import BaseSelect from '@/components/seal-form/base/select'; import BaseSelect from '@/components/seal-form/base/select';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Col, Row } from 'antd'; import { Col, Row } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
@@ -16,11 +15,8 @@ const resourceChartHeight = 400;
const SystemLoad = () => { const SystemLoad = () => {
const intl = useIntl(); const intl = useIntl();
const { system_load, fetchData } = useContext(DashboardContext); const { system_load, fetchData, clusterList } = useContext(DashboardContext);
const [systemLoadData, setSystemLoadData] = useState<any>(system_load || {}); const [systemLoadData, setSystemLoadData] = useState<any>(system_load || {});
const [clusterList, setClusterList] = useState<Global.BaseOption<number>[]>(
[]
);
const chartData = useMemo(() => { const chartData = useMemo(() => {
const data = systemLoadData?.current || {}; const data = systemLoadData?.current || {};
@@ -53,22 +49,6 @@ const SystemLoad = () => {
} }
}; };
useEffect(() => {
const fetchClusters = async () => {
try {
const res = await queryClusterList({ page: -1 });
const options = res.items.map((cluster: any) => ({
label: cluster.name,
value: cluster.id
}));
setClusterList(options);
} catch (error) {
setClusterList([]);
}
};
fetchClusters();
}, []);
return ( return (
<div> <div>
<div className="system-load"> <div className="system-load">
@@ -4,10 +4,18 @@ import { DashboardProps } from './types';
export const DashboardContext = createContext< export const DashboardContext = createContext<
DashboardProps & { DashboardProps & {
fetchData: (params?: { [key: string]: any }) => Promise<void>; fetchData: (params?: { [key: string]: any }) => Promise<void>;
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
} }
>( >(
{} as DashboardProps & { {} as DashboardProps & {
fetchData: (params?: { [key: string]: any }) => Promise<void>; fetchData: (params?: { [key: string]: any }) => Promise<void>;
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
} }
); );
+49 -8
View File
@@ -1,3 +1,6 @@
import useClusterList from '@/pages/cluster-management/hooks/use-cluster-list';
import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { Spin } from 'antd'; import { Spin } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -8,32 +11,70 @@ import DashboardContext from './config/dashboard-context';
import { DashboardProps } from './config/types'; import { DashboardProps } from './config/types';
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
const intl = useIntl();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState<DashboardProps>({} as DashboardProps); const [data, setData] = useState<DashboardProps>({} as DashboardProps);
const { fetchAll, clusterList, workerList } = useClusterList();
const getDashboardData = useMemoizedFn(async () => { const { noResourceResult } = useNoResourceResult({
loadend: true,
loading: false,
dataSource: clusterList.length > 0 ? workerList : [],
queryParams: {},
iconType: 'icon-dashboard',
title:
clusterList.length > 0
? intl.formatMessage({ id: 'noresult.workers.title' })
: intl.formatMessage({ id: 'noresult.cluster.title' }),
noClusters: !clusterList.length,
noWorkers: workerList.length === 0 && clusterList.length > 0,
defaultContent: {
subTitle: intl.formatMessage({ id: 'noresult.workers.subTitle' }),
noFoundText: intl.formatMessage({ id: 'noresult.workers.nofound' }),
buttonText: intl.formatMessage({ id: 'noresult.workers.button.add' }),
onClick: () => {}
}
});
const fetchDashboardData = useMemoizedFn(async () => {
try { try {
setLoading(true);
const res = await queryDashboardData(); const res = await queryDashboardData();
setData(res); setData(res);
setLoading(false);
} catch (error) { } catch (error) {
setLoading(false);
setData({} as DashboardProps); setData({} as DashboardProps);
} }
}); });
const initData = useMemoizedFn(async () => {
try {
setLoading(true);
const { hasClusters, hasWorkers } = await fetchAll();
if (!hasClusters || !hasWorkers) {
setLoading(false);
return;
}
await fetchDashboardData();
setLoading(false);
} catch (error) {
setLoading(false);
}
});
useEffect(() => { useEffect(() => {
getDashboardData(); initData();
}, []); }, []);
return ( return (
<DashboardContext.Provider <DashboardContext.Provider
value={{ ...data, fetchData: queryDashboardData }} value={{ ...data, fetchData: fetchDashboardData, clusterList }}
> >
<PageBox> <PageBox>
<Spin spinning={loading}> <Spin spinning={loading} style={{ minHeight: 300 }}>
<DashboardInner /> {(!clusterList.length || !workerList.length) && !loading ? (
noResourceResult
) : loading ? null : (
<DashboardInner />
)}
</Spin> </Spin>
</PageBox> </PageBox>
</DashboardContext.Provider> </DashboardContext.Provider>
@@ -83,7 +83,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
handleOnValuesChange, handleOnValuesChange,
warningStatus warningStatus
} = useCheckCompatibility(); } = useCheckCompatibility();
const { getClusterList, clusterList } = useFormInitialValues(); const { getClusterList, getWorkerList, clusterList } = useFormInitialValues();
const intl = useIntl(); const intl = useIntl();
const form = useRef<any>({}); const form = useRef<any>({});
const [isGGUF, setIsGGUF] = useState<boolean>(false); const [isGGUF, setIsGGUF] = useState<boolean>(false);
@@ -295,6 +295,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
useEffect(() => { useEffect(() => {
getClusterList(); getClusterList();
getWorkerList();
}, []); }, []);
useEffect(() => { useEffect(() => {
+27 -28
View File
@@ -2,7 +2,6 @@ import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models';
import DeleteModal from '@/components/delete-modal'; import DeleteModal from '@/components/delete-modal';
import DropDownActions from '@/components/drop-down-actions'; import DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons'; import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font';
import { PageSize } from '@/components/logs-viewer/config'; import { PageSize } from '@/components/logs-viewer/config';
import PageTools from '@/components/page-tools'; import PageTools from '@/components/page-tools';
import BaseSelect from '@/components/seal-form/base/select'; import BaseSelect from '@/components/seal-form/base/select';
@@ -12,9 +11,8 @@ import { PageActionType } from '@/config/types';
import useBodyScroll from '@/hooks/use-body-scroll'; import useBodyScroll from '@/hooks/use-body-scroll';
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableRowSelection from '@/hooks/use-table-row-selection';
import NoResult from '@/pages/_components/no-result';
import PageBox from '@/pages/_components/page-box'; import PageBox from '@/pages/_components/page-box';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result';
import { handleBatchRequest } from '@/utils'; import { handleBatchRequest } from '@/utils';
import { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons'; import { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
import { useIntl, useNavigate } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
@@ -84,7 +82,6 @@ interface ModelsProps {
categories?: string[]; categories?: string[];
}; };
deleteIds?: number[]; deleteIds?: number[];
workerList: WorkerListItem[];
dataSource: ListItem[]; dataSource: ListItem[];
loading: boolean; loading: boolean;
loadend: boolean; loadend: boolean;
@@ -119,14 +116,18 @@ const Models: React.FC<ModelsProps> = ({
onStart, onStart,
deleteIds, deleteIds,
dataSource, dataSource,
workerList,
queryParams, queryParams,
loading, loading,
loadend, loadend,
total total
}) => { }) => {
const { generateFormValues, clusterList, getClusterList } = const {
useFormInitialValues(); generateFormValues,
clusterList,
getClusterList,
getWorkerList,
workerList
} = useFormInitialValues();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [updateFormInitials, setUpdateFormInitials] = useState<{ const [updateFormInitials, setUpdateFormInitials] = useState<{
data: any; data: any;
@@ -206,7 +207,7 @@ const Models: React.FC<ModelsProps> = ({
useEffect(() => { useEffect(() => {
const getData = async () => { const getData = async () => {
await getClusterList(); await Promise.all([getClusterList(), getWorkerList()]);
}; };
getData(); getData();
return () => { return () => {
@@ -594,6 +595,23 @@ const Models: React.FC<ModelsProps> = ({
}); });
}; };
const { noResourceResult } = useNoResourceResult({
loadend: loadend,
loading: loading,
dataSource: dataSource,
queryParams: queryParams,
iconType: 'icon-resources',
title: intl.formatMessage({ id: 'noresult.deployments.title' }),
noClusters: !clusterList.length,
noWorkers: workerList.length === 0 && clusterList.length > 0,
defaultContent: {
subTitle: intl.formatMessage({ id: 'noresult.deployments.subTitle' }),
noFoundText: intl.formatMessage({ id: 'noresult.mymodels.nofound' }),
buttonText: intl.formatMessage({ id: 'models.table.button.deploy' }),
onClick: () => handleClickDropdown({ key: 'catalog' })
}
});
useEffect(() => { useEffect(() => {
if (modelsSession.source && loadend) { if (modelsSession.source && loadend) {
handleClickDropdown({ handleClickDropdown({
@@ -709,26 +727,7 @@ const Models: React.FC<ModelsProps> = ({
loadChildren={getModelInstances} loadChildren={getModelInstances}
loadChildrenAPI={generateChildrenRequestAPI} loadChildrenAPI={generateChildrenRequestAPI}
renderChildren={renderChildren} renderChildren={renderChildren}
empty={ empty={noResourceResult}
<NoResult
loading={loading}
loadend={loadend}
dataSource={dataSource}
image={<IconFont type="icon-rocket-launch1" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.mymodels.nofound'
})}
title={intl.formatMessage({ id: 'noresult.deployments.title' })}
subTitle={intl.formatMessage({
id: 'noresult.deployments.subTitle'
})}
onClick={() => handleClickDropdown({ key: 'catalog' })}
buttonText={intl?.formatMessage?.({
id: 'models.table.button.deploy'
})}
></NoResult>
}
pagination={{ pagination={{
showSizeChanger: true, showSizeChanger: true,
pageSize: queryParams.perPage, pageSize: queryParams.perPage,
+7 -3
View File
@@ -1,4 +1,4 @@
import { clusterListAtom } from '@/atoms/models'; import { clusterListAtom, workerListAtom } from '@/atoms/models';
import { createAxiosToken } from '@/hooks/use-chunk-request'; import { createAxiosToken } from '@/hooks/use-chunk-request';
import { queryModelFilesList } from '@/pages/resources/apis'; import { queryModelFilesList } from '@/pages/resources/apis';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
@@ -132,6 +132,7 @@ export const useCheckCompatibility = () => {
const updateStatusTimer = useRef<any>(null); const updateStatusTimer = useRef<any>(null);
const isLockWarningStatus = useRef<boolean>(false); const isLockWarningStatus = useRef<boolean>(false);
const clusterList = useAtomValue(clusterListAtom); const clusterList = useAtomValue(clusterListAtom);
const workerList = useAtomValue(workerListAtom);
const [warningStatus, setWarningStatus] = useState<MessageStatus>({ const [warningStatus, setWarningStatus] = useState<MessageStatus>({
show: false, show: false,
title: '', title: '',
@@ -175,12 +176,15 @@ export const useCheckCompatibility = () => {
try { try {
// when no cluster selected, show warning and prompt user to add cluster first // when no cluster selected, show warning and prompt user to add cluster first
console.log('handleEvaluate', data); console.log('handleEvaluate', data);
if (!data.cluster_id) {
if (!data.cluster_id || workerList.length === 0) {
setWarningStatus({ setWarningStatus({
show: true, show: true,
title: '', title: '',
type: 'warning', type: 'warning',
message: intl.formatMessage({ id: 'noresult.resources.cluster' }) message: !data.cluster_id
? intl.formatMessage({ id: 'noresult.resources.cluster' })
: intl.formatMessage({ id: 'noresult.resources.worker' })
}); });
return; return;
} }
@@ -1,4 +1,4 @@
import { clusterListAtom } from '@/atoms/models'; import { clusterListAtom, workerListAtom } from '@/atoms/models';
import { queryClusterList } from '@/pages/cluster-management/apis'; import { queryClusterList } from '@/pages/cluster-management/apis';
import { ClusterListItem } from '@/pages/cluster-management/config/types'; import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { queryWorkersList } from '@/pages/resources/apis'; import { queryWorkersList } from '@/pages/resources/apis';
@@ -232,11 +232,14 @@ export const useGenerateWorkerOptions = () => {
export default function useFormInitialValues() { export default function useFormInitialValues() {
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions(); const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
const [, setClusterListAtom] = useAtom(clusterListAtom); const [, setClusterListAtom] = useAtom(clusterListAtom);
const [, setWorkerListAtom] = useAtom(workerListAtom);
const [clusterList, setClusterList] = useState< const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string }>[] Global.BaseOption<number, { provider: string; state: string }>[]
>([]); >([]);
const [workerList, setWorkerList] = useState<WorkerListItem[]>([]);
const getClusterList = async (): Promise<Global.BaseOption<number>[]> => { const getClusterList = async (): Promise<Global.BaseOption<number>[]> => {
try { try {
const response = await queryClusterList({ const response = await queryClusterList({
@@ -259,6 +262,26 @@ export default function useFormInitialValues() {
} }
}; };
// get worker list
const getWorkerList = async (): Promise<any> => {
try {
const data = await queryWorkersList({ page: -1 });
const list =
data.items?.map((item) => ({
label: item.name,
value: item.id
})) || [];
setWorkerList(data.items);
setWorkerListAtom(list);
return data;
} catch (error) {
// ingore
setWorkerList([]);
setWorkerListAtom([]);
return {};
}
};
/** /**
* before set the form initial values, generate the form values * before set the form initial values, generate the form values
* @param data * @param data
@@ -280,6 +303,8 @@ export default function useFormInitialValues() {
getGPUOptionList, getGPUOptionList,
generateFormValues, generateFormValues,
getClusterList, getClusterList,
getWorkerList,
workerList,
clusterList, clusterList,
gpuOptions gpuOptions
}; };
@@ -7,6 +7,12 @@ import { useMemoizedFn } from 'ahooks';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
/**Title: Generally, this is from the activation page.
* DefaultContent: This content from the activate page, for example, the activate page is Deployments page,
* then the defaultContent is for deployment.
* @param props
* @returns
*/
const useNoResourceResult = (props: { const useNoResourceResult = (props: {
iconType: string; iconType: string;
loading?: boolean; loading?: boolean;
@@ -84,6 +90,7 @@ const useNoResourceResult = (props: {
onClick: handleClick onClick: handleClick
}; };
} }
return { return {
...defaultContent ...defaultContent
}; };
+1 -20
View File
@@ -1,8 +1,6 @@
import TableContext from '@/components/seal-table/table-context'; import TableContext from '@/components/seal-table/table-context';
import useSetChunkRequest from '@/hooks/use-chunk-request'; import useSetChunkRequest from '@/hooks/use-chunk-request';
import useUpdateChunkedList from '@/hooks/use-update-chunk-list'; import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { queryWorkersList } from '@/pages/resources/apis';
import { ListItem as WokerListItem } from '@/pages/resources/config/types';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import _ from 'lodash'; import _ from 'lodash';
import qs from 'query-string'; import qs from 'query-string';
@@ -35,7 +33,6 @@ const Models: React.FC = () => {
total: 0 total: 0
}); });
const [workerList, setWorkerList] = useState<WokerListItem[]>([]);
const chunkRequedtRef = useRef<any>(); const chunkRequedtRef = useRef<any>();
const chunkInstanceRequedtRef = useRef<any>(); const chunkInstanceRequedtRef = useRef<any>();
const isPageHidden = useRef(false); const isPageHidden = useRef(false);
@@ -320,22 +317,8 @@ const Models: React.FC = () => {
} }
}; };
// get worker list
const getWorkerList = async (): Promise<any> => {
try {
const data = await queryWorkersList({ page: -1 });
return data;
} catch (error) {
// ingore
return {};
}
};
const init = async () => { const init = async () => {
const [modelRes, workerRes] = await Promise.all([ const [modelRes] = await Promise.all([getTableData()]);
getTableData(),
getWorkerList()
]);
setDataSource({ setDataSource({
dataList: modelRes.items || [], dataList: modelRes.items || [],
@@ -344,7 +327,6 @@ const Models: React.FC = () => {
total: modelRes.pagination?.total || 0, total: modelRes.pagination?.total || 0,
deletedIds: [] deletedIds: []
}); });
setWorkerList(workerRes.items || []);
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout(() => { timer = setTimeout(() => {
@@ -420,7 +402,6 @@ const Models: React.FC = () => {
loadend={dataSource.loadend} loadend={dataSource.loadend}
total={dataSource.total} total={dataSource.total}
deleteIds={dataSource.deletedIds} deleteIds={dataSource.deletedIds}
workerList={workerList}
></TableList> ></TableList>
</TableContext.Provider> </TableContext.Provider>
); );