Commit 4e6dcdf1 authored by chenshouchao's avatar chenshouchao

feat: 硬件队列选取器完成cup部分 差GPU

parent 2f1b86bd
......@@ -48,6 +48,7 @@ const RESTAPI = {
API_PROJECT_USERS_LIST:`${BACKEND_API_URI_PREFIX}/cpp/project/listusers`, // 获取项目成员
API_ADD_PROJECT_USER:`${BACKEND_API_URI_PREFIX}/cpp/project/updatemember`, // 添加项目成员
API_GET_PROJECT_POWER:`${BACKEND_API_URI_PREFIX}/cpp/project/listroles`, // 获取项目权限
API_GET_HARDWARE:`${BACKEND_API_URI_PREFIX}/cpp/cpce/hardware`, // 获取计算队列列表
};
export default RESTAPI;
......@@ -346,6 +346,16 @@ const fetchProjectPower = (params: {id: string}) => {
});
};
// 获取队列列表
const getHardwreList = (params: {zoneId: string, computeType: string}) => {
return request({
url: Api.API_GET_HARDWARE,
method: "get",
params,
});
}
//
export {
current,
menu,
......@@ -373,5 +383,6 @@ export {
fetchFlowOutputNumber,
fetchProjectUsersList,
addProjectUser,
fetchProjectPower
fetchProjectPower,
getHardwreList
};
......@@ -20,23 +20,25 @@ const NoProject = observer((props: any) => {
// 新建弹窗显示控制
const [addOpen, setAddOpen] = useState(false);
const { currentProjectStore } = useStores();
let projectList = toJS(currentProjectStore.projectList)
let projectList = toJS(currentProjectStore.projectList);
return (
<div className={style.noProject}>
<img src={noData} alt="" className={style.noDataImg} />
<div className={style.text1}>{projectList.length > 0 ? "当前未选中任何项目" : "当前产品暂无项目"}</div>
<div className={style.text2}>{projectList.length > 0 ? "或创建新项目" : "请先创建项目"}</div>
<div className={style.text1}>
{projectList.length > 0 ? "当前未选中任何项目" : "当前产品暂无项目"}
</div>
<div className={style.text2}>
{projectList.length > 0 ? "或创建新项目" : "请先创建项目"}
</div>
<MyButton
text='创建项目'
text="创建项目"
variant="contained"
size="large"
startIcon={<AddIcon />}
onClick={() => setAddOpen(true)}
style={{ backgroundColor: "#1370ff", color: "#fff" }}
/>
<AddProject addOpen={addOpen} setAddOpen={setAddOpen} />
{addOpen && <AddProject addOpen={addOpen} setAddOpen={setAddOpen} />}
</div>
);
});
......
.queueSelectContentBox {
width: 852px;
}
.table {
height: calc(80vh - 180px);
margin-top: 20px;
}
.cpuType {
display: flex;
justify-content: space-between;
align-items: center;
}
.cpuIcon {
display: block;
width: 36px;
height: 36px;
border-radius: 6px;
margin-right: 12px;
}
.cpuInfo {
overflow: hidden;
flex: 1;
}
.cpuName {
font-size: 14px;
line-height: 22px;
color: rgba(30, 38, 51, 1);
white-space: nowrap;
font-weight: 600;
}
.cpuDetailsName {
font-size: 12px;
line-height: 20px;
color: rgba(138, 144, 153, 1);
white-space: nowrap;
}
.cpuMemory {
cursor: pointer;
}
.totalBox {
font-size: 14px;
line-height: 22px;
color: rgba(138, 144, 153, 1);
display: flex;
align-items: center;
}
.total {
font-size: 24px;
line-height: 32px;
color: rgba(255, 78, 78, 1);
font-weight: 600;
}
import MyInput from "@/components/mui/MyInput";
import MyDialog from "@/components/mui/MyDialog";
import RadioGroupOfButtonStyle from "@/components/CommonComponents/RadioGroupOfButtonStyle";
import style from "./index.module.css";
import MyTable from "@/components/mui/MyTableNew";
import MyMenu from "@/components/mui/MyMenu";
import cloneDeep from "lodash/cloneDeep";
import { useMessage } from "@/components/MySnackbar";
import { useEffect, useMemo, useState } from "react";
export type IQueueLi = {
name: string;
picUrl: string;
cpuName: string;
gpuName: string;
gpuNum: number; // 卡数
coreNum: number;
memory: number;
total: number;
id: string;
};
type IQueueSelectProps = {
value: string;
onChange: any;
originalCpuList: Array<IQueueLi>;
originalGpuList: Array<IQueueLi>;
};
type IMemoryLi = {
memory: number;
total: number;
id: string;
};
export type IShowCPULi = {
name: string;
picUrl: string;
cpuName: string;
coreNum: number;
id: string;
memoryList: Array<IMemoryLi>;
};
const queueTypes = [
{
label: "CPU",
value: "CPU",
},
{
label: "GPU",
value: "GPU",
},
];
const QueueSelect = (props: IQueueSelectProps) => {
const { value, onChange, originalCpuList, originalGpuList } = props;
const Message = useMessage();
const [activeId, setActiveId] = useState("");
const [open, setOpen] = useState(false);
const [queueType, setQueueType] = useState("CPU");
const [showCpuList, setShowCpuList] = useState<Array<IShowCPULi>>([]);
const handleOpen = () => {
// setActiveId("");
setOpen(true);
};
// 队列id和前端展示的队列信息的映射关系
const idInfoMap = useMemo(() => {
return [
...originalCpuList.map((item) => ({
id: item.id,
title: `${item.name}/${item.coreNum}${item.coreNum * item.memory}G/${
item.coreNum
}总核心数`,
total: item.total,
})),
...originalGpuList.map((item) => ({
id: item.id,
title: `${item.name}/${item.gpuNum}${item.coreNum}${item.memory}G/${item.gpuNum}总卡数`,
total: item.total,
})),
];
}, [originalCpuList, originalGpuList]);
useEffect(() => {
let resultList: Array<IShowCPULi> = [];
originalCpuList.forEach((item) => {
const targetIndex = resultList.findIndex((resultLi) => {
return resultLi.name === item.name;
});
if (targetIndex === -1) {
resultList.push({
name: item.name,
picUrl: item.picUrl,
cpuName: item.cpuName,
coreNum: item.coreNum,
id: item.id,
memoryList: [
{
memory: item.memory,
total: item.total,
id: item.id,
},
],
});
} else {
resultList[targetIndex].memoryList.push({
memory: item.memory,
total: item.total,
id: item.id,
});
}
});
setShowCpuList(resultList);
}, [originalCpuList]);
const cpuHeadCells = [
{
id: "type",
label: "类型",
width: 510,
},
{
id: "coreNumRender",
label: "节点核数",
width: 180,
},
{
id: "memoryRender",
label: "设置内存配比",
width: 150,
},
];
const renderType = (item: IShowCPULi) => {
return (
<div className={style.cpuType}>
<img className={style.cpuIcon} src={item.picUrl} alt="" />
<div className={style.cpuInfo}>
<div className={style.cpuName}>{item.name}</div>
<div className={style.cpuDetailsName}>{item.cpuName}</div>
</div>
</div>
);
};
const renderCoreNum = (item: IShowCPULi) => {
return item.coreNum + "核";
};
const setMemory = (id: string) => {
const copyShowCpuList: Array<IShowCPULi> = cloneDeep(showCpuList);
const selectIndex = copyShowCpuList.findIndex((item) => {
return item.id === activeId;
});
if (selectIndex !== -1) {
copyShowCpuList[selectIndex].id = id;
}
// 防止被 handleRow 中的 setActiveId覆盖
setTimeout(() => {
setActiveId(id);
}, 10);
setShowCpuList(copyShowCpuList);
console.log(id);
console.log(copyShowCpuList);
};
const renderMemory = (item: IShowCPULi) => {
return (
<MyMenu
value={item.id}
options={item.memoryList.map((memoryLi) => ({
label: `${memoryLi.memory}GB/核`,
value: memoryLi.id,
}))}
hasTriangle={true}
setValue={setMemory}
>
<span className={style.cpuMemory}>
{
item.memoryList.filter((memoryLi) => memoryLi.id === item.id)[0]
.memory
}
GB/核
</span>
</MyMenu>
);
};
const handleRow = (e: any) => {
console.log(e);
setActiveId(e.id);
};
const handleConfirm = () => {
if (activeId) {
onChange && onChange(activeId);
setOpen(false);
} else {
Message.error("请先选择一个硬件队列。");
}
};
const getTotal = () => {
let total = 0;
// activeId
idInfoMap.forEach((item) => {
if (item.id === activeId) {
total = item.total;
}
});
return total;
};
const getTitle = () => {
let title = "";
// activeId
idInfoMap.forEach((item) => {
if (item.id === value) {
title = item.title;
}
});
return title;
};
return (
<>
<MyInput value={getTitle()} onClick={() => handleOpen()}></MyInput>
{open && (
<MyDialog
open={open}
title="选择硬件队列"
onClose={() => setOpen(false)}
onConfirm={() => handleConfirm()}
leftSideOfButtonContent={
<div className={style.totalBox}>
费用(每小时):
<span className={style.total}>
{activeId ? getTotal() : "-"}
</span>
</div>
}
>
<div className={style.queueSelectContentBox}>
<RadioGroupOfButtonStyle
value={queueType}
radioOptions={queueTypes}
handleRadio={(e: string) => setQueueType(e)}
RadiosBoxStyle={{ width: "140px" }}
></RadioGroupOfButtonStyle>
<div className={style.table}>
{queueType === "CPU" && (
<MyTable
rows={showCpuList.map((item) => ({
...item,
type: renderType(item),
coreNumRender: renderCoreNum(item),
memoryRender: renderMemory(item),
}))}
headCells={cpuHeadCells}
fixedHead={true}
handleRow={(e: any) => handleRow(e)}
activeId={activeId}
></MyTable>
)}
{queueType === "GPU" && (
<MyTable
rows={originalGpuList.map((item) => ({
...item,
// type: renderType(item),
// coreNumRender: renderCoreNum(item),
// memoryRender: renderMemory(item),
}))}
headCells={cpuHeadCells}
fixedHead={true}
handleRow={(e: any) => handleRow(e)}
activeId={activeId}
></MyTable>
)}
</div>
</div>
</MyDialog>
)}
</>
);
};
export default QueueSelect;
......@@ -53,12 +53,10 @@ export interface IDialogProps {
| "warning"; //按钮颜色风格
loading?: boolean; // 确认按钮是否处于loading状态
isText?: boolean; // 是否文本弹窗
leftSideOfButtonContent?: React.ReactNode; // 取消确认按钮前的内容
}
const theme = createTheme({
// 0px 12px 15px -7px rgb(0 0 0 / 11%), 0px 24px 38px 3px rgb(0 0 0 / 9%), 0px 9px 46px 8px rgb(0 0 0 / 9%)
// .css-1t1j96h-MuiPaper-root-MuiDialog-paper
components: {
MuiPaper: {
styleOverrides: {
......@@ -70,6 +68,13 @@ const theme = createTheme({
},
},
},
MuiDialogActions: {
styleOverrides: {
root: {
justifyContent: "space-between",
},
},
},
},
});
......@@ -95,6 +100,7 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
loading = false,
okColor = "primary",
isText = false,
leftSideOfButtonContent = null,
} = props;
const handelClose = (
......@@ -113,26 +119,29 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
footerRender()
) : (
<DialogActions style={{ padding: "16px 24px 16px 24px" }}>
{showCancel ? (
<MyButton
text={cancelText || "取消"}
onClick={onClose}
variant="outlined"
color="secondary"
/>
) : null}
{showConfirm ? (
<MyButton
text={okText || "确定"}
onClick={onConfirm}
variant="contained"
color={okColor}
disabled={disabledConfirm}
isLoadingButton={true}
loading={loading}
style={{ marginLeft: "12px" }}
/>
) : null}
<div>{leftSideOfButtonContent && leftSideOfButtonContent}</div>
<div>
{showCancel ? (
<MyButton
text={cancelText || "取消"}
onClick={onClose}
variant="outlined"
color="secondary"
/>
) : null}
{showConfirm ? (
<MyButton
text={okText || "确定"}
onClick={onConfirm}
variant="contained"
color={okColor}
disabled={disabledConfirm}
isLoadingButton={true}
loading={loading}
style={{ marginLeft: "12px" }}
/>
) : null}
</div>
</DialogActions>
);
}, [
......@@ -147,6 +156,7 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
isHideFooter,
loading,
showConfirm,
leftSideOfButtonContent,
]);
const contentPadding = useMemo(() => {
......
......@@ -5,6 +5,7 @@ import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import CheckIcon from "@mui/icons-material/Check";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import triangle from "@/assets/project/triangle.svg";
type IOption = {
label: string;
......@@ -16,6 +17,7 @@ type IMyMenuProps = {
options: Array<IOption>;
value: string;
setValue?: any;
hasTriangle?: boolean;
};
const theme = createTheme({
......@@ -63,7 +65,7 @@ const theme = createTheme({
});
const MyMenu = (props: IMyMenuProps) => {
const { children, options, value, setValue } = props;
const { children, options, value, setValue, hasTriangle = false } = props;
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
......@@ -81,7 +83,27 @@ const MyMenu = (props: IMyMenuProps) => {
return (
<ThemeProvider theme={theme}>
<div>
<div onClick={handleClick}>{children}</div>
<div
onClick={handleClick}
style={{
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
}}
>
{children}
{hasTriangle && (
<img
style={{
width: "16px",
height: "16px",
transform: open ? "rotateX(180deg)" : "",
}}
src={triangle}
alt=""
/>
)}
</div>
<Menu
id="basic-menu"
anchorEl={anchorEl}
......
......@@ -44,6 +44,8 @@ interface IMyTableProps {
rowsPerPage?: number; // 每页多少条数据
handleChangeRowsPerPage?: any; // 每页多少条数据变化
nodataText?: any; // 无数据文案
handleRow?: any; // 点击一行
activeId?: string; // 选中的一行的id
}
const MyTable = (props: IMyTableProps) => {
......@@ -67,6 +69,8 @@ const MyTable = (props: IMyTableProps) => {
handleChangeRowsPerPage,
totalElements = 0,
nodataText,
handleRow,
activeId,
} = props;
const theme = useMemo(() => {
......@@ -364,7 +368,14 @@ const MyTable = (props: IMyTableProps) => {
return (
<TableBody>
{rows.map((row, rowIndex) => (
<TableRow key={row[tableKey] || rowIndex}>
<TableRow
key={row[tableKey] || rowIndex}
onClick={() => handleRow && handleRow(row)}
sx={{
background:
activeId === row[tableKey] ? "rgba(237, 244, 255, 1)" : "",
}}
>
{hasCheckbox && (
<TableCell
align="left"
......@@ -422,6 +433,8 @@ const MyTable = (props: IMyTableProps) => {
headCells,
nodataText,
loading,
handleRow,
activeId,
]);
const randerTableFooter = useMemo(() => {
......
......@@ -96,9 +96,9 @@ const BaseInfo = observer(() => {
// 获取计算区信息
const { run: getZone } = useMyRequest(hpczone, {
onSuccess: (result: any) => {
setZoneIdOptions(result);
setZoneIdOptions(result.data);
let zoneMap: Map<string, string> = new Map();
result.forEach((item: zoneIdOption) => {
result.data.forEach((item: zoneIdOption) => {
zoneMap.set(item.id, item.name);
});
setZoneIdMap(zoneMap);
......
......@@ -52,8 +52,8 @@ const AddProject = observer((props: IAddProjectProps) => {
// 设置计算区
const { run } = useMyRequest(hpczone, {
onSuccess: (result: any) => {
setZoneIdOptions(result);
setZoneId(result[0].id || "");
setZoneIdOptions(result.data);
setZoneId(result.data[0].id || "");
},
});
const { run: addProjectRun } = useMyRequest(addProject, {
......
......@@ -73,7 +73,7 @@ const CurrentProject = observer(() => {
style={{ fontSize: 12 }}
/>
</div>
<AddProject addOpen={addOpen} setAddOpen={setAddOpen} />
{addOpen && <AddProject addOpen={addOpen} setAddOpen={setAddOpen} />}
<Popper
id={id}
open={projectListOpen}
......
......@@ -21,8 +21,8 @@ export const getProjectList = async (id = 'cadd') => {
export const setFileServerEndPointLocalStorage = async (zoneId: string) => {
const res = await hpczone();
let fileServerEndPoint = "";
if (Array.isArray(res)) {
res.forEach((item: any) => {
if (Array.isArray(res.data)) {
res.data.forEach((item: any) => {
if (item.id === zoneId) {
fileServerEndPoint = item.storageConfig?.fileServerEndPoint;
}
......
import QueueSelect from "@/components/BusinessComponents/QueueSelect";
import { useEffect, useState } from "react";
import useMyRequest from "@/hooks/useMyRequest";
import { getHardwreList } from "@/api/project_api";
import { IQueueLi } from "@/components/BusinessComponents/QueueSelect";
const QueueSelectDemo = () => {
const [value, setValue] = useState("");
const [cpuList, setCpuList] = useState<Array<IQueueLi>>([]);
const [gpuList, setGpuList] = useState<Array<IQueueLi>>([]);
const onChange = (e: any) => {
console.log(e);
setValue(e);
};
const { run } = useMyRequest(getHardwreList, {
onSuccess: (res, params) => {
if (params[0].computeType === "CPU") {
setCpuList(res.data);
} else {
setGpuList(res.data);
}
},
});
// computeType
// coreNum
// cpuName
// gpuName
// gpuNum
// id
// memory
// name
// originalPrice
// partition
// partitionId
// picUrl
// price
useEffect(() => {
run({
zoneId: "CE-Z1",
computeType: "CPU",
});
}, [run]);
useEffect(() => {
run({
zoneId: "CE-Z1",
computeType: "GPU",
});
}, [run]);
return (
<>
<QueueSelect
value={value}
onChange={onChange}
originalCpuList={cpuList}
originalGpuList={gpuList}
></QueueSelect>
</>
);
};
export default QueueSelectDemo;
import MyTableDemo from "./MyTableDemo";
import QueueSelectDemo from "./QueueSelectDemo";
import RadioGroupOfButtonStyle from "@/components/CommonComponents/RadioGroupOfButtonStyle";
import { useCallback, useMemo, useState } from "react";
import { useState } from "react";
import styles from "./index.module.css";
const Demo = () => {
const demoList = useCallback(() => {
return [
{
name: "表格",
element: MyTableDemo,
},
{
name: "box",
element: () => {
return <div>box</div>;
},
},
];
}, []);
const radioOptionsArr = [
{
value: "队列选择器",
label: "队列选择器",
},
{
value: "表格",
label: "表格",
......@@ -32,10 +22,7 @@ const Demo = () => {
const handleRadio = (e: string) => {
setSelectDemo(e);
};
const [selectDemo, setSelectDemo] = useState("表格");
const randerDemo = useMemo(() => {
return demoList().filter((item) => item.name === selectDemo)[0].element;
}, [selectDemo, demoList]);
const [selectDemo, setSelectDemo] = useState("队列选择器");
return (
<div className={styles.demoBox}>
......@@ -44,7 +31,11 @@ const Demo = () => {
radioOptions={radioOptionsArr}
handleRadio={handleRadio}
/>
<div className={styles.demoContentBox}>{randerDemo()}</div>
<div className={styles.demoContentBox}>
{selectDemo === "队列选择器" && <QueueSelectDemo></QueueSelectDemo>}
{selectDemo === "表格" && <MyTableDemo></MyTableDemo>}
{selectDemo === "box" && <div>box</div>}
</div>
</div>
);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment