fix: add missing imports.js and background-tasks.js to marketplace module

Copy JS files from monitoring to marketplace static folder to match
the template locations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 21:50:15 +01:00
parent b58dd9d19d
commit 6b588ba27c
2 changed files with 605 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
// noqa: js-006 - async init pattern is safe, loadData has try/catch
/**
* Background Tasks Monitoring Component
* Manages the background tasks monitoring page
*/
// Use centralized logger
const backgroundTasksLog = window.LogConfig.createLogger('BACKGROUND-TASKS');
function backgroundTasks() {
return {
// Extend base data
...data(),
// Set current page for navigation
currentPage: 'background-tasks',
// Page-specific data
loading: false,
error: null,
filterType: null,
pollInterval: null,
// Statistics
stats: {
total_tasks: 0,
running: 0,
completed: 0,
failed: 0,
tasks_today: 0,
avg_duration_seconds: null,
import_jobs: {},
test_runs: {}
},
// Tasks
tasks: [],
runningTasks: [],
async init() {
// Guard against multiple initialization
if (window._adminBackgroundTasksInitialized) return;
window._adminBackgroundTasksInitialized = true;
try {
backgroundTasksLog.info('Initializing background tasks monitor');
await this.loadStats();
await this.loadTasks();
await this.loadRunningTasks();
// Poll for updates every 5 seconds
this.pollInterval = setInterval(() => {
this.loadRunningTasks();
if (this.runningTasks.length > 0) {
this.loadStats();
}
}, 5000);
} catch (error) {
backgroundTasksLog.error('Failed to initialize background tasks:', error);
}
},
destroy() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
},
async loadStats() {
try {
const stats = await apiClient.get('/admin/background-tasks/tasks/stats');
this.stats = stats;
backgroundTasksLog.info('Stats loaded:', stats);
} catch (err) {
backgroundTasksLog.error('Failed to load stats:', err);
}
},
async loadTasks() {
this.loading = true;
this.error = null;
try {
let url = '/admin/background-tasks/tasks?limit=50';
if (this.filterType) {
url += `&task_type=${this.filterType}`;
}
const tasks = await apiClient.get(url);
this.tasks = tasks;
backgroundTasksLog.info('Tasks loaded:', tasks.length);
} catch (err) {
backgroundTasksLog.error('Failed to load tasks:', err);
this.error = err.message;
if (err.message.includes('Unauthorized')) {
window.location.href = '/admin/login';
}
} finally {
this.loading = false;
}
},
async loadRunningTasks() {
try {
const running = await apiClient.get('/admin/background-tasks/tasks/running');
this.runningTasks = running;
// Update elapsed time for running tasks
const now = new Date();
this.runningTasks.forEach(task => {
if (task.started_at) {
const started = new Date(task.started_at);
task.duration_seconds = (now - started) / 1000;
}
});
} catch (err) {
backgroundTasksLog.error('Failed to load running tasks:', err);
}
},
async refresh() {
await this.loadStats();
await this.loadTasks();
await this.loadRunningTasks();
},
formatDuration(seconds) {
if (seconds === null || seconds === undefined) return 'N/A';
if (seconds < 1) return `${Math.round(seconds * 1000)}ms`;
if (seconds < 60) return `${Math.round(seconds)}s`;
const minutes = Math.floor(seconds / 60);
const secs = Math.round(seconds % 60);
return `${minutes}m ${secs}s`;
}
};
}

View File

@@ -0,0 +1,468 @@
// noqa: js-006 - async init pattern is safe, loadData has try/catch
// static/admin/js/imports.js
/**
* Admin platform monitoring - all import jobs
*/
// ✅ Use centralized logger
const adminImportsLog = window.LogConfig.loggers.imports;
adminImportsLog.info('Loading...');
function adminImports() {
adminImportsLog.debug('adminImports() called');
return {
// ✅ Inherit base layout state
...data(),
// ✅ Set page identifier
currentPage: 'imports',
// Loading states
loading: false,
error: '',
// Vendors list
vendors: [],
// Stats
stats: {
total: 0,
active: 0,
completed: 0,
failed: 0
},
// Filters
filters: {
vendor_id: '',
status: '',
marketplace: '',
created_by: '' // 'me' or empty
},
// Import jobs
jobs: [],
pagination: {
page: 1,
per_page: 20,
total: 0,
pages: 0
},
// Modal state
showJobModal: false,
selectedJob: null,
// Job errors state
jobErrors: [],
jobErrorsTotal: 0,
jobErrorsPage: 1,
loadingErrors: false,
// Auto-refresh for active jobs
autoRefreshInterval: 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._adminImportsInitialized) {
return;
}
window._adminImportsInitialized = true;
// Load platform settings for rows per page
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
}
// IMPORTANT: Call parent init first
const parentInit = data().init;
if (parentInit) {
await parentInit.call(this);
}
await this.loadVendors();
await this.loadJobs();
await this.loadStats();
// Auto-refresh active jobs every 15 seconds
this.startAutoRefresh();
},
/**
* Load all vendors for filtering
*/
async loadVendors() {
try {
const response = await apiClient.get('/admin/vendors?limit=1000');
this.vendors = response.vendors || [];
adminImportsLog.debug('Loaded vendors:', this.vendors.length);
} catch (error) {
adminImportsLog.error('Failed to load vendors:', error);
}
},
/**
* Load statistics
*/
async loadStats() {
try {
const response = await apiClient.get('/admin/marketplace-import-jobs/stats');
this.stats = {
total: response.total || 0,
active: (response.pending || 0) + (response.processing || 0),
completed: response.completed || 0,
failed: response.failed || 0
};
adminImportsLog.debug('Loaded stats:', this.stats);
} catch (error) {
adminImportsLog.error('Failed to load stats:', error);
// Non-critical, don't show error
}
},
/**
* Load ALL import jobs (with filters)
*/
async loadJobs() {
this.loading = true;
this.error = '';
try {
// Build query params
const params = new URLSearchParams({
skip: (this.pagination.page - 1) * this.pagination.per_page,
limit: this.pagination.per_page
});
// Add filters
if (this.filters.vendor_id) {
params.append('vendor_id', this.filters.vendor_id);
}
if (this.filters.status) {
params.append('status', this.filters.status);
}
if (this.filters.marketplace) {
params.append('marketplace', this.filters.marketplace);
}
if (this.filters.created_by === 'me') {
params.append('created_by_me', 'true');
}
const response = await apiClient.get(
`/admin/marketplace-import-jobs?${params.toString()}`
);
this.jobs = response.items || [];
this.pagination.total = response.total || 0;
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
adminImportsLog.debug('Loaded all jobs:', this.jobs.length);
} catch (error) {
adminImportsLog.error('Failed to load jobs:', error);
this.error = error.message || 'Failed to load import jobs';
} finally {
this.loading = false;
}
},
/**
* Apply filters and reload
*/
async applyFilters() {
this.pagination.page = 1; // Reset to first page when filtering
await this.loadJobs();
await this.loadStats(); // Update stats based on filters
},
/**
* Clear all filters and reload
*/
async clearFilters() {
this.filters.vendor_id = '';
this.filters.status = '';
this.filters.marketplace = '';
this.filters.created_by = '';
this.pagination.page = 1;
await this.loadJobs();
await this.loadStats();
},
/**
* Refresh jobs list
*/
async refreshJobs() {
await this.loadJobs();
await this.loadStats();
},
/**
* Refresh single job status
*/
async refreshJobStatus(jobId) {
try {
const response = await apiClient.get(`/admin/marketplace-import-jobs/${jobId}`);
// Update job in list
const index = this.jobs.findIndex(j => j.id === jobId);
if (index !== -1) {
this.jobs[index] = response;
}
// Update selected job if modal is open
if (this.selectedJob && this.selectedJob.id === jobId) {
this.selectedJob = response;
}
adminImportsLog.debug('Refreshed job:', jobId);
} catch (error) {
adminImportsLog.error('Failed to refresh job:', error);
}
},
/**
* View job details in modal
*/
async viewJobDetails(jobId) {
try {
const response = await apiClient.get(`/admin/marketplace-import-jobs/${jobId}`);
this.selectedJob = response;
this.showJobModal = true;
adminImportsLog.debug('Viewing job details:', jobId);
} catch (error) {
adminImportsLog.error('Failed to load job details:', error);
this.error = error.message || 'Failed to load job details';
}
},
/**
* Close job details modal
*/
closeJobModal() {
this.showJobModal = false;
this.selectedJob = null;
// Clear errors state
this.jobErrors = [];
this.jobErrorsTotal = 0;
this.jobErrorsPage = 1;
},
/**
* Load errors for a specific job
*/
async loadJobErrors(jobId) {
if (!jobId) return;
this.loadingErrors = true;
this.jobErrorsPage = 1;
try {
const response = await apiClient.get(
`/admin/marketplace-import-jobs/${jobId}/errors?page=1&limit=20`
);
this.jobErrors = response.errors || [];
this.jobErrorsTotal = response.total || 0;
adminImportsLog.debug('Loaded job errors:', this.jobErrors.length);
} catch (error) {
adminImportsLog.error('Failed to load job errors:', error);
this.error = error.message || 'Failed to load import errors';
} finally {
this.loadingErrors = false;
}
},
/**
* Load more errors (pagination)
*/
async loadMoreJobErrors(jobId) {
if (!jobId || this.loadingErrors) return;
this.loadingErrors = true;
this.jobErrorsPage++;
try {
const response = await apiClient.get(
`/admin/marketplace-import-jobs/${jobId}/errors?page=${this.jobErrorsPage}&limit=20`
);
const newErrors = response.errors || [];
this.jobErrors = [...this.jobErrors, ...newErrors];
adminImportsLog.debug('Loaded more job errors:', newErrors.length);
} catch (error) {
adminImportsLog.error('Failed to load more job errors:', error);
this.jobErrorsPage--; // Revert page on failure
} finally {
this.loadingErrors = false;
}
},
/**
* Get vendor name by ID
*/
getVendorName(vendorId) {
const vendor = this.vendors.find(v => v.id === vendorId);
return vendor ? `${vendor.name} (${vendor.vendor_code})` : `Vendor #${vendorId}`;
},
/**
* Pagination: Previous page
*/
previousPage() {
if (this.pagination.page > 1) {
this.pagination.page--;
this.loadJobs();
}
},
/**
* Pagination: Next page
*/
nextPage() {
if (this.pagination.page < this.totalPages) {
this.pagination.page++;
this.loadJobs();
}
},
/**
* Pagination: Go to specific page
*/
goToPage(pageNum) {
if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) {
this.pagination.page = pageNum;
this.loadJobs();
}
},
/**
* Format date for display
*/
formatDate(dateString) {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (error) {
return dateString;
}
},
/**
* Calculate duration between start and end
*/
calculateDuration(job) {
if (!job.started_at) {
return 'Not started';
}
const start = new Date(job.started_at);
const end = job.completed_at ? new Date(job.completed_at) : new Date();
const durationMs = end - start;
// Convert to human-readable format
const seconds = Math.floor(durationMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
},
/**
* Start auto-refresh for active jobs
*/
startAutoRefresh() {
// Clear any existing interval
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
}
// Refresh every 15 seconds if there are active jobs
this.autoRefreshInterval = setInterval(async () => {
const hasActiveJobs = this.jobs.some(job =>
job.status === 'pending' || job.status === 'processing'
);
if (hasActiveJobs) {
adminImportsLog.debug('Auto-refreshing active jobs...');
await this.loadJobs();
await this.loadStats();
}
}, 15000); // 15 seconds
},
/**
* Stop auto-refresh (cleanup)
*/
stopAutoRefresh() {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
this.autoRefreshInterval = null;
}
}
};
}
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (window._adminImportsInstance && window._adminImportsInstance.stopAutoRefresh) {
window._adminImportsInstance.stopAutoRefresh();
}
});