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

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

@ -22,3 +22,8 @@ export function stopContainer(deployEnvId) {
export function getContainer(deployEnvId) {
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 { Modal, Form, Select, Grid, Slider, Switch, Input, InputNumber, Message } from '@arco-design/web-react';
import React, { useEffect, useState, useMemo } from 'react';
import {
Modal,
Form,
Select,
Grid,
Slider,
Switch,
Input,
InputNumber,
Message,
Button,
Spin
} from '@arco-design/web-react';
import EditableTable from '@/components/EditableTable';
import { containerRegister } from '@/api/userContainer';
import { containerRegister, getContainerLimit } from '@/api/userContainer';
import { getNetworkMode } from '@/api/componentDeploy';
const FormItem = Form.Item;
@ -124,6 +136,12 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
const [currentNetworkCard, setCurrentNetworkCard] = useState(''); // 当前选择的网卡
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 = () => {
@ -135,6 +153,8 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
setSelectedNetworkType([]);
setCurrentNetworkCard('');
setCurrentIp('');
setConfigLoaded(false);
setConfigLoading(false);
setVisible(false);
};
@ -168,7 +188,7 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
volumes: directoryMountData, // 目录挂载
devices: deviceMountData, // 设备挂载
// 下面是暂时写死的数据,
'restartPolicy': 'always',
'restartPolicy': 'always'
};
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(() => {
if (envType.length > 0) setEnvTypeOptions(envType);
}, [envType]);
@ -196,12 +261,7 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
useEffect(() => {
if (visible) {
// 设置表单默认值
form.setFieldsValue({
cpuCount: 1,
memory: 2048,
useGpu: false
});
// 不再自动加载,等用户点击按钮
}
else {
// 关闭时重置表单和数据
@ -213,6 +273,8 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
setSelectedNetworkType([]);
setCurrentNetworkCard('');
setCurrentIp('');
setConfigLoaded(false);
setConfigLoading(false);
}
}, [visible]);
@ -228,195 +290,209 @@ const ContainerModal = ({ addItem, visible, envType, setVisible, onSuccess }) =>
style={{ width: '80%' }}
>
<Form form={form} autoComplete="off" labelCol={{ flex: 'none' }}>
<>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem
label="CPU核数"
field="cpuCount"
initialValue={1}
>
<Slider
min={1}
max={4}
step={1}
showTicks={true}
marks={{
1: '1核',
2: '',
3: '',
4: '4核'
}}
style={{
width: '90%',
whiteSpace: 'nowrap'
}}
/>
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem
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)}
{/* 容器资源配置区域 - 需要点击按钮获取 */}
{!configLoaded ? (
<div style={{
textAlign: 'center',
padding: '20px 0',
marginBottom: 16,
background: '#f7f8fa',
borderRadius: 4
}}>
<Button
type="primary"
loading={configLoading}
onClick={getContainerConfig}
>
</Button>
<div style={{ marginTop: 8, color: '#86909c', fontSize: 13 }}>
</div>
</div>
) : (
<>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem
label="CPU核数"
field="cpuCount"
initialValue={1}
>
{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] : []);
}}
<Slider
min={1}
max={containerConfig.maxCpuCores}
step={1}
showTicks={true}
marks={cpuMarks}
style={{
width: '90%',
whiteSpace: 'nowrap'
}}
/>
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem
label="是否使用GPU"
field="useGpu"
initialValue={false}
triggerPropName="checked"
>
{networkTypeList.map((option, index) => (
<Option key={option.type} value={option.name}>
{option.name}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={8}>
<FormItem
label="IP"
field="ip"
style={{ flexWrap: 'nowrap' }}
<Switch checkedText="ON" uncheckedText="OFF" />
</FormItem>
</Grid.Col>
</Grid.Row>
<FormItem
label="最大内存:"
field="memory"
initialValue={2048}
>
<Slider
min={1024}
max={containerConfig.maxMemoryMiB}
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
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地址');
}
{networkTypeList.map((option, index) => (
<Option key={option.type} value={option.name}>
{option.name}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={8}>
<FormItem
label="IP"
field="ip"
style={{ flexWrap: 'nowrap' }}
>
<Input
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,
color: '#c0c4cc',
marginTop: 10
}}>: {selectedNetworkType.length > 0 ? selectedNetworkType[0]?.subnet : '-'}</div>
</FormItem>
</Grid.Col>
</Grid.Row>
<FormItem label="端口映射:">
<EditableTable
columns={portColumns}
data={portMappingData}
onDataChange={(newData) => setPortMappingData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
<FormItem label="目录挂载:">
<EditableTable
columns={directoryColumns}
data={directoryMountData}
onDataChange={(newData) => setDirectoryMountData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
<FormItem label="设备挂载:">
<EditableTable
columns={deviceColumns}
data={deviceMountData}
onDataChange={(newData) => setDeviceMountData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
</>
}
}}
/>
<div style={{
fontSize: 12,
color: '#c0c4cc',
marginTop: 10
}}>: {selectedNetworkType.length > 0 ? selectedNetworkType[0]?.subnet : '-'}</div>
</FormItem>
</Grid.Col>
</Grid.Row>
<FormItem label="端口映射:">
<EditableTable
columns={portColumns}
data={portMappingData}
onDataChange={(newData) => setPortMappingData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
<FormItem label="目录挂载:">
<EditableTable
columns={directoryColumns}
data={directoryMountData}
onDataChange={(newData) => setDirectoryMountData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
<FormItem label="设备挂载:">
<EditableTable
columns={deviceColumns}
data={deviceMountData}
onDataChange={(newData) => setDeviceMountData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
</Form>
</Modal>

Loading…
Cancel
Save