Skip to content

Commit

Permalink
feat: implement backup index to json file
Browse files Browse the repository at this point in the history
Signed-off-by: seven <[email protected]>
  • Loading branch information
Blankll committed Nov 14, 2024
1 parent e330018 commit 2ca9fc0
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 20 deletions.
3 changes: 2 additions & 1 deletion src/datasources/fetchApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,13 @@ const loadHttpClient = (con: {
password?: string;
sslCertVerification: boolean;
}) => ({
get: async (path?: string, queryParameters?: string) =>
get: async (path?: string, queryParameters?: string, payload?: string) =>
fetchWrapper({
...con,
method: 'GET',
path,
queryParameters,
payload,
ssl: con.sslCertVerification,
}),
post: async (path: string, queryParameters?: string, payload?: string) =>
Expand Down
7 changes: 4 additions & 3 deletions src/datasources/sourceFileApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {

import { CustomError, debug } from '../common';

const saveFile = async (filePath: string, content: string) => {
const saveFile = async (filePath: string, content: string, append: boolean) => {
try {
const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));

if (!(await exists(folderPath, { dir: BaseDirectory.AppData }))) {
await createDir(folderPath, { dir: BaseDirectory.AppData, recursive: true });
}
await writeTextFile(filePath, content, { dir: BaseDirectory.AppConfig, append: false });
await writeTextFile(filePath, content, { dir: BaseDirectory.AppConfig, append });
debug('save file success');
} catch (err) {
debug(`saveFile error: ${err}`);
Expand Down Expand Up @@ -60,7 +60,8 @@ const renameFileOrFolder = async (oldPath: string, newPath: string) => {
};

const sourceFileApi = {
saveFile: (filePath: string, content: string) => saveFile(filePath, content),
saveFile: (filePath: string, content: string, append = false) =>
saveFile(filePath, content, append),
readFile: (filePath: string) => readFromFile(filePath),
createFolder: (folderPath: string) => createDir(folderPath),
deleteFileOrFolder,
Expand Down
2 changes: 2 additions & 0 deletions src/lang/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export const enUS = {
backup: 'Backup',
restore: 'Restore',
restoreSourceDesc: 'Click or drag a file to this area to upload your JSON/CSV file',
backupToFileSuccess: 'Successfully backed up to file',
backupForm: {
connection: 'Connection',
index: 'Index',
Expand All @@ -186,6 +187,7 @@ export const enUS = {
backupFileNameRequired: 'Please enter Backup File Name',
validationFailed: 'Backup Config validation failed!',
validationPassed: 'Backup Config validation passed!',
backupFileTypeInvalid: 'Backup file type is invalid',
},
},
version: {
Expand Down
2 changes: 2 additions & 0 deletions src/lang/zhCN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export const zhCN = {
backup: '备份',
restore: '恢复',
restoreSourceDesc: '点击或拖动文件到此区域上传您的 JSON/CSV 文件',
backupToFileSuccess: '成功备份到文件',
backupForm: {
connection: '选择连接',
index: '选择索引',
Expand All @@ -187,6 +188,7 @@ export const zhCN = {
backupFileNameRequired: '请输入备份文件名',
validationFailed: '备份操作配置校验失败!',
validationPassed: '备份操作配置校验通过!',
backupFileTypeInvalid: '备份文件类型无效',
},
},
version: {
Expand Down
75 changes: 75 additions & 0 deletions src/store/backupRestoreStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import { open } from '@tauri-apps/api/dialog';
import { defineStore } from 'pinia';
import { CustomError } from '../common';
import { get } from 'lodash';
import { Connection } from './connectionStore.ts';
import { loadHttpClient, sourceFileApi } from '../datasources';
// import { loadHttpClient } from '../datasources';
export type typeBackupInput = {
connection: Connection;
index: string;
backupFolder: string;
backupFileName: string;
backupFileType: string;
};

export const useBackupRestoreStore = defineStore('backupRestoreStore', {
state(): { folderPath: string; fileName: string } {
Expand All @@ -24,5 +34,70 @@ export const useBackupRestoreStore = defineStore('backupRestoreStore', {
);
}
},
async backupToFile(input: typeBackupInput) {
const client = loadHttpClient(input.connection);
const filePath = `${input.backupFolder}/${input.backupFileName}.${input.backupFileType}`;
let searchAfter: any[] | undefined = undefined;
let hasMore = true;
try {
while (hasMore) {
const response = await client.get(
`/${input.index}/_search`,
undefined,
JSON.stringify({
size: 1000,
search_after: searchAfter,
sort: [{ _doc: 'asc' }],
}),
);

console.log('es response', response);

const hits = response.hits.hits;
if (hits.length === 0) {
hasMore = false;
} else {
searchAfter = hits[hits.length - 1].sort;
const dataToWrite =
input.backupFileType === 'json'
? JSON.stringify(hits)
: JSON.stringify(convertToCsv(hits));
await sourceFileApi.saveFile(filePath, dataToWrite, true);
}
}
return filePath;
} catch (error) {
throw new CustomError(
get(error, 'status', 500),
get(error, 'details', get(error, 'message', '')),
);
}
},
},
});

const flattenObject = (obj: any, parent: string = '', res: any = {}) => {
for (let key in obj) {
const propName = parent ? `${parent}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
flattenObject(obj[key], propName, res);
} else {
res[propName] = obj[key];
}
}
return res;
};

const convertToCsv = (data: any[]) => {
if (data.length === 0) {
return { headers: [], data: [] };
}

const flattenedData = data.map(row => flattenObject(row));
const headers = Array.from(new Set(flattenedData.flatMap(row => Object.keys(row))));
const csvRows = flattenedData.map(row =>
headers.map(header => JSON.stringify(row[header] ?? '')).join(','),
);

return { headers, data: csvRows };
};
3 changes: 3 additions & 0 deletions src/store/connectionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export const useConnectionStore = defineStore('connectionStore', {
establishedIndexNames(state) {
return state.established?.indices.map(({ index }) => index) ?? [];
},
establishedIndexOptions(state) {
return state.established?.indices.map(({ index }) => ({ label: index, value: index })) ?? [];
},
},
actions: {
async fetchConnections() {
Expand Down
82 changes: 66 additions & 16 deletions src/views/backup-restore/components/backup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<n-grid-item span="8">
<n-form-item :label="$t('backup.backupForm.index')" path="index">
<n-select
:options="indexOptions"
:options="establishedIndexOptions"
:placeholder="$t('connection.selectIndex')"
v-model:value="backupFormData.index"
remote
Expand All @@ -51,7 +51,7 @@
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon size="48">
<n-icon size="48" @click="submitBackup">
<DocumentExport />
</n-icon>
</template>
Expand All @@ -77,12 +77,23 @@
</n-grid-item>
<n-grid-item span="8">
<n-form-item :label="$t('backup.backupForm.backupFileName')" path="backupFileName">
<n-input
v-model:value="backupFormData.backupFileName"
clearable
:input-props="inputProps"
:placeholder="$t('backup.backupForm.backupFileName')"
/>
<n-input-group>
<n-input
v-model:value="backupFormData.backupFileName"
clearable
:input-props="inputProps"
:placeholder="$t('backup.backupForm.backupFileName')"
:style="{ width: '70%' }"
/>
<n-select
:style="{ width: '30%' }"
v-model:value="backupFormData.backupFileType"
:options="[
{ label: 'json', value: 'json' },
{ label: 'csv', value: 'csv' },
]"
/>
</n-input-group>
</n-form-item>
</n-grid-item>
</n-grid>
Expand All @@ -103,24 +114,32 @@ const message = useMessage();
const lang = useLang();
const fileFormRef = ref();
const connectionStore = useConnectionStore();
const { fetchConnections, fetchIndices, establishConnection, selectIndex } = connectionStore;
const { established, connections, establishedIndexNames } = storeToRefs(connectionStore);
const {
fetchConnections,
fetchIndices,
establishConnection,
selectIndex,
establishedIndexOptions,
} = connectionStore;
const { established, connections } = storeToRefs(connectionStore);
const backupRestoreStore = useBackupRestoreStore();
const { selectFolder } = backupRestoreStore;
const { selectFolder, backupToFile } = backupRestoreStore;
const { folderPath } = storeToRefs(backupRestoreStore);
const defaultFormData = {
connection: '',
index: '',
backupFolder: '',
backupFolder: folderPath.value,
backupFileName: '',
backupFileType: 'json',
};
const backupFormData = ref<{
connection: string;
index: string;
backupFolder: string;
backupFileName: string;
backupFileType: string;
}>(defaultFormData);
const backupFormRules = reactive<FormRules>({
// @ts-ignore
Expand Down Expand Up @@ -152,6 +171,14 @@ const backupFormRules = reactive<FormRules>({
trigger: ['input', 'blur'],
},
],
// backupFileType: [
// {
// required: true,
// validator: (_, value) => ['csv', 'json'].includes(value),
// renderMessage: () => lang.t('backup.backupForm.backupFileTypeRequired'),
// trigger: ['input', 'blur'],
// },
// ],
});
const loadingRefs = ref<{ connection: boolean; index: boolean }>({
connection: false,
Expand All @@ -162,10 +189,6 @@ const connectionOptions = computed(() =>
connections.value.map(({ name }) => ({ label: name, value: name })),
);
const indexOptions = computed(() =>
establishedIndexNames.value.map(name => ({ label: name, value: name })),
);
const handleOpen = async (isOpen: boolean, target: string) => {
if (!isOpen) return;
if (target === 'connection') {
Expand Down Expand Up @@ -220,12 +243,39 @@ const handleSelectUpdate = async (value: string, target: string) => {
};
const handleValidate = () => {
console.log('validate', backupFormData.value);
fileFormRef.value?.validate((errors: boolean) =>
errors
? message.error(lang.t('backup.backupForm.validationFailed'))
: message.success(lang.t('connection.validationPassed')),
);
};
const submitBackup = async () => {
console.log('submitBackup start');
const isPass = fileFormRef.value?.validate((errors: boolean) => {
if (errors) {
message.error(lang.t('backup.backupForm.validationFailed'));
return false;
}
return true;
});
console.log('submitBackup', isPass);
const connection = connections.value.find(({ name }) => name === backupFormData.value.connection);
if (!isPass || !connection) return;
try {
const filePath = await backupToFile({ ...backupFormData.value, connection });
message.success(lang.t('backup.backupToFileSuccess') + `: ${filePath}`);
} catch (err) {
const error = err as CustomError;
message.error(`status: ${error.status}, details: ${error.details}`, {
closable: true,
keepAliveOnHover: true,
duration: 3600,
});
}
};
</script>
<style lang="scss" scoped>
Expand Down

0 comments on commit 2ca9fc0

Please sign in to comment.