@mostajs/audit 1.0.5 → 1.1.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/i18n/fr/audit.json +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/handlers/audit-log.d.ts +1 -0
- package/dist/lib/handlers/audit-log.js +28 -0
- 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.d.ts +4 -0
- package/dist/register.js +45 -0
- package/i18n/fr/audit.json +29 -0
- package/package.json +30 -1
- package/wire.json +12 -0
|
@@ -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 @@
|
|
|
1
|
+
export declare function GET(req: Request): Promise<Response>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @mostajs/audit — Route handler for audit-log
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
// Bare handler — NO auth. The socle catch-all does it via permission.
|
|
4
|
+
import { AuditLogRepository } from '../../repositories/audit-log.repository.js';
|
|
5
|
+
export async function GET(req) {
|
|
6
|
+
const url = new URL(req.url);
|
|
7
|
+
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
|
8
|
+
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
9
|
+
const module = url.searchParams.get('module') || undefined;
|
|
10
|
+
const action = url.searchParams.get('action') || undefined;
|
|
11
|
+
const userId = url.searchParams.get('userId') || undefined;
|
|
12
|
+
const status = url.searchParams.get('status') || undefined;
|
|
13
|
+
const dateFrom = url.searchParams.get('dateFrom') || url.searchParams.get('from') || null;
|
|
14
|
+
const dateTo = url.searchParams.get('dateTo') || url.searchParams.get('to') || null;
|
|
15
|
+
const { getDialect } = await import('@mostajs/orm');
|
|
16
|
+
const dialect = await getDialect();
|
|
17
|
+
const repo = new AuditLogRepository(dialect);
|
|
18
|
+
const { data: logs, total } = await repo.findPaginated({
|
|
19
|
+
module, action, userId, status,
|
|
20
|
+
from: dateFrom ? new Date(dateFrom) : undefined,
|
|
21
|
+
to: dateTo ? new Date(dateTo + 'T23:59:59.999Z') : undefined,
|
|
22
|
+
page, limit,
|
|
23
|
+
});
|
|
24
|
+
return Response.json({
|
|
25
|
+
data: logs,
|
|
26
|
+
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -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
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @mostajs/audit — Runtime module registration
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
import { AuditLogSchema } from './schemas/audit-log.schema.js';
|
|
4
|
+
import { AuditLogRepository } from './repositories/audit-log.repository.js';
|
|
5
|
+
import { auditMenuContribution } from './lib/menu.js';
|
|
6
|
+
import { GET as auditLogGET } from './lib/handlers/audit-log.js';
|
|
7
|
+
import AuditPage from './pages/AuditPage.js';
|
|
8
|
+
export function register(registry) {
|
|
9
|
+
registry.register({
|
|
10
|
+
manifest: {
|
|
11
|
+
name: 'audit',
|
|
12
|
+
package: '@mostajs/audit',
|
|
13
|
+
version: '2.0.0',
|
|
14
|
+
type: 'core',
|
|
15
|
+
priority: 5,
|
|
16
|
+
dependencies: ['auth'],
|
|
17
|
+
displayName: 'Audit',
|
|
18
|
+
description: 'Audit logging — activity journal with filtering and pagination',
|
|
19
|
+
icon: 'FileText',
|
|
20
|
+
register: './dist/register.js',
|
|
21
|
+
},
|
|
22
|
+
schemas: [
|
|
23
|
+
{ name: 'AuditLog', schema: AuditLogSchema },
|
|
24
|
+
],
|
|
25
|
+
repositories: {
|
|
26
|
+
auditLogRepo: (dialect) => new AuditLogRepository(dialect),
|
|
27
|
+
},
|
|
28
|
+
permissions: {
|
|
29
|
+
permissions: { AUDIT_VIEW: 'audit:view' },
|
|
30
|
+
definitions: [
|
|
31
|
+
{ code: 'audit:view', name: 'audit:view', description: 'Consulter les journaux d\'audit', category: 'audit' },
|
|
32
|
+
],
|
|
33
|
+
categories: [
|
|
34
|
+
{ name: 'audit', label: 'Audit', description: 'Consultation des journaux d\'audit', icon: 'FileText', order: 20, system: true },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
routes: [
|
|
38
|
+
{ path: 'audit-log', handlers: { GET: auditLogGET }, permission: 'audit:view' },
|
|
39
|
+
],
|
|
40
|
+
pages: [
|
|
41
|
+
{ path: 'audit', component: AuditPage, permission: 'audit:view' },
|
|
42
|
+
],
|
|
43
|
+
menu: auditMenuContribution,
|
|
44
|
+
});
|
|
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": "1.0
|
|
3
|
+
"version": "1.1.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",
|
|
@@ -32,10 +32,27 @@
|
|
|
32
32
|
"types": "./dist/lib/menu.d.ts",
|
|
33
33
|
"import": "./dist/lib/menu.js",
|
|
34
34
|
"default": "./dist/lib/menu.js"
|
|
35
|
+
},
|
|
36
|
+
"./register": {
|
|
37
|
+
"types": "./dist/register.d.ts",
|
|
38
|
+
"import": "./dist/register.js",
|
|
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"
|
|
35
50
|
}
|
|
36
51
|
},
|
|
37
52
|
"files": [
|
|
38
53
|
"dist",
|
|
54
|
+
"i18n",
|
|
55
|
+
"wire.json",
|
|
39
56
|
"audit.wire.json",
|
|
40
57
|
"LICENSE",
|
|
41
58
|
"README.md"
|
|
@@ -64,6 +81,9 @@
|
|
|
64
81
|
},
|
|
65
82
|
"peerDependencies": {
|
|
66
83
|
"@mostajs/menu": ">=1.0.2",
|
|
84
|
+
"@mostajs/socle": ">=2.0.0",
|
|
85
|
+
"@mostajs/ui": ">=1.0.0",
|
|
86
|
+
"lucide-react": ">=0.400.0",
|
|
67
87
|
"next": ">=14",
|
|
68
88
|
"react": ">=18"
|
|
69
89
|
},
|
|
@@ -76,11 +96,20 @@
|
|
|
76
96
|
},
|
|
77
97
|
"@mostajs/menu": {
|
|
78
98
|
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@mostajs/ui": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"lucide-react": {
|
|
104
|
+
"optional": true
|
|
79
105
|
}
|
|
80
106
|
},
|
|
81
107
|
"devDependencies": {
|
|
82
108
|
"@mostajs/menu": "^1.0.2",
|
|
109
|
+
"@mostajs/socle": "^2.0.0",
|
|
110
|
+
"@mostajs/ui": "^1.0.3",
|
|
83
111
|
"@types/react": "^19.0.0",
|
|
112
|
+
"lucide-react": "^0.575.0",
|
|
84
113
|
"next": "^16.1.6",
|
|
85
114
|
"react": "^19.0.0",
|
|
86
115
|
"typescript": "^5.6.0"
|
package/wire.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "audit",
|
|
3
|
+
"package": "@mostajs/audit",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"type": "core",
|
|
6
|
+
"priority": 5,
|
|
7
|
+
"dependencies": ["auth"],
|
|
8
|
+
"displayName": "Audit",
|
|
9
|
+
"description": "Audit logging — activity journal with filtering and pagination",
|
|
10
|
+
"icon": "FileText",
|
|
11
|
+
"register": "./dist/register.js"
|
|
12
|
+
}
|