@mostajs/audit 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/route.js +2 -9
- package/dist/i18n/fr/audit.json +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/audit-factory.d.ts +14 -0
- package/dist/lib/audit-factory.js +108 -0
- package/dist/lib/audit-init.d.ts +1 -0
- package/dist/lib/audit-init.js +22 -0
- package/dist/lib/audit.d.ts +1 -1
- package/dist/lib/audit.js +2 -6
- package/dist/lib/data-mode.d.ts +1 -0
- package/dist/lib/data-mode.js +5 -0
- package/dist/lib/handlers/audit-log.js +2 -4
- package/dist/lib/i18n.d.ts +1 -0
- package/dist/lib/i18n.js +28 -0
- package/dist/pages/AuditPage.d.ts +1 -0
- package/dist/pages/AuditPage.js +73 -0
- package/dist/register.js +4 -0
- package/i18n/fr/audit.json +29 -0
- package/package.json +27 -4
package/dist/api/route.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
// @mosta/audit — API Route template
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
//
|
|
4
|
-
// Copy to: src/app/api/admin/audit/route.ts
|
|
5
|
-
// Usage:
|
|
6
|
-
// import { createAuditHandlers } from '@mosta/audit/api/route'
|
|
7
|
-
// import { checkPermission } from '@mosta/auth'
|
|
8
|
-
// export const { GET } = createAuditHandlers('audit:view', checkPermission)
|
|
9
3
|
import { NextResponse } from 'next/server';
|
|
10
|
-
import {
|
|
11
|
-
import { AuditLogRepository } from '../repositories/audit-log.repository';
|
|
4
|
+
import { getAuditRepo } from '../lib/audit-factory.js';
|
|
12
5
|
/**
|
|
13
6
|
* Creates a GET handler for paginated audit log consultation.
|
|
14
7
|
*/
|
|
@@ -28,7 +21,7 @@ export function createAuditHandlers(permission, checkPermission) {
|
|
|
28
21
|
page: parseInt(url.searchParams.get('page') || '1', 10),
|
|
29
22
|
limit: parseInt(url.searchParams.get('limit') || '50', 10),
|
|
30
23
|
};
|
|
31
|
-
const repo =
|
|
24
|
+
const repo = await getAuditRepo();
|
|
32
25
|
const { data, total } = await repo.findPaginated(filters);
|
|
33
26
|
return NextResponse.json({
|
|
34
27
|
data,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_author": "Dr Hamid MADANI drmdh@msn.com",
|
|
3
|
+
"title": "Journal d'audit",
|
|
4
|
+
"fields": {
|
|
5
|
+
"timestamp": "Date/Heure",
|
|
6
|
+
"user": "Utilisateur",
|
|
7
|
+
"action": "Action",
|
|
8
|
+
"module": "Module",
|
|
9
|
+
"resource": "Ressource",
|
|
10
|
+
"status": "Statut",
|
|
11
|
+
"details": "D\u00e9tails"
|
|
12
|
+
},
|
|
13
|
+
"filters": {
|
|
14
|
+
"module": "Module",
|
|
15
|
+
"action": "Rechercher action..."
|
|
16
|
+
},
|
|
17
|
+
"modules": {
|
|
18
|
+
"clients": "Clients",
|
|
19
|
+
"tickets": "Tickets",
|
|
20
|
+
"scan": "Scan",
|
|
21
|
+
"lockers": "Vestiaires",
|
|
22
|
+
"rfid": "RFID",
|
|
23
|
+
"access": "Acc\u00e8s",
|
|
24
|
+
"users": "Utilisateurs",
|
|
25
|
+
"activities": "Activit\u00e9s",
|
|
26
|
+
"plans": "Plans",
|
|
27
|
+
"settings": "Param\u00e8tres"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,6 @@ export { AuditLogRepository } from './repositories/audit-log.repository';
|
|
|
3
3
|
export { AuditLogSchema } from './schemas/audit-log.schema';
|
|
4
4
|
export { createAuditHandlers } from './api/route';
|
|
5
5
|
export { auditMenuContribution } from './lib/menu';
|
|
6
|
+
export { default as AuditPage } from './pages/AuditPage';
|
|
7
|
+
export { t as auditT } from './lib/i18n';
|
|
6
8
|
export type { MostaAuditConfig, AuditParams, AuditFilters, AuditLogDTO, } from './types/index';
|
package/dist/index.js
CHANGED
|
@@ -9,3 +9,7 @@ export { AuditLogSchema } from './schemas/audit-log.schema';
|
|
|
9
9
|
export { createAuditHandlers } from './api/route';
|
|
10
10
|
// Menu contribution
|
|
11
11
|
export { auditMenuContribution } from './lib/menu';
|
|
12
|
+
// Pages
|
|
13
|
+
export { default as AuditPage } from './pages/AuditPage';
|
|
14
|
+
// I18n
|
|
15
|
+
export { t as auditT } from './lib/i18n';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuditLogDTO, AuditFilters } from '../types/index.js';
|
|
2
|
+
export interface IAuditLogRepository {
|
|
3
|
+
create(data: Record<string, unknown>): Promise<AuditLogDTO>;
|
|
4
|
+
findPaginated(filters: AuditFilters): Promise<{
|
|
5
|
+
data: AuditLogDTO[];
|
|
6
|
+
total: number;
|
|
7
|
+
}>;
|
|
8
|
+
findByResource(resourceId: string, modules?: string[]): Promise<AuditLogDTO[]>;
|
|
9
|
+
deleteOlderThan(days: number): Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
/** Get audit repository for the current data mode (ORM or NET) */
|
|
12
|
+
export declare function getAuditRepo(): Promise<IAuditLogRepository>;
|
|
13
|
+
/** Reset cache (for tests) */
|
|
14
|
+
export declare function resetAuditRepo(): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// audit-factory.ts — Centralized repository factory for dual ORM/NET mode
|
|
2
|
+
// Same pattern as @mosta/rbac repos-factory.ts
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
import { isNetMode } from './data-mode.js';
|
|
5
|
+
import { AuditLogSchema } from '../schemas/audit-log.schema.js';
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Factory
|
|
8
|
+
// ============================================================
|
|
9
|
+
let _cached = null;
|
|
10
|
+
let _schemaReady = false;
|
|
11
|
+
/** Get audit repository for the current data mode (ORM or NET) */
|
|
12
|
+
export async function getAuditRepo() {
|
|
13
|
+
if (_cached)
|
|
14
|
+
return _cached;
|
|
15
|
+
if (isNetMode()) {
|
|
16
|
+
await ensureSchemaNet();
|
|
17
|
+
_cached = createNetRepo();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await ensureSchemaOrm();
|
|
21
|
+
_cached = await createOrmRepo();
|
|
22
|
+
}
|
|
23
|
+
return _cached;
|
|
24
|
+
}
|
|
25
|
+
/** Reset cache (for tests) */
|
|
26
|
+
export function resetAuditRepo() { _cached = null; _schemaReady = false; }
|
|
27
|
+
// ============================================================
|
|
28
|
+
// Schema init
|
|
29
|
+
// ============================================================
|
|
30
|
+
async function ensureSchemaOrm() {
|
|
31
|
+
if (_schemaReady)
|
|
32
|
+
return;
|
|
33
|
+
const { registerSchemas } = await import('@mostajs/orm');
|
|
34
|
+
registerSchemas([AuditLogSchema]);
|
|
35
|
+
_schemaReady = true;
|
|
36
|
+
}
|
|
37
|
+
async function ensureSchemaNet() {
|
|
38
|
+
if (_schemaReady)
|
|
39
|
+
return;
|
|
40
|
+
const { NetClient } = await import('@mostajs/net/client');
|
|
41
|
+
const client = new NetClient({ url: process.env.MOSTA_NET_URL });
|
|
42
|
+
const result = await client.compareSchema(AuditLogSchema);
|
|
43
|
+
if (!result.exists || !result.compatible) {
|
|
44
|
+
await client.applySchema([AuditLogSchema]);
|
|
45
|
+
}
|
|
46
|
+
_schemaReady = true;
|
|
47
|
+
}
|
|
48
|
+
// ============================================================
|
|
49
|
+
// ORM mode
|
|
50
|
+
// ============================================================
|
|
51
|
+
async function createOrmRepo() {
|
|
52
|
+
const { getDialect } = await import('@mostajs/orm');
|
|
53
|
+
const { AuditLogRepository } = await import('../repositories/audit-log.repository.js');
|
|
54
|
+
const dialect = await getDialect();
|
|
55
|
+
return new AuditLogRepository(dialect);
|
|
56
|
+
}
|
|
57
|
+
// ============================================================
|
|
58
|
+
// NET mode
|
|
59
|
+
// ============================================================
|
|
60
|
+
function createNetRepo() {
|
|
61
|
+
const clientPromise = import('@mostajs/net/client').then(m => new m.NetClient({ url: process.env.MOSTA_NET_URL }));
|
|
62
|
+
return {
|
|
63
|
+
async create(data) {
|
|
64
|
+
const c = await clientPromise;
|
|
65
|
+
return c.create('auditlogs', data);
|
|
66
|
+
},
|
|
67
|
+
async findPaginated(filters) {
|
|
68
|
+
const c = await clientPromise;
|
|
69
|
+
const netFilter = {};
|
|
70
|
+
if (filters.module)
|
|
71
|
+
netFilter.module = filters.module;
|
|
72
|
+
if (filters.action)
|
|
73
|
+
netFilter.action = { $regex: filters.action, $regexFlags: 'i' };
|
|
74
|
+
if (filters.userId)
|
|
75
|
+
netFilter.userId = filters.userId;
|
|
76
|
+
if (filters.status)
|
|
77
|
+
netFilter.status = filters.status;
|
|
78
|
+
if (filters.from || filters.to) {
|
|
79
|
+
const ts = {};
|
|
80
|
+
if (filters.from)
|
|
81
|
+
ts.$gte = new Date(filters.from).toISOString();
|
|
82
|
+
if (filters.to)
|
|
83
|
+
ts.$lte = new Date(filters.to).toISOString();
|
|
84
|
+
netFilter.timestamp = ts;
|
|
85
|
+
}
|
|
86
|
+
const page = filters.page || 1;
|
|
87
|
+
const limit = filters.limit || 50;
|
|
88
|
+
const options = { sort: { timestamp: -1 }, skip: (page - 1) * limit, limit };
|
|
89
|
+
const [data, total] = await Promise.all([
|
|
90
|
+
c.findAll('auditlogs', netFilter, options),
|
|
91
|
+
c.count('auditlogs', netFilter),
|
|
92
|
+
]);
|
|
93
|
+
return { data, total };
|
|
94
|
+
},
|
|
95
|
+
async findByResource(resourceId, modules) {
|
|
96
|
+
const c = await clientPromise;
|
|
97
|
+
const filter = { resourceId };
|
|
98
|
+
if (modules?.length)
|
|
99
|
+
filter.module = { $in: modules };
|
|
100
|
+
return c.findAll('auditlogs', filter, { sort: { timestamp: -1 } });
|
|
101
|
+
},
|
|
102
|
+
async deleteOlderThan(days) {
|
|
103
|
+
const c = await clientPromise;
|
|
104
|
+
const cutoff = new Date(Date.now() - days * 86400000).toISOString();
|
|
105
|
+
return c.deleteMany('auditlogs', { timestamp: { $lt: cutoff } });
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureAuditSchema(): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// audit-init.ts — Ensure AuditLog schema is ready (ORM or NET)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
import { isNetMode } from './data-mode.js';
|
|
4
|
+
import { AuditLogSchema } from '../schemas/audit-log.schema.js';
|
|
5
|
+
let initialized = false;
|
|
6
|
+
export async function ensureAuditSchema() {
|
|
7
|
+
if (initialized)
|
|
8
|
+
return;
|
|
9
|
+
if (isNetMode()) {
|
|
10
|
+
const { NetClient } = await import('@mostajs/net/client');
|
|
11
|
+
const client = new NetClient({ url: process.env.MOSTA_NET_URL });
|
|
12
|
+
const result = await client.compareSchema(AuditLogSchema);
|
|
13
|
+
if (!result.exists || !result.compatible) {
|
|
14
|
+
await client.applySchema([AuditLogSchema]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const { registerSchemas } = await import('@mostajs/orm');
|
|
19
|
+
registerSchemas([AuditLogSchema]);
|
|
20
|
+
}
|
|
21
|
+
initialized = true;
|
|
22
|
+
}
|
package/dist/lib/audit.d.ts
CHANGED
package/dist/lib/audit.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// @mosta/audit — Core audit functions
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import {
|
|
4
|
-
import { AuditLogRepository } from '../repositories/audit-log.repository';
|
|
5
|
-
import { AuditLogSchema } from '../schemas/audit-log.schema';
|
|
6
|
-
// Auto-register audit schema into ORM registry (idempotent)
|
|
7
|
-
registerSchemas([AuditLogSchema]);
|
|
3
|
+
import { getAuditRepo } from './audit-factory.js';
|
|
8
4
|
/**
|
|
9
5
|
* Log an audit entry. Fire-and-forget — never throws, never blocks.
|
|
10
6
|
*/
|
|
11
7
|
export async function logAudit(params) {
|
|
12
8
|
try {
|
|
13
|
-
const repo =
|
|
9
|
+
const repo = await getAuditRepo();
|
|
14
10
|
await repo.create({
|
|
15
11
|
userId: params.userId,
|
|
16
12
|
userName: params.userName,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isNetMode(): boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @mostajs/audit — Route handler for audit-log
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
// Bare handler — NO auth. The socle catch-all does it via permission.
|
|
4
|
-
import {
|
|
4
|
+
import { getAuditRepo } from '../audit-factory.js';
|
|
5
5
|
export async function GET(req) {
|
|
6
6
|
const url = new URL(req.url);
|
|
7
7
|
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
|
@@ -12,9 +12,7 @@ export async function GET(req) {
|
|
|
12
12
|
const status = url.searchParams.get('status') || undefined;
|
|
13
13
|
const dateFrom = url.searchParams.get('dateFrom') || url.searchParams.get('from') || null;
|
|
14
14
|
const dateTo = url.searchParams.get('dateTo') || url.searchParams.get('to') || null;
|
|
15
|
-
const
|
|
16
|
-
const dialect = await getDialect();
|
|
17
|
-
const repo = new AuditLogRepository(dialect);
|
|
15
|
+
const repo = await getAuditRepo();
|
|
18
16
|
const { data: logs, total } = await repo.findPaginated({
|
|
19
17
|
module, action, userId, status,
|
|
20
18
|
from: dateFrom ? new Date(dateFrom) : undefined,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function t(key: string, params?: Record<string, string | number>): string;
|
package/dist/lib/i18n.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @mostajs/audit — Minimal i18n helper
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
import audit from '../i18n/fr/audit.json' with { type: 'json' };
|
|
4
|
+
function getNestedValue(obj, path) {
|
|
5
|
+
const keys = path.split('.');
|
|
6
|
+
let current = obj;
|
|
7
|
+
for (const key of keys) {
|
|
8
|
+
if (current && typeof current === 'object' && key in current) {
|
|
9
|
+
current = current[key];
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
return path;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return typeof current === 'string' ? current : path;
|
|
16
|
+
}
|
|
17
|
+
export function t(key, params) {
|
|
18
|
+
const [namespace, ...rest] = key.split('.');
|
|
19
|
+
if (namespace !== 'audit')
|
|
20
|
+
return key;
|
|
21
|
+
let value = getNestedValue(audit, rest.join('.'));
|
|
22
|
+
if (params) {
|
|
23
|
+
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
|
24
|
+
value = value.replace(`{{${paramKey}}}`, String(paramValue));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function AuditPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @mostajs/audit — AuditPage component
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
'use client';
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useState, useEffect } from 'react';
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@mostajs/ui/card';
|
|
7
|
+
import { Button } from '@mostajs/ui/button';
|
|
8
|
+
import { Input } from '@mostajs/ui/input';
|
|
9
|
+
import { Badge } from '@mostajs/ui/badge';
|
|
10
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@mostajs/ui/select';
|
|
11
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@mostajs/ui/table';
|
|
12
|
+
import { FileText, Search, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
13
|
+
import { t } from '../lib/i18n.js';
|
|
14
|
+
const MODULES = [
|
|
15
|
+
'clients', 'tickets', 'scan', 'lockers', 'rfid',
|
|
16
|
+
'access', 'users', 'activities', 'plans', 'settings',
|
|
17
|
+
];
|
|
18
|
+
const MODULE_COLORS = {
|
|
19
|
+
clients: 'bg-blue-100 text-blue-800',
|
|
20
|
+
tickets: 'bg-amber-100 text-amber-800',
|
|
21
|
+
scan: 'bg-green-100 text-green-800',
|
|
22
|
+
lockers: 'bg-purple-100 text-purple-800',
|
|
23
|
+
rfid: 'bg-cyan-100 text-cyan-800',
|
|
24
|
+
access: 'bg-orange-100 text-orange-800',
|
|
25
|
+
users: 'bg-red-100 text-red-800',
|
|
26
|
+
activities: 'bg-teal-100 text-teal-800',
|
|
27
|
+
plans: 'bg-indigo-100 text-indigo-800',
|
|
28
|
+
settings: 'bg-gray-100 text-gray-800',
|
|
29
|
+
};
|
|
30
|
+
export default function AuditPage() {
|
|
31
|
+
const [logs, setLogs] = useState([]);
|
|
32
|
+
const [loading, setLoading] = useState(true);
|
|
33
|
+
const [page, setPage] = useState(1);
|
|
34
|
+
const [totalPages, setTotalPages] = useState(1);
|
|
35
|
+
const [total, setTotal] = useState(0);
|
|
36
|
+
const [moduleFilter, setModuleFilter] = useState('');
|
|
37
|
+
const [actionFilter, setActionFilter] = useState('');
|
|
38
|
+
const [dateFrom, setDateFrom] = useState('');
|
|
39
|
+
const [dateTo, setDateTo] = useState('');
|
|
40
|
+
async function fetchLogs() {
|
|
41
|
+
setLoading(true);
|
|
42
|
+
try {
|
|
43
|
+
const params = new URLSearchParams({ page: String(page), limit: '30' });
|
|
44
|
+
if (moduleFilter)
|
|
45
|
+
params.set('module', moduleFilter);
|
|
46
|
+
if (actionFilter)
|
|
47
|
+
params.set('action', actionFilter);
|
|
48
|
+
if (dateFrom)
|
|
49
|
+
params.set('dateFrom', dateFrom);
|
|
50
|
+
if (dateTo)
|
|
51
|
+
params.set('dateTo', dateTo);
|
|
52
|
+
const res = await fetch(`/api/audit-log?${params}`);
|
|
53
|
+
const data = await res.json();
|
|
54
|
+
setLogs(data.data || []);
|
|
55
|
+
setTotalPages(data.pagination?.totalPages || 1);
|
|
56
|
+
setTotal(data.pagination?.total || 0);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
setLogs([]);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
setLoading(false);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
fetchLogs();
|
|
67
|
+
}, [page, moduleFilter]);
|
|
68
|
+
function handleSearch() {
|
|
69
|
+
setPage(1);
|
|
70
|
+
fetchLogs();
|
|
71
|
+
}
|
|
72
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsx("h1", { className: "text-2xl font-bold text-gray-900", children: t('audit.title') }), _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex flex-wrap gap-3", children: [_jsxs(Select, { value: moduleFilter, onValueChange: (v) => { setModuleFilter(v === 'all' ? '' : v); setPage(1); }, children: [_jsx(SelectTrigger, { className: "w-[180px]", children: _jsx(SelectValue, { placeholder: t('audit.filters.module') }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Tous les modules" }), MODULES.map((m) => (_jsx(SelectItem, { value: m, children: t(`audit.modules.${m}`) }, m)))] })] }), _jsx(Input, { placeholder: t('audit.filters.action'), value: actionFilter, onChange: (e) => setActionFilter(e.target.value), className: "w-[200px]" }), _jsx(Input, { type: "date", value: dateFrom, onChange: (e) => setDateFrom(e.target.value), className: "w-[160px]" }), _jsx(Input, { type: "date", value: dateTo, onChange: (e) => setDateTo(e.target.value), className: "w-[160px]" }), _jsxs(Button, { onClick: handleSearch, children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), "Filtrer"] })] }) }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "h-5 w-5" }), t('audit.title'), " (", total, ")"] }) }), _jsxs(CardContent, { children: [loading ? (_jsx("div", { className: "py-8 text-center text-gray-400", children: "Chargement..." })) : logs.length === 0 ? (_jsx("div", { className: "py-8 text-center text-gray-400", children: "Aucune entr\u00E9e" })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[160px]", children: "Date" }), _jsx(TableHead, { children: "Utilisateur" }), _jsx(TableHead, { children: "Module" }), _jsx(TableHead, { children: "Action" }), _jsx(TableHead, { children: "Ressource" }), _jsx(TableHead, { className: "w-[80px]", children: "Statut" })] }) }), _jsx(TableBody, { children: logs.map((log) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-xs text-gray-500", children: new Date(log.timestamp).toLocaleString('fr-FR') }), _jsxs(TableCell, { children: [_jsx("div", { className: "text-sm font-medium", children: log.userName }), _jsx("div", { className: "text-xs text-gray-400", children: log.userRole })] }), _jsx(TableCell, { children: _jsx(Badge, { className: MODULE_COLORS[log.module] || 'bg-gray-100 text-gray-800', children: log.module }) }), _jsx(TableCell, { className: "text-sm", children: log.action }), _jsxs(TableCell, { className: "text-xs text-gray-500", children: [log.resource && `${log.resource}`, log.resourceId && ` #${log.resourceId.slice(-6)}`] }), _jsx(TableCell, { children: _jsx(Badge, { variant: log.status === 'success' ? 'default' : 'destructive', children: log.status }) })] }, log.id))) })] })), totalPages > 1 && (_jsxs("div", { className: "mt-4 flex items-center justify-between", children: [_jsxs("div", { className: "text-sm text-gray-500", children: ["Page ", page, " / ", totalPages] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: page <= 1, onClick: () => setPage((p) => p - 1), children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "outline", size: "sm", disabled: page >= totalPages, onClick: () => setPage((p) => p + 1), children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] }))] })] })] }));
|
|
73
|
+
}
|
package/dist/register.js
CHANGED
|
@@ -4,6 +4,7 @@ import { AuditLogSchema } from './schemas/audit-log.schema.js';
|
|
|
4
4
|
import { AuditLogRepository } from './repositories/audit-log.repository.js';
|
|
5
5
|
import { auditMenuContribution } from './lib/menu.js';
|
|
6
6
|
import { GET as auditLogGET } from './lib/handlers/audit-log.js';
|
|
7
|
+
import AuditPage from './pages/AuditPage.js';
|
|
7
8
|
export function register(registry) {
|
|
8
9
|
registry.register({
|
|
9
10
|
manifest: {
|
|
@@ -36,6 +37,9 @@ export function register(registry) {
|
|
|
36
37
|
routes: [
|
|
37
38
|
{ path: 'audit-log', handlers: { GET: auditLogGET }, permission: 'audit:view' },
|
|
38
39
|
],
|
|
40
|
+
pages: [
|
|
41
|
+
{ path: 'audit', component: AuditPage, permission: 'audit:view' },
|
|
42
|
+
],
|
|
39
43
|
menu: auditMenuContribution,
|
|
40
44
|
});
|
|
41
45
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_author": "Dr Hamid MADANI drmdh@msn.com",
|
|
3
|
+
"title": "Journal d'audit",
|
|
4
|
+
"fields": {
|
|
5
|
+
"timestamp": "Date/Heure",
|
|
6
|
+
"user": "Utilisateur",
|
|
7
|
+
"action": "Action",
|
|
8
|
+
"module": "Module",
|
|
9
|
+
"resource": "Ressource",
|
|
10
|
+
"status": "Statut",
|
|
11
|
+
"details": "D\u00e9tails"
|
|
12
|
+
},
|
|
13
|
+
"filters": {
|
|
14
|
+
"module": "Module",
|
|
15
|
+
"action": "Rechercher action..."
|
|
16
|
+
},
|
|
17
|
+
"modules": {
|
|
18
|
+
"clients": "Clients",
|
|
19
|
+
"tickets": "Tickets",
|
|
20
|
+
"scan": "Scan",
|
|
21
|
+
"lockers": "Vestiaires",
|
|
22
|
+
"rfid": "RFID",
|
|
23
|
+
"access": "Acc\u00e8s",
|
|
24
|
+
"users": "Utilisateurs",
|
|
25
|
+
"activities": "Activit\u00e9s",
|
|
26
|
+
"plans": "Plans",
|
|
27
|
+
"settings": "Param\u00e8tres"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/audit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Reusable audit logging module — fire-and-forget logAudit() with paginated consultation",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,10 +37,21 @@
|
|
|
37
37
|
"types": "./dist/register.d.ts",
|
|
38
38
|
"import": "./dist/register.js",
|
|
39
39
|
"default": "./dist/register.js"
|
|
40
|
+
},
|
|
41
|
+
"./pages/AuditPage": {
|
|
42
|
+
"types": "./dist/pages/AuditPage.d.ts",
|
|
43
|
+
"import": "./dist/pages/AuditPage.js",
|
|
44
|
+
"default": "./dist/pages/AuditPage.js"
|
|
45
|
+
},
|
|
46
|
+
"./lib/i18n": {
|
|
47
|
+
"types": "./dist/lib/i18n.d.ts",
|
|
48
|
+
"import": "./dist/lib/i18n.js",
|
|
49
|
+
"default": "./dist/lib/i18n.js"
|
|
40
50
|
}
|
|
41
51
|
},
|
|
42
52
|
"files": [
|
|
43
53
|
"dist",
|
|
54
|
+
"i18n",
|
|
44
55
|
"wire.json",
|
|
45
56
|
"audit.wire.json",
|
|
46
57
|
"LICENSE",
|
|
@@ -65,12 +76,11 @@
|
|
|
65
76
|
"build": "tsc",
|
|
66
77
|
"prepublishOnly": "npm run build"
|
|
67
78
|
},
|
|
68
|
-
"dependencies": {
|
|
69
|
-
"@mostajs/orm": "^1.0.0"
|
|
70
|
-
},
|
|
71
79
|
"peerDependencies": {
|
|
72
80
|
"@mostajs/menu": ">=1.0.2",
|
|
73
81
|
"@mostajs/socle": ">=2.0.0",
|
|
82
|
+
"@mostajs/ui": ">=1.0.0",
|
|
83
|
+
"lucide-react": ">=0.400.0",
|
|
74
84
|
"next": ">=14",
|
|
75
85
|
"react": ">=18"
|
|
76
86
|
},
|
|
@@ -83,14 +93,27 @@
|
|
|
83
93
|
},
|
|
84
94
|
"@mostajs/menu": {
|
|
85
95
|
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@mostajs/ui": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"lucide-react": {
|
|
101
|
+
"optional": true
|
|
86
102
|
}
|
|
87
103
|
},
|
|
88
104
|
"devDependencies": {
|
|
89
105
|
"@mostajs/menu": "^1.0.2",
|
|
90
106
|
"@mostajs/socle": "^2.0.0",
|
|
107
|
+
"@mostajs/ui": "^1.0.3",
|
|
108
|
+
"@types/node": "^25.5.0",
|
|
91
109
|
"@types/react": "^19.0.0",
|
|
110
|
+
"lucide-react": "^0.575.0",
|
|
92
111
|
"next": "^16.1.6",
|
|
93
112
|
"react": "^19.0.0",
|
|
94
113
|
"typescript": "^5.6.0"
|
|
114
|
+
},
|
|
115
|
+
"dependencies": {
|
|
116
|
+
"@mostajs/net": "^2.0.0",
|
|
117
|
+
"@mostajs/orm": "^1.7.0"
|
|
95
118
|
}
|
|
96
119
|
}
|