Files
mayfly-go/frontend/src/views/ops/docker/image/ImageList.vue
2025-08-31 21:46:10 +08:00

210 lines
6.9 KiB
Vue

<template>
<div class="card !p-2">
<el-row :gutter="5">
<el-col :span="4">
<el-input :placeholder="$t('docker.imageName')" v-model="params.name" plain clearable></el-input>
</el-col>
<el-col :span="4">
<EnumSelect v-model="params.state" :enums="ImageStateEnum" :placeholder="$t('docker.status')" clearable />
</el-col>
<el-col :span="4">
<div class="flex items-center gap-2">
<el-button @click="getImages" type="primary" icon="refresh" circle plain></el-button>
<el-upload :on-success="uploadSuccess" action="" :http-request="uploadImage" :headers="{ token }" :show-file-list="false" name="file">
<el-button type="primary" icon="upload" circle plain></el-button>
</el-upload>
</div>
</el-col>
</el-row>
</div>
<el-table :data="filterTableDatas" v-loading="state.loadingImages">
<el-table-column prop="id" label="ID" :min-width="100" show-overflow-tooltip>
<template #default="{ row }">
<el-link type="primary" :underline="false">
{{ row.id.split(':')[1].substring(0, 12) }}
</el-link>
</template>
</el-table-column>
<el-table-column prop="tags" :label="$t('docker.tag')" :min-width="250">
<template #default="{ row }">
<el-tag v-for="tag in row.tags" :key="tag" type="primary">{{ tag || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="size" :label="$t('docker.size')" :min-width="50">
<template #default="{ row }">
{{ formatByteSize(row.size) }}
</template>
</el-table-column>
<el-table-column prop="createTime" :label="$t('common.createTime')" width="160">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="isUse" :label="$t('common.status')" :min-width="50">
<template #default="{ row }">
<EnumTag :enums="ImageStateEnum" :value="row.isUse" />
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" width="130">
<template #default="{ row }">
<el-button @click="exportImage(row)" type="warning" link plain>{{ $t('docker.export') }}</el-button>
<el-popconfirm :title="$t('docker.stopImageConfirm')" @confirm="imageRemove(row)" width="170">
<template #reference>
<el-button :disabled="row.isUse == ImageStateEnum.Used.value" type="danger" link plain>
{{ $t('common.delete') }}
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-dialog
v-if="terminalDialog.visible"
:title="terminalDialog.title"
v-model="terminalDialog.visible"
width="80%"
:close-on-click-modal="false"
:modal="false"
@close="closeTerminal"
draggable
append-to-body
>
<TerminalBody ref="terminal" :socket-url="getDockerExecSocketUrl(params.host, terminalDialog.containerId)" height="560px" />
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, toRefs } from 'vue';
import { dockerApi, getDockerExecSocketUrl } from '../api';
import { formatByteSize, formatDate } from '@/common/utils/format';
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
import { ImageStateEnum } from '../enums';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { fuzzyMatchField } from '@/common/utils/string';
import TerminalBody from '@/components/terminal/TerminalBody.vue';
import config from '@/common/config';
import { joinClientParams } from '@/common/request';
import { getToken } from '@/common/utils/storage';
import { ElMessage } from 'element-plus';
import { i18n } from '@/i18n';
const props = defineProps({
host: {
type: String,
default: '',
},
});
const state = reactive({
params: {
host: props.host,
name: '',
state: null,
},
loadingImages: false,
images: [],
terminalDialog: {
visible: false,
title: '',
containerId: '',
},
});
const { params, terminalDialog } = toRefs(state);
const token = getToken();
onMounted(() => {
getImages();
});
const filterTableDatas = computed(() => {
let tables: any = state.images;
const nameSearch = state.params.name;
const stateSearch = state.params.state;
if (stateSearch != null) {
tables = tables.filter((table: any) => {
return table.isUse === stateSearch;
});
}
if (nameSearch) {
tables = fuzzyMatchField(nameSearch, tables, (table: any) => table.tags[0]);
}
return tables;
});
const getImages = async () => {
state.loadingImages = true;
try {
state.images = await dockerApi.images.request(state.params);
} finally {
state.loadingImages = false;
}
};
const exportImage = async (row: any) => {
const a = document.createElement('a');
a.setAttribute('href', `${config.baseApiUrl}/docker/images/save?host=${state.params.host}&tag=${row.tags[0]}&${joinClientParams()}`);
a.setAttribute('target', '_blank');
a.click();
};
const uploadImage = (content: any) => {
const params = new FormData();
// const path = state.nowPath;
params.append('file', content.file);
params.append('host', state.params.host);
params.append('token', token);
dockerApi.imageUpload
.xhrReq(params, {
headers: { 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryF1uyUD0tWdqmJqpl' },
// onUploadProgress: onUploadProgress,
timeout: 3 * 60 * 60 * 1000,
})
.then(() => {
ElMessage.success(i18n.global.t('machine.uploadSuccess'));
setTimeout(() => {
getImages();
}, 3000);
})
.catch(() => {
// state.uploadProgressShow = false;
});
ElMessage.info(i18n.global.t('docker.imageUploading'));
};
const uploadSuccess = (res: any) => {
if (res.code !== 200) {
ElMessage.error(res.msg);
}
};
const imageRemove = async (row: any) => {
await dockerApi.imageRemove.request({ host: state.params.host, imageId: row.id });
getImages();
};
const openTerminal = (row: any) => {
state.terminalDialog.containerId = row.containerId;
state.terminalDialog.title = `Terminal - ${row.name}`;
state.terminalDialog.visible = true;
};
const closeTerminal = () => {
state.terminalDialog.visible = false;
};
</script>