feat: Add quick operation features to node overview page (#11476)

This commit is contained in:
ssongliu 2025-12-26 16:35:03 +08:00 committed by GitHub
parent 9a17bc74b0
commit d25b5f4f2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 108 additions and 45 deletions

View file

@ -8,8 +8,9 @@ import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
// backup-agent
export const getLocalBackupDir = () => {
return http.get<string>(`/backups/local`);
export const getLocalBackupDir = (node?: string) => {
const params = node ? `?operateNode=${node}` : '';
return http.get<string>(`/backups/local${params}`);
};
export const searchBackup = (params: Backup.SearchWithType) => {
return http.post<ResPage<Backup.BackupInfo>>(`/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<Array<Backup.BackupOption>>(`/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<string>(`/backups/record/download`, params, TimeoutEnum.T_10M);
export const downloadBackupRecord = (params: Backup.RecordDownload, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<string>(`/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<ResPage<Backup.RecordInfo>>(`/backups/record/search`, params, TimeoutEnum.T_5M);
export const searchBackupRecords = (params: Backup.SearchBackupRecord, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search${query}`, params, TimeoutEnum.T_5M);
};
export const loadRecordSize = (param: Backup.SearchForSize) => {
return http.post<Array<Backup.RecordFileSize>>(`/backups/record/size`, param);
export const loadRecordSize = (param: Backup.SearchForSize, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<Array<Backup.RecordFileSize>>(`/backups/record/size${query}`, param);
};
export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => {
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M);

View file

@ -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<ResPage<Cronjob.CronjobInfo>>(`/cronjobs/search`, params);
export const searchCronjobPage = (params: Cronjob.Search, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<ResPage<Cronjob.CronjobInfo>>(`/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) => {

View file

@ -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<ResPage<Database.PostgresqlDBInfo>>(`/databases/pg/search`, params);
export const searchPostgresqlDBs = (params: Database.SearchDBWithPage, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<ResPage<Database.PostgresqlDBInfo>>(`/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<ResPage<Database.MysqlDBInfo>>(`/databases/search`, params);
export const searchMysqlDBs = (params: Database.SearchDBWithPage, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<ResPage<Database.MysqlDBInfo>>(`/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<ResPage<Database.DatabaseInfo>>(`/databases/db/search`, params);
};
export const listDatabases = (type: string) => {
return http.get<Array<Database.DatabaseOption>>(`/databases/db/list/${type}`);
export const listDatabases = (type: string, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.get<Array<Database.DatabaseOption>>(`/databases/db/list/${type}${query}`);
};
export const listDbItems = (type: string) => {
return http.get<Array<Database.DbItem>>(`/databases/db/item/${type}`);

View file

@ -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<ResPage<Website.WebsiteRes>>(`/websites/search`, req);
export const searchWebsites = (req: Website.WebSiteSearch, node?: string) => {
const params = node ? `?operateNode=${node}` : '';
return http.post<ResPage<Website.WebsiteRes>>(`/websites/search${params}`, req);
};
export const listWebsites = () => {
@ -22,8 +23,9 @@ export const createWebsite = (req: Website.WebSiteCreateReq) => {
return http.post<any>(`/websites`, request, TimeoutEnum.T_10M);
};
export const opWebsite = (req: Website.WebSiteOp) => {
return http.post<any>(`/websites/operate`, req);
export const opWebsite = (req: Website.WebSiteOp, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<any>(`/websites/operate${query}`, req);
};
export const opWebsiteLog = (req: Website.WebSiteOpLog) => {

View file

@ -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 },
});
};

View file

@ -2,11 +2,11 @@
<div>
<el-card :style="{ height: height }" class="home-card">
<div class="header">
<div class="header-left">
<div class="header-left flex flex-wrap gap-3">
<span class="header-span">{{ header }}</span>
<slot name="header-l" />
</div>
<div class="header-right">
<div class="header-right flex flex-wrap gap-3">
<slot name="header-r" />
</div>
</div>

View file

@ -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;
}
}
}

View file

@ -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<InstanceType<typeof Terminal> | null>(null);
@ -81,6 +84,7 @@ const terminalRef = ref<InstanceType<typeof Terminal> | null>(null);
interface DialogProps {
containerID: string;
title: string;
node?: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
terminalVisible.value = true;
@ -90,6 +94,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
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: '',
});