UNPKG

json-object-editor

Version:

JOE the Json Object Editor | Platform Edition

225 lines (196 loc) 7.49 kB
class FieldJobsContainer extends HTMLElement { constructor() { super(); this.objectId = null; this.fieldName = null; this.jobs = []; this.updateInterval = null; } static get observedAttributes() { return ['data-object-id', 'data-field-name']; } connectedCallback() { this.objectId = this.getAttribute('data-object-id'); this.fieldName = this.getAttribute('data-field-name'); this.render(); this.startElapsedTimeUpdates(); // Immediately poll for jobs on connect if (this.objectId && this.fieldName) { var self = this; $.get('/API/aijobs/' + encodeURIComponent(this.objectId) + '/' + encodeURIComponent(this.fieldName)) .then(function(data) { if (data && data.jobs && Array.isArray(data.jobs)) { self.updateJobs(data.jobs); } else { self.updateJobs([]); } }) .fail(function(err) { // Silently fail - will retry on next poll if (window.DEBUG_MODE || (window.$c && $c.DEBUG_MODE)) { console.warn('[field-jobs-container] initial poll failed:', err); } }); } } disconnectedCallback() { this.stopElapsedTimeUpdates(); } attributeChangedCallback(name, oldValue, newValue) { if (name === 'data-object-id') { this.objectId = newValue; } else if (name === 'data-field-name') { this.fieldName = newValue; } if (this.objectId && this.fieldName) { this.render(); } } /** * Update jobs from server response */ updateJobs(jobs) { if (!Array.isArray(jobs)) { jobs = []; } this.jobs = jobs; this.render(); } /** * Calculate elapsed seconds from startTime */ calculateElapsed(startTime) { if (!startTime) return 0; try { var start = new Date(startTime); var now = new Date(); var elapsedMs = now - start; return Math.floor(elapsedMs / 1000); } catch (e) { return 0; } } /** * Start elapsed time updates (every second) */ startElapsedTimeUpdates() { if (this.updateInterval) return; // Already running this.updateInterval = setInterval(() => { // Check if we have any active jobs var hasActive = this.jobs.some(function(job) { return (job.status === 'running' || job.status === 'starting') && (job.total == null || job.progress == null || job.progress < job.total); }); if (hasActive) { // Force re-render to update elapsed times this.render(); } else { // Stop if no active jobs this.stopElapsedTimeUpdates(); } }, 1000); } /** * Stop elapsed time updates */ stopElapsedTimeUpdates() { if (this.updateInterval) { clearInterval(this.updateInterval); this.updateInterval = null; } } /** * Render the component */ render() { // Build endpoint URL for token link (if we have both objectId and fieldName) var endpointUrl = null; var tokenLinkHtml = ''; var fullToken = null; // Try to get a sample token from jobs, or construct lookup key if (this.jobs.length > 0 && this.jobs[0].token) { fullToken = this.jobs[0].token; } else if (this.objectId && this.fieldName) { // If no jobs, show lookup key format fullToken = this.objectId + '_' + this.fieldName; } if (this.objectId && this.fieldName) { endpointUrl = '/API/aijobs/' + encodeURIComponent(this.objectId) + '/' + encodeURIComponent(this.fieldName); var titleAttr = fullToken ? 'title="' + fullToken.replace(/"/g, '&quot;') + '"' : 'title="View endpoint"'; tokenLinkHtml = '<a href="' + endpointUrl + '" target="_blank" class="field-jobs-token-link" ' + titleAttr + '>[' + this.objectId.substring(0, 8) + '...|' + this.fieldName + ']</a>'; } if (!this.objectId || !this.fieldName) { var html = '<div class="field-jobs-title">'; html += '<span class="field-jobs-title-text">0 active jobs</span>'; html += tokenLinkHtml; html += '</div>'; this.innerHTML = html; return; } // Filter active jobs (for count) var activeJobs = this.jobs.filter(function(job) { return job.status !== 'complete' && job.status !== 'error'; }); // Update token link with full token if we have jobs (recalculate in case jobs were added) if (this.jobs.length > 0 && this.jobs[0].token) { fullToken = this.jobs[0].token; if (endpointUrl) { var titleAttr = 'title="' + fullToken.replace(/"/g, '&quot;') + '"'; tokenLinkHtml = '<a href="' + endpointUrl + '" target="_blank" class="field-jobs-token-link" ' + titleAttr + '>[' + this.objectId.substring(0, 8) + '...|' + this.fieldName + ']</a>'; } } // Build HTML var html = ''; // Title with active job count and token link on the right html += '<div class="field-jobs-title">'; html += '<span class="field-jobs-title-text">' + activeJobs.length + ' active job' + (activeJobs.length !== 1 ? 's' : '') + '</span>'; html += tokenLinkHtml; html += '</div>'; // Job rows (show all jobs, including completed) if (this.jobs.length > 0) { var self = this; this.jobs.forEach(function(job) { var jobName = job.promptName || job.promptId || 'Job'; var elapsedSeconds = job.elapsedSeconds != null ? job.elapsedSeconds : self.calculateElapsed(job.startTime); var elapsedText = elapsedSeconds > 0 ? ' (' + elapsedSeconds + 's)' : ''; // Status text (capitalized) var statusText = ''; if (job.status) { statusText = ' - ' + job.status.charAt(0).toUpperCase() + job.status.slice(1); } var percent = null; if (job.total != null && job.progress != null) { percent = Math.round((job.progress / job.total) * 100); } var statusClass = ''; if (job.status === 'complete') { statusClass = 'field-jobs-complete'; percent = 100; } else if (job.status === 'error') { statusClass = 'field-jobs-error'; } html += '<div class="field-jobs-row ' + statusClass + '">'; html += '<div class="field-jobs-row-content">'; html += '<div class="field-jobs-row-title">' + jobName + statusText + elapsedText + '</div>'; html += '<div class="field-jobs-row-message">' + (job.message || '') + '</div>'; html += '</div>'; if (percent != null) { html += '<div class="field-jobs-row-percent">' + percent + '%</div>'; } else { html += '<div class="field-jobs-row-percent">—</div>'; } html += '</div>'; }); } this.innerHTML = html; // Restart elapsed time updates if we have active jobs var hasActive = this.jobs.some(function(job) { return (job.status === 'running' || job.status === 'starting') && (job.total == null || job.progress == null || job.progress < job.total); }); if (hasActive) { this.startElapsedTimeUpdates(); } } } window.customElements.define('field-jobs-container', FieldJobsContainer);