mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-16 18:54:43 +08:00
feat: 容器操作增加制作容器镜像功能 (#5447)
This commit is contained in:
parent
584234c4f1
commit
2513c10d22
@ -346,6 +346,26 @@ func (b *BaseApi) ContainerRename(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Container
|
||||||
|
// @Summary Commit Container
|
||||||
|
// @Description 容器提交生成新镜像
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ContainerCommit true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Router /containers/commit [post]
|
||||||
|
func (b *BaseApi) ContainerCommit(c *gin.Context) {
|
||||||
|
var req dto.ContainerCommit
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerService.ContainerCommit(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Container
|
// @Tags Container
|
||||||
// @Summary Operate Container
|
// @Summary Operate Container
|
||||||
// @Description 容器操作
|
// @Description 容器操作
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type PageContainer struct {
|
type PageContainer struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
@ -122,6 +124,15 @@ type ContainerRename struct {
|
|||||||
NewName string `json:"newName" validate:"required"`
|
NewName string `json:"newName" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerCommit struct {
|
||||||
|
ContainerId string `json:"containerID" validate:"required"`
|
||||||
|
ContainerName string `json:"containerName"`
|
||||||
|
NewImageName string `json:"newImageName"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Pause bool `json:"pause"`
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerPrune struct {
|
type ContainerPrune struct {
|
||||||
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network buildcache"`
|
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network buildcache"`
|
||||||
WithTagAll bool `json:"withTagAll"`
|
WithTagAll bool `json:"withTagAll"`
|
||||||
|
@ -60,6 +60,7 @@ type IContainerService interface {
|
|||||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
ContainerListStats() ([]dto.ContainerListStats, error)
|
||||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||||
ContainerRename(req dto.ContainerRename) error
|
ContainerRename(req dto.ContainerRename) error
|
||||||
|
ContainerCommit(req dto.ContainerCommit) error
|
||||||
ContainerLogClean(req dto.OperationWithName) error
|
ContainerLogClean(req dto.OperationWithName) error
|
||||||
ContainerOperation(req dto.ContainerOperation) error
|
ContainerOperation(req dto.ContainerOperation) error
|
||||||
ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error
|
ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error
|
||||||
@ -597,6 +598,28 @@ func (u *ContainerService) ContainerRename(req dto.ContainerRename) error {
|
|||||||
return client.ContainerRename(ctx, req.Name, req.NewName)
|
return client.ContainerRename(ctx, req.Name, req.NewName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
options := container.CommitOptions{
|
||||||
|
Reference: req.NewImageName,
|
||||||
|
Comment: req.Comment,
|
||||||
|
Author: req.Author,
|
||||||
|
Changes: nil,
|
||||||
|
Pause: req.Pause,
|
||||||
|
Config: nil,
|
||||||
|
}
|
||||||
|
_, err = client.ContainerCommit(ctx, req.ContainerId, options)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to commit container, err: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -31,6 +31,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.POST("/load/log", baseApi.LoadContainerLog)
|
baRouter.POST("/load/log", baseApi.LoadContainerLog)
|
||||||
baRouter.POST("/inspect", baseApi.Inspect)
|
baRouter.POST("/inspect", baseApi.Inspect)
|
||||||
baRouter.POST("/rename", baseApi.ContainerRename)
|
baRouter.POST("/rename", baseApi.ContainerRename)
|
||||||
|
baRouter.POST("/commit", baseApi.ContainerCommit)
|
||||||
baRouter.POST("/operate", baseApi.ContainerOperation)
|
baRouter.POST("/operate", baseApi.ContainerOperation)
|
||||||
baRouter.POST("/prune", baseApi.ContainerPrune)
|
baRouter.POST("/prune", baseApi.ContainerPrune)
|
||||||
|
|
||||||
|
@ -9,6 +9,14 @@ export namespace Container {
|
|||||||
name: string;
|
name: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
}
|
}
|
||||||
|
export interface ContainerCommit {
|
||||||
|
containerID: string;
|
||||||
|
containerName: string;
|
||||||
|
newImageName: string;
|
||||||
|
comment: string;
|
||||||
|
author: string;
|
||||||
|
pause: boolean;
|
||||||
|
}
|
||||||
export interface ContainerSearch extends ReqPage {
|
export interface ContainerSearch extends ReqPage {
|
||||||
name: string;
|
name: string;
|
||||||
state: string;
|
state: string;
|
||||||
|
@ -21,6 +21,9 @@ export const updateContainer = (params: Container.ContainerHelper) => {
|
|||||||
export const upgradeContainer = (name: string, image: string, forcePull: boolean) => {
|
export const upgradeContainer = (name: string, image: string, forcePull: boolean) => {
|
||||||
return http.post(`/containers/upgrade`, { name: name, image: image, forcePull: forcePull }, TimeoutEnum.T_10M);
|
return http.post(`/containers/upgrade`, { name: name, image: image, forcePull: forcePull }, TimeoutEnum.T_10M);
|
||||||
};
|
};
|
||||||
|
export const commitContainer = (params: Container.ContainerCommit) => {
|
||||||
|
return http.post(`/containers/commit`, params);
|
||||||
|
};
|
||||||
export const loadContainerInfo = (name: string) => {
|
export const loadContainerInfo = (name: string) => {
|
||||||
return http.post<Container.ContainerHelper>(`/containers/info`, { name: name });
|
return http.post<Container.ContainerHelper>(`/containers/info`, { name: name });
|
||||||
};
|
};
|
||||||
|
@ -811,6 +811,13 @@ const message = {
|
|||||||
cleanImagesHelper: '( Clean up all images that are not used by any containers )',
|
cleanImagesHelper: '( Clean up all images that are not used by any containers )',
|
||||||
cleanContainersHelper: '( Clean up all stopped containers )',
|
cleanContainersHelper: '( Clean up all stopped containers )',
|
||||||
cleanVolumesHelper: '( Clean up all unused local volumes )',
|
cleanVolumesHelper: '( Clean up all unused local volumes )',
|
||||||
|
|
||||||
|
makeImage: 'Create Image',
|
||||||
|
newImageName: 'New Image Name',
|
||||||
|
commitMessage: 'Commit Message',
|
||||||
|
author: 'Author',
|
||||||
|
ifPause: 'Pause Container During Creation',
|
||||||
|
ifMakeImageWithContainer: 'Create New Image from This Container?',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Create Cronjob',
|
create: 'Create Cronjob',
|
||||||
|
@ -776,6 +776,13 @@ const message = {
|
|||||||
cleanImagesHelper: '( 清理所有未被任何容器使用的鏡像 )',
|
cleanImagesHelper: '( 清理所有未被任何容器使用的鏡像 )',
|
||||||
cleanContainersHelper: '( 清理所有處於停止狀態的容器 )',
|
cleanContainersHelper: '( 清理所有處於停止狀態的容器 )',
|
||||||
cleanVolumesHelper: '( 清理所有未被使用的本地存儲卷 )',
|
cleanVolumesHelper: '( 清理所有未被使用的本地存儲卷 )',
|
||||||
|
|
||||||
|
makeImage: '製作鏡像',
|
||||||
|
newImageName: '新鏡像名稱',
|
||||||
|
commitMessage: '提交信息',
|
||||||
|
author: '作者',
|
||||||
|
ifPause: '製作過程中是否暫停容器',
|
||||||
|
ifMakeImageWithContainer: '是否根據此容器製作新鏡像?',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: '創建計劃任務',
|
create: '創建計劃任務',
|
||||||
|
@ -777,6 +777,13 @@ const message = {
|
|||||||
cleanImagesHelper: '( 清理所有未被任何容器使用的镜像 )',
|
cleanImagesHelper: '( 清理所有未被任何容器使用的镜像 )',
|
||||||
cleanContainersHelper: '( 清理所有处于停止状态的容器 )',
|
cleanContainersHelper: '( 清理所有处于停止状态的容器 )',
|
||||||
cleanVolumesHelper: '( 清理所有未被使用的本地存储卷 )',
|
cleanVolumesHelper: '( 清理所有未被使用的本地存储卷 )',
|
||||||
|
|
||||||
|
makeImage: '制作镜像',
|
||||||
|
newImageName: '新镜像名称',
|
||||||
|
commitMessage: '提交信息',
|
||||||
|
author: '作者',
|
||||||
|
ifPause: '制作过程中是否暂停容器',
|
||||||
|
ifMakeImageWithContainer: '是否根据此容器制作新镜像?',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: '创建计划任务',
|
create: '创建计划任务',
|
||||||
|
119
frontend/src/views/container/container/commit/index.vue
Normal file
119
frontend/src/views/container/container/commit/index.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisible"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
size="50%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('container.makeImage')" :resource="form.containerName" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-row v-loading="loading">
|
||||||
|
<el-col :span="22" :offset="1">
|
||||||
|
<el-form @submit.prevent ref="formRef" :model="form" label-position="top">
|
||||||
|
<el-form-item prop="newImageName" :rules="Rules.imageName">
|
||||||
|
<template #label>
|
||||||
|
{{ $t('container.newImageName') }}
|
||||||
|
</template>
|
||||||
|
<el-input v-model="form.newImageName" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="comment">
|
||||||
|
<template #label>
|
||||||
|
{{ $t('container.commitMessage') }}
|
||||||
|
</template>
|
||||||
|
<el-input v-model="form.comment" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="author">
|
||||||
|
<template #label>
|
||||||
|
{{ $t('container.author') }}
|
||||||
|
</template>
|
||||||
|
<el-input v-model="form.author" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="pause">
|
||||||
|
<el-checkbox v-model="form.pause">
|
||||||
|
{{ $t('container.ifPause') }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="drawerVisible = false">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { commitContainer } from '@/api/modules/container';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const drawerVisible = ref<boolean>(false);
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
const loading = ref(false);
|
||||||
|
const form = reactive({
|
||||||
|
containerID: '',
|
||||||
|
containerName: '',
|
||||||
|
newImageName: '',
|
||||||
|
comment: '',
|
||||||
|
author: '',
|
||||||
|
pause: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
containerID: string;
|
||||||
|
containerName: string;
|
||||||
|
}
|
||||||
|
const acceptParams = (props: DialogProps): void => {
|
||||||
|
form.containerID = props.containerID;
|
||||||
|
form.containerName = props.containerName;
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(i18n.global.t('container.ifMakeImageWithContainer'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await commitContainer(form)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = async () => {
|
||||||
|
drawerVisible.value = false;
|
||||||
|
emit('search');
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -311,6 +311,7 @@
|
|||||||
<ContainerLogDialog ref="dialogContainerLogRef" />
|
<ContainerLogDialog ref="dialogContainerLogRef" />
|
||||||
<OperateDialog @search="search" ref="dialogOperateRef" />
|
<OperateDialog @search="search" ref="dialogOperateRef" />
|
||||||
<UpgradeDialog @search="search" ref="dialogUpgradeRef" />
|
<UpgradeDialog @search="search" ref="dialogUpgradeRef" />
|
||||||
|
<CommitDialog @search="search" ref="dialogCommitRef" />
|
||||||
<MonitorDialog ref="dialogMonitorRef" />
|
<MonitorDialog ref="dialogMonitorRef" />
|
||||||
<TerminalDialog ref="dialogTerminalRef" />
|
<TerminalDialog ref="dialogTerminalRef" />
|
||||||
|
|
||||||
@ -323,6 +324,7 @@ import PruneDialog from '@/views/container/container/prune/index.vue';
|
|||||||
import RenameDialog from '@/views/container/container/rename/index.vue';
|
import RenameDialog from '@/views/container/container/rename/index.vue';
|
||||||
import OperateDialog from '@/views/container/container/operate/index.vue';
|
import OperateDialog from '@/views/container/container/operate/index.vue';
|
||||||
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
||||||
|
import CommitDialog from '@/views/container/container/commit/index.vue';
|
||||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||||
import ContainerLogDialog from '@/views/container/container/log/index.vue';
|
import ContainerLogDialog from '@/views/container/container/log/index.vue';
|
||||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||||
@ -363,6 +365,7 @@ const paginationConfig = reactive({
|
|||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
const searchState = ref('all');
|
const searchState = ref('all');
|
||||||
const dialogUpgradeRef = ref();
|
const dialogUpgradeRef = ref();
|
||||||
|
const dialogCommitRef = ref();
|
||||||
const dialogPortJumpRef = ref();
|
const dialogPortJumpRef = ref();
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const includeAppStore = ref(true);
|
const includeAppStore = ref(true);
|
||||||
@ -688,6 +691,15 @@ const buttons = [
|
|||||||
return row.isFromCompose;
|
return row.isFromCompose;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.makeImage'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
dialogCommitRef.value!.acceptParams({ containerID: row.containerID, containerName: row.name });
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('commit', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('container.start'),
|
label: i18n.global.t('container.start'),
|
||||||
click: (row: Container.ContainerInfo) => {
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user