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 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); // Load initial data (non-blocking) this.loadInitialData(); } private async loadInitialData() { try { await appstate.dataState.dispatchAction(appstate.taskActions.getTasks, {}); await appstate.dataState.dispatchAction(appstate.taskActions.getTaskExecutions, {}); } catch (error) { console.error('Failed to load initial task data:', error); } } 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; } `, ]; private async triggerTask(taskName: string) { try { await appstate.dataState.dispatchAction( appstate.taskActions.triggerTask, { taskName } ); // Reload tasks and executions to show the new execution await appstate.dataState.dispatchAction(appstate.taskActions.getTasks, {}); await this.loadExecutionsWithFilter(); } catch (error) { console.error('Failed to trigger task:', error); } } private async loadExecutionsWithFilter() { try { const filter: any = {}; if (this.filterStatus !== 'all') { filter.status = this.filterStatus; } await appstate.dataState.dispatchAction( appstate.taskActions.getTaskExecutions, { filter } ); } catch (error) { console.error('Failed to load executions:', 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 executions = this.data.taskExecutions || []; const lastExecution = 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`
${execution.data.error.stack}` : ''}