From d25b5f4f2a7ae275f649f22046473b1731ba5028 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:35:03 +0800 Subject: [PATCH] feat: Add quick operation features to node overview page (#11476) --- frontend/src/api/modules/backup.ts | 40 ++++++++++-------- frontend/src/api/modules/cronjob.ts | 15 ++++--- frontend/src/api/modules/database.ts | 15 ++++--- frontend/src/api/modules/website.ts | 10 +++-- frontend/src/components/backup/index.vue | 21 ++++++---- .../src/components/card-with-header/index.vue | 4 +- frontend/src/styles/common.scss | 41 ++++++++++++++++++- .../container/container/terminal/index.vue | 7 +++- 8 files changed, 108 insertions(+), 45 deletions(-) diff --git a/frontend/src/api/modules/backup.ts b/frontend/src/api/modules/backup.ts index 11ee0666b..db8aa5b7b 100644 --- a/frontend/src/api/modules/backup.ts +++ b/frontend/src/api/modules/backup.ts @@ -8,8 +8,9 @@ import { GlobalStore } from '@/store'; const globalStore = GlobalStore(); // backup-agent -export const getLocalBackupDir = () => { - return http.get(`/backups/local`); +export const getLocalBackupDir = (node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`/backups/local${params}`); }; export const searchBackup = (params: Backup.SearchWithType) => { return http.post>(`/backups/search`, params); @@ -40,35 +41,42 @@ export const listBucket = (params: Backup.ForBucket) => { } return http.post('/backups/buckets', request, TimeoutEnum.T_40S); }; -export const handleBackup = (params: Backup.Backup) => { - return http.post(`/backups/backup`, params, TimeoutEnum.T_10M); +export const handleBackup = (params: Backup.Backup, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/backup${query}`, params, TimeoutEnum.T_10M); }; export const listBackupOptions = () => { return http.get>(`/backups/options`); }; -export const handleRecover = (params: Backup.Recover) => { - return http.post(`/backups/recover`, params, TimeoutEnum.T_10M); +export const handleRecover = (params: Backup.Recover, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/recover${query}`, params, TimeoutEnum.T_10M); }; export const handleRecoverByUpload = (params: Backup.Recover) => { return http.post(`/backups/recover/byupload`, params, TimeoutEnum.T_10M); }; -export const downloadBackupRecord = (params: Backup.RecordDownload) => { - return http.post(`/backups/record/download`, params, TimeoutEnum.T_10M); +export const downloadBackupRecord = (params: Backup.RecordDownload, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/record/download${query}`, params, TimeoutEnum.T_10M); }; -export const deleteBackupRecord = (params: { ids: number[] }) => { - return http.post(`/backups/record/del`, params); +export const deleteBackupRecord = (params: { ids: number[] }, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/record/del${query}`, params); }; -export const updateRecordDescription = (id: Number, description: String) => { - return http.post(`/backups/record/description/update`, { id: id, description: description }); +export const updateRecordDescription = (id: Number, description: String, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/record/description/update${query}`, { id: id, description: description }); }; export const uploadByRecover = (filePath: string, targetDir: String) => { return http.post(`/backups/upload`, { filePath: filePath, targetDir: targetDir }); }; -export const searchBackupRecords = (params: Backup.SearchBackupRecord) => { - return http.post>(`/backups/record/search`, params, TimeoutEnum.T_5M); +export const searchBackupRecords = (params: Backup.SearchBackupRecord, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/backups/record/search${query}`, params, TimeoutEnum.T_5M); }; -export const loadRecordSize = (param: Backup.SearchForSize) => { - return http.post>(`/backups/record/size`, param); +export const loadRecordSize = (param: Backup.SearchForSize, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/backups/record/size${query}`, param); }; export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => { return http.post>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M); diff --git a/frontend/src/api/modules/cronjob.ts b/frontend/src/api/modules/cronjob.ts index 2a0c6490a..384726887 100644 --- a/frontend/src/api/modules/cronjob.ts +++ b/frontend/src/api/modules/cronjob.ts @@ -3,8 +3,9 @@ import { ResPage, SearchWithPage } from '../interface'; import { Cronjob } from '../interface/cronjob'; import { TimeoutEnum } from '@/enums/http-enum'; -export const searchCronjobPage = (params: Cronjob.Search) => { - return http.post>(`/cronjobs/search`, params); +export const searchCronjobPage = (params: Cronjob.Search, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/cronjobs/search${query}`, params); }; export const loadNextHandle = (spec: string) => { @@ -58,12 +59,14 @@ export const cleanRecords = (id: number, cleanData: boolean, cleanRemoteData: bo return http.post(`cronjobs/records/clean`, { cronjobID: id, cleanData: cleanData, cleanRemoteData }); }; -export const updateStatus = (params: Cronjob.UpdateStatus) => { - return http.post(`cronjobs/status`, params); +export const updateStatus = (params: Cronjob.UpdateStatus, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`cronjobs/status${query}`, params); }; -export const handleOnce = (id: number) => { - return http.post(`cronjobs/handle`, { id: id }); +export const handleOnce = (id: number, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`cronjobs/handle${query}`, { id: id }); }; export const searchScript = (params: SearchWithPage) => { diff --git a/frontend/src/api/modules/database.ts b/frontend/src/api/modules/database.ts index 05518e887..3dbd356d7 100644 --- a/frontend/src/api/modules/database.ts +++ b/frontend/src/api/modules/database.ts @@ -30,8 +30,9 @@ export const bindPostgresqlUser = (params: Database.PgBind) => { export const changePrivileges = (params: Database.PgChangePrivileges) => { return http.post(`/databases/pg/privileges`, params, TimeoutEnum.T_40S); }; -export const searchPostgresqlDBs = (params: Database.SearchDBWithPage) => { - return http.post>(`/databases/pg/search`, params); +export const searchPostgresqlDBs = (params: Database.SearchDBWithPage, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/databases/pg/search${query}`, params); }; export const updatePostgresqlDescription = (params: DescriptionUpdate) => { return http.post(`/databases/pg/description`, params); @@ -54,8 +55,9 @@ export const deletePostgresqlDB = (params: Database.PostgresqlDBDelete) => { }; // mysql -export const searchMysqlDBs = (params: Database.SearchDBWithPage) => { - return http.post>(`/databases/search`, params); +export const searchMysqlDBs = (params: Database.SearchDBWithPage, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/databases/search${query}`, params); }; export const addMysqlDB = (params: Database.MysqlDBCreate) => { let request = deepCopy(params) as Database.MysqlDBCreate; @@ -152,8 +154,9 @@ export const getDatabase = (name: string) => { export const searchDatabases = (params: Database.SearchDatabasePage) => { return http.post>(`/databases/db/search`, params); }; -export const listDatabases = (type: string) => { - return http.get>(`/databases/db/list/${type}`); +export const listDatabases = (type: string, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.get>(`/databases/db/list/${type}${query}`); }; export const listDbItems = (type: string) => { return http.get>(`/databases/db/item/${type}`); diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 4d0d12849..cd95d8089 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -6,8 +6,9 @@ import { TimeoutEnum } from '@/enums/http-enum'; import { deepCopy } from '@/utils/util'; import { Base64 } from 'js-base64'; -export const searchWebsites = (req: Website.WebSiteSearch) => { - return http.post>(`/websites/search`, req); +export const searchWebsites = (req: Website.WebSiteSearch, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post>(`/websites/search${params}`, req); }; export const listWebsites = () => { @@ -22,8 +23,9 @@ export const createWebsite = (req: Website.WebSiteCreateReq) => { return http.post(`/websites`, request, TimeoutEnum.T_10M); }; -export const opWebsite = (req: Website.WebSiteOp) => { - return http.post(`/websites/operate`, req); +export const opWebsite = (req: Website.WebSiteOp, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/websites/operate${query}`, req); }; export const opWebsiteLog = (req: Website.WebSiteOpLog) => { diff --git a/frontend/src/components/backup/index.vue b/frontend/src/components/backup/index.vue index 706c9896f..7a2030d83 100644 --- a/frontend/src/components/backup/index.vue +++ b/frontend/src/components/backup/index.vue @@ -200,6 +200,7 @@ const secret = ref(); const description = ref(); const timeoutItem = ref(30); const timeoutUnit = ref('m'); +const node = ref(); const open = ref(); const isBackup = ref(); @@ -212,9 +213,11 @@ interface DialogProps { detailName: string; status: string; appInstallID?: number; + node?: string; } const acceptParams = (params: DialogProps): void => { type.value = params.type; + node.value = params.node || currentNode.value; if (type.value === 'app') { appInstallID.value = params.appInstallID || 0; loadBackupDir(); @@ -235,7 +238,7 @@ const handleBackupClose = () => { }; const loadBackupDir = async () => { - const res = await getLocalBackupDir(); + const res = await getLocalBackupDir(node.value); backupPath.value = res.data; }; @@ -244,7 +247,7 @@ const goFile = async () => { }; const onChange = async (info: any) => { - await updateRecordDescription(info.id, info.description); + await updateRecordDescription(info.id, info.description, node.value); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); }; @@ -257,7 +260,7 @@ const search = async () => { detailName: detailName.value, }; loading.value = true; - await searchBackupRecords(params) + await searchBackupRecords(params, node.value) .then((res) => { loading.value = false; loadSize(params); @@ -270,7 +273,7 @@ const search = async () => { }; const loadSize = async (params: any) => { - await loadRecordSize(params) + await loadRecordSize(params, node.value) .then((res) => { let stats = res.data || []; if (stats.length === 0) { @@ -310,7 +313,7 @@ const backup = async () => { description: description.value, }; loading.value = true; - await handleBackup(params) + await handleBackup(params, node.value) .then(() => { loading.value = false; openTaskLog(taskID); @@ -335,7 +338,7 @@ const recover = async (row?: any) => { timeout: timeoutItem.value === -1 ? -1 : transferTimeToSecond(timeoutItem.value + timeoutUnit.value), }; loading.value = true; - await handleRecover(params) + await handleRecover(params, node.value) .then(() => { loading.value = false; openTaskLog(taskID); @@ -374,8 +377,8 @@ const onDownload = async (row: Backup.RecordInfo) => { fileDir: row.fileDir, fileName: row.fileName, }; - await downloadBackupRecord(params).then(async (res) => { - downloadFile(res.data, currentNode.value); + await downloadBackupRecord(params, node.value).then(async (res) => { + downloadFile(res.data, node.value); }); }; @@ -399,7 +402,7 @@ const onBatchDelete = async (row: Backup.RecordInfo | null) => { i18n.global.t('commons.button.backup'), i18n.global.t('commons.button.delete'), ]), - params: { ids: ids }, + params: { ids: ids, node: node.value }, }); }; diff --git a/frontend/src/components/card-with-header/index.vue b/frontend/src/components/card-with-header/index.vue index 1f9fa3700..5d1bf4b8b 100644 --- a/frontend/src/components/card-with-header/index.vue +++ b/frontend/src/components/card-with-header/index.vue @@ -2,11 +2,11 @@
-
+
{{ header }}
-
+
diff --git a/frontend/src/styles/common.scss b/frontend/src/styles/common.scss index c393becc6..c6ff12958 100644 --- a/frontend/src/styles/common.scss +++ b/frontend/src/styles/common.scss @@ -126,7 +126,7 @@ html { } .search-button { - width: 250px; + width: 200px; } .drawer-header-button { @@ -351,3 +351,42 @@ html { --el-dialog-padding-primary: 0px !important; } } + +.node-dashboard-card { + .header { + display: flex; + justify-content: space-between; + align-items: center; + + .header-left { + display: flex; + align-items: center; + gap: 12px; + + .header-span { + position: relative; + font-size: 16px; + font-weight: 500; + margin-left: 18px; + display: flex; + align-items: center; + + &::before { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: -13px; + width: 4px; + height: 14px; + content: ''; + background: $primary-color; + border-radius: 10px; + } + } + } + .header-right { + display: flex; + align-items: center; + } + } +} \ No newline at end of file diff --git a/frontend/src/views/container/container/terminal/index.vue b/frontend/src/views/container/container/terminal/index.vue index c02de54a9..729bd1c7c 100644 --- a/frontend/src/views/container/container/terminal/index.vue +++ b/frontend/src/views/container/container/terminal/index.vue @@ -64,6 +64,8 @@ import { reactive, ref, nextTick } from 'vue'; import { ElForm, FormInstance } from 'element-plus'; import { Rules } from '@/global/form-rules'; import Terminal from '@/components/terminal/index.vue'; +import { useGlobalStore } from '@/composables/useGlobalStore'; +const { currentNode } = useGlobalStore(); const title = ref(); const terminalVisible = ref(false); @@ -74,6 +76,7 @@ const form = reactive({ user: '', containerID: '', containerIDList: [], + node: '', }); const formRef = ref(); const terminalRef = ref | null>(null); @@ -81,6 +84,7 @@ const terminalRef = ref | null>(null); interface DialogProps { containerID: string; title: string; + node?: string; } const acceptParams = async (params: DialogProps): Promise => { terminalVisible.value = true; @@ -90,6 +94,7 @@ const acceptParams = async (params: DialogProps): Promise => { form.isCustom = false; form.user = ''; form.command = '/bin/sh'; + form.node = params.node || currentNode.value; terminalOpen.value = false; }; @@ -105,7 +110,7 @@ const initTerm = (formEl: FormInstance | undefined) => { await nextTick(); terminalRef.value!.acceptParams({ endpoint: '/api/v2/containers/exec', - args: `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}`, + args: `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}&node=${form.node}`, error: '', initCmd: '', });