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[]>([]);
@@ -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 PageTools from '@/components/page-tools';
import BaseSelect from '@/components/seal-form/base/select';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { useIntl } from '@umijs/max';
import { Col, Row } from 'antd';
import _ from 'lodash';
@@ -16,11 +15,8 @@ const resourceChartHeight = 400;
const SystemLoad = () => {
const intl = useIntl();
const { system_load, fetchData } = useContext(DashboardContext);
const { system_load, fetchData, clusterList } = useContext(DashboardContext);
const [systemLoadData, setSystemLoadData] = useState<any>(system_load || {});
const [clusterList, setClusterList] = useState<Global.BaseOption<number>[]>(
[]
);
const chartData = useMemo(() => {
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 (
<div>
<div className="system-load">
@@ -4,10 +4,18 @@ import { DashboardProps } from './types';
export const DashboardContext = createContext<
DashboardProps & {
fetchData: (params?: { [key: string]: any }) => Promise<void>;
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
}
>(
{} as DashboardProps & {
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 { Spin } from 'antd';
import { useEffect, useState } from 'react';
@@ -8,32 +11,70 @@ import DashboardContext from './config/dashboard-context';
import { DashboardProps } from './config/types';
const Dashboard: React.FC = () => {
const intl = useIntl();
const [loading, setLoading] = useState(false);
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 {
setLoading(true);
const res = await queryDashboardData();
setData(res);
setLoading(false);
} catch (error) {
setLoading(false);
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(() => {
getDashboardData();
initData();
}, []);
return (
<DashboardContext.Provider
value={{ ...data, fetchData: queryDashboardData }}
value={{ ...data, fetchData: fetchDashboardData, clusterList }}
>
<PageBox>
<Spin spinning={loading}>
<DashboardInner />
<Spin spinning={loading} style={{ minHeight: 300 }}>
{(!clusterList.length || !workerList.length) && !loading ? (
noResourceResult
) : loading ? null : (
<DashboardInner />
)}
</Spin>
</PageBox>
</DashboardContext.Provider>
@@ -83,7 +83,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
handleOnValuesChange,
warningStatus
} = useCheckCompatibility();
const { getClusterList, clusterList } = useFormInitialValues();
const { getClusterList, getWorkerList, clusterList } = useFormInitialValues();
const intl = useIntl();
const form = useRef<any>({});
const [isGGUF, setIsGGUF] = useState<boolean>(false);
@@ -295,6 +295,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
useEffect(() => {
getClusterList();
getWorkerList();
}, []);
useEffect(() => {
+27 -28
View File
@@ -2,7 +2,6 @@ import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models';
import DeleteModal from '@/components/delete-modal';
import DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font';
import { PageSize } from '@/components/logs-viewer/config';
import PageTools from '@/components/page-tools';
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 useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import NoResult from '@/pages/_components/no-result';
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 { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
import { useIntl, useNavigate } from '@umijs/max';
@@ -84,7 +82,6 @@ interface ModelsProps {
categories?: string[];
};
deleteIds?: number[];
workerList: WorkerListItem[];
dataSource: ListItem[];
loading: boolean;
loadend: boolean;
@@ -119,14 +116,18 @@ const Models: React.FC<ModelsProps> = ({
onStart,
deleteIds,
dataSource,
workerList,
queryParams,
loading,
loadend,
total
}) => {
const { generateFormValues, clusterList, getClusterList } =
useFormInitialValues();
const {
generateFormValues,
clusterList,
getClusterList,
getWorkerList,
workerList
} = useFormInitialValues();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [updateFormInitials, setUpdateFormInitials] = useState<{
data: any;
@@ -206,7 +207,7 @@ const Models: React.FC<ModelsProps> = ({
useEffect(() => {
const getData = async () => {
await getClusterList();
await Promise.all([getClusterList(), getWorkerList()]);
};
getData();
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(() => {
if (modelsSession.source && loadend) {
handleClickDropdown({
@@ -709,26 +727,7 @@ const Models: React.FC<ModelsProps> = ({
loadChildren={getModelInstances}
loadChildrenAPI={generateChildrenRequestAPI}
renderChildren={renderChildren}
empty={
<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>
}
empty={noResourceResult}
pagination={{
showSizeChanger: true,
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 { queryModelFilesList } from '@/pages/resources/apis';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
@@ -132,6 +132,7 @@ export const useCheckCompatibility = () => {
const updateStatusTimer = useRef<any>(null);
const isLockWarningStatus = useRef<boolean>(false);
const clusterList = useAtomValue(clusterListAtom);
const workerList = useAtomValue(workerListAtom);
const [warningStatus, setWarningStatus] = useState<MessageStatus>({
show: false,
title: '',
@@ -175,12 +176,15 @@ export const useCheckCompatibility = () => {
try {
// when no cluster selected, show warning and prompt user to add cluster first
console.log('handleEvaluate', data);
if (!data.cluster_id) {
if (!data.cluster_id || workerList.length === 0) {
setWarningStatus({
show: true,
title: '',
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;
}
@@ -1,4 +1,4 @@
import { clusterListAtom } from '@/atoms/models';
import { clusterListAtom, workerListAtom } from '@/atoms/models';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { queryWorkersList } from '@/pages/resources/apis';
@@ -232,11 +232,14 @@ export const useGenerateWorkerOptions = () => {
export default function useFormInitialValues() {
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
const [, setClusterListAtom] = useAtom(clusterListAtom);
const [, setWorkerListAtom] = useAtom(workerListAtom);
const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string }>[]
>([]);
const [workerList, setWorkerList] = useState<WorkerListItem[]>([]);
const getClusterList = async (): Promise<Global.BaseOption<number>[]> => {
try {
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
* @param data
@@ -280,6 +303,8 @@ export default function useFormInitialValues() {
getGPUOptionList,
generateFormValues,
getClusterList,
getWorkerList,
workerList,
clusterList,
gpuOptions
};
@@ -7,6 +7,12 @@ import { useMemoizedFn } from 'ahooks';
import { useAtom } from 'jotai';
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: {
iconType: string;
loading?: boolean;
@@ -84,6 +90,7 @@ const useNoResourceResult = (props: {
onClick: handleClick
};
}
return {
...defaultContent
};
+1 -20
View File
@@ -1,8 +1,6 @@
import TableContext from '@/components/seal-table/table-context';
import useSetChunkRequest from '@/hooks/use-chunk-request';
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 _ from 'lodash';
import qs from 'query-string';
@@ -35,7 +33,6 @@ const Models: React.FC = () => {
total: 0
});
const [workerList, setWorkerList] = useState<WokerListItem[]>([]);
const chunkRequedtRef = useRef<any>();
const chunkInstanceRequedtRef = useRef<any>();
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 [modelRes, workerRes] = await Promise.all([
getTableData(),
getWorkerList()
]);
const [modelRes] = await Promise.all([getTableData()]);
setDataSource({
dataList: modelRes.items || [],
@@ -344,7 +327,6 @@ const Models: React.FC = () => {
total: modelRes.pagination?.total || 0,
deletedIds: []
});
setWorkerList(workerRes.items || []);
clearTimeout(timer);
timer = setTimeout(() => {
@@ -420,7 +402,6 @@ const Models: React.FC = () => {
loadend={dataSource.loadend}
total={dataSource.total}
deleteIds={dataSource.deletedIds}
workerList={workerList}
></TableList>
</TableContext.Provider>
);