feat(container): 动态加载容器资源配置

master
钟良源 2 months ago
parent 2dd5786d50
commit 406845dae2

@ -21,4 +21,9 @@ export function stopContainer(deployEnvId) {
// 根据组件实例ID获取容器数据 // 根据组件实例ID获取容器数据
export function getContainer(deployEnvId) { export function getContainer(deployEnvId) {
return axios.get(`${urlPrefix}/userContainer/${deployEnvId}/get`); return axios.get(`${urlPrefix}/userContainer/${deployEnvId}/get`);
}
// 获取容器限制配置
export function getContainerLimit(deployEnvId) {
return axios.get(`${urlPrefix}/userContainer/${deployEnvId}/limits`);
} }

@ -1,7 +1,19 @@
import React, { useEffect, useState } from 'react' ; import React, { useEffect, useState, useMemo } from 'react';
import { Modal, Form, Select, Grid, Slider, Switch, Input, InputNumber, Message } from '@arco-design/web-react'; import {
Modal,
Form,
Select,
Grid,
Slider,
Switch,
Input,
InputNumber,
Message,
Button,
Spin
} from '@arco-design/web-react';
import EditableTable from '@/components/EditableTable'; import EditableTable from '@/components/EditableTable';
import { containerRegister } from '@/api/userContainer'; import { containerRegister, getContainerLimit } from '@/api/userContainer';
import { getNetworkMode } from '@/api/componentDeploy'; import { getNetworkMode } from '@/api/componentDeploy';
const FormItem = Form.Item; const FormItem = Form.Item;
@ -124,6 +136,12 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
const [currentNetworkCard, setCurrentNetworkCard] = useState(''); // 当前选择的网卡 const [currentNetworkCard, setCurrentNetworkCard] = useState(''); // 当前选择的网卡
const [currentIp, setCurrentIp] = useState(''); // 当前输入的IP const [currentIp, setCurrentIp] = useState(''); // 当前输入的IP
const [containerConfig, setContainerConfig] = useState<{
maxCpuCores: number,
maxMemoryMiB: number
}>({ maxCpuCores: 4, maxMemoryMiB: 4096 }); // 容器配置数据
const [configLoaded, setConfigLoaded] = useState(false); // 容器配置是否已加载
const [configLoading, setConfigLoading] = useState(false); // 容器配置加载中
// 处理取消 // 处理取消
const handleCancel = () => { const handleCancel = () => {
@ -135,6 +153,8 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
setSelectedNetworkType([]); setSelectedNetworkType([]);
setCurrentNetworkCard(''); setCurrentNetworkCard('');
setCurrentIp(''); setCurrentIp('');
setConfigLoaded(false);
setConfigLoading(false);
setVisible(false); setVisible(false);
}; };
@ -168,7 +188,7 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
volumes: directoryMountData, // 目录挂载 volumes: directoryMountData, // 目录挂载
devices: deviceMountData, // 设备挂载 devices: deviceMountData, // 设备挂载
// 下面是暂时写死的数据, // 下面是暂时写死的数据,
'restartPolicy': 'always', 'restartPolicy': 'always'
}; };
const res: any = await containerRegister(submitData); const res: any = await containerRegister(submitData);
@ -189,6 +209,51 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
} }
}; };
const getContainerConfig = async () => {
setConfigLoading(true);
try {
const res: any = await getContainerLimit(addItem.id);
if (res.code === 200) {
setContainerConfig(res.data);
setConfigLoaded(true);
// 设置表单默认值
form.setFieldsValue({
cpuCount: 1,
memory: 2048,
useGpu: false
});
}
} finally {
setConfigLoading(false);
}
};
// 动态生成CPU核数的marks
const cpuMarks = useMemo(() => {
const max = containerConfig.maxCpuCores;
const marks: Record<number, string> = {};
marks[1] = '1核';
if (max > 1) marks[max] = `${max}`;
// 中间值不显示文字
for (let i = 2; i < max; i++) {
marks[i] = '';
}
return marks;
}, [containerConfig.maxCpuCores]);
// 动态生成内存的marks (按2的幂次方: 1, 2, 4, 8, 16GB)
const memoryMarks = useMemo(() => {
const max = containerConfig.maxMemoryMiB;
const marks: Record<number, string> = {};
for (let gb = 2; gb <= 128; gb *= 2) {
const mb = gb * 1024;
if (mb <= max) {
marks[mb] = `${gb}GB`;
}
}
return marks;
}, [containerConfig.maxMemoryMiB]);
useEffect(() => { useEffect(() => {
if (envType.length > 0) setEnvTypeOptions(envType); if (envType.length > 0) setEnvTypeOptions(envType);
}, [envType]); }, [envType]);
@ -196,12 +261,7 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
useEffect(() => { useEffect(() => {
if (visible) { if (visible) {
// 设置表单默认值 // 不再自动加载,等用户点击按钮
form.setFieldsValue({
cpuCount: 1,
memory: 2048,
useGpu: false
});
} }
else { else {
// 关闭时重置表单和数据 // 关闭时重置表单和数据
@ -213,6 +273,8 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
setSelectedNetworkType([]); setSelectedNetworkType([]);
setCurrentNetworkCard(''); setCurrentNetworkCard('');
setCurrentIp(''); setCurrentIp('');
setConfigLoaded(false);
setConfigLoading(false);
} }
}, [visible]); }, [visible]);
@ -228,195 +290,209 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
style={{ width: '80%' }} style={{ width: '80%' }}
> >
<Form form={form} autoComplete="off" labelCol={{ flex: 'none' }}> <Form form={form} autoComplete="off" labelCol={{ flex: 'none' }}>
<> {/* 容器资源配置区域 - 需要点击按钮获取 */}
<Grid.Row gutter={8}> {!configLoaded ? (
<Grid.Col span={12}> <div style={{
<FormItem textAlign: 'center',
label="CPU核数" padding: '20px 0',
field="cpuCount" marginBottom: 16,
initialValue={1} background: '#f7f8fa',
> borderRadius: 4
<Slider }}>
min={1} <Button
max={4} type="primary"
step={1} loading={configLoading}
showTicks={true} onClick={getContainerConfig}
marks={{ >
1: '1核',
2: '', </Button>
3: '', <div style={{ marginTop: 8, color: '#86909c', fontSize: 13 }}>
4: '4核'
}} </div>
style={{ </div>
width: '90%', ) : (
whiteSpace: 'nowrap' <>
}} <Grid.Row gutter={8}>
/> <Grid.Col span={12}>
</FormItem> <FormItem
</Grid.Col> label="CPU核数"
<Grid.Col span={12}> field="cpuCount"
<FormItem initialValue={1}
label="是否使用GPU"
field="useGpu"
initialValue={false}
triggerPropName="checked"
>
<Switch checkedText="ON" uncheckedText="OFF" />
</FormItem>
</Grid.Col>
</Grid.Row>
<FormItem
label="最大内存:"
field="memory"
initialValue={2048}
>
<Slider
min={64}
max={4069}
step={64}
marks={{
64: '64MB',
1024: '1GB',
2048: '2GB',
4069: '4GB'
}}
showInput={{
hideControl: false,
suffix: 'MB',
style: {
width: 100
}
}}
style={{ width: '100%' }}
/>
</FormItem>
<Grid.Row gutter={8}>
<Grid.Col span={8}>
<FormItem
label="网络模式:"
field="networkMode"
rules={[{ required: true, message: '请选择网络模式' }]}
style={{ flexWrap: 'nowrap' }}
>
<Select
placeholder="请选择网络模式"
style={{ width: '80%', minWidth: 120 }}
onChange={(value) => fetchNetworkMode(value, addItem.id)}
> >
{netType.map((option, index) => ( <Slider
<Option key={option.value} value={option.value}> min={1}
{option.label} max={containerConfig.maxCpuCores}
</Option> step={1}
))} showTicks={true}
</Select> marks={cpuMarks}
</FormItem> style={{
</Grid.Col> width: '90%',
<Grid.Col span={8}> whiteSpace: 'nowrap'
<FormItem }}
label="网卡:" />
field="networkType" </FormItem>
rules={[{ required: true, message: '请选择网卡' }]} </Grid.Col>
style={{ flexWrap: 'nowrap' }} <Grid.Col span={12}>
> <FormItem
<Select label="是否使用GPU"
placeholder="请选择网卡" field="useGpu"
style={{ width: '90%', minWidth: 120 }} initialValue={false}
value={currentNetworkCard} triggerPropName="checked"
onChange={value => {
setCurrentNetworkCard(value);
form.setFieldValue('networkType', value);
const selectedCard = networkTypeList.find(item => item.name === value);
setSelectedNetworkType(selectedCard ? [selectedCard] : []);
}}
> >
{networkTypeList.map((option, index) => ( <Switch checkedText="ON" uncheckedText="OFF" />
<Option key={option.type} value={option.name}> </FormItem>
{option.name} </Grid.Col>
</Option> </Grid.Row>
))} <FormItem
</Select> label="最大内存:"
</FormItem> field="memory"
</Grid.Col> initialValue={2048}
<Grid.Col span={8}> >
<FormItem <Slider
label="IP" min={1024}
field="ip" max={containerConfig.maxMemoryMiB}
style={{ flexWrap: 'nowrap' }} step={64}
marks={memoryMarks}
showInput={{
hideControl: false,
suffix: 'MB',
style: {
width: 100
}
}}
style={{ width: '100%' }}
/>
</FormItem>
</>
)}
{/* 以下配置默认展示 */}
<Grid.Row gutter={8}>
<Grid.Col span={8}>
<FormItem
label="网络模式:"
field="networkMode"
rules={[{ required: true, message: '请选择网络模式' }]}
style={{ flexWrap: 'nowrap' }}
>
<Select
placeholder="请选择网络模式"
style={{ width: '80%', minWidth: 120 }}
onChange={(value) => fetchNetworkMode(value, addItem.id)}
>
{netType.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={8}>
<FormItem
label="网卡:"
field="networkType"
rules={[{ required: true, message: '请选择网卡' }]}
style={{ flexWrap: 'nowrap' }}
>
<Select
placeholder="请选择网卡"
style={{ width: '90%', minWidth: 120 }}
value={currentNetworkCard}
onChange={value => {
setCurrentNetworkCard(value);
form.setFieldValue('networkType', value);
const selectedCard = networkTypeList.find(item => item.name === value);
setSelectedNetworkType(selectedCard ? [selectedCard] : []);
}}
> >
<Input {networkTypeList.map((option, index) => (
style={{ width: '90%', minWidth: 120 }} <Option key={option.type} value={option.name}>
allowClear {option.name}
placeholder="请输入IP" </Option>
value={currentIp} ))}
onChange={(value) => { </Select>
setCurrentIp(value); </FormItem>
form.setFieldValue('ip', value); </Grid.Col>
}} <Grid.Col span={8}>
onBlur={(e) => { <FormItem
const value = e.target.value; label="IP"
if (value) { field="ip"
// IP地址正则表达式 style={{ flexWrap: 'nowrap' }}
const ipRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; >
if (!ipRegex.test(value)) { <Input
Message.warning('请输入有效的IP地址'); style={{ width: '90%', minWidth: 120 }}
} allowClear
placeholder="请输入IP"
value={currentIp}
onChange={(value) => {
setCurrentIp(value);
form.setFieldValue('ip', value);
}}
onBlur={(e) => {
const value = e.target.value;
if (value) {
// IP地址正则表达式
const ipRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
if (!ipRegex.test(value)) {
Message.warning('请输入有效的IP地址');
} }
}} }
/> }}
<div style={{ />
fontSize: 12, <div style={{
color: '#c0c4cc', fontSize: 12,
marginTop: 10 color: '#c0c4cc',
}}>: {selectedNetworkType.length > 0 ? selectedNetworkType[0]?.subnet : '-'}</div> marginTop: 10
</FormItem> }}>: {selectedNetworkType.length > 0 ? selectedNetworkType[0]?.subnet : '-'}</div>
</Grid.Col> </FormItem>
</Grid.Row> </Grid.Col>
<FormItem label="端口映射:"> </Grid.Row>
<EditableTable <FormItem label="端口映射:">
columns={portColumns} <EditableTable
data={portMappingData} columns={portColumns}
onDataChange={(newData) => setPortMappingData(newData)} data={portMappingData}
showAddButton={true} onDataChange={(newData) => setPortMappingData(newData)}
addButtonText="添加行" showAddButton={true}
showDeleteButton={true} addButtonText="添加行"
deleteButtonText="删除" showDeleteButton={true}
tableProps={{ deleteButtonText="删除"
pagination: { pageSize: 5 }, tableProps={{
scroll: { y: 400 } pagination: { pageSize: 5 },
}} scroll: { y: 400 }
/> }}
</FormItem> />
<FormItem label="目录挂载:"> </FormItem>
<EditableTable <FormItem label="目录挂载:">
columns={directoryColumns} <EditableTable
data={directoryMountData} columns={directoryColumns}
onDataChange={(newData) => setDirectoryMountData(newData)} data={directoryMountData}
showAddButton={true} onDataChange={(newData) => setDirectoryMountData(newData)}
addButtonText="添加行" showAddButton={true}
showDeleteButton={true} addButtonText="添加行"
deleteButtonText="删除" showDeleteButton={true}
tableProps={{ deleteButtonText="删除"
pagination: { pageSize: 5 }, tableProps={{
scroll: { y: 400 } pagination: { pageSize: 5 },
}} scroll: { y: 400 }
/> }}
</FormItem> />
<FormItem label="设备挂载:"> </FormItem>
<EditableTable <FormItem label="设备挂载:">
columns={deviceColumns} <EditableTable
data={deviceMountData} columns={deviceColumns}
onDataChange={(newData) => setDeviceMountData(newData)} data={deviceMountData}
showAddButton={true} onDataChange={(newData) => setDeviceMountData(newData)}
addButtonText="添加行" showAddButton={true}
showDeleteButton={true} addButtonText="添加行"
deleteButtonText="删除" showDeleteButton={true}
tableProps={{ deleteButtonText="删除"
pagination: { pageSize: 5 }, tableProps={{
scroll: { y: 400 } pagination: { pageSize: 5 },
}} scroll: { y: 400 }
/> }}
</FormItem> />
</> </FormItem>
</Form> </Form>
</Modal> </Modal>

Loading…
Cancel
Save