Files
lucia-frontend/src/stores/files.ts
2026-03-07 08:55:33 +08:00

313 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/**
* @module stores/files File management store for listing, uploading,
* renaming, downloading, and deleting log/filter/rule files.
*/
import { defineStore } from "pinia";
import apiClient from "@/api/client.js";
import moment from 'moment';
import apiError from '@/module/apiError.js';
import Swal from 'sweetalert2';
import { uploadFailedFirst, uploadFailedSecond, uploadloader, uploadSuccess, deleteSuccess } from '@/module/alertModal.js';
import { useLoadingStore } from '@/stores/loading';
/** Map of file type to API path segment. */
const FILE_TYPE_PATHS = {
'log': 'logs',
'filter': 'filters',
'log-check': 'log-checks',
'filter-check': 'filter-checks',
};
/**
* Returns the API base path for a file type and ID.
* @param {string} type - The file type.
* @param {number} id - The file ID.
* @returns {string} The API base path.
*/
function getFileApiBase(type, id) {
return `/api/${FILE_TYPE_PATHS[type]}/${id}`;
}
/** Pinia store for file CRUD operations and upload workflow. */
export const useFilesStore = defineStore('filesStore', {
state: () => ({
allEventFiles: [
{
parentLog: '',
fileType: '',
ownerName: '',
}
],
switchFilesTagData: {
ALL: ['Log', 'Filter', 'Rule', 'Design'],
DISCOVER: ['Log', 'Filter', 'Rule'],
COMPARE: ['Log','Filter'],
DESIGN: ['Log', 'Design'],
},
filesTag: 'ALL',
httpStatus: 200,
uploadId: null,
allUploadDetail: null,
uploadLogId: null,
uploadFileName: null,
allDependentsData: null,
}),
getters: {
/**
* Get allFiles and switch files tag
*/
allFiles: state => {
let result = state.allEventFiles;
const data = state.switchFilesTagData;
const filesTag = state.filesTag;
result = result.filter(file => data[filesTag].includes(file.fileType));
return result;
},
/**
* Get upload preview
*/
uploadDetail: state => {
return state.allUploadDetail;
},
/**
* Get dependents of files data
*/
dependentsData: state => {
return state.allDependentsData;
}
},
actions: {
/**
* Fetch All Files api
*/
async fetchAllFiles() {
const api = '/api/files';
let icon = '';
let fileType = '';
let parentLog = '';
try {
const response = await apiClient.get(api);
this.allEventFiles = response.data;
this.allEventFiles.forEach(o => {
switch (o.type) {
case 'log':
icon = 'work_history';
fileType = 'Log';
parentLog = o.name;
break;
case 'filter':
icon = 'tornado';
fileType = 'Filter';
parentLog = o.parent.name;
break;
case 'log-check':
case 'filter-check':
icon = 'local_police';
fileType = 'Rule';
parentLog = o.parent.name;
break;
case 'design':
icon = 'shape_line';
break;
}
o.icon = icon;
o.parentLog = parentLog;
o.fileType = fileType;
o.ownerName = o.owner.name;
o.updated_base = o.updated_at;
o.accessed_base = o.accessed_at;
o.updated_at = moment(o.updated_at).utcOffset('+08:00').format('YYYY-MM-DD HH:mm');
o.accessed_at = o.accessed_at ? moment(o.accessed_at).utcOffset('+08:00').format('YYYY-MM-DD HH:mm') : null;
});
} catch(error) {
apiError(error, 'Failed to load the files.');
};
},
/**
* Uploads a CSV log file (first stage).
* @param {Object} fromData - The form data to send to the backend.
*/
async upload(fromData) {
const api = '/api/logs/csv-uploads';
const config = {
data: true,
headers: {
'Content-Type': 'multipart/form-data',
},
};
uploadloader(); // 進度條
try {
const response = await apiClient.post(api, fromData, config);
this.uploadId = response.data.id;
this.$router.push({name: 'Upload'});
Swal.close(); // 關閉進度條
} catch(error) {
if(error.response?.status === 422) {
// msg: 'not in UTF-8' | 'insufficient columns' | 'the csv file is empty' | 'the filename does not ends with .csv'
// type: 'encoding' | 'insufficient_columns' | 'empty' | 'name_suffix'
const detail = error.response.data.detail;
uploadFailedFirst(detail[0].type, detail[0].msg, detail[0].loc[2]);
} else {
Swal.close(); // 關閉進度條
apiError(error, 'Failed to upload the files.');
}
}
},
/**
* Fetch upload detail
*/
async getUploadDetail() {
const uploadId = this.uploadId;
const api = `/api/logs/csv-uploads/${uploadId}`;
try {
const response = await apiClient.get(api);
this.allUploadDetail = response.data.preview;
} catch(error) {
apiError(error, 'Failed to get upload detail.');
}
},
/**
* Adds a log from an uploaded CSV log file (second stage).
* @param {Object} data - The request payload for the backend.
*/
async uploadLog(data) {
const uploadId = this.uploadId;
const api = `/api/logs/csv-uploads/${uploadId}`;
uploadloader(); // 進度條
try {
const response = await apiClient.post(api, data);
this.uploadLogId = await response.data.id;
await Swal.close(); // 關閉進度條
await this.rename(); // 改檔名
await uploadSuccess();
this.$router.push({name: 'Files'});
} catch(error) {
if(error.response?.status === 422) {
const detail = [...error.response.data.detail];
uploadFailedSecond(detail);
} else {
Swal.close(); // 關閉進度條
apiError(error, 'Failed to upload the log files.');
}
}
},
/**
* Rename a Log
* @param {string} type - The file type ('log', 'filter', 'log-check', or 'filter-check').
* @param {number} id - The file ID.
* @param {string} name - The file name.
*/
async rename(type, id, fileName) {
// 先判斷有沒有 uploadLogId有就設定 id 和 type再判斷檔案型別。
if(this.uploadId && this.uploadFileName) {
type = 'log';
id = this.uploadLogId;
fileName = this.uploadFileName;
}
const data = {"name": fileName};
try {
await apiClient.put(`${getFileApiBase(type, id)}/name`, data);
this.uploadFileName = null;
await this.fetchAllFiles();
} catch(error) {
apiError(error, 'Failed to rename.');
}
},
/**
* Get the Dependents of the files
* @param {string} type - The file type ('log', 'filter', 'log-check', or 'filter-check').
* @param {number} id - The file ID.
*/
async getDependents(type, id) {
try {
const response = await apiClient.get(`${getFileApiBase(type, id)}/dependents`);
this.allDependentsData = response.data;
} catch(error) {
apiError(error, 'Failed to get Dependents of the files.');
}
},
/**
* Delete file
* @param {string} type - The file type ('log', 'filter', 'log-check', or 'filter-check').
* @param {number} id - The file ID.
*/
async deleteFile(type, id) {
if(id === null || id === undefined || isNaN(id)) {
console.error('Delete File API Error: invalid id');
return;
};
const loading = useLoadingStore();
loading.isLoading = true;
try {
await apiClient.delete(getFileApiBase(type, id));
await this.fetchAllFiles();
await deleteSuccess();
} catch(error) {
apiError(error, 'Failed to delete.');
} finally {
loading.isLoading = false;
}
},
/**
* Removes a deletion record permanently.
* @param {number} id - The file ID.
*/
async deletionRecord(id) {
let api = '';
const loading = useLoadingStore();
loading.isLoading = true;
api = `/api/deletion/${id}`;
try {
await apiClient.delete(api);
} catch(error) {
apiError(error, 'Failed to Remove a Deletion Record.')
} finally {
loading.isLoading = false;
}
},
/**
* Download file as CSV
* @param {string} type - The file type ('log', 'filter', 'log-check', or 'filter-check').
* @param {number} id - The file ID.
* @param {string} fileName - The file name.
*/
async downloadFileCSV(type, id, fileName) {
if (type !== 'log' && type !== 'filter') return;
try {
const response = await apiClient.get(`${getFileApiBase(type, id)}/csv`);
const csvData = response.data;
const blob = new Blob([csvData], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${fileName}.csv`;
link.click();
window.URL.revokeObjectURL(url);
} catch(error) {
apiError(error, 'Failed to download.');
}
},
},
})