332 lines
9.8 KiB
TypeScript
332 lines
9.8 KiB
TypeScript
// 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";
|
|
|
|
try {
|
|
const response = await apiClient.get(api);
|
|
|
|
this.allEventFiles = response.data;
|
|
this.allEventFiles.forEach((o) => {
|
|
let icon = "";
|
|
let fileType = "";
|
|
let parentLog = o.name;
|
|
|
|
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";
|
|
fileType = "Design";
|
|
parentLog = o.name;
|
|
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(); // Show loading progress bar
|
|
try {
|
|
const response = await apiClient.post(api, fromData, config);
|
|
|
|
this.uploadId = response.data.id;
|
|
this.$router.push({ name: "Upload" });
|
|
Swal.close(); // Close the loading progress bar
|
|
} 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;
|
|
if (Array.isArray(detail) && detail.length > 0) {
|
|
const loc = detail[0].loc;
|
|
uploadFailedFirst(detail[0].type, detail[0].msg, Array.isArray(loc) && loc.length > 2 ? loc[2] : undefined);
|
|
}
|
|
} else {
|
|
Swal.close(); // Close the loading progress bar
|
|
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(); // Show loading progress bar
|
|
try {
|
|
const response = await apiClient.post(api, data);
|
|
|
|
this.uploadLogId = response.data.id;
|
|
Swal.close(); // Close the loading progress bar
|
|
await this.rename(); // Rename the file
|
|
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(); // Close the loading progress bar
|
|
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) {
|
|
// If uploadLogId exists, set id and type accordingly; then check the file 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.");
|
|
}
|
|
},
|
|
},
|
|
});
|