mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-13 10:34:08 +08:00
fix: Modify the style of the file exclusion control (#10504)
This commit is contained in:
parent
3f141346fa
commit
e87bf0b3fc
14 changed files with 143 additions and 137 deletions
|
|
@ -1,121 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" v-loading="loading" class="mt-2">
|
||||
<el-form-item prop="tmpRule">
|
||||
<div class="w-full">
|
||||
<el-input
|
||||
v-model="form.tmpRule"
|
||||
:rows="5"
|
||||
style="width: calc(100% - 50px)"
|
||||
type="textarea"
|
||||
:placeholder="$t('setting.ignoreHelper1')"
|
||||
/>
|
||||
<el-button icon="Folder" @click="fileRef.acceptParams({ path: baseDir, isAll: true })" />
|
||||
</div>
|
||||
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-button :disabled="form.tmpRule === ''" @click="handleAdd(formRef)">
|
||||
{{ $t('xpack.tamper.addRule') }}
|
||||
</el-button>
|
||||
|
||||
<el-table :data="tableList">
|
||||
<el-table-column prop="value" />
|
||||
<el-table-column min-width="18">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleDelete(scope.$index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<FileList ref="fileRef" @choose="loadDir" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
|
||||
const loading = ref();
|
||||
const fileRef = ref();
|
||||
const baseDir = ref();
|
||||
const tableList = ref();
|
||||
const em = defineEmits(['update:files']);
|
||||
|
||||
const props = defineProps({
|
||||
files: {
|
||||
type: Array<String>,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
tmpRule: '',
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
const rules = reactive({
|
||||
tmpRule: [{ validator: checkData, trigger: 'blur' }],
|
||||
});
|
||||
function checkData(rule: any, value: any, callback: any) {
|
||||
if (form.tmpRule !== '') {
|
||||
const reg = /^[^\\\"'|<>?]{1,128}$/;
|
||||
let items = value.split('\n');
|
||||
for (const item of items) {
|
||||
if (item.indexOf(' ') !== -1) {
|
||||
callback(new Error(i18n.global.t('setting.noSpace')));
|
||||
}
|
||||
if (!reg.test(item) && value !== '') {
|
||||
callback(new Error(i18n.global.t('commons.rule.linuxName', ['\\:?\'"<>|'])));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const loadPath = async () => {
|
||||
const pathRes = await loadBaseDir();
|
||||
baseDir.value = pathRes.data;
|
||||
};
|
||||
|
||||
const loadDir = async (path: string) => {
|
||||
form.tmpRule += path + '\n';
|
||||
};
|
||||
|
||||
const handleAdd = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let itemData = form.tmpRule.split('\n');
|
||||
for (const item of itemData) {
|
||||
if (item) {
|
||||
tableList.value.push({ value: item });
|
||||
}
|
||||
}
|
||||
em(
|
||||
'update:files',
|
||||
tableList.value.map((item) => item.value),
|
||||
);
|
||||
form.tmpRule = '';
|
||||
});
|
||||
};
|
||||
const handleDelete = (index: number) => {
|
||||
tableList.value.splice(index, 1);
|
||||
em(
|
||||
'update:files',
|
||||
tableList.value.map((item) => item.value),
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadPath();
|
||||
tableList.value = props.files.map((item) => {
|
||||
return { value: item };
|
||||
});
|
||||
});
|
||||
</script>
|
||||
85
frontend/src/components/input-tag/index.vue
Normal file
85
frontend/src/components/input-tag/index.vue
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-input-tag
|
||||
v-model="tmpTags"
|
||||
trigger="Enter"
|
||||
@paste="handlePaste"
|
||||
@change="handleUpdate"
|
||||
@add-tag="handleAdd"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-tooltip v-if="withFile" :content="$t('website.select')">
|
||||
<el-button
|
||||
class="-mr-3"
|
||||
link
|
||||
icon="Folder"
|
||||
@click="fileRef.acceptParams({ path: baseDir, isAll: true })"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="$t('commons.button.copy')">
|
||||
<el-button class="-mr-3" link icon="CopyDocument" @click="copyText(tmpTags.join('\n'))" />
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="$t('commons.button.clean')">
|
||||
<el-button link icon="Close" @click="handleClean" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-input-tag>
|
||||
<span v-if="props.egHelp" class="input-help">{{ props.egHelp }}</span>
|
||||
|
||||
<FileList ref="fileRef" @choose="loadFile" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { copyText } from '@/utils/util';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
const em = defineEmits(['update:tags']);
|
||||
|
||||
const tmpTags = ref([]);
|
||||
const fileRef = ref();
|
||||
|
||||
const props = defineProps({
|
||||
egHelp: { type: String, default: 'key=val' },
|
||||
tags: { type: Array<string>, default: [] },
|
||||
|
||||
withFile: { type: Boolean, default: false },
|
||||
baseDir: { type: String, default: '/' },
|
||||
});
|
||||
watch(
|
||||
() => props.tags,
|
||||
(newVal) => {
|
||||
tmpTags.value = newVal || [];
|
||||
},
|
||||
);
|
||||
|
||||
const loadFile = async (path: string) => {
|
||||
handleAdd(path);
|
||||
};
|
||||
|
||||
const handlePaste = (event: any) => {
|
||||
event.preventDefault();
|
||||
const pasteData = event.clipboardData.getData('text');
|
||||
const tags = pasteData.split('\n');
|
||||
for (const item of tags) {
|
||||
if (item) {
|
||||
handleAdd(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleAdd = (val: string) => {
|
||||
tmpTags.value = tmpTags.value?.filter((item) => item !== val);
|
||||
tmpTags.value.push(val);
|
||||
handleUpdate();
|
||||
};
|
||||
const handleUpdate = () => {
|
||||
em('update:tags', tmpTags.value);
|
||||
};
|
||||
const handleClean = () => {
|
||||
tmpTags.value = [];
|
||||
handleUpdate();
|
||||
};
|
||||
onMounted(() => {
|
||||
tmpTags.value = props.tags || [];
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1070,7 +1070,8 @@ const message = {
|
|||
snapshot: 'System snapshot',
|
||||
allOptionHelper: `The current task plan is to back up all [{0}]. Direct download isn't supported at the moment. You can check the backup list of [{0}] menu.`,
|
||||
exclusionRules: 'Exclusive rule',
|
||||
exclusionRulesHelper: 'The exclusion rules will apply to all compression operations of this backup.',
|
||||
exclusionRulesHelper:
|
||||
'Select or enter exclusion rules, press Enter after each set to continue. Exclusion rules will apply to all compression operations in this backup',
|
||||
default_download_path: 'Default download link',
|
||||
saveLocal: 'Retain local backups (the same as the number of cloud storage copies)',
|
||||
url: 'URL Address',
|
||||
|
|
|
|||
|
|
@ -1069,7 +1069,7 @@ const message = {
|
|||
allOptionHelper: `El plan actual es respaldar todos los [{0}]. La descarga directa no está soportada por ahora. Puede consultar la lista de respaldos en el menú [{0}].`,
|
||||
exclusionRules: 'Reglas de exclusión',
|
||||
exclusionRulesHelper:
|
||||
'Las reglas de exclusión se aplicarán a todas las operaciones de compresión de este respaldo.',
|
||||
'Seleccione o ingrese reglas de exclusión, presione Enter después de cada conjunto para continuar. Las reglas de exclusión se aplicarán a todas las operaciones de compresión en esta copia de seguridad',
|
||||
default_download_path: 'Enlace de descarga predeterminado',
|
||||
saveLocal: 'Retener respaldos locales (igual al número de copias en la nube)',
|
||||
url: 'Dirección URL',
|
||||
|
|
|
|||
|
|
@ -1040,7 +1040,8 @@ const message = {
|
|||
allOptionHelper:
|
||||
'現在のタスク計画は、すべての[{0}]をバックアップすることです。直接ダウンロードは現時点ではサポートされていません。[{{0}]メニューのバックアップリストを確認できます。',
|
||||
exclusionRules: '排他的ルール',
|
||||
exclusionRulesHelper: '除外ルールは、このバックアップのすべての圧縮操作に適用されます。',
|
||||
exclusionRulesHelper:
|
||||
'除外ルールを選択または入力し、各セット入力後にEnterキーを押して続行します。除外ルールはこのバックアップのすべての圧縮操作に適用されます',
|
||||
default_download_path: 'デフォルトのダウンロードリンク',
|
||||
saveLocal: 'ローカルバックアップを保持します(クラウドストレージコピーの数と同じ)',
|
||||
url: 'URLアドレス',
|
||||
|
|
|
|||
|
|
@ -1028,7 +1028,8 @@ const message = {
|
|||
snapshot: '시스템 스냅샷',
|
||||
allOptionHelper: `현재 작업 계획은 모든 [{0}]을 백업하는 것입니다. 현재 직접 다운로드는 지원되지 않습니다. [{0}] 메뉴에서 백업 목록을 확인하실 수 있습니다.`,
|
||||
exclusionRules: '배제 규칙',
|
||||
exclusionRulesHelper: '배제 규칙은 이 백업의 모든 압축 작업에 적용됩니다.',
|
||||
exclusionRulesHelper:
|
||||
'제외 규칙을 선택하거나 입력하고, 각 세트 입력 후 Enter 키를 눌러 계속합니다. 제외 규칙은 이 백업의 모든 압축 작업에 적용됩니다',
|
||||
default_download_path: '기본 다운로드 링크',
|
||||
saveLocal: '로컬 백업 보관 (클라우드 저장소 복사본 수와 동일)',
|
||||
url: 'URL 주소',
|
||||
|
|
|
|||
|
|
@ -1062,7 +1062,8 @@ const message = {
|
|||
allOptionHelper:
|
||||
'Pelan tugas semasa adalah untuk menyandarkan semua [{0}]. Muat turun terus tidak disokong buat masa ini. Anda boleh menyemak senarai sandaran dalam menu [{0}].',
|
||||
exclusionRules: 'Peraturan pengecualian',
|
||||
exclusionRulesHelper: 'Peraturan pengecualian akan terpakai pada semua operasi mampatan bagi sandaran ini.',
|
||||
exclusionRulesHelper:
|
||||
'Pilih atau masukkan peraturan pengecualian, tekan Enter selepas setiap set untuk teruskan. Peraturan pengecualian akan digunakan untuk semua operasi mampatan dalam sandaran ini',
|
||||
default_download_path: 'Pautan muat turun lalai',
|
||||
saveLocal: 'Simpan sandaran tempatan (sama seperti bilangan salinan storan awan)',
|
||||
url: 'Alamat URL',
|
||||
|
|
|
|||
|
|
@ -1059,7 +1059,8 @@ const message = {
|
|||
allOptionHelper:
|
||||
'O plano de tarefa atual é fazer backup de todos os [{0}]. O download direto não é suportado no momento. Você pode verificar a lista de backups no menu [{0}].',
|
||||
exclusionRules: 'Regras de exclusão',
|
||||
exclusionRulesHelper: 'As regras de exclusão se aplicam a todas as operações de compressão deste backup.',
|
||||
exclusionRulesHelper:
|
||||
'Selecione ou insira regras de exclusão, pressione Enter após cada conjunto para continuar. As regras de exclusão se aplicarão a todas as operações de compactação neste backup',
|
||||
default_download_path: 'Link de download padrão',
|
||||
saveLocal: 'Manter backups locais (o mesmo número de cópias na nuvem)',
|
||||
url: 'Endereço URL',
|
||||
|
|
|
|||
|
|
@ -1056,7 +1056,8 @@ const message = {
|
|||
allOptionHelper:
|
||||
'Текущий план задачи - резервное копирование всех [{0}]. Прямое скачивание сейчас не поддерживается. Вы можете проверить список резервных копий в меню [{0}].',
|
||||
exclusionRules: 'Правило исключения',
|
||||
exclusionRulesHelper: 'Правила исключения будут применяться ко всем операциям сжатия этой резервной копии.',
|
||||
exclusionRulesHelper:
|
||||
'Выберите или введите правила исключения, нажмите Enter после каждого набора для продолжения. Правила исключения будут применяться ко всем операциям сжатия в этой резервной копии',
|
||||
default_download_path: 'Ссылка для скачивания по умолчанию',
|
||||
saveLocal: 'Сохранять локальные резервные копии (столько же, сколько копий в облачном хранилище)',
|
||||
url: 'URL-адрес',
|
||||
|
|
|
|||
|
|
@ -1083,7 +1083,8 @@ const message = {
|
|||
allOptionHelper:
|
||||
'Mevcut görev planı tüm [{0}] öğelerini yedeklemektir. Doğrudan indirme şu anda desteklenmiyor. [{0}] menüsünün yedekleme listesini kontrol edebilirsiniz.',
|
||||
exclusionRules: 'Hariç tutma kuralı',
|
||||
exclusionRulesHelper: 'Hariç tutma kuralları bu yedeğin tüm sıkıştırma işlemlerine uygulanacaktır.',
|
||||
exclusionRulesHelper:
|
||||
'Hariç tutma kurallarını seçin veya girin, her setten sonra Enter basarak devam edin. Hariç tutma kuralları bu yedeklemedeki tüm sıkıştırma işlemlerine uygulanacaktır',
|
||||
default_download_path: 'Varsayılan indirme bağlantısı',
|
||||
saveLocal: 'Yerel yedeklemeleri sakla (bulut depolama kopyalarının sayısı ile aynı)',
|
||||
url: 'URL Adresi',
|
||||
|
|
|
|||
|
|
@ -1016,7 +1016,7 @@ const message = {
|
|||
snapshot: '系統快照',
|
||||
allOptionHelper: '目前計劃任務為備份所有【{0}】,暫不支援直接下載,可在【{0}】備份列表中查看',
|
||||
exclusionRules: '排除規則',
|
||||
exclusionRulesHelper: '排除規則將對此次備份的所有壓縮操作生效',
|
||||
exclusionRulesHelper: '選擇或輸入排除規則,輸入完一組後回車繼續,排除規則將對此次備份的所有壓縮操作生效',
|
||||
default_download_path: '預設下載網址',
|
||||
saveLocal: '同時保留本機備份(和雲端儲存保留份數一致)',
|
||||
url: 'URL 地址',
|
||||
|
|
|
|||
|
|
@ -1015,7 +1015,7 @@ const message = {
|
|||
snapshot: '系统快照',
|
||||
allOptionHelper: '当前计划任务为备份所有【{0}】,暂不支持直接下载,可在【{0}】备份列表中查看',
|
||||
exclusionRules: '排除规则',
|
||||
exclusionRulesHelper: '排除规则将对此次备份的所有压缩操作生效',
|
||||
exclusionRulesHelper: '选择或输入排除规则,输入完一组后回车继续,排除规则将对此次备份的所有压缩操作生效',
|
||||
default_download_path: '默认下载地址',
|
||||
saveLocal: '同时保留本地备份(和云存储保留份数一致)',
|
||||
url: 'URL 地址',
|
||||
|
|
|
|||
|
|
@ -624,8 +624,13 @@
|
|||
<el-row :gutter="20">
|
||||
<LayoutCol :span="20" v-if="hasExclusionRules()">
|
||||
<el-form-item :label="$t('cronjob.exclusionRules')" prop="exclusionRules">
|
||||
<IgnoreFile class="w-full" v-model:files="form.ignoreFiles"></IgnoreFile>
|
||||
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
|
||||
<InputTag
|
||||
class="w-full"
|
||||
v-model:tags="form.ignoreFiles"
|
||||
:withFile="true"
|
||||
:baseDir="loadItemDir()"
|
||||
:egHelp="$t('cronjob.exclusionRulesHelper')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</LayoutCol>
|
||||
</el-row>
|
||||
|
|
@ -757,7 +762,7 @@ import { ElForm } from 'element-plus';
|
|||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
import { addCronjob, editCronjob, loadCronjobInfo, loadNextHandle, loadScriptOptions } from '@/api/modules/cronjob';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import IgnoreFile from '@/components/file-batch/index.vue';
|
||||
import InputTag from '@/components/input-tag/index.vue';
|
||||
import LayoutCol from '@/components/layout-col/form.vue';
|
||||
import { listDbItems } from '@/api/modules/database';
|
||||
import { getWebsiteOptions } from '@/api/modules/website';
|
||||
|
|
@ -782,6 +787,7 @@ import LicenseImport from '@/components/license-import/index.vue';
|
|||
import { splitTimeFromSecond, transferTimeToSecond } from '@/utils/util';
|
||||
import { getGroupList } from '@/api/modules/group';
|
||||
import { routerToName, routerToPath } from '@/utils/router';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
const router = useRouter();
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
|
|
@ -793,6 +799,8 @@ const { isProductPro } = storeToRefs(globalStore);
|
|||
const loading = ref();
|
||||
const nextTimes = ref([]);
|
||||
|
||||
const baseDir = ref();
|
||||
|
||||
const isCreate = ref();
|
||||
const defaultGroupID = ref();
|
||||
const form = reactive<Cronjob.CronjobInfo>({
|
||||
|
|
@ -1162,6 +1170,18 @@ const loadScriptDir = async (path: string) => {
|
|||
form.script = path;
|
||||
};
|
||||
|
||||
const loadItemDir = () => {
|
||||
if (form.type === 'directory' && form.isDir) {
|
||||
return form.sourceDir;
|
||||
}
|
||||
return baseDir.value;
|
||||
};
|
||||
|
||||
const loadInstallDir = async () => {
|
||||
const pathRes = await loadBaseDir();
|
||||
baseDir.value = pathRes.data;
|
||||
};
|
||||
|
||||
const loadGroups = async () => {
|
||||
const res = await getGroupList('cronjob');
|
||||
groupOptions.value = res.data || [];
|
||||
|
|
@ -1468,6 +1488,7 @@ onMounted(() => {
|
|||
}
|
||||
loadGroups();
|
||||
search();
|
||||
loadInstallDir();
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
|
|
|||
|
|
@ -158,7 +158,13 @@
|
|||
</fu-step>
|
||||
<template #footer></template>
|
||||
<fu-step id="ignoreFiles" :title="$t('cronjob.exclusionRules')">
|
||||
<IgnoreFile v-model:files="form.ignoreFiles"></IgnoreFile>
|
||||
<InputTag
|
||||
class="w-full"
|
||||
v-model:tags="form.ignoreFiles"
|
||||
:withFile="true"
|
||||
:baseDir="baseDir"
|
||||
:egHelp="$t('cronjob.exclusionRulesHelper')"
|
||||
/>
|
||||
</fu-step>
|
||||
</fu-steps>
|
||||
<template #footer>
|
||||
|
|
@ -174,11 +180,11 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { loadSnapshotInfo, snapshotCreate } from '@/api/modules/setting';
|
||||
import { loadBaseDir, loadSnapshotInfo, snapshotCreate } from '@/api/modules/setting';
|
||||
import { computeSize, newUUID, transferTimeToSecond } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import IgnoreFile from '@/components/file-batch/index.vue';
|
||||
import InputTag from '@/components/input-tag/index.vue';
|
||||
import { listBackupOptions } from '@/api/modules/backup';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { ElForm } from 'element-plus';
|
||||
|
|
@ -194,6 +200,8 @@ const panelRef = ref();
|
|||
const backupRef = ref();
|
||||
const taskLogRef = ref();
|
||||
|
||||
const baseDir = ref();
|
||||
|
||||
const backupOptions = ref();
|
||||
const accountOptions = ref();
|
||||
|
||||
|
|
@ -249,6 +257,7 @@ const acceptParams = (): void => {
|
|||
nowIndex.value = 0;
|
||||
search();
|
||||
loadBackups();
|
||||
loadInstallDir();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
|
|
@ -466,6 +475,11 @@ const selectAllImage = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const loadInstallDir = async () => {
|
||||
const pathRes = await loadBaseDir();
|
||||
baseDir.value = pathRes.data;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
await loadSnapshotInfo()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue