fix: 增加 docker 仓库授信删除判断

This commit is contained in:
ssongliu 2022-12-20 13:23:01 +08:00
parent bdecd777a7
commit 326941ad48
13 changed files with 140 additions and 31 deletions

View File

@ -59,7 +59,7 @@ func (b *BaseApi) CreateRepo(c *gin.Context) {
}
func (b *BaseApi) DeleteRepo(c *gin.Context) {
var req dto.BatchDeleteReq
var req dto.ImageRepoDelete
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -69,7 +69,7 @@ func (b *BaseApi) DeleteRepo(c *gin.Context) {
return
}
if err := imageRepoService.BatchDelete(req.Ids); err != nil {
if err := imageRepoService.BatchDelete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}

View File

@ -38,3 +38,8 @@ type ImageRepoOption struct {
Name string `json:"name"`
DownloadUrl string `json:"downloadUrl"`
}
type ImageRepoDelete struct {
DeleteInsecure bool `json:"deleteInsecure"`
Ids []uint `json:"ids" validate:"required"`
}

View File

@ -23,7 +23,7 @@ type IImageRepoService interface {
List() ([]dto.ImageRepoOption, error)
Create(req dto.ImageRepoCreate) error
Update(req dto.ImageRepoUpdate) error
BatchDelete(ids []uint) error
BatchDelete(req dto.ImageRepoDelete) error
}
func NewIImageRepoService() IImageRepoService {
@ -70,6 +70,13 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if err := copier.Copy(&imageRepo, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
cmd := exec.Command("systemctl", "restart", "docker")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
imageRepo.Status = constant.StatusSuccess
if err := u.checkConn(req.DownloadUrl, req.Username, req.Password); err != nil {
imageRepo.Status = constant.StatusFailed
@ -79,21 +86,22 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return err
}
cmd := exec.Command("systemctl", "restart", "docker")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func (u *ImageRepoService) BatchDelete(ids []uint) error {
for _, id := range ids {
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
for _, id := range req.Ids {
if id == 1 {
return errors.New("The default value cannot be edit !")
}
}
repos, err := imageRepoRepo.List(commonRepo.WithIdsIn(ids))
if !req.DeleteInsecure {
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
return err
}
return nil
}
repos, err := imageRepoRepo.List(commonRepo.WithIdsIn(req.Ids))
if err != nil {
return err
}
@ -106,7 +114,7 @@ func (u *ImageRepoService) BatchDelete(ids []uint) error {
_, _ = cmd.CombinedOutput()
}
}
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(ids)); err != nil {
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
return err
}
cmd := exec.Command("systemctl", "restart", "docker")
@ -129,9 +137,14 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if repo.DownloadUrl != req.DownloadUrl {
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
if repo.Auth {
cmd := exec.Command("docker", "logout", fmt.Sprintf("%s://%s", repo.Protocol, repo.DownloadUrl))
cmd := exec.Command("docker", "logout", repo.DownloadUrl)
_, _ = cmd.CombinedOutput()
}
cmd := exec.Command("systemctl", "restart", "docker")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
}
upMap := make(map[string]interface{})

View File

@ -152,6 +152,10 @@ export namespace Container {
password: string;
auth: boolean;
}
export interface RepoDelete {
ids: Array<number>;
deleteInsecure: boolean;
}
export interface RepoInfo {
id: number;
createdAt: Date;

View File

@ -88,7 +88,7 @@ export const createImageRepo = (params: Container.RepoCreate) => {
export const updateImageRepo = (params: Container.RepoUpdate) => {
return http.post(`/containers/repo/update`, params);
};
export const deleteImageRepo = (params: { ids: number[] }) => {
export const deleteImageRepo = (params: Container.RepoDelete) => {
return http.post(`/containers/repo/del`, params);
};

View File

@ -439,6 +439,9 @@ export default {
repo: 'Repo',
name: 'Name',
protocol: 'protocol',
httpRepo: 'The http repository needs to restart the docker service to add credit',
delInsecure: 'Deletion of credit',
delInsecureHelper: 'docker service needs to be restarted to delete the credit. Do you want to delete it?',
downloadUrl: 'Download URL',
imageRepo: 'Image repo',
repoHelper: 'Does it include a mirror repository/organization/project?',

View File

@ -415,6 +415,9 @@ export default {
imageDelete: '删除镜像',
repoName: '仓库名',
imageName: '镜像名',
httpRepo: 'http 仓库添加授信需要重启 docker 服务',
delInsecure: '删除授信',
delInsecureHelper: '删除授信需要重启 docker 服务是否删除',
pull: '拉取',
path: '路径',
importImage: '导入镜像',

View File

@ -144,7 +144,7 @@ const search = async () => {
};
const loadRepos = async () => {
const res = await listImageRepo();
repos.value = res.data;
repos.value = res.data || [];
};
const onOpenPull = () => {

View File

@ -22,7 +22,7 @@
prop="repoID"
>
<el-select style="width: 100%" filterable v-model="form.repoID">
<el-option v-for="item in dialogData.repos" :key="item.id" :value="item.id" :label="item.name" />
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
</el-select>
</el-form-item>
<el-form-item :label="$t('container.imageName')" :rules="Rules.requiredInput" prop="imageName">
@ -75,7 +75,7 @@ import { LoadFile } from '@/api/modules/files';
const pullVisiable = ref(false);
const form = reactive({
fromRepo: true,
repoID: 1,
repoID: null as number,
imageName: '',
});
@ -89,16 +89,13 @@ let timer: NodeJS.Timer | null = null;
interface DialogProps {
repos: Array<Container.RepoOptions>;
}
const dialogData = ref<DialogProps>({
repos: [] as Array<Container.RepoOptions>,
});
const repos = ref();
const acceptParams = async (params: DialogProps): Promise<void> => {
pullVisiable.value = true;
form.fromRepo = true;
form.repoID = 1;
form.imageName = '';
dialogData.value.repos = params.repos;
repos.value = params.repos;
buttonDisabled.value = false;
logInfo.value = '';
};
@ -138,7 +135,7 @@ const onCloseLog = async () => {
};
function loadDetailInfo(id: number) {
for (const item of dialogData.value.repos) {
for (const item of repos.value) {
if (item.id === id) {
return item.downloadUrl;
}

View File

@ -0,0 +1,66 @@
<template>
<el-dialog v-model="repoVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
<template #header>
<div class="card-header">
<span>{{ $t('commons.button.delete') }}</span>
</div>
</template>
<el-form v-loading="loading" label-width="20px">
<el-form-item>
<el-checkbox v-model="isDelete">{{ $t('container.delInsecure') }}</el-checkbox>
<span class="input-help">{{ $t('container.delInsecureHelper') }}</span>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="repoVisiable = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import i18n from '@/lang';
import { ElForm, ElMessage } from 'element-plus';
import { deleteImageRepo } from '@/api/modules/container';
const loading = ref(false);
const isDelete = ref(false);
interface DialogProps {
ids?: Array<number>;
}
const repoVisiable = ref(false);
const ids = ref();
const acceptParams = (params: DialogProps): void => {
ids.value = params.ids;
repoVisiable.value = true;
};
const emit = defineEmits<{ (e: 'search'): void }>();
const onSubmit = async () => {
loading.value = true;
await deleteImageRepo({ ids: ids.value, deleteInsecure: isDelete.value })
.then(() => {
loading.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
repoVisiable.value = false;
})
.catch(() => {
loading.value = false;
});
return;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -47,18 +47,19 @@
</ComplexTable>
</el-card>
<OperatorDialog @search="search" ref="dialogRef" />
<DeleteDialog @search="search" ref="dialogDeleteRef" />
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import OperatorDialog from '@/views/container/repo/operator/index.vue';
import DeleteDialog from '@/views/container/repo/delete/index.vue';
import Submenu from '@/views/container/index.vue';
import { reactive, onMounted, ref } from 'vue';
import { dateFromat } from '@/utils/util';
import { Container } from '@/api/interface/container';
import { deleteImageRepo, loadDockerStatus, searchImageRepo } from '@/api/modules/container';
import { useDeleteData } from '@/hooks/use-delete-data';
import { loadDockerStatus, searchImageRepo } from '@/api/modules/container';
import i18n from '@/lang';
import router from '@/routers';
@ -112,6 +113,7 @@ const onOpenDialog = async (
dialogRef.value!.acceptParams(params);
};
const dialogDeleteRef = ref();
const onBatchDelete = async (row: Container.RepoInfo | null) => {
let ids: Array<number> = [];
if (row) {
@ -121,8 +123,7 @@ const onBatchDelete = async (row: Container.RepoInfo | null) => {
ids.push(item.id);
});
}
await useDeleteData(deleteImageRepo, { ids: ids }, 'commons.msg.delete');
search();
dialogDeleteRef.value!.acceptParams({ ids: ids });
};
const buttons = [

View File

@ -7,7 +7,11 @@
</template>
<el-form ref="formRef" v-loading="loading" :model="dialogData.rowData" :rules="rules" label-width="120px">
<el-form-item :label="$t('container.name')" prop="name">
<el-input :disabled="dialogData.title === 'edit'" v-model="dialogData.rowData!.name"></el-input>
<el-input
clearable
:disabled="dialogData.title === 'edit'"
v-model="dialogData.rowData!.name"
></el-input>
</el-form-item>
<el-form-item :label="$t('container.auth')" prop="auth">
<el-radio-group v-model="dialogData.rowData!.auth">
@ -16,19 +20,29 @@
</el-radio-group>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.auth" :label="$t('commons.login.username')" prop="username">
<el-input v-model="dialogData.rowData!.username"></el-input>
<el-input clearable v-model="dialogData.rowData!.username"></el-input>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.auth" :label="$t('commons.login.password')" prop="password">
<el-input type="password" v-model="dialogData.rowData!.password"></el-input>
<el-input clearable type="password" show-password v-model="dialogData.rowData!.password"></el-input>
</el-form-item>
<el-form-item :label="$t('container.downloadUrl')" prop="downloadUrl">
<el-input v-model="dialogData.rowData!.downloadUrl" :placeholder="'172.16.10.10:8081'"></el-input>
<el-input
clearable
v-model="dialogData.rowData!.downloadUrl"
:placeholder="'172.16.10.10:8081'"
></el-input>
<span v-if="dialogData.rowData!.downloadUrl" class="input-help">
docker pull {{ dialogData.rowData!.downloadUrl }}/nginx
</span>
</el-form-item>
<el-form-item :label="$t('container.protocol')" prop="protocol">
<el-radio-group v-model="dialogData.rowData!.protocol">
<el-radio label="http">http</el-radio>
<el-radio label="https">https</el-radio>
</el-radio-group>
<span v-if="dialogData.rowData!.protocol === 'http'" class="input-help">
{{ $t('container.httpRepo') }}
</span>
</el-form-item>
</el-form>
<template #footer>

View File

@ -282,6 +282,9 @@ const loadHost = async () => {
const res = await getHostTree({});
hostTree.value = res.data;
for (const item of hostTree.value) {
if (!item.children) {
continue;
}
for (const host of item.children) {
if (host.label.indexOf('127.0.0.1')) {
localHostID.value = host.id;