import * as shared from '../elements/shared/index.js'; import * as plugins from '../plugins.js'; import { DeesElement, customElement, html, state, css, cssManager, property, } from '@design.estate/dees-element'; import * as appstate from '../appstate.js'; @customElement('cloudly-view-tasks') export class CloudlyViewTasks extends DeesElement { @state() private data: appstate.IDataState = {}; @state() private tasks: any[] = []; @state() private executions: plugins.interfaces.data.ITaskExecution[] = []; @state() private selectedExecution: plugins.interfaces.data.ITaskExecution | null = null; @state() private loading = false; @state() private filterStatus: string = 'all'; constructor() { super(); const subscription = appstate.dataState .select((stateArg) => stateArg) .subscribe((dataArg) => { this.data = dataArg; }); this.rxSubscriptions.push(subscription); } public static styles = [ cssManager.defaultStyles, shared.viewHostCss, css` .task-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; margin-bottom: 32px; } .task-card { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 16px; cursor: pointer; transition: all 0.2s; } .task-card:hover { background: #222; border-color: #555; } .task-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .task-name { font-size: 1.1em; font-weight: 600; color: #fff; } .task-description { color: #999; font-size: 0.9em; margin-bottom: 12px; } .task-meta { display: flex; gap: 8px; flex-wrap: wrap; } .category-badge, .status-badge { padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; } .category-maintenance { background: #ff9800; color: white; } .category-deployment { background: #2196f3; color: white; } .category-backup { background: #4caf50; color: white; } .category-monitoring { background: #9c27b0; color: white; } .category-cleanup { background: #795548; color: white; } .category-system { background: #607d8b; color: white; } .category-security { background: #f44336; color: white; } .status-running { background: #2196f3; color: white; } .status-completed { background: #4caf50; color: white; } .status-failed { background: #f44336; color: white; } .status-cancelled { background: #ff9800; color: white; } .trigger-button { padding: 6px 12px; background: #2196f3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em; transition: background 0.2s; } .trigger-button:hover { background: #1976d2; } .trigger-button:disabled { background: #666; cursor: not-allowed; } .schedule-info { color: #666; font-size: 0.85em; margin-top: 8px; } .last-run { color: #888; font-size: 0.85em; margin-top: 4px; } .execution-logs { background: #0a0a0a; border: 1px solid #333; border-radius: 4px; padding: 16px; margin-top: 16px; max-height: 400px; overflow-y: auto; } .log-entry { font-family: monospace; font-size: 0.9em; margin-bottom: 8px; padding: 4px 8px; border-radius: 4px; } .log-info { color: #2196f3; } .log-warning { color: #ff9800; background: rgba(255, 152, 0, 0.1); } .log-error { color: #f44336; background: rgba(244, 67, 54, 0.1); } .log-success { color: #4caf50; background: rgba(76, 175, 80, 0.1); } .filter-bar { display: flex; gap: 8px; margin-bottom: 16px; } .filter-button { padding: 6px 12px; background: #333; color: #ccc; border: 1px solid #555; border-radius: 4px; cursor: pointer; transition: all 0.2s; } .filter-button.active { background: #2196f3; color: white; border-color: #2196f3; } .filter-button:hover:not(.active) { background: #444; } .metrics { display: flex; gap: 16px; margin-top: 12px; padding-top: 12px; border-top: 1px solid #333; } .metric { display: flex; flex-direction: column; } .metric-label { color: #666; font-size: 0.85em; } .metric-value { color: #fff; font-size: 1.1em; font-weight: 600; } `, ]; async connectedCallback() { super.connectedCallback(); await this.loadTasks(); await this.loadExecutions(); } private async loadTasks() { this.loading = true; try { const response: any = await appstate.dataState.dispatchAction( appstate.taskActions.getTasks, {} ); this.tasks = response.tasks || []; } catch (error) { console.error('Failed to load tasks:', error); } finally { this.loading = false; } } private async loadExecutions() { try { const filter: any = {}; if (this.filterStatus !== 'all') { filter.status = this.filterStatus; } const response: any = await appstate.dataState.dispatchAction( appstate.taskActions.getTaskExecutions, { filter } ); this.executions = response.executions || []; } catch (error) { console.error('Failed to load executions:', error); } } private async triggerTask(taskName: string) { try { await appstate.dataState.dispatchAction( appstate.taskActions.triggerTask, { taskName } ); // Reload tasks and executions to show the new execution await this.loadTasks(); await this.loadExecutions(); } catch (error) { console.error('Failed to trigger task:', error); } } private formatDate(timestamp: number): string { return new Date(timestamp).toLocaleString(); } private formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; if (ms < 3600000) return `${(ms / 60000).toFixed(1)}m`; return `${(ms / 3600000).toFixed(1)}h`; } private getCategoryIcon(category: string): string { switch (category) { case 'maintenance': return 'lucide:Wrench'; case 'deployment': return 'lucide:Rocket'; case 'backup': return 'lucide:Archive'; case 'monitoring': return 'lucide:Activity'; case 'cleanup': return 'lucide:Trash2'; case 'system': return 'lucide:Settings'; case 'security': return 'lucide:Shield'; default: return 'lucide:Play'; } } private renderTaskCard(task: any) { const lastExecution = this.executions .filter(e => e.data.taskName === task.name) .sort((a, b) => (b.data.startedAt || 0) - (a.data.startedAt || 0))[0]; const isRunning = lastExecution?.data.status === 'running'; return html`
${task.name}
${task.description}
${task.category} ${!task.enabled ? html`Disabled` : ''}
${task.schedule ? html`
Schedule: ${task.schedule}
` : ''} ${task.lastRun ? html`
Last run: ${this.formatDate(task.lastRun)}
` : ''} ${lastExecution ? html`
Status ${lastExecution.data.status}
${lastExecution.data.duration ? html`
Duration ${this.formatDuration(lastExecution.data.duration)}
` : ''}
` : ''}
`; } private renderExecutionDetails(execution: plugins.interfaces.data.ITaskExecution) { return html`

Execution Details: ${execution.data.taskName}

Started ${this.formatDate(execution.data.startedAt)}
${execution.data.completedAt ? html`
Completed ${this.formatDate(execution.data.completedAt)}
` : ''} ${execution.data.duration ? html`
Duration ${this.formatDuration(execution.data.duration)}
` : ''}
Triggered By ${execution.data.triggeredBy}
${execution.data.logs && execution.data.logs.length > 0 ? html`

Logs

${execution.data.logs.map(log => html`
${this.formatDate(log.timestamp)} - ${log.message}
`)}
` : ''} ${execution.data.metrics ? html`

Metrics

${Object.entries(execution.data.metrics).map(([key, value]) => html`
${key} ${typeof value === 'object' ? JSON.stringify(value) : value}
`)}
` : ''} ${execution.data.error ? html`

Error

${execution.data.error.message} ${execution.data.error.stack ? html`
${execution.data.error.stack}
` : ''}
` : ''}
`; } public render() { return html` Tasks
${this.tasks.map(task => this.renderTaskCard(task))}
Execution History { return { Task: itemArg.data.taskName, Status: html`${itemArg.data.status}`, 'Started At': this.formatDate(itemArg.data.startedAt), Duration: itemArg.data.duration ? this.formatDuration(itemArg.data.duration) : '-', 'Triggered By': itemArg.data.triggeredBy, Logs: itemArg.data.logs?.length || 0, }; }} .actionFunction=${async (itemArg) => [ { name: 'View Details', iconName: 'lucide:Eye', type: ['inRow'], actionFunc: async () => { this.selectedExecution = itemArg; }, }, ]} > ${this.selectedExecution ? html` Execution Details ${this.renderExecutionDetails(this.selectedExecution)} ` : ''} `; } }