1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-13 17:24:44 +08:00

feat: 增加容器创建表单内容 (#6772)

This commit is contained in:
ssongliu 2024-10-18 16:43:37 +08:00 committed by GitHub
parent 5821cf0061
commit 810891236b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 245 additions and 121 deletions

View File

@ -71,12 +71,18 @@ type ContainerOperate struct {
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
Network string `json:"network"`
Hostname string `json:"hostname"`
DomainName string `json:"domainName"`
MacAddr string `json:"macAddr"`
DNS []string `json:"dns"`
Ipv4 string `json:"ipv4"`
Ipv6 string `json:"ipv6"`
PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"`
Tty bool `json:"tty"`
OpenStdin bool `json:"openStdin"`
WorkingDir string `json:"workingDir"`
User string `json:"user"`
Cmd []string `json:"cmd"`
Entrypoint []string `json:"entrypoint"`
CPUShares int64 `json:"cpuShares"`

View File

@ -557,14 +557,20 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
data.MacAddr = bridgeNetworkSettings.MacAddress
data.Ipv4 = ipv4Address
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
data.Ipv6 = ipv6Address
} else {
data.Ipv4 = bridgeNetworkSettings.IPAddress
}
data.Hostname = oldContainer.Config.Hostname
data.DNS = oldContainer.HostConfig.DNS
data.DomainName = oldContainer.Config.Domainname
data.Cmd = oldContainer.Config.Cmd
data.WorkingDir = oldContainer.Config.WorkingDir
data.User = oldContainer.Config.User
data.OpenStdin = oldContainer.Config.OpenStdin
data.Tty = oldContainer.Config.Tty
data.Entrypoint = oldContainer.Config.Entrypoint
@ -973,7 +979,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
return nil, err
}
res.Body.Close()
var stats *types.StatsJSON
var stats *container.StatsResponse
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
}
@ -1037,7 +1043,7 @@ func stringsToMap(list []string) map[string]string {
return labelMap
}
func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
func calculateCPUPercentUnix(stats *container.StatsResponse) float64 {
cpuPercent := 0.0
cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)
@ -1050,7 +1056,7 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
}
return cpuPercent
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
func calculateMemPercentUnix(memStats container.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage)
memLimit := float64(memStats.Limit)
@ -1059,7 +1065,7 @@ func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
}
return memPercent
}
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
func calculateBlockIO(blkio container.BlkioStats) (blkRead float64, blkWrite float64) {
for _, bioEntry := range blkio.IoServiceBytesRecursive {
switch strings.ToLower(bioEntry.Op) {
case "read":
@ -1070,7 +1076,7 @@ func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64
}
return
}
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
func calculateNetwork(network map[string]container.NetworkStats) (float64, float64) {
var rx, tx float64
for _, v := range network {
@ -1132,11 +1138,11 @@ func pullImages(ctx context.Context, client *client.Client, imageName string) er
return nil
}
func loadCpuAndMem(client *client.Client, container string) dto.ContainerListStats {
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
data := dto.ContainerListStats{
ContainerID: container,
ContainerID: containerItem,
}
res, err := client.ContainerStats(context.Background(), container, false)
res, err := client.ContainerStats(context.Background(), containerItem, false)
if err != nil {
return data
}
@ -1146,7 +1152,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
if err != nil {
return data
}
var stats *types.StatsJSON
var stats *container.StatsResponse
if err := json.Unmarshal(body, &stats); err != nil {
return data
}
@ -1235,6 +1241,10 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
config.ExposedPorts = exposed
config.OpenStdin = req.OpenStdin
config.Tty = req.Tty
config.Hostname = req.Hostname
config.Domainname = req.DomainName
config.User = req.User
config.WorkingDir = req.WorkingDir
if len(req.Network) != 0 {
switch req.Network {
@ -1242,13 +1252,11 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
hostConf.NetworkMode = container.NetworkMode(req.Network)
}
if req.Ipv4 != "" || req.Ipv6 != "" {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{
req.Network: {
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: req.Ipv4,
IPv6Address: req.Ipv6,
},
}}
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: req.Ipv4,
IPv6Address: req.Ipv6,
}, MacAddress: req.MacAddr}}
} else {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
}
@ -1273,6 +1281,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
hostConf.PortBindings = portMap
hostConf.Binds = []string{}
hostConf.Mounts = []mount.Mount{}
hostConf.DNS = req.DNS
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
if volume.Type == "volume" {

View File

@ -55,12 +55,18 @@ export namespace Container {
imageInput: boolean;
forcePull: boolean;
network: string;
hostname: string;
domainName: string;
macAddr: string;
ipv4: string;
ipv6: string;
dns: Array<string>;
cmdStr: string;
entrypointStr: string;
memoryItem: number;
cmd: Array<string>;
workingDir: string;
user: string;
openStdin: boolean;
tty: boolean;
entrypoint: Array<string>;
@ -73,9 +79,7 @@ export namespace Container {
privileged: boolean;
autoRemove: boolean;
labels: Array<string>;
labelsStr: string;
env: Array<string>;
envStr: string;
restartPolicy: string;
}
export interface Port {

View File

@ -636,6 +636,7 @@ const message = {
cleanLogHelper:
'Clearing logs requires restarting the container, and this operation cannot be rolled back. Do you want to continue?',
newName: 'New name',
workingDir: 'Working Dir',
source: 'Resource Rate',
cpuUsage: 'CPU Usage',
cpuTotal: 'CPU Total',
@ -688,6 +689,7 @@ const message = {
cpuQuota: 'NacosCPU',
memoryLimit: 'Memory',
limitHelper: 'If you limit it to 0, then the limitation is turned off, and the maximum available is {0}.',
macAddr: 'MAC Address',
mount: 'Mount',
volumeOption: 'Volume',
hostOption: 'Host',

View File

@ -615,6 +615,7 @@ const message = {
downLogHelper2: '即將下載 {0} 容器最近 {1} 條日誌是否繼續',
cleanLogHelper: '清空日誌需要重啟容器該操作無法回滾是否繼續',
newName: '新名稱',
workingDir: '工作目錄',
source: '資源使用率',
cpuUsage: 'CPU 使用',
cpuTotal: 'CPU 總計',
@ -662,6 +663,7 @@ const message = {
cpuQuota: 'CPU 限製',
memoryLimit: '內存限製',
limitHelper: '限製為 0 則關閉限製最大可用為 {0}',
macAddr: 'MAC 地址',
mount: '掛載',
volumeOption: '掛載卷',
hostOption: '本機目錄',

View File

@ -616,6 +616,7 @@ const message = {
downLogHelper2: '即将下载 {0} 容器最近 {1} 条日志是否继续',
cleanLogHelper: '清空日志需要重启容器该操作无法回滚是否继续',
newName: '新名称',
workingDir: '工作目录',
source: '资源使用率',
cpuUsage: 'CPU 使用',
cpuTotal: 'CPU 总计',
@ -626,8 +627,8 @@ const message = {
ip: 'IP 地址',
cpuShare: 'CPU 权重',
cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间',
inputIpv4: '请输入 ipv4 地址',
inputIpv6: '请输入 ipv6 地址',
inputIpv4: '请输入 IPv4 地址',
inputIpv6: '请输入 IPv6 地址',
containerFromAppHelper: '检测到该容器来源于应用商店应用操作可能会导致当前编辑失效',
containerFromAppHelper1: '在已安装应用列表点击 `参数` 按钮进入编辑页面即可修改容器名称',
@ -663,6 +664,7 @@ const message = {
cpuQuota: 'CPU 限制',
memoryLimit: '内存限制',
limitHelper: '限制为 0 则关闭限制最大可用为 {0}',
macAddr: 'MAC 地址',
mount: '挂载',
volumeOption: '挂载卷',
hostOption: '本机目录',
@ -2286,7 +2288,7 @@ const message = {
targetPort: '目标端口',
forwardHelper1: '如果是本机端口转发目标IP为127.0.0.1',
forwardHelper2: '如果目标IP不填写则默认为本机端口转发',
forwardHelper3: '当前仅支持 ipv4 的端口转发',
forwardHelper3: '当前仅支持 IPv4 的端口转发',
},
runtime: {
runtime: '运行环境',

View File

@ -128,30 +128,69 @@
<el-tabs type="border-card" class="mt-5">
<el-tab-pane :label="$t('container.network')">
<el-form-item :label="$t('container.network')" prop="network">
<el-select class="mini-form-item" v-model="form.network">
<el-option
v-for="(item, indexV) of networks"
:key="indexV"
:value="item.option"
:label="item.option"
/>
</el-select>
</el-form-item>
<el-form-item label="ipv4" prop="ipv4">
<el-input
class="mini-form-item"
v-model="form.ipv4"
:placeholder="$t('container.inputIpv4')"
/>
</el-form-item>
<el-form-item label="ipv6" prop="ipv6">
<el-input
class="mini-form-item"
v-model="form.ipv6"
:placeholder="$t('container.inputIpv6')"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item :label="$t('container.network')" prop="network">
<el-select v-model="form.network">
<el-option
v-for="(item, indexV) of networks"
:key="indexV"
:value="item.option"
:label="item.option"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item :label="$t('toolbox.device.hostname')" prop="hostname">
<el-input v-model="form.hostname" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item label="Domain" prop="domainName">
<el-input v-model="form.domainName" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item :label="$t('container.macAddr')" prop="macAddr">
<el-input v-model="form.macAddr" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item label="IPv4" prop="ipv4">
<el-input
v-model="form.ipv4"
:placeholder="$t('container.inputIpv4')"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item label="IPv6" prop="ipv6">
<el-input
v-model="form.ipv6"
:placeholder="$t('container.inputIpv6')"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item label="DNS" prop="dns">
<div v-for="(_, index) of form.dns" :key="index" class="w-full">
<el-input class="mt-2" v-model="form.dns[index]">
<template #append>
<el-button
link
icon="Delete"
@click="form.dns.splice(index, 1)"
/>
</template>
</el-input>
</div>
<el-button @click="form.dns.push('')">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane :label="$t('container.mount')">
@ -226,16 +265,40 @@
</el-form-item>
</el-tab-pane>
<el-tab-pane label="Command">
<el-form-item label="Command" prop="cmdStr">
<el-input v-model="form.cmdStr" :placeholder="$t('container.cmdHelper')" />
</el-form-item>
<el-form-item label="Entrypoint" prop="entrypointStr">
<el-input
v-model="form.entrypointStr"
:placeholder="$t('container.entrypointHelper')"
/>
</el-form-item>
<el-tab-pane :label="$t('terminal.command')">
<el-row :gutter="20">
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
<el-form-item label="Command" prop="cmdStr">
<el-input
v-model="form.cmdStr"
:placeholder="$t('container.cmdHelper')"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
<el-form-item label="Entrypoint" prop="entrypointStr">
<el-input
v-model="form.entrypointStr"
:placeholder="$t('container.entrypointHelper')"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item :label="$t('container.workingDir')" prop="workingDir">
<el-input v-model="form.workingDir" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<el-form-item :label="$t('commons.table.user')" prop="user">
<el-input v-model="form.user" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('container.console')">
<el-checkbox v-model="form.tty">{{ $t('container.tty') }}</el-checkbox>
<el-checkbox v-model="form.openStdin">
@ -285,22 +348,52 @@
</el-tab-pane>
<el-tab-pane :label="$t('container.tag') + ' & ' + $t('container.env')">
<el-form-item :label="$t('container.tag')" prop="labelsStr">
<el-input
type="textarea"
:placeholder="$t('container.tagHelper')"
:rows="3"
v-model="form.labelsStr"
/>
</el-form-item>
<el-form-item :label="$t('container.env')" prop="envStr">
<el-input
type="textarea"
:placeholder="$t('container.tagHelper')"
:rows="3"
v-model="form.envStr"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
<el-form-item :label="$t('container.tag')" prop="labels">
<div v-for="(_, index) of form.labels" :key="index" class="w-full">
<el-input
class="mt-2"
placeholder="e.g. key=val"
v-model="form.labels[index]"
>
<template #append>
<el-button
link
icon="Delete"
@click="form.labels.splice(index, 1)"
/>
</template>
</el-input>
</div>
<el-button @click="form.labels.push('')">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
<el-form-item :label="$t('container.env')" prop="envStr">
<div v-for="(_, index) of form.env" :key="index" class="w-full">
<el-input
class="mt-2"
placeholder="e.g. key=val"
v-model="form.env[index]"
>
<template #append>
<el-button
link
icon="Delete"
@click="form.env.splice(index, 1)"
/>
</template>
</el-input>
</div>
<el-button @click="form.env.push('')">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane :label="$t('container.restartPolicy')">
@ -364,12 +457,18 @@ const form = reactive<Container.ContainerHelper>({
imageInput: false,
forcePull: false,
network: '',
hostname: '',
domainName: '',
macAddr: '',
ipv4: '',
ipv6: '',
dns: [],
cmdStr: '',
entrypointStr: '',
memoryItem: 0,
cmd: [],
workingDir: '',
user: '',
openStdin: false,
tty: false,
entrypoint: [],
@ -382,58 +481,64 @@ const form = reactive<Container.ContainerHelper>({
privileged: false,
autoRemove: false,
labels: [],
labelsStr: '',
env: [],
envStr: '',
restartPolicy: 'no',
});
const search = async () => {
if (!isCreate.value) {
const res = await loadContainerInfo(form.containerID);
if (res.data) {
form.name = res.data.name;
form.image = res.data.image;
form.network = res.data.network;
form.ipv4 = res.data.ipv4;
form.ipv6 = res.data.ipv6;
form.openStdin = res.data.openStdin;
form.tty = res.data.tty;
form.publishAllPorts = res.data.publishAllPorts;
form.nanoCPUs = res.data.nanoCPUs;
form.cpuShares = res.data.cpuShares;
form.privileged = res.data.privileged;
form.autoRemove = res.data.autoRemove;
form.restartPolicy = res.data.restartPolicy;
form.memory = Number(res.data.memory.toFixed(2));
form.cmd = res.data.cmd || [];
let itemCmd = '';
for (const item of form.cmd) {
itemCmd += `'${item}' `;
}
form.cmdStr = itemCmd ? itemCmd.substring(0, itemCmd.length - 1) : '';
let itemEntrypoint = '';
if (res.data.entrypoint) {
for (const item of res.data.entrypoint) {
itemEntrypoint += `'${item}' `;
loading.value = true;
await loadContainerInfo(form.containerID)
.then((res) => {
loading.value = false;
form.name = res.data.name;
form.image = res.data.image;
form.network = res.data.network;
form.hostname = res.data.hostname;
form.domainName = res.data.domainName;
form.dns = res.data.dns;
form.ipv4 = res.data.ipv4;
form.ipv6 = res.data.ipv6;
form.openStdin = res.data.openStdin;
form.tty = res.data.tty;
form.publishAllPorts = res.data.publishAllPorts;
form.nanoCPUs = res.data.nanoCPUs;
form.cpuShares = res.data.cpuShares;
form.privileged = res.data.privileged;
form.autoRemove = res.data.autoRemove;
form.restartPolicy = res.data.restartPolicy;
form.memory = Number(res.data.memory.toFixed(2));
form.cmd = res.data.cmd || [];
form.user = res.data.user;
form.workingDir = res.data.workingDir;
let itemCmd = '';
for (const item of form.cmd) {
itemCmd += `'${item}' `;
}
}
form.cmdStr = itemCmd ? itemCmd.substring(0, itemCmd.length - 1) : '';
form.entrypointStr = itemEntrypoint ? itemEntrypoint.substring(0, itemEntrypoint.length - 1) : '';
form.labels = res.data.labels || [];
form.env = res.data.env || [];
form.labelsStr = res.data.labels.join('\n');
form.envStr = res.data.env.join('\n');
form.exposedPorts = res.data.exposedPorts || [];
for (const item of res.data.exposedPorts) {
if (item.hostIP) {
item.host = item.hostIP + ':' + item.hostPort;
} else {
item.host = item.hostPort;
let itemEntrypoint = '';
if (res.data.entrypoint) {
for (const item of res.data.entrypoint) {
itemEntrypoint += `'${item}' `;
}
}
}
form.volumes = res.data.volumes || [];
}
form.entrypointStr = itemEntrypoint ? itemEntrypoint.substring(0, itemEntrypoint.length - 1) : '';
form.labels = res.data.labels || [];
form.env = res.data.env || [];
form.exposedPorts = res.data.exposedPorts || [];
for (const item of res.data.exposedPorts) {
if (item.hostIP) {
item.host = item.hostIP + ':' + item.hostPort;
} else {
item.host = item.hostPort;
}
}
form.volumes = res.data.volumes || [];
})
.catch(() => {
loading.value = false;
});
}
loadLimit();
loadImageOptions();
@ -530,12 +635,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (form.envStr) {
form.env = form.envStr.split('\n');
}
if (form.labelsStr) {
form.labels = form.labelsStr.split('\n');
}
form.cmd = [];
if (form.cmdStr) {
if (form.cmdStr.indexOf(`'`) !== -1) {