Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
272
admin-ui/js/core/audit-logger.js
Normal file
272
admin-ui/js/core/audit-logger.js
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Audit Logger - Phase 8 Enterprise Pattern
|
||||
*
|
||||
* Tracks all state changes, user actions, and workflow transitions
|
||||
* for compliance, debugging, and analytics.
|
||||
*/
|
||||
|
||||
class AuditLogger {
|
||||
constructor() {
|
||||
this.logs = [];
|
||||
this.maxLogs = 1000;
|
||||
this.storageKey = 'dss-audit-logs';
|
||||
this.sessionId = this.generateSessionId();
|
||||
this.logLevel = 'info'; // 'debug', 'info', 'warn', 'error'
|
||||
this.loadFromStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique session ID
|
||||
*/
|
||||
generateSessionId() {
|
||||
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create audit log entry
|
||||
*/
|
||||
createLogEntry(action, category, details = {}, level = 'info') {
|
||||
return {
|
||||
id: `log-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
|
||||
timestamp: new Date().toISOString(),
|
||||
sessionId: this.sessionId,
|
||||
action,
|
||||
category,
|
||||
level,
|
||||
details,
|
||||
userAgent: navigator.userAgent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Log user action
|
||||
*/
|
||||
logAction(action, details = {}) {
|
||||
const entry = this.createLogEntry(action, 'user_action', details, 'info');
|
||||
this.addLog(entry);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log state change
|
||||
*/
|
||||
logStateChange(key, oldValue, newValue, details = {}) {
|
||||
const entry = this.createLogEntry(
|
||||
`state_change`,
|
||||
'state',
|
||||
{
|
||||
key,
|
||||
oldValue: this.sanitize(oldValue),
|
||||
newValue: this.sanitize(newValue),
|
||||
...details
|
||||
},
|
||||
'info'
|
||||
);
|
||||
this.addLog(entry);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log API call
|
||||
*/
|
||||
logApiCall(method, endpoint, status, responseTime = 0, details = {}) {
|
||||
const entry = this.createLogEntry(
|
||||
`api_${method.toLowerCase()}`,
|
||||
'api',
|
||||
{
|
||||
endpoint,
|
||||
method,
|
||||
status,
|
||||
responseTime,
|
||||
...details
|
||||
},
|
||||
status >= 400 ? 'warn' : 'info'
|
||||
);
|
||||
this.addLog(entry);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error
|
||||
*/
|
||||
logError(error, context = '') {
|
||||
const entry = this.createLogEntry(
|
||||
'error',
|
||||
'error',
|
||||
{
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
context
|
||||
},
|
||||
'error'
|
||||
);
|
||||
this.addLog(entry);
|
||||
console.error('[AuditLogger]', error);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning
|
||||
*/
|
||||
logWarning(message, details = {}) {
|
||||
const entry = this.createLogEntry(
|
||||
'warning',
|
||||
'warning',
|
||||
{ message, ...details },
|
||||
'warn'
|
||||
);
|
||||
this.addLog(entry);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log permission check
|
||||
*/
|
||||
logPermissionCheck(action, allowed, user, reason = '') {
|
||||
const entry = this.createLogEntry(
|
||||
'permission_check',
|
||||
'security',
|
||||
{
|
||||
action,
|
||||
allowed,
|
||||
user,
|
||||
reason
|
||||
},
|
||||
allowed ? 'info' : 'warn'
|
||||
);
|
||||
this.addLog(entry);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add log entry to collection
|
||||
*/
|
||||
addLog(entry) {
|
||||
this.logs.unshift(entry);
|
||||
if (this.logs.length > this.maxLogs) {
|
||||
this.logs.pop();
|
||||
}
|
||||
this.saveToStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize sensitive data before logging
|
||||
*/
|
||||
sanitize(value) {
|
||||
if (typeof value !== 'object') return value;
|
||||
|
||||
const sanitized = { ...value };
|
||||
const sensitiveKeys = ['password', 'token', 'apiKey', 'secret', 'key'];
|
||||
|
||||
for (const key of Object.keys(sanitized)) {
|
||||
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
|
||||
sanitized[key] = '***REDACTED***';
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logs filtered by criteria
|
||||
*/
|
||||
getLogs(filters = {}) {
|
||||
let result = [...this.logs];
|
||||
|
||||
if (filters.action) {
|
||||
result = result.filter(l => l.action === filters.action);
|
||||
}
|
||||
if (filters.category) {
|
||||
result = result.filter(l => l.category === filters.category);
|
||||
}
|
||||
if (filters.level) {
|
||||
result = result.filter(l => l.level === filters.level);
|
||||
}
|
||||
if (filters.startTime) {
|
||||
result = result.filter(l => new Date(l.timestamp) >= new Date(filters.startTime));
|
||||
}
|
||||
if (filters.endTime) {
|
||||
result = result.filter(l => new Date(l.timestamp) <= new Date(filters.endTime));
|
||||
}
|
||||
if (filters.sessionId) {
|
||||
result = result.filter(l => l.sessionId === filters.sessionId);
|
||||
}
|
||||
if (filters.limit) {
|
||||
result = result.slice(0, filters.limit);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
totalLogs: this.logs.length,
|
||||
sessionId: this.sessionId,
|
||||
byCategory: this.logs.reduce((acc, log) => {
|
||||
acc[log.category] = (acc[log.category] || 0) + 1;
|
||||
return acc;
|
||||
}, {}),
|
||||
byLevel: this.logs.reduce((acc, log) => {
|
||||
acc[log.level] = (acc[log.level] || 0) + 1;
|
||||
return acc;
|
||||
}, {}),
|
||||
oldestLog: this.logs[this.logs.length - 1]?.timestamp,
|
||||
newestLog: this.logs[0]?.timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export logs as JSON
|
||||
*/
|
||||
exportLogs(filters = {}) {
|
||||
const logs = this.getLogs(filters);
|
||||
return JSON.stringify({
|
||||
exportDate: new Date().toISOString(),
|
||||
sessionId: this.sessionId,
|
||||
count: logs.length,
|
||||
logs
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logs
|
||||
*/
|
||||
clearLogs() {
|
||||
this.logs = [];
|
||||
this.saveToStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save logs to localStorage
|
||||
*/
|
||||
saveToStorage() {
|
||||
try {
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(this.logs));
|
||||
} catch (e) {
|
||||
console.warn('[AuditLogger] Failed to save to storage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load logs from localStorage
|
||||
*/
|
||||
loadFromStorage() {
|
||||
try {
|
||||
const stored = localStorage.getItem(this.storageKey);
|
||||
if (stored) {
|
||||
this.logs = JSON.parse(stored);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[AuditLogger] Failed to load from storage:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export singleton
|
||||
const auditLogger = new AuditLogger();
|
||||
|
||||
export { AuditLogger };
|
||||
export default auditLogger;
|
||||
Reference in New Issue
Block a user