Some checks failed
CI / ruff (push) Successful in 9s
CI / architecture (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / audit (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Prevents .env from being baked into Docker image (was overriding config defaults). Adds env_file directive so containers load host .env properly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
249 lines
8.0 KiB
JavaScript
249 lines
8.0 KiB
JavaScript
// noqa: js-006 - async init pattern is safe, loadData has try/catch
|
|
// static/admin/js/logs.js
|
|
// noqa: JS-003 - Uses ...baseData which is data() with safety check
|
|
|
|
const logsLog = window.LogConfig?.loggers?.logs || console;
|
|
|
|
function adminLogs() {
|
|
// Get base data with safety check for standalone usage
|
|
const baseData = typeof data === 'function' ? data() : {};
|
|
|
|
return {
|
|
// Inherit base layout functionality from init-alpine.js
|
|
...baseData,
|
|
|
|
// Logs-specific state
|
|
currentPage: 'logs',
|
|
loading: true,
|
|
error: null,
|
|
successMessage: null,
|
|
logSource: 'database',
|
|
logs: [],
|
|
stats: {
|
|
total_count: 0,
|
|
warning_count: 0,
|
|
error_count: 0,
|
|
critical_count: 0
|
|
},
|
|
selectedLog: null,
|
|
filters: {
|
|
level: '',
|
|
module: '',
|
|
search: ''
|
|
},
|
|
pagination: {
|
|
page: 1,
|
|
per_page: 20,
|
|
total: 0,
|
|
pages: 0
|
|
},
|
|
logFiles: [],
|
|
selectedFile: '',
|
|
fileContent: null,
|
|
|
|
// Computed: Total pages
|
|
get totalPages() {
|
|
return this.pagination.pages;
|
|
},
|
|
|
|
// Computed: Start index for pagination display
|
|
get startIndex() {
|
|
if (this.pagination.total === 0) return 0;
|
|
return (this.pagination.page - 1) * this.pagination.per_page + 1;
|
|
},
|
|
|
|
// Computed: End index for pagination display
|
|
get endIndex() {
|
|
const end = this.pagination.page * this.pagination.per_page;
|
|
return end > this.pagination.total ? this.pagination.total : end;
|
|
},
|
|
|
|
// Computed: Page numbers for pagination
|
|
get pageNumbers() {
|
|
const pages = [];
|
|
const totalPages = this.totalPages;
|
|
const current = this.pagination.page;
|
|
|
|
if (totalPages <= 7) {
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
pages.push(i);
|
|
}
|
|
} else {
|
|
pages.push(1);
|
|
if (current > 3) {
|
|
pages.push('...');
|
|
}
|
|
const start = Math.max(2, current - 1);
|
|
const end = Math.min(totalPages - 1, current + 1);
|
|
for (let i = start; i <= end; i++) {
|
|
pages.push(i);
|
|
}
|
|
if (current < totalPages - 2) {
|
|
pages.push('...');
|
|
}
|
|
pages.push(totalPages);
|
|
}
|
|
return pages;
|
|
},
|
|
|
|
async init() {
|
|
// Guard against multiple initialization
|
|
if (window._adminLogsInitialized) {
|
|
logsLog.warn('Already initialized, skipping');
|
|
return;
|
|
}
|
|
window._adminLogsInitialized = true;
|
|
|
|
logsLog.info('=== LOGS PAGE INITIALIZING ===');
|
|
|
|
// Load platform settings for rows per page
|
|
if (window.PlatformSettings) {
|
|
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
|
}
|
|
|
|
await this.loadStats();
|
|
await this.loadLogs();
|
|
},
|
|
|
|
async refresh() {
|
|
this.error = null;
|
|
this.successMessage = null;
|
|
await this.loadStats();
|
|
if (this.logSource === 'database') {
|
|
await this.loadLogs();
|
|
} else {
|
|
await this.loadFileLogs();
|
|
}
|
|
},
|
|
|
|
async loadStats() {
|
|
try {
|
|
const data = await apiClient.get('/admin/logs/statistics?days=7');
|
|
this.stats = data;
|
|
logsLog.info('Log statistics loaded:', this.stats);
|
|
} catch (error) {
|
|
logsLog.error('Failed to load log statistics:', error);
|
|
}
|
|
},
|
|
|
|
async loadLogs() {
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (this.filters.level) params.append('level', this.filters.level);
|
|
if (this.filters.module) params.append('module', this.filters.module);
|
|
if (this.filters.search) params.append('search', this.filters.search);
|
|
params.append('skip', (this.pagination.page - 1) * this.pagination.per_page);
|
|
params.append('limit', this.pagination.per_page);
|
|
|
|
const data = await apiClient.get(`/admin/logs/database?${params}`);
|
|
this.logs = data.logs;
|
|
this.pagination.total = data.total;
|
|
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
|
|
logsLog.info(`Loaded ${this.logs.length} logs (total: ${this.pagination.total})`);
|
|
} catch (error) {
|
|
logsLog.error('Failed to load logs:', error);
|
|
this.error = error.response?.data?.detail || 'Failed to load logs';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async loadFileLogs() {
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
const data = await apiClient.get('/admin/logs/files');
|
|
this.logFiles = data.files;
|
|
|
|
if (this.logFiles.length > 0 && !this.selectedFile) {
|
|
this.selectedFile = this.logFiles[0].filename;
|
|
await this.loadFileContent();
|
|
}
|
|
logsLog.info(`Loaded ${this.logFiles.length} log files`);
|
|
} catch (error) {
|
|
logsLog.error('Failed to load log files:', error);
|
|
this.error = error.response?.data?.detail || 'Failed to load log files';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async loadFileContent() {
|
|
if (!this.selectedFile) return;
|
|
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
const data = await apiClient.get(`/admin/logs/files/${this.selectedFile}?lines=500`);
|
|
this.fileContent = data;
|
|
logsLog.info(`Loaded file content for ${this.selectedFile}`);
|
|
} catch (error) {
|
|
logsLog.error('Failed to load file content:', error);
|
|
this.error = error.response?.data?.detail || 'Failed to load file content';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async downloadLogFile() {
|
|
if (!this.selectedFile) return;
|
|
|
|
try {
|
|
const token = localStorage.getItem('admin_token');
|
|
// Note: window.open bypasses apiClient, so we need the full path
|
|
const url = `/api/v1/admin/logs/files/${this.selectedFile}/download`;
|
|
window.open(`${url}?token=${token}`, '_blank'); // # noqa: SEC-022
|
|
} catch (error) {
|
|
logsLog.error('Failed to download log file:', error);
|
|
this.error = 'Failed to download log file';
|
|
}
|
|
},
|
|
|
|
resetFilters() {
|
|
this.filters = {
|
|
level: '',
|
|
module: '',
|
|
search: ''
|
|
};
|
|
this.pagination.page = 1;
|
|
this.loadLogs();
|
|
},
|
|
|
|
previousPage() {
|
|
if (this.pagination.page > 1) {
|
|
this.pagination.page--;
|
|
this.loadLogs();
|
|
}
|
|
},
|
|
|
|
nextPage() {
|
|
if (this.pagination.page < this.totalPages) {
|
|
this.pagination.page++;
|
|
this.loadLogs();
|
|
}
|
|
},
|
|
|
|
goToPage(pageNum) {
|
|
if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) {
|
|
this.pagination.page = pageNum;
|
|
this.loadLogs();
|
|
}
|
|
},
|
|
|
|
showLogDetail(log) {
|
|
this.selectedLog = log;
|
|
},
|
|
|
|
formatTimestamp(timestamp) {
|
|
return new Date(timestamp).toLocaleString();
|
|
}
|
|
};
|
|
}
|
|
|
|
logsLog.info('Logs module loaded');
|