Compare commits

...
Author SHA1 Message Date
guoyuqi f91db55daa refactor(frontend): unify dialogs and improve deletion flows 2026-10-27 14:52:05 +08:00
61 changed files with 236 additions and 509 deletions
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="submitVisible" :title="header" size="mini">
<DialogPro v-model="submitVisible" :title="header">
<div>
<span v-if="operationInfo" class="operation-info">{{ operationInfo }}</span>
<slot />
+19 -21
View File
@@ -3,7 +3,7 @@
<component
:is="drawer ? DrawerPro : DialogPro"
v-model="open"
v-bind="drawer ? { header: form.title, size: 'large' } : { title: form.title, size: 'small' }"
v-bind="drawer ? { header: form.title, size: 'large' } : { title: form.title }"
@close="handleClose"
>
<div v-loading="loading" class="del-dialog-body">
@@ -56,8 +56,8 @@ const form = reactive({
api: null as Function,
params: {},
});
const loading = ref();
const open = ref();
const loading = ref(false);
const open = ref(false);
const successMsg = ref('');
const noMsg = ref(false);
@@ -68,8 +68,8 @@ interface DialogProps {
api: Function;
params: object;
successMsg: string;
noMsg: boolean;
successMsg?: string;
noMsg?: boolean;
}
const acceptParams = (props: DialogProps): void => {
form.title = props.title;
@@ -78,34 +78,32 @@ const acceptParams = (props: DialogProps): void => {
form.api = props.api;
form.params = props.params;
successMsg.value = props.successMsg;
if (props.noMsg) {
noMsg.value = props.noMsg;
}
noMsg.value = props.noMsg ?? false;
open.value = true;
};
const emit = defineEmits(['search', 'cancel', 'submit']);
const onConfirm = async () => {
if (loading.value) return;
if (form.api === null) {
emit('submit');
open.value = false;
return;
}
loading.value = true;
await form
.api(form.params)
.then(() => {
emit('search');
if (!noMsg.value) {
MsgSuccess(successMsg.value ?? i18n.global.t('commons.msg.deleteSuccess'));
}
open.value = false;
loading.value = false;
})
.catch(() => {
loading.value = false;
});
try {
await form.api(form.params);
emit('search');
if (!noMsg.value) {
MsgSuccess(successMsg.value ?? i18n.global.t('commons.msg.deleteSuccess'));
}
open.value = false;
} catch {
// API errors are reported by the request interceptor; keep the dialog open for retry.
} finally {
loading.value = false;
}
};
const handleClose = () => {
+17 -26
View File
@@ -5,10 +5,11 @@
:destroy-on-close="true"
:close-on-click-modal="closeOnClickModal"
:show-close="showClose"
:width="size"
:width="width"
:class="{ 'dialog-full-width': props.size === 'full', 'dialog-sized': !!props.size && props.size !== 'full' }"
append-to-body
draggable
:open="open"
@open="open"
@opened="opened"
:before-close="handleBeforeClose"
>
@@ -46,7 +47,7 @@ const props = defineProps({
},
size: {
type: String,
default: 'normal',
default: '',
},
modelValue: {
type: Boolean,
@@ -54,33 +55,23 @@ const props = defineProps({
},
});
// Explicit sizes preserve layouts such as imports; unspecified dialogs use the shared width.
const widths: Record<string, string> = {
mini: '20%',
small: '30%',
normal: '40%',
large: '50%',
full: '100%',
'w-60': '60%',
'w-70': '70%',
'w-90': '90%',
};
const width = computed(() => (props.size ? (widths[props.size] ?? '50%') : undefined));
const slots = useSlots();
const emit = defineEmits(['update:modelValue', 'close', 'open', 'opened']);
const size = computed(() => {
switch (props.size) {
case 'mini':
return '20%';
case 'small':
return '30%';
case 'normal':
return '40%';
case 'large':
return '50%';
case 'full':
return '100%';
case 'w-60':
return '60%';
case 'w-70':
return '70%';
case 'w-90':
return '90%';
default:
return '50%';
}
});
const dialogVisible = computed({
get() {
return props.modelValue;
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('commons.msg.infoTitle')" size="small">
<DialogPro v-model="open" :title="$t('commons.msg.infoTitle')">
<el-form ref="formRef" label-position="top" @submit.prevent>
<el-form-item :label="$t('xpack.node.syncProxyHelper')">
<el-radio-group v-model="restart">
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" size="small">
<DialogPro v-model="open">
<el-form ref="formRef" label-position="top" @submit.prevent>
<el-form-item :label="title">
<el-radio-group v-model="restart">
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div>
<DialogPro v-model="dialogVisible" :title="$t('file.existFileTitle')" size="normal">
<DialogPro v-model="dialogVisible" :title="$t('file.existFileTitle')">
<el-alert :show-icon="true" type="warning" :closable="false">
<div class="whitespace-break-spaces">
<span>{{ $t('file.existFileHelper') }}</span>
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<DialogPro :title="$t('file.list')" size="w-60" v-model="open" @close="handleClose">
<DialogPro :title="$t('file.list')" v-model="open" @close="handleClose">
<div>
<div
v-show="!searchableStatus"
@@ -1,6 +1,6 @@
<template>
<div>
<DialogPro v-model="open" class="level-up-pro" @close="handleClose">
<DialogPro size="normal" v-model="open" class="level-up-pro" @close="handleClose">
<div style="text-align: center" v-loading="loading">
<span class="text-3xl font-medium title">
{{ $t('license.importLicense') }}
@@ -32,12 +32,7 @@
<div class="log-container" :style="styleVars">
<div class="xterm-log-viewer" ref="terminalElement"></div>
</div>
<DialogPro
v-model="downloadDialogVisible"
:title="$t('commons.button.download')"
size="small"
:close-on-click-modal="true"
>
<DialogPro v-model="downloadDialogVisible" :title="$t('commons.button.download')" :close-on-click-modal="true">
<el-form label-position="top">
<el-form-item :label="$t('container.fetch')">
<el-select v-model="downloadForm.mode" class="w-full">
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div>
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<el-alert :closable="false" :title="$t('setting.systemIPWarning')" type="info">
<el-link icon="Position" @click="jumpToPath(router, '/settings/panel')" type="primary">
{{ $t('firewall.quickJump') }}
@@ -15,7 +15,6 @@
v-if="isAdmin"
v-model="open"
:title="$t('menu.terminal')"
size="w-70"
:show-close="false"
:modal="false"
modal-penetrable
@@ -205,12 +205,7 @@
</template>
</DialogPro>
<DialogPro
v-model="passkeyPrereqDialogOpen"
:title="$t('setting.passkey')"
size="small"
@close="handlePasskeyPrereqDialogClose"
>
<DialogPro v-model="passkeyPrereqDialogOpen" :title="$t('setting.passkey')" @close="handlePasskeyPrereqDialogClose">
<div class="passkey-prereq-dialog">
<div class="passkey-prereq-title">{{ $t('setting.passkeyPrereqTitle') }}</div>
<div class="passkey-prereq-list">
+8
View File
@@ -413,3 +413,11 @@ html {
}
}
}
// Standard dialogs share one width; explicitly sized dialogs keep their layout.
.el-dialog:not(.is-fullscreen):not(.dialog-full-width):not(.dialog-sized),
.el-message-box {
width: 60vw !important;
min-width: 0;
max-width: 60vw;
}
+8 -2
View File
@@ -84,9 +84,12 @@
}
@media only screen and (max-width: 1024px) {
.el-dialog:not(.is-fullscreen) {
.el-dialog.dialog-sized:not(.is-fullscreen) {
min-width: min(420px, calc(100vw - 24px));
max-width: calc(100vw - 24px);
}
.el-dialog:not(.is-fullscreen) {
max-height: calc(100vh - 24px);
max-height: calc(100dvh - 24px);
display: flex;
@@ -133,10 +136,13 @@
}
@media only screen and (max-width: 767px) {
.el-dialog:not(.is-fullscreen) {
.el-dialog.dialog-sized:not(.is-fullscreen) {
width: calc(100vw - 24px) !important;
min-width: 0;
max-width: calc(100vw - 24px);
}
.el-dialog:not(.is-fullscreen) {
margin: 12px auto;
}
+11
View File
@@ -0,0 +1,11 @@
import { ElMessageBox, type ElMessageBoxOptions } from 'element-plus';
import i18n from '@/lang';
// Keep cancellation as a rejection and leave the business operation to the caller.
export const confirmAction = (message: string, title: string, options: ElMessageBoxOptions = {}) => {
return ElMessageBox.confirm(message, title, {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
...options,
});
};
+21
View File
@@ -0,0 +1,21 @@
// Wait for every request, keeping at most four deletions in flight. Return only
// failed items so a retry cannot delete an already-removed item a second time.
export async function deleteFilesWithLimit<T>(
items: readonly T[],
remove: (item: T) => Promise<unknown>,
): Promise<T[]> {
let next = 0;
const failed = new Set<number>();
const worker = async () => {
while (next < items.length) {
const index = next++;
try {
await remove(items[index]);
} catch {
failed.add(index);
}
}
};
await Promise.all(Array.from({ length: Math.min(4, items.length) }, worker));
return items.filter((_, index) => failed.has(index));
}
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="header" size="w-60" @close="handleClose">
<DialogPro v-model="open" :title="header" @close="handleClose">
<div v-loading="loading" class="agent-file-drawer">
<el-alert v-if="!agentId || !workspace" :title="$t('commons.msg.noneData')" type="info" :closable="false" />
<template v-else>
@@ -1,10 +1,5 @@
<template>
<DialogPro
v-model="open"
:title="$t('commons.button.delete') + ' - ' + agentName"
size="small"
@close="handleClose"
>
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + agentName" @close="handleClose">
<el-form ref="deleteForm" label-position="left" v-loading="loading">
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('website.forceDelete')" />
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="submitVisible">
<DialogPro size="normal" v-model="submitVisible">
<template #header>
{{ $t('aiTools.mcp.importMcpJson') }}
</template>
@@ -49,7 +49,7 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import { confirmAction } from '@/utils/confirm';
import {
createAgentAccountModel,
deleteAgentAccountModel,
@@ -180,13 +180,9 @@ const submit = async () => {
};
const onDelete = async (row: AI.AgentAccountModel) => {
await ElMessageBox.confirm(
await confirmAction(
i18n.global.t('commons.msg.delete', [row.name || row.id]),
i18n.global.t('commons.button.delete'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
);
await deleteAgentAccountModel({
accountId: account.id,
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('app.composeDiff')" @close="handleClose" size="w-90">
<DialogPro v-model="open" :title="$t('app.composeDiff')" @close="handleClose">
<div>
<el-text type="warning">{{ $t('app.diffHelper') }}</el-text>
<div ref="container" class="compose-diff"></div>
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + composeName" size="small">
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + composeName">
<el-form ref="deleteForm" v-loading="loading">
<el-form-item>
<el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" />
@@ -96,7 +96,7 @@
</template>
</DrawerPro>
<FileUpload ref="uploadRef" @close="loadContainerFiles" />
<DialogPro v-model="previewVisible" :title="previewTitle" size="w-70" :close-on-click-modal="true">
<DialogPro v-model="previewVisible" :title="previewTitle" :close-on-click-modal="true">
<el-alert
v-if="previewTruncated"
:title="$t('file.previewTruncated')"
@@ -407,11 +407,7 @@
</ComplexTable>
</template>
<DialogPro
v-model="portsDialogVisible"
:title="`${$t('commons.table.port')} - ${portsDialogContainer}`"
size="small"
>
<DialogPro v-model="portsDialogVisible" :title="`${$t('commons.table.port')} - ${portsDialogContainer}`">
<template #content>
<div class="container-port-dialog-filters">
<el-input v-model="portFilter" clearable :placeholder="$t('commons.button.search')" />
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.edit')" size="small">
<DialogPro v-model="open" :title="$t('commons.button.edit')">
<div v-if="isFromApp" class="leading-6">
<div>
<span>{{ $t('container.updateHelper1') }}</span>
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('container.containerPrune')" size="small">
<DialogPro v-model="open" :title="$t('container.containerPrune')">
<div>
<ul class="help-ul">
<li class="lineClass" style="color: red">{{ $t('container.containerPruneHelper1') }}</li>
@@ -197,7 +197,7 @@
</template>
</LayoutContent>
<DialogPro v-model="open" :title="$t('container.iptablesDisable')" size="small">
<DialogPro v-model="open" :title="$t('container.iptablesDisable')">
<div class="mt-2.5">
<span class="text-rose-500">{{ $t('container.iptablesHelper2') }}</span>
<div class="mt-2.5">
@@ -231,7 +231,7 @@
</template>
</LayoutContent>
<DialogPro v-model="open" :title="$t('commons.button.clean')" size="small">
<DialogPro v-model="open" :title="$t('commons.button.clean')">
<el-form ref="deleteForm" label-position="left" v-loading="delLoading">
<el-form-item>
<el-checkbox v-model="cleanData" :label="$t('cronjob.cleanData')" />
@@ -1,6 +1,6 @@
<template>
<DialogPro v-model="open" :title="$t('database.unBindRemoteDB') + ' - ' + deleteReq.database" size="small">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<DialogPro v-model="open" :title="$t('database.unBindRemoteDB') + ' - ' + deleteReq.database">
<el-form v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('database.unBindForce')" />
<span class="input-help">
@@ -21,7 +21,7 @@
<el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="submit" :disabled="loading">
<el-button type="primary" @click="submit" :loading="loading" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -30,7 +30,6 @@
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { ref } from 'vue';
import i18n from '@/lang';
import { deleteDatabase } from '@/api/modules/database';
@@ -45,8 +44,6 @@ const deleteReq = ref({
const open = ref(false);
const loading = ref(false);
const deleteForm = ref<FormInstance>();
interface DialogProps {
id: number;
database: string;
@@ -54,7 +51,7 @@ interface DialogProps {
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = async (prop: DialogProps) => {
const acceptParams = (prop: DialogProps) => {
deleteReq.value = {
id: prop.id,
database: prop.database,
@@ -65,17 +62,18 @@ const acceptParams = async (prop: DialogProps) => {
};
const submit = async () => {
if (loading.value) return;
loading.value = true;
deleteDatabase(deleteReq.value)
.then(() => {
loading.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
})
.catch(() => {
loading.value = false;
});
try {
await deleteDatabase(deleteReq.value);
emit('search');
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
} catch {
// The request interceptor reports failures; allow retry without losing the selection.
} finally {
loading.value = false;
}
};
defineExpose({
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + dbName" size="small">
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + dbName">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('app.forceDelete')" />
@@ -295,7 +295,7 @@
</template>
</DrawerPro>
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
<el-link icon="Position" type="primary" @click="getAppDetail">
@@ -81,7 +81,7 @@ import { toRef, onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/postgresql/check/index.vue';
import OperateDialog from '@/views/database/mongodb/remote/operate/index.vue';
import DeleteDialog from '@/views/database/mongodb/remote/delete/index.vue';
import DeleteDialog from '@/views/database/components/remote-delete/index.vue';
import i18n from '@/lang';
import { Database } from '@/api/interface/database';
@@ -60,12 +60,7 @@
</template>
</DialogPro>
<DialogPro
v-if="!props.viewOnly"
v-model="addDialogVisible"
:title="$t('database.addUserAuthorization')"
size="small"
>
<DialogPro v-if="!props.viewOnly" v-model="addDialogVisible" :title="$t('database.addUserAuthorization')">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" v-loading="loading">
<el-form-item :label="$t('commons.table.type')" prop="mode">
<el-radio-group v-model="form.mode" @change="changeMode">
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + dbName" size="small">
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + dbName">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('app.forceDelete')" />
+1 -1
View File
@@ -198,7 +198,7 @@
</span>
</el-card>
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
<el-link icon="Position" @click="getAppDetail" type="primary">
@@ -1,83 +0,0 @@
<template>
<DialogPro v-model="open" :title="$t('database.unBindRemoteDB') + ' - ' + deleteMysqlReq.database" size="small">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('database.unBindForce')" />
<span class="input-help">
{{ $t('database.unBindForceHelper') }}
</span>
</el-form-item>
<el-form-item>
<el-checkbox v-model="deleteMysqlReq.deleteBackup" :label="$t('app.deleteBackup')" />
<span class="input-help">
{{ $t('database.deleteBackupHelper') }}
</span>
</el-form-item>
<span style="font-size: 12px">{{ $t('database.unBindRemoteHelper') }}</span>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="submit" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { ref } from 'vue';
import i18n from '@/lang';
import { deleteDatabase } from '@/api/modules/database';
import { MsgSuccess } from '@/utils/message';
let deleteMysqlReq = ref({
id: 0,
database: '',
deleteBackup: false,
forceDelete: false,
});
let open = ref(false);
let loading = ref(false);
const deleteForm = ref<FormInstance>();
interface DialogProps {
id: number;
name: string;
database: string;
}
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = async (prop: DialogProps) => {
deleteMysqlReq.value = {
id: prop.id,
database: prop.database,
deleteBackup: false,
forceDelete: false,
};
open.value = true;
};
const submit = async () => {
loading.value = true;
deleteDatabase(deleteMysqlReq.value)
.then(() => {
loading.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
})
.catch(() => {
loading.value = false;
});
};
defineExpose({
acceptParams,
});
</script>
@@ -80,7 +80,7 @@ import { toRef, onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/mysql/check/index.vue';
import OperateDialog from '@/views/database/mysql/remote/operate/index.vue';
import DeleteDialog from '@/views/database/mysql/remote/delete/index.vue';
import DeleteDialog from '@/views/database/components/remote-delete/index.vue';
import i18n from '@/lang';
import { Database } from '@/api/interface/database';
@@ -106,7 +106,7 @@
</template>
</LayoutContent>
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<el-alert :closable="false" :title="$t('database.confNotFound')" type="info">
<el-link icon="Position" @click="goUpgrade()" type="primary">
{{ $t('database.goUpgrade') }}
@@ -91,7 +91,7 @@
</ComplexTable>
</DrawerPro>
<DialogPro v-model="userDialogVisible" :title="userDialogTitle" size="small">
<DialogPro v-model="userDialogVisible" :title="userDialogTitle">
<el-form ref="userFormRef" :model="userForm" :rules="userRules" label-position="top" v-loading="loading">
<el-form-item :label="$t('commons.login.username')" prop="username">
<el-input v-model.trim="userForm.username" :disabled="userDialogMode === 'edit'" />
@@ -133,7 +133,7 @@
</template>
</DialogPro>
<DialogPro v-model="supplementDialogVisible" :title="$t('database.supplementPassword')" size="small">
<DialogPro v-model="supplementDialogVisible" :title="$t('database.supplementPassword')">
<el-form
ref="supplementFormRef"
:model="supplementForm"
@@ -159,11 +159,7 @@
</template>
</DialogPro>
<DialogPro
v-model="deleteDialogVisible"
:title="$t('commons.button.delete') + ' - ' + deleteUserIdentity"
size="small"
>
<DialogPro v-model="deleteDialogVisible" :title="$t('commons.button.delete') + ' - ' + deleteUserIdentity">
<div v-loading="loading">
<div class="delete-user-title">{{ deleteUserIdentity }} {{ $t('database.userBoundDatabases') }}</div>
<div class="delete-user-section">
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + dbName" size="small">
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + dbName">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('app.forceDelete')" />
@@ -210,7 +210,7 @@
<span>{{ $t('commons.service.serviceNotStarted', ['PostgreSQL']) }}</span>
</el-card>
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
<el-link icon="Position" @click="getAppDetail" type="primary">
@@ -1,83 +0,0 @@
<template>
<DialogPro v-model="open" :title="$t('database.unBindRemoteDB') + ' - ' + deleteReq.database" size="small">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('database.unBindForce')" />
<span class="input-help">
{{ $t('database.unBindForceHelper') }}
</span>
</el-form-item>
<el-form-item>
<el-checkbox v-model="deleteReq.deleteBackup" :label="$t('app.deleteBackup')" />
<span class="input-help">
{{ $t('database.deleteBackupHelper') }}
</span>
</el-form-item>
<span style="font-size: 12px">{{ $t('database.unBindRemoteHelper') }}</span>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="submit" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { ref } from 'vue';
import i18n from '@/lang';
import { deleteDatabase } from '@/api/modules/database';
import { MsgSuccess } from '@/utils/message';
let deleteReq = ref({
id: 0,
database: '',
deleteBackup: false,
forceDelete: false,
});
let open = ref(false);
let loading = ref(false);
const deleteForm = ref<FormInstance>();
interface DialogProps {
id: number;
name: string;
database: string;
}
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = async (prop: DialogProps) => {
deleteReq.value = {
id: prop.id,
database: prop.database,
deleteBackup: false,
forceDelete: false,
};
open.value = true;
};
const submit = async () => {
loading.value = true;
deleteDatabase(deleteReq.value)
.then(() => {
loading.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
})
.catch(() => {
loading.value = false;
});
};
defineExpose({
acceptParams,
});
</script>
@@ -80,7 +80,7 @@ import { toRef, onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/postgresql/check/index.vue';
import OperateDialog from '@/views/database/postgresql/remote/operate/index.vue';
import DeleteDialog from '@/views/database/postgresql/remote/delete/index.vue';
import DeleteDialog from '@/views/database/components/remote-delete/index.vue';
import i18n from '@/lang';
import { Database } from '@/api/interface/database';
@@ -59,7 +59,7 @@
</template>
</LayoutContent>
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<el-alert :closable="false" :title="$t('database.confNotFound')" type="info">
<el-link icon="Position" @click="goUpgrade()" type="primary">
{{ $t('database.goUpgrade') }}
+1 -1
View File
@@ -125,7 +125,7 @@
<Setting ref="settingRef" style="margin-top: 30px" />
<Conn ref="connRef" @check-exist="reOpenTerminal" @close-terminal="closeTerminal(true)" />
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
<DialogPro v-model="open" :title="$t('app.checkTitle')">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', ['Redis-Commander']) }}
<el-link icon="Position" @click="getAppDetail('redis-commander')" type="primary">
@@ -1,83 +0,0 @@
<template>
<DialogPro v-model="open" :title="$t('database.unBindRemoteDB') + ' - ' + deleteMysqlReq.database" size="small">
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('database.unBindForce')" />
<span class="input-help">
{{ $t('database.unBindForceHelper') }}
</span>
</el-form-item>
<el-form-item>
<el-checkbox v-model="deleteMysqlReq.deleteBackup" :label="$t('app.deleteBackup')" />
<span class="input-help">
{{ $t('database.deleteBackupHelper') }}
</span>
</el-form-item>
<span style="font-size: 12px">{{ $t('database.unBindRemoteHelper') }}</span>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="submit" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { ref } from 'vue';
import i18n from '@/lang';
import { deleteDatabase } from '@/api/modules/database';
import { MsgSuccess } from '@/utils/message';
let deleteMysqlReq = ref({
id: 0,
database: '',
deleteBackup: false,
forceDelete: false,
});
let open = ref(false);
let loading = ref(false);
const deleteForm = ref<FormInstance>();
interface DialogProps {
id: number;
name: string;
database: string;
}
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = async (prop: DialogProps) => {
deleteMysqlReq.value = {
id: prop.id,
database: prop.database,
deleteBackup: false,
forceDelete: false,
};
open.value = true;
};
const submit = async () => {
loading.value = true;
deleteDatabase(deleteMysqlReq.value)
.then(() => {
loading.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
})
.catch(() => {
loading.value = false;
});
};
defineExpose({
acceptParams,
});
</script>
@@ -79,7 +79,7 @@ import { toRef, onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/redis/check/index.vue';
import OperateDialog from '@/views/database/redis/remote/operate/index.vue';
import DeleteDialog from '@/views/database/redis/remote/delete/index.vue';
import DeleteDialog from '@/views/database/components/remote-delete/index.vue';
import i18n from '@/lang';
import { Database } from '@/api/interface/database';
+1 -1
View File
@@ -430,7 +430,7 @@
<QuickJump @search="onLoadBaseInfo(false, 'all')" ref="quickJumpRef" />
<DialogPro v-model="welcomeOpen" size="w-70" id="welcomeDialog">
<DialogPro v-model="welcomeOpen" id="welcomeDialog">
<div ref="shadowContainer" />
</DialogPro>
</div>
@@ -1,7 +1,7 @@
<template>
<DialogPro
v-model="open"
:size="isMobile ? 'full' : 'w-70'"
:size="isMobile ? 'full' : undefined"
class="code-dialog !p-0"
@opened="onOpen"
:show-close="false"
@@ -0,0 +1,25 @@
<template>
<div class="delete-file-list">
<div class="flx-align-center mb-1" v-for="(file, index) in files" :key="index">
<svg-icon
className="table-icon mr-1"
:iconName="file.isDir ? 'p-file-folder' : getIcon(file.extension || '')"
/>
<span class="sle">{{ file.name }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { getIcon } from '@/utils/file';
defineProps<{ files: { name: string; isDir: boolean; extension?: string }[] }>();
</script>
<style scoped>
.delete-file-list {
max-height: 400px;
overflow-y: auto;
margin-top: 15px;
}
</style>
@@ -1,5 +1,10 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.delete')" size="small">
<DialogPro
v-model="open"
:title="$t('commons.button.delete')"
:show-close="!loading"
:close-on-press-escape="!loading"
>
<div v-loading="loading">
<el-row>
<el-col :span="22" :offset="1">
@@ -20,23 +25,7 @@
</el-checkbox>
</div>
<div class="file-list">
<div class="flx-align-center mb-1" v-for="(row, index) in files" :key="index">
<div>
<svg-icon
v-if="row.isDir"
className="table-icon mr-1 "
iconName="p-file-folder"
></svg-icon>
<svg-icon
v-else
className="table-icon mr-1"
:iconName="getIconName(row.extension)"
></svg-icon>
</div>
<span class="sle">{{ row.name }}</span>
</div>
</div>
<DeleteFileList :files="files" />
</el-col>
</el-row>
</div>
@@ -57,26 +46,27 @@
import i18n from '@/lang';
import { ref } from 'vue';
import { File } from '@/api/interface/file';
import { getIcon } from '@/utils/file';
import DeleteFileList from '../components/delete-file-list.vue';
import { deleteFilesWithLimit } from '@/utils/file-delete';
import { deleteFile, deleteFileByNode, getRecycleStatus, getRecycleStatusByNode } from '@/api/modules/files';
import { MsgSuccess, MsgWarning } from '@/utils/message';
import { loadBaseDir } from '@/api/modules/setting';
const open = ref(false);
const files = ref();
const files = ref<File.File[]>([]);
const loading = ref(false);
const em = defineEmits(['close']);
const forceDelete = ref(false);
const recycleStatus = ref('Enable');
const reqNode = ref('');
const acceptParams = (props: File.File[], node: string) => {
const acceptParams = (props: File.File[], node = '') => {
reqNode.value = '';
if (node != '') {
reqNode.value = node;
}
getStatus();
files.value = props;
files.value = [...props];
open.value = true;
forceDelete.value = false;
};
@@ -101,67 +91,51 @@ const getStatus = async () => {
};
const onConfirm = async () => {
if (loading.value) return;
loading.value = true;
const selected = [...files.value];
const node = reqNode.value;
const force = forceDelete.value;
try {
const pros = [];
let baseDir = '';
if (files.value.some((item) => item['isDir'])) {
if (selected.some((item) => item.isDir)) {
const pathRes = await loadBaseDir();
baseDir = pathRes.data;
}
for (const s of files.value) {
if (s['isDir']) {
if (s['path'].indexOf('.1panel_clash') > -1) {
MsgWarning(i18n.global.t('file.clashDeleteAlert'));
return;
}
if (s['path'] === baseDir) {
MsgWarning(i18n.global.t('file.panelInstallDir'));
return;
}
// Validate the entire selection before starting any deletion request.
for (const file of selected) {
if (!file.isDir) continue;
if (file.path.includes('.1panel_clash')) {
MsgWarning(i18n.global.t('file.clashDeleteAlert'));
return;
}
if (reqNode.value != '') {
pros.push(
deleteFileByNode(
{ path: s['path'], isDir: s['isDir'], forceDelete: forceDelete.value },
reqNode.value,
),
);
} else {
pros.push(deleteFile({ path: s['path'], isDir: s['isDir'], forceDelete: forceDelete.value }));
if (file.path === baseDir) {
MsgWarning(i18n.global.t('file.panelInstallDir'));
return;
}
}
await Promise.all(pros);
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
em('close');
files.value = await deleteFilesWithLimit(selected, (file) => {
const params = { path: file.path, isDir: file.isDir, forceDelete: force };
return node ? deleteFileByNode(params, node) : deleteFile(params);
});
if (files.value.length === 0) {
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
}
if (files.value.length < selected.length) em('close');
} catch {
// The request interceptor reports errors such as a failed base-directory lookup.
} finally {
loading.value = false;
}
};
const getIconName = (extension: string) => {
return getIcon(extension);
};
defineExpose({
acceptParams,
});
</script>
<style scoped>
.resource {
margin-top: 10px;
max-height: 400px;
overflow: auto;
}
.file-list {
max-height: 400px;
overflow-y: auto;
margin-top: 15px;
}
.delete-warn {
line-height: 20px;
word-wrap: break-word;
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('file.downloadProcess')" size="small" @close="handleClose">
<DialogPro v-model="open" :title="$t('file.downloadProcess')" @close="handleClose">
<template #content>
<div v-loading="loading" class="space-y-4 p-4 min-h-[160px]">
<div
@@ -1,15 +1,14 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.delete')" size="small">
<DialogPro
v-model="open"
:title="$t('commons.button.delete')"
:show-close="!loading"
:close-on-press-escape="!loading"
>
<el-row>
<el-col :span="20" :offset="2">
<el-alert :title="$t('file.deleteRecycleHelper')" show-icon type="error" :closable="false"></el-alert>
<div class="flx-align-center mb-1 mt-1" v-for="(row, index) in files" :key="index">
<div>
<svg-icon v-if="row.isDir" className="table-icon mr-1 " iconName="p-file-folder"></svg-icon>
<svg-icon v-else className="table-icon mr-1" :iconName="getIconName(row.extension)"></svg-icon>
</div>
<span class="sle">{{ row.name }}</span>
</div>
<DeleteFileList :files="files" />
</el-col>
</el-row>
@@ -18,7 +17,7 @@
<el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onConfirm" v-loading="loading">
<el-button type="primary" @click="onConfirm" :loading="loading" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -29,52 +28,40 @@
import i18n from '@/lang';
import { ref } from 'vue';
import { File } from '@/api/interface/file';
import { getIcon } from '@/utils/file';
import DeleteFileList from '../../components/delete-file-list.vue';
import { deleteFilesWithLimit } from '@/utils/file-delete';
import { deleteFile } from '@/api/modules/files';
import { MsgSuccess } from '@/utils/message';
const open = ref(false);
const files = ref();
const files = ref<File.RecycleBin[]>([]);
const loading = ref(false);
const em = defineEmits(['close']);
const forceDelete = ref(false);
const acceptParams = (props: File.RecycleBin[]) => {
files.value = props;
files.value = [...props];
open.value = true;
forceDelete.value = false;
};
const onConfirm = () => {
const pros = [];
for (const s of files.value) {
pros.push(deleteFile({ path: s.from + '/' + s.rName, isDir: s.isDir, forceDelete: true }));
}
const onConfirm = async () => {
if (loading.value) return;
loading.value = true;
Promise.all(pros)
.then(() => {
const selected = [...files.value];
try {
files.value = await deleteFilesWithLimit(selected, (file) =>
deleteFile({ path: file.from + '/' + file.rName, isDir: file.isDir, forceDelete: true }),
);
if (files.value.length === 0) {
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
em('close');
})
.finally(() => {
loading.value = false;
});
};
const getIconName = (extension: string) => {
return getIcon(extension);
}
if (files.value.length < selected.length) em('close');
} finally {
loading.value = false;
}
};
defineExpose({
acceptParams,
});
</script>
<style scoped>
.resource {
margin-top: 10px;
max-height: 400px;
overflow: auto;
}
</style>
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('file.reduce')" size="small">
<DialogPro v-model="open" :title="$t('file.reduce')">
<el-row>
<el-col :span="20" :offset="2">
<el-alert :title="$t('file.confirmReduce')" show-icon type="error" :closable="false"></el-alert>
@@ -75,7 +75,7 @@
</span>
</template>
</DrawerPro>
<DialogPro v-model="qrDialogOpen" :title="$t('file.shareQrDialogTitle')" size="small">
<DialogPro v-model="qrDialogOpen" :title="$t('file.shareQrDialogTitle')">
<div class="share-qr-dialog">
<div class="share-qr-title">{{ $t('file.shareQrDialogTitle') }}</div>
<img v-if="qrCodeUrl" class="share-qr-image" :src="qrCodeUrl" :alt="$t('file.shareQrCode')" />
+1 -1
View File
@@ -58,7 +58,7 @@
</template>
</LayoutContent>
<DialogPro v-model="open" :title="$t('commons.button.export')" size="mini">
<DialogPro v-model="open" :title="$t('commons.button.export')">
<el-form class="mt-5" ref="backupForm" @submit.prevent v-loading="loading">
<el-form-item :label="$t('commons.table.status')">
<el-select v-model="exportConfig.status" class="w-full">
@@ -33,7 +33,7 @@
</template>
</DrawerPro>
<DialogPro v-model="connOpen" :title="$t('ssh.pubkey')" size="small" :showClose="false">
<DialogPro v-model="connOpen" :title="$t('ssh.pubkey')" :showClose="false">
<el-descriptions class="margin-top" :column="1" border>
<el-descriptions-item align="center" :label="$t('ssh.password')">
<div>
@@ -1,10 +1,5 @@
<template>
<DialogPro
v-model="open"
:title="$t('commons.button.delete') + ' - ' + resourceName"
size="small"
@close="handleClose"
>
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + resourceName" @close="handleClose">
<div :key="key" :loading="loading">
<el-form ref="deleteForm" label-position="left">
<el-form-item>
@@ -1,10 +1,5 @@
<template>
<DialogPro
v-model="open"
:title="$t('commons.button.' + operate) + $t('php.extensions')"
size="small"
@close="handleClose"
>
<DialogPro v-model="open" :title="$t('commons.button.' + operate) + $t('php.extensions')" @close="handleClose">
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-form @submit.prevent ref="extensionsForm" label-position="top" :model="extensions" :rules="rules">
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('ssl.apply')" size="small" @close="handleClose">
<DialogPro v-model="open" :title="$t('ssl.apply')" @close="handleClose">
<div class="text-center" v-loading="loading">
<div v-if="ssl.websites && ssl.websites.length > 0">
<span>{{ $t('ssl.renewWebsite') }}</span>
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="open" :title="$t('website.delete') + ' - ' + websiteName" size="small" @close="handleClose">
<DialogPro v-model="open" :title="$t('website.delete') + ' - ' + websiteName" @close="handleClose">
<div :key="key" v-loading="loading">
<el-form ref="deleteForm" label-position="left">
<el-form-item>