-
Notifications
You must be signed in to change notification settings - Fork 8
Feature/audit log #670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/audit log #670
Changes from 17 commits
735b460
211f5ba
b50694c
1dcd90e
728020c
6381d51
3c18feb
89768bb
63018bf
4a873cc
f126fe4
71cba12
9afb8fe
31b982a
3e00106
bb3e96e
efcc858
5f8db6f
db6b964
c22a4d4
200834c
9b176f5
1de2e77
7bbb2b7
c27aa75
6553de1
33bb119
b39ac30
7027f55
4e0b065
52d7d4a
c68dbcd
e4caa35
f6320a9
1e25054
7491e66
e31be7a
bfe94a2
cc45917
b0e886e
5321412
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -119,9 +119,8 @@ const createTestStack = async (req: Request): Promise<LoginServiceType> => { | |
| return { | ||
| data: { | ||
| data: res.data, | ||
| url: `${ | ||
| config.CS_URL[token_payload?.region as keyof typeof config.CS_URL] | ||
| }/stack/${res.data.stack.api_key}/dashboard`, | ||
| url: `${config.CS_URL[token_payload?.region as keyof typeof config.CS_URL] | ||
|
||
| }/stack/${res.data.stack.api_key}/dashboard`, | ||
| }, | ||
| status: res.status, | ||
| }; | ||
|
|
@@ -142,6 +141,159 @@ const createTestStack = async (req: Request): Promise<LoginServiceType> => { | |
| } | ||
| }; | ||
|
|
||
| const getAuditData = async (req: Request): Promise<any> => { | ||
| const projectId = path.basename(req?.params?.projectId); | ||
|
||
| const stackId = path.basename(req?.params?.stackId); | ||
| const moduleName = path.basename(req?.params?.moduleName); | ||
| const limit = parseInt(req?.params?.limit); | ||
| const startIndex = parseInt(req?.params?.startIndex); | ||
| const stopIndex = startIndex + limit; | ||
| const searchText = req?.params?.searchText; | ||
| const filter = req?.params?.filter; | ||
| const srcFunc = "getAuditData"; | ||
|
|
||
| if (projectId.includes('..') || stackId.includes('..') || moduleName.includes('..')) { | ||
|
||
| throw new BadRequestError("Invalid projectId, stackId, or moduleName"); | ||
| } | ||
|
|
||
| try { | ||
| const mainPath = process.cwd().split("migration-v2")[0]; | ||
|
||
| const logsDir = path.join(mainPath, "migration-v2", "api", "migration-data"); | ||
|
|
||
| const stackFolders = fs.readdirSync(logsDir); | ||
|
|
||
| const stackFolder = stackFolders.find(folder => folder.startsWith(stackId)); | ||
|
||
| if (!stackFolder) { | ||
| throw new BadRequestError("Migration data not found for this stack"); | ||
| } | ||
|
|
||
| const auditLogPath = path.resolve(logsDir, stackFolder, "logs", "audit", "audit-report"); | ||
|
||
| if (!fs.existsSync(auditLogPath)) { | ||
| throw new BadRequestError("Audit log path not found"); | ||
| } | ||
|
|
||
|
|
||
| // Read and parse the JSON file for the module | ||
| const filePath = path.resolve(auditLogPath, `${moduleName}.json`); | ||
| let fileData; | ||
| if (fs.existsSync(filePath)) { | ||
|
||
| const fileContent = await fsPromises.readFile(filePath, 'utf8'); | ||
| fileData = JSON.parse(fileContent); | ||
|
||
| } | ||
|
|
||
| // If no matching module was found | ||
| if (!fileData) { | ||
| throw new BadRequestError(`No audit data found for module: ${moduleName}`); | ||
| } | ||
| // Transform and flatten the data with sequential tuid | ||
| const filterKey = moduleName === 'Entries_Select_feild' ? 'display_type' : 'data_type'; | ||
|
||
| console.info("🚀 ~ getAuditData ~ filterKey:", filterKey) | ||
|
||
|
|
||
| let transformedData = transformAndFlattenData(fileData); | ||
| // console.info(transformedData) | ||
| if (filter != "all") { | ||
| const filters = filter.split("-"); | ||
|
||
| moduleName === 'Entries_Select_feild' ? transformedData = transformedData.filter((log) => { | ||
| return filters.some((filter) => { | ||
| //eslint-disable-next-line | ||
| console.log("🚀 ~ getAuditData ~ filter:", log) | ||
|
||
| return ( | ||
| log?.display_type?.toLowerCase()?.includes(filter?.toLowerCase()) | ||
| ); | ||
| }); | ||
| }) : transformedData = transformedData.filter((log) => { | ||
| return filters.some((filter) => { | ||
| return ( | ||
| log?.data_type?.toLowerCase()?.includes(filter?.toLowerCase()) | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| } | ||
| // Apply search filter if searchText is provided and not "null" | ||
| if (searchText && searchText !== "null") { | ||
|
||
| transformedData = transformedData.filter((item: any) => { | ||
| // Adjust these fields based on your actual audit data structure | ||
| return Object.values(item).some(value => | ||
| value && | ||
| typeof value === 'string' && | ||
| value.toLowerCase().includes(searchText.toLowerCase()) | ||
|
||
| ); | ||
| }); | ||
| } | ||
|
|
||
| // Apply pagination | ||
| const paginatedData = transformedData.slice(startIndex, stopIndex); | ||
|
|
||
| return { | ||
| data: paginatedData, | ||
| totalCount: transformedData.length | ||
|
||
| }; | ||
|
|
||
| } catch (error: any) { | ||
| logger.error( | ||
| getLogMessage( | ||
| srcFunc, | ||
| `Error getting audit log data for module: ${moduleName}`, | ||
| error | ||
| ) | ||
| ); | ||
| throw new ExceptionFunction( | ||
| error?.message || HTTP_TEXTS.INTERNAL_ERROR, | ||
| error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR | ||
| ); | ||
| } | ||
| }; | ||
| /** | ||
| * Transforms and flattens nested data structure into an array of items | ||
| * with sequential tuid values | ||
| */ | ||
| const transformAndFlattenData = (data: any): Array<{ [key: string]: any, id: number }> => { | ||
| try { | ||
| const flattenedItems: Array<{ [key: string]: any }> = []; | ||
|
|
||
| // Handle the data based on its structure | ||
| if (Array.isArray(data)) { | ||
| // If data is already an array, use it directly | ||
| data.forEach((item, index) => { | ||
| flattenedItems.push({ | ||
|
||
| ...item, | ||
| uid: item.uid || `item-${index}` | ||
| }); | ||
| }); | ||
| } else if (typeof data === 'object' && data !== null) { | ||
| // Process object data | ||
| Object.entries(data).forEach(([key, value]) => { | ||
|
||
| if (Array.isArray(value)) { | ||
| // If property contains an array, flatten each item | ||
| value.forEach((item, index) => { | ||
| flattenedItems.push({ | ||
| ...item, | ||
|
||
| parentKey: key, | ||
| uid: item.uid || `${key}-${index}` | ||
| }); | ||
| }); | ||
| } else if (typeof value === 'object' && value !== null) { | ||
| // If property contains an object, add it as an item | ||
| flattenedItems.push({ | ||
| ...value, | ||
| key, | ||
| uid: (value as any).uid || key | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| // Add sequential tuid to each item | ||
| return flattenedItems.map((item, index) => ({ | ||
|
||
| ...item, | ||
| id: index + 1 | ||
| })); | ||
| } catch (error) { | ||
| console.error('Error transforming data:', error); | ||
| return []; | ||
| } | ||
| }; | ||
| /** | ||
| * Deletes a test stack. | ||
| * @param req - The request object. | ||
|
|
@@ -971,4 +1123,5 @@ export const migrationService = { | |
| getLogs, | ||
| createSourceLocales, | ||
| updateLocaleMapper, | ||
| getAuditData | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { FilterOption } from "../AuditLogs/auditLogs.interface"; | ||
|
|
||
| export type FilterModaleProps = { | ||
| isOpen: boolean; | ||
| closeModal: () => void; | ||
| updateValue: (params: { value: FilterOption; isChecked: boolean }) => void; | ||
| onApply: () => void; | ||
| selectedLevels: FilterOption[]; | ||
| setFilterValue: (levels: FilterOption[]) => void; | ||
| selectedFileType: string; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| @import '../../scss/variables'; | ||
|
|
||
| .tableFilterModalStories { | ||
| position: absolute; | ||
| z-index: 1000; | ||
| width: 350px; | ||
| background-color: #ffffff; | ||
| border-radius: 12px; | ||
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); | ||
| display: flex; | ||
| flex-direction: column; | ||
| max-height: 350px; | ||
| overflow: hidden; | ||
| font-family: 'Inter', sans-serif; | ||
| } | ||
|
|
||
| .tableFilterModalStories__header { | ||
| padding: 16px 16px; | ||
| color: #3d3f4c; | ||
| border-bottom: 1px solid #e5e7eb; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| } | ||
|
|
||
| .tableFilterModalStories__suggestion-item { | ||
| padding: 8px 16px; | ||
| } | ||
|
|
||
| .Checkbox { | ||
| display: flex; | ||
| align-items: center; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .Checkbox .Checkbox__tick svg { | ||
| display: block !important; | ||
| } | ||
|
|
||
| .Checkbox .Checkbox__label { | ||
| font-size: $size-font-medium; | ||
| line-height: $line-height-reset; | ||
| color: #3d3f4c; | ||
| text-transform: capitalize; | ||
| } | ||
|
|
||
| .tableFilterModalStories__footer { | ||
| display: flex; | ||
| justify-content: flex-end; | ||
| align-items: center; | ||
| gap: 16px; | ||
| padding: 16px 8px; | ||
| border-top: 1px solid #e5e7eb; | ||
| background: #fff; | ||
| font-size: $size-font-medium; | ||
| line-height: $line-height-reset; | ||
| } | ||
|
|
||
| .close-btn { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| width: 32px; | ||
| height: 32px; | ||
| } | ||
|
|
||
| .close-btn:hover { | ||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | ||
| transform: scale(1.05); | ||
| border-radius: 8px; | ||
| background-color: #f0f1f3; | ||
| cursor: pointer; | ||
| transition: | ||
| box-shadow 0.2s ease, | ||
| transform 0.2s ease; | ||
| } | ||
|
|
||
| .close-btn:active { | ||
| transform: scale(0.95); | ||
| } | ||
|
|
||
| .text-size { | ||
| font-size: $size-font-medium; | ||
| line-height: $line-height-reset; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.