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
194 lines
4.8 KiB
JavaScript
194 lines
4.8 KiB
JavaScript
/**
|
|
* Audit Log Service - User action history tracking
|
|
*/
|
|
|
|
import api from '../core/api.js';
|
|
import logger from '../core/logger.js';
|
|
|
|
class AuditService {
|
|
constructor() {
|
|
this.cache = {
|
|
categories: null,
|
|
actions: null
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get audit log with filters
|
|
*/
|
|
async getAuditLog(filters = {}) {
|
|
const params = new URLSearchParams();
|
|
|
|
if (filters.project_id) params.append('project_id', filters.project_id);
|
|
if (filters.user_id) params.append('user_id', filters.user_id);
|
|
if (filters.action) params.append('action', filters.action);
|
|
if (filters.category) params.append('category', filters.category);
|
|
if (filters.entity_type) params.append('entity_type', filters.entity_type);
|
|
if (filters.severity) params.append('severity', filters.severity);
|
|
if (filters.start_date) params.append('start_date', filters.start_date);
|
|
if (filters.end_date) params.append('end_date', filters.end_date);
|
|
if (filters.limit) params.append('limit', filters.limit);
|
|
if (filters.offset) params.append('offset', filters.offset);
|
|
|
|
try {
|
|
const response = await api.get(`/audit?${params.toString()}`);
|
|
return response;
|
|
} catch (error) {
|
|
logger.error('AuditService', 'Failed to fetch audit log', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get audit statistics
|
|
*/
|
|
async getStats() {
|
|
try {
|
|
return await api.get('/audit/stats');
|
|
} catch (error) {
|
|
logger.error('AuditService', 'Failed to fetch audit stats', error);
|
|
return {
|
|
by_category: {},
|
|
by_user: {},
|
|
total_count: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all categories
|
|
*/
|
|
async getCategories() {
|
|
if (this.cache.categories) {
|
|
return this.cache.categories;
|
|
}
|
|
|
|
try {
|
|
this.cache.categories = await api.get('/audit/categories');
|
|
return this.cache.categories;
|
|
} catch (error) {
|
|
logger.error('AuditService', 'Failed to fetch categories', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all actions
|
|
*/
|
|
async getActions() {
|
|
if (this.cache.actions) {
|
|
return this.cache.actions;
|
|
}
|
|
|
|
try {
|
|
this.cache.actions = await api.get('/audit/actions');
|
|
return this.cache.actions;
|
|
} catch (error) {
|
|
logger.error('AuditService', 'Failed to fetch actions', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create audit entry
|
|
*/
|
|
async logAction(entry) {
|
|
try {
|
|
await api.post('/audit', entry);
|
|
logger.info('AuditService', 'Action logged', { action: entry.action });
|
|
} catch (error) {
|
|
logger.error('AuditService', 'Failed to log action', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export audit log
|
|
*/
|
|
async export(filters = {}, format = 'json') {
|
|
const params = new URLSearchParams({ format });
|
|
|
|
if (filters.project_id) params.append('project_id', filters.project_id);
|
|
if (filters.category) params.append('category', filters.category);
|
|
if (filters.start_date) params.append('start_date', filters.start_date);
|
|
if (filters.end_date) params.append('end_date', filters.end_date);
|
|
|
|
try {
|
|
const url = `${api.baseUrl}/audit/export?${params.toString()}`;
|
|
window.open(url, '_blank');
|
|
logger.info('AuditService', 'Export initiated', { format });
|
|
} catch (error) {
|
|
logger.error('AuditService', 'Failed to export', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get severity badge class
|
|
*/
|
|
getSeverityClass(severity) {
|
|
const classes = {
|
|
'info': 'badge--primary',
|
|
'warning': 'badge--warning',
|
|
'critical': 'badge--destructive'
|
|
};
|
|
return classes[severity] || 'badge--secondary';
|
|
}
|
|
|
|
/**
|
|
* Get category icon
|
|
*/
|
|
getCategoryIcon(category) {
|
|
const icons = {
|
|
'design_system': '🎨',
|
|
'code': '⚛️',
|
|
'configuration': '⚙️',
|
|
'project': '📁',
|
|
'team': '👥',
|
|
'storybook': '📚',
|
|
'other': '📌'
|
|
};
|
|
return icons[category] || '📌';
|
|
}
|
|
|
|
/**
|
|
* Format timestamp
|
|
*/
|
|
formatTimestamp(timestamp) {
|
|
const date = new Date(timestamp);
|
|
const now = new Date();
|
|
const diff = now - date;
|
|
|
|
// Less than 1 minute
|
|
if (diff < 60000) {
|
|
return 'Just now';
|
|
}
|
|
|
|
// Less than 1 hour
|
|
if (diff < 3600000) {
|
|
const minutes = Math.floor(diff / 60000);
|
|
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
// Less than 1 day
|
|
if (diff < 86400000) {
|
|
const hours = Math.floor(diff / 3600000);
|
|
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
// Less than 7 days
|
|
if (diff < 604800000) {
|
|
const days = Math.floor(diff / 86400000);
|
|
return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
// Show full date
|
|
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
const auditService = new AuditService();
|
|
|
|
export default auditService;
|
|
export { AuditService };
|