1Panel/frontend/src/views/toolbox/clean/index.vue
John Bro c31b3e9ddc
Some checks failed
Build / SonarCloud (push) Failing after -4m17s
sync2gitee / repo-sync (push) Failing after -4m20s
Build Test / build-linux-binary (push) Failing after 7s
feat: 工具箱的缓存清理添加容器垃圾清理功能 (#5024)
2024-05-17 06:02:37 +00:00

645 lines
28 KiB
Vue

<template>
<div v-loading="loading">
<LayoutContent :title="$t('setting.diskClean')" :divider="true">
<template #toolbar>
<el-row>
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
<el-button type="primary" @click="scanData">
{{ $t('clean.scan') }}
</el-button>
</el-col>
</el-row>
</template>
<template #main>
<el-row class="mt-5 mb-5">
<el-col :span="1"><br /></el-col>
<el-col :xs="24" :sm="20" :md="20" :lg="10" :xl="10">
<div v-if="scanStatus !== 'scanned'">
<div v-if="scanStatus === 'beforeScan'">
<div v-if="form.lastCleanTime">
<el-text class="clean_title">
{{ $t('clean.lastCleanTime', [form.lastCleanTime || '-']) }}
</el-text>
<div class="mt-4">
<el-text>
{{
$t('clean.lastCleanHelper', [
form.lastCleanData || '-',
form.lastCleanSize ? computeSize(Number(form.lastCleanSize)) : '-',
])
}}
</el-text>
</div>
</div>
<div else>
<el-text class="clean_title">
<el-icon>
<MagicStick />
</el-icon>
{{ $t('clean.scanHelper') }}
</el-text>
</div>
<div class="app-card">
<el-card class="e-card">
<el-row>
<el-col :span="4">
<el-button icon="Setting" link class="card_icon" />
</el-col>
<el-col :span="20">
<div>
<el-text class="mx-1 card_title" type="primary">
{{ $t('clean.system') }}
</el-text>
</div>
<span class="input-help">{{ $t('clean.systemHelper') }}</span>
</el-col>
</el-row>
</el-card>
<el-card class="e-card">
<el-row>
<el-col :span="4">
<svg-icon iconName="p-docker" class="svg-icon"></svg-icon>
<el-button link class="card_icon" />
</el-col>
<el-col :span="20">
<div>
<el-text class="mx-1 card_title" type="primary">
{{ $t('clean.containerTrash') }}
</el-text>
</div>
<span class="input-help">
{{ $t('container.cleanDockerDiskZone') }}
</span>
</el-col>
</el-row>
</el-card>
<el-card class="e-card">
<el-row>
<el-col :span="4">
<el-button icon="Upload" link class="card_icon" />
</el-col>
<el-col :span="20">
<div>
<el-text class="mx-1 card_title" type="primary">
{{ $t('clean.upload') }}
</el-text>
</div>
<span class="input-help">{{ $t('clean.uploadHelper') }}</span>
</el-col>
</el-row>
</el-card>
<el-card class="e-card">
<el-row>
<el-col :span="4">
<el-button icon="Download" link class="card_icon" />
</el-col>
<el-col :span="20">
<div>
<el-text class="mx-1 card_title" type="primary">
{{ $t('clean.download') }}
</el-text>
</div>
<span class="input-help">{{ $t('clean.downloadHelper') }}</span>
</el-col>
</el-row>
</el-card>
<el-card class="e-card">
<el-row>
<el-col :span="4">
<el-button icon="Document" link class="card_icon" />
</el-col>
<el-col :span="20">
<div>
<el-text class="mx-1 card_title" type="primary">
{{ $t('clean.systemLog') }}
</el-text>
</div>
<span class="input-help">
{{ $t('clean.systemLogHelper') }}
</span>
</el-col>
</el-row>
</el-card>
</div>
</div>
<div v-if="scanStatus === 'afterScan'">
<el-text class="clean_title">{{ $t('clean.cleanSuccessful') }}</el-text>
<div class="mt-4">
<el-text>
{{
$t('clean.currentCleanHelper', [
form.lastCleanData,
computeSize(Number(form.lastCleanSize)),
])
}}
</el-text>
</div>
</div>
</div>
<div v-if="scanStatus === 'scanned'">
<div>
<el-text class="clean_title">
{{ $t('clean.totalScan') }} {{ computeSize(totalSize) }}
</el-text>
<div class="mt-4">
<el-text type="info">
{{ $t('clean.selectScan') }} {{ computeSize(selectSize) }}
</el-text>
</div>
<div class="large_button">
<el-button type="primary" size="large" @click="onSubmitClean">
{{ $t('clean.clean') }}
</el-button>
</div>
</div>
<el-collapse v-model="activeNames" class="mt-5">
<el-collapse-item :title="$t('clean.system')" name="system">
<el-tree
ref="systemRef"
:data="cleanData.systemClean"
node-key="id"
:default-checked-keys="systemDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
>
<template #default="{ node, data }">
<div class="float-left">
<span>{{ load18n(data.label) }}</span>
</div>
<div class="ml-4 float-left">
<span v-if="data.size">{{ computeSize(data.size) }}</span>
</div>
<div class="ml-4 float-left">
<span>{{ loadTag(node, data) }}</span>
</div>
</template>
</el-tree>
</el-collapse-item>
<el-collapse-item :title="$t('clean.containerTrash')" name="container_trash">
<el-tree
ref="containerRef"
:data="cleanData.containerClean"
node-key="id"
:default-checked-keys="containerDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
>
<template #default="{ node, data }">
<div class="float-left">
<span>{{ load18n(data.label) }}</span>
</div>
<div class="ml-4 float-left">
<span v-if="data.size">{{ computeSize(data.size) }}</span>
</div>
<div class="ml-4 float-left">
<span>{{ loadTag(node, data) }}</span>
</div>
</template>
</el-tree>
</el-collapse-item>
<el-collapse-item :title="$t('clean.upload')" name="upload">
<el-tree
ref="uploadRef"
:data="cleanData.uploadClean"
node-key="id"
:default-checked-keys="uploadDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
>
<template #default="{ node, data }">
<div class="float-left">
<span>{{ load18n(data.label) }}</span>
</div>
<div class="ml-4 float-left">
<span v-if="data.size">{{ computeSize(data.size) }}</span>
</div>
<div class="ml-4 float-left">
<span>{{ loadTag(node, data) }}</span>
</div>
</template>
</el-tree>
</el-collapse-item>
<el-collapse-item :title="$t('clean.download')" name="download">
<el-tree
ref="downloadRef"
:data="cleanData.downloadClean"
node-key="id"
:default-checked-keys="downloadDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
>
<template #default="{ node, data }">
<div class="float-left">
<span>{{ load18n(data.label) }}</span>
</div>
<div class="ml-4 float-left">
<span v-if="data.size">{{ computeSize(data.size) }}</span>
</div>
<div class="ml-4 float-left">
<span>{{ loadTag(node, data) }}</span>
</div>
</template>
</el-tree>
</el-collapse-item>
<el-collapse-item :title="$t('clean.systemLog')" name="system_log">
<el-tree
ref="systemLogRef"
:data="cleanData.systemLogClean"
node-key="id"
:default-checked-keys="systemLogDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
>
<template #default="{ node, data }">
<div class="float-left">
<span>{{ load18n(data.label) }}</span>
</div>
<div class="ml-4 float-left">
<span v-if="data.size">{{ computeSize(data.size) }}</span>
</div>
<div class="ml-4 float-left">
<span>{{ loadTag(node, data) }}</span>
</div>
</template>
</el-tree>
</el-collapse-item>
</el-collapse>
</div>
</el-col>
</el-row>
</template>
</LayoutContent>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { computeSize } from '@/utils/util';
import { getSettingInfo } from '@/api/modules/setting';
import { clean, scan } from '@/api/modules/toolbox';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const loading = ref();
const totalSize = ref<number>(0);
const selectSize = ref<number>(0);
const scanStatus = ref<string>('beforeScan');
const defaultProps = {
children: 'children',
label: 'label',
};
const cleanData = reactive({
systemClean: [],
uploadClean: [],
downloadClean: [],
systemLogClean: [],
containerClean: [],
});
const systemRef = ref();
const systemDefaultCheck = ref([]);
const uploadRef = ref();
const uploadDefaultCheck = ref([]);
const downloadRef = ref();
const downloadDefaultCheck = ref([]);
const systemLogRef = ref();
const systemLogDefaultCheck = ref([]);
const containerRef = ref();
const containerDefaultCheck = ref([]);
const activeNames = ref(['system', 'upload', 'download', 'system_log', 'container_trash']);
const submitCleans = ref();
const form = reactive({
lastCleanTime: '',
lastCleanSize: '',
lastCleanData: '',
});
const scanData = async () => {
loading.value = true;
await scan()
.then((res) => {
loading.value = false;
selectSize.value = 0;
totalSize.value = 0;
cleanData.systemClean = res.data.systemClean || [];
for (const item of cleanData.systemClean) {
totalSize.value += item.size;
}
cleanData.uploadClean = res.data.uploadClean || [];
for (const item of cleanData.uploadClean) {
totalSize.value += item.size;
}
cleanData.downloadClean = res.data.downloadClean || [];
for (const item of cleanData.downloadClean) {
totalSize.value += item.size;
}
cleanData.systemLogClean = res.data.systemLogClean || [];
for (const item of cleanData.systemLogClean) {
totalSize.value += item.size;
}
cleanData.containerClean = res.data.containerClean || [];
for (const item of cleanData.containerClean) {
totalSize.value += item.size;
}
loadCheck(cleanData.systemClean, systemDefaultCheck.value);
loadCheck(cleanData.uploadClean, uploadDefaultCheck.value);
loadCheck(cleanData.downloadClean, downloadDefaultCheck.value);
loadCheck(cleanData.systemLogClean, systemLogDefaultCheck.value);
loadCheck(cleanData.containerClean, containerDefaultCheck.value);
scanStatus.value = 'scanned';
})
.catch(() => {
loading.value = false;
});
};
const onSubmitClean = async () => {
ElMessageBox.confirm(i18n.global.t('clean.cleanHelper'), i18n.global.t('clean.clean'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
submitCleans.value = [];
let restart = false;
loadSubmitCheck(cleanData.systemClean);
loadSubmitCheck(cleanData.uploadClean);
loadSubmitCheck(cleanData.downloadClean);
loadSubmitCheck(cleanData.systemLogClean);
loadSubmitCheck(cleanData.containerClean);
for (const item of submitCleans.value) {
if (item.treeType === 'cache') {
restart = true;
break;
}
}
await clean(submitCleans.value)
.then(() => {
form.lastCleanSize = selectSize.value + '';
form.lastCleanData = submitCleans.value.length + '';
scanStatus.value = 'afterScan';
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
loading.value = false;
if (restart) {
let href = window.location.href;
globalStore.isLogin = false;
let address = href.split('://')[1];
if (globalStore.entrance) {
address = address.replaceAll('settings/panel', globalStore.entrance);
} else {
address = address.replaceAll('settings/panel', 'login');
}
window.open(`http://${address}`, '_self');
}
})
.catch(() => {
loading.value = false;
});
});
};
const search = async () => {
const res = await getSettingInfo();
form.lastCleanTime = res.data.lastCleanTime;
form.lastCleanSize = res.data.lastCleanSize;
form.lastCleanData = res.data.lastCleanData;
};
const loadSubmitCheck = (data: any) => {
if (data.children === null) {
if (data.isCheck) {
submitCleans.value.push({ treeType: data.type, name: data.name, size: data.size });
}
return;
}
for (const item of data) {
if (item.isCheck) {
submitCleans.value.push({ treeType: item.type, name: item.name, size: item.size });
continue;
}
if (item.children) {
loadSubmitCheck(item.children);
}
}
};
const changeCheckStatus = (data: any, isCheck: boolean) => {
data.isCheck = isCheck;
if (data.children) {
for (const item of data.children) {
changeCheckStatus(item, isCheck);
}
}
};
function onChange(data: any, isCheck: boolean) {
changeCheckStatus(data, isCheck);
selectSize.value = 0;
let systemSelects = systemRef.value.getCheckedNodes(false, true);
for (const item of systemSelects) {
if (item.children === null) {
selectSize.value = selectSize.value + Number(item.size);
}
}
let uploadSelects = uploadRef.value.getCheckedNodes(false, true);
for (const item of uploadSelects) {
if (item.children === null) {
selectSize.value = selectSize.value + Number(item.size);
}
}
let downloadSelects = downloadRef.value.getCheckedNodes(false, true);
for (const item of downloadSelects) {
if (item.children === null) {
selectSize.value = selectSize.value + Number(item.size);
}
}
let systemLogSelects = systemLogRef.value.getCheckedNodes(false, true);
for (const item of systemLogSelects) {
if (item.children === null) {
selectSize.value = selectSize.value + Number(item.size);
}
}
let containerSelects = containerRef.value.getCheckedNodes(false, true);
for (const item of containerSelects) {
if (item.children === null) {
selectSize.value = selectSize.value + Number(item.size);
}
}
}
function loadCheck(data: any, checkList: any) {
if (data.children === null) {
if (data.isCheck) {
checkList.push(data.id);
}
return;
}
for (const item of data) {
if (item.isCheck) {
selectSize.value = selectSize.value + Number(item.size);
checkList.push(item.id);
continue;
}
if (item.children) {
loadCheck(item.children, checkList);
}
}
}
function loadTag(node: any, data: any) {
if (node.level !== 1) {
return '';
}
if (data.size === 0) {
return i18n.global.t('clean.statusClean');
}
if (data.label === 'container_images') {
return i18n.global.t('container.cleanImagesHelper');
}
if (data.label === 'container_containers') {
return i18n.global.t('container.cleanContainersHelper');
}
if (data.label === 'container_volumes') {
return i18n.global.t('container.cleanVolumesHelper');
}
if (data.label === 'upgrade') {
return i18n.global.t('clean.upgradeHelper');
}
if (data.label === 'cache') {
return i18n.global.t('clean.cacheHelper');
}
return data.isRecommend ? i18n.global.t('clean.statusSuggest') : i18n.global.t('clean.statusWarning');
}
function load18n(label: string) {
switch (label) {
case '1panel_original':
return i18n.global.t('clean.panelOriginal');
case 'upgrade':
return i18n.global.t('clean.upgrade');
case 'cache':
return i18n.global.t('clean.cache');
case 'snapshot':
return i18n.global.t('clean.snapshot');
case 'snapshot_tmp':
return i18n.global.t('clean.snapshotTmp');
case 'snapshot_local':
return i18n.global.t('clean.snapshotLocal');
case 'rollback':
return i18n.global.t('clean.rollback');
case 'unused':
return i18n.global.t('clean.unused');
case 'old_original':
return i18n.global.t('clean.oldOriginal');
case 'old_apps_bak':
return i18n.global.t('clean.oldAppsBak');
case 'old_upgrade':
return i18n.global.t('clean.oldUpgrade');
case 'upload':
case 'upload_tmp':
return i18n.global.t('clean.upload');
case 'download':
return i18n.global.t('clean.download');
case 'upload_website':
case 'rollback_website':
case 'download_website':
return i18n.global.t('clean.website');
case 'upload_app':
case 'rollback_app':
case 'download_app':
return i18n.global.t('clean.app');
case 'upload_database':
case 'rollback_database':
case 'download_database':
return i18n.global.t('clean.database');
case 'upload_directory':
case 'download_directory':
return i18n.global.t('clean.directory');
case 'system_log':
return i18n.global.t('clean.systemLog');
case 'docker_log':
return i18n.global.t('clean.dockerLog');
case 'task_log':
return i18n.global.t('clean.taskLog');
case 'shell':
return i18n.global.t('clean.shell');
case 'containerShell':
return i18n.global.t('clean.containerShell');
case 'curl':
return i18n.global.t('clean.curl');
case 'container_images':
return i18n.global.t('clean.images');
case 'container_containers':
return i18n.global.t('clean.containers');
case 'container_volumes':
return i18n.global.t('clean.volumes');
case 'build_cache':
return i18n.global.t('clean.buildCache');
default:
return label;
}
}
onMounted(() => {
search();
scanStatus.value = 'beforeScan';
});
</script>
<style lang="scss" scoped>
.app-card {
cursor: pointer;
width: 100%;
&:hover .app-icon {
transform: scale(1.2);
}
.e-card {
margin-top: 20px;
cursor: pointer;
border: var(--panel-border) !important;
&:hover {
cursor: pointer;
border: 1px solid var(--el-color-primary) !important;
}
}
}
.card_icon {
font-size: 36px;
float: right;
margin-right: 15px;
}
.card_title {
font-size: 18px;
}
.clean_title {
font-size: 22px;
}
.large_button {
float: right;
margin-top: -40px;
}
.svg-icon {
font-size: 14px;
float: right;
margin-right: 15px;
}
</style>