feat(tasks): Enhance task management with identity handling and initial data loading
This commit is contained in:
		| @@ -1,5 +1,6 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../plugins.js'; | ||||||
| import * as data from '../data/index.js'; | import * as data from '../data/index.js'; | ||||||
|  | import * as userInterfaces from '../data/user.js'; | ||||||
|  |  | ||||||
| // Get all tasks | // Get all tasks | ||||||
| export interface IRequest_Any_Cloudly_GetTasks | export interface IRequest_Any_Cloudly_GetTasks | ||||||
| @@ -8,7 +9,9 @@ export interface IRequest_Any_Cloudly_GetTasks | |||||||
|     IRequest_Any_Cloudly_GetTasks |     IRequest_Any_Cloudly_GetTasks | ||||||
|   > { |   > { | ||||||
|   method: 'getTasks'; |   method: 'getTasks'; | ||||||
|   request: {}; |   request: { | ||||||
|  |     identity: userInterfaces.IIdentity; | ||||||
|  |   }; | ||||||
|   response: { |   response: { | ||||||
|     tasks: Array<{ |     tasks: Array<{ | ||||||
|       name: string; |       name: string; | ||||||
| @@ -29,6 +32,7 @@ export interface IRequest_Any_Cloudly_GetTaskExecutions | |||||||
|   > { |   > { | ||||||
|   method: 'getTaskExecutions'; |   method: 'getTaskExecutions'; | ||||||
|   request: { |   request: { | ||||||
|  |     identity: userInterfaces.IIdentity; | ||||||
|     filter?: { |     filter?: { | ||||||
|       taskName?: string; |       taskName?: string; | ||||||
|       status?: string; |       status?: string; | ||||||
| @@ -49,6 +53,7 @@ export interface IRequest_Any_Cloudly_GetTaskExecutionById | |||||||
|   > { |   > { | ||||||
|   method: 'getTaskExecutionById'; |   method: 'getTaskExecutionById'; | ||||||
|   request: { |   request: { | ||||||
|  |     identity: userInterfaces.IIdentity; | ||||||
|     executionId: string; |     executionId: string; | ||||||
|   }; |   }; | ||||||
|   response: { |   response: { | ||||||
| @@ -64,6 +69,7 @@ export interface IRequest_Any_Cloudly_TriggerTask | |||||||
|   > { |   > { | ||||||
|   method: 'triggerTask'; |   method: 'triggerTask'; | ||||||
|   request: { |   request: { | ||||||
|  |     identity: userInterfaces.IIdentity; | ||||||
|     taskName: string; |     taskName: string; | ||||||
|     userId?: string; |     userId?: string; | ||||||
|   }; |   }; | ||||||
| @@ -80,6 +86,7 @@ export interface IRequest_Any_Cloudly_CancelTask | |||||||
|   > { |   > { | ||||||
|   method: 'cancelTask'; |   method: 'cancelTask'; | ||||||
|   request: { |   request: { | ||||||
|  |     identity: userInterfaces.IIdentity; | ||||||
|     executionId: string; |     executionId: string; | ||||||
|   }; |   }; | ||||||
|   response: { |   response: { | ||||||
|   | |||||||
| @@ -53,6 +53,8 @@ export interface IDataState { | |||||||
|   deployments?: plugins.interfaces.data.IDeployment[]; |   deployments?: plugins.interfaces.data.IDeployment[]; | ||||||
|   domains?: plugins.interfaces.data.IDomain[]; |   domains?: plugins.interfaces.data.IDomain[]; | ||||||
|   dnsEntries?: plugins.interfaces.data.IDnsEntry[]; |   dnsEntries?: plugins.interfaces.data.IDnsEntry[]; | ||||||
|  |   tasks?: any[]; | ||||||
|  |   taskExecutions?: plugins.interfaces.data.ITaskExecution[]; | ||||||
|   mails?: any[]; |   mails?: any[]; | ||||||
|   logs?: any[]; |   logs?: any[]; | ||||||
|   s3?: any[]; |   s3?: any[]; | ||||||
| @@ -71,6 +73,8 @@ export const dataState = await appstate.getStatePart<IDataState>( | |||||||
|     deployments: [], |     deployments: [], | ||||||
|     domains: [], |     domains: [], | ||||||
|     dnsEntries: [], |     dnsEntries: [], | ||||||
|  |     tasks: [], | ||||||
|  |     taskExecutions: [], | ||||||
|     mails: [], |     mails: [], | ||||||
|     logs: [], |     logs: [], | ||||||
|     s3: [], |     s3: [], | ||||||
| @@ -676,7 +680,10 @@ export const taskActions = { | |||||||
|       const response = await trGetTasks.fire({ |       const response = await trGetTasks.fire({ | ||||||
|         identity: loginStatePart.getState().identity, |         identity: loginStatePart.getState().identity, | ||||||
|       }); |       }); | ||||||
|       return response as any; |       return { | ||||||
|  |         ...currentState, | ||||||
|  |         tasks: response.tasks, | ||||||
|  |       }; | ||||||
|     } |     } | ||||||
|   ), |   ), | ||||||
|    |    | ||||||
| @@ -692,7 +699,10 @@ export const taskActions = { | |||||||
|         identity: loginStatePart.getState().identity, |         identity: loginStatePart.getState().identity, | ||||||
|         filter: payloadArg.filter, |         filter: payloadArg.filter, | ||||||
|       }); |       }); | ||||||
|       return response as any; |       return { | ||||||
|  |         ...currentState, | ||||||
|  |         taskExecutions: response.executions, | ||||||
|  |       }; | ||||||
|     } |     } | ||||||
|   ), |   ), | ||||||
|    |    | ||||||
| @@ -708,7 +718,7 @@ export const taskActions = { | |||||||
|         identity: loginStatePart.getState().identity, |         identity: loginStatePart.getState().identity, | ||||||
|         executionId: payloadArg.executionId, |         executionId: payloadArg.executionId, | ||||||
|       }); |       }); | ||||||
|       return response as any; |       return currentState; | ||||||
|     } |     } | ||||||
|   ), |   ), | ||||||
|    |    | ||||||
|   | |||||||
| @@ -44,39 +44,8 @@ export class CloudlyDashboard extends DeesElement { | |||||||
|     clusters: [], |     clusters: [], | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   constructor() { |   // Keep view tabs stable across renders to preserve active selection | ||||||
|     super(); |   private readonly viewTabs: plugins.deesCatalog.IView[] = [ | ||||||
|     document.title = `cloudly v${commitinfo.version}`; |  | ||||||
|     const subcription = appstate.dataState |  | ||||||
|       .select((stateArg) => stateArg) |  | ||||||
|       .subscribe((dataArg) => { |  | ||||||
|         this.data = dataArg; |  | ||||||
|       }); |  | ||||||
|     this.rxSubscriptions.push(subcription); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public static styles = [ |  | ||||||
|     cssManager.defaultStyles, |  | ||||||
|     css` |  | ||||||
|       .maincontainer { |  | ||||||
|         position: relative; |  | ||||||
|         width: 100vw; |  | ||||||
|         height: 100vh; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       h1 { |  | ||||||
|         font-weight: 400; |  | ||||||
|         font-size: 24px; |  | ||||||
|         font-family: 'Cal Sans'; |  | ||||||
|       } |  | ||||||
|     `, |  | ||||||
|   ]; |  | ||||||
|   public render() { |  | ||||||
|     return html` |  | ||||||
|       <div class="maincontainer"> |  | ||||||
|         <dees-simple-login name="cloudly v${commitinfo.version}"> |  | ||||||
|           <dees-simple-appdash name="cloudly v${commitinfo.version}" |  | ||||||
|             .viewTabs=${[ |  | ||||||
|     { |     { | ||||||
|       name: 'Overview', |       name: 'Overview', | ||||||
|       iconName: 'lucide:LayoutDashboard', |       iconName: 'lucide:LayoutDashboard', | ||||||
| @@ -171,8 +140,42 @@ export class CloudlyDashboard extends DeesElement { | |||||||
|       name: 'Fleet', |       name: 'Fleet', | ||||||
|       iconName: 'lucide:Truck', |       iconName: 'lucide:Truck', | ||||||
|       element: CloudlyViewBackups, |       element: CloudlyViewBackups, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   constructor() { | ||||||
|  |     super(); | ||||||
|  |     document.title = `cloudly v${commitinfo.version}`; | ||||||
|  |     const subcription = appstate.dataState | ||||||
|  |       .select((stateArg) => stateArg) | ||||||
|  |       .subscribe((dataArg) => { | ||||||
|  |         this.data = dataArg; | ||||||
|  |       }); | ||||||
|  |     this.rxSubscriptions.push(subcription); | ||||||
|   } |   } | ||||||
|             ] as plugins.deesCatalog.IView[]} |  | ||||||
|  |   public static styles = [ | ||||||
|  |     cssManager.defaultStyles, | ||||||
|  |     css` | ||||||
|  |       .maincontainer { | ||||||
|  |         position: relative; | ||||||
|  |         width: 100vw; | ||||||
|  |         height: 100vh; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       h1 { | ||||||
|  |         font-weight: 400; | ||||||
|  |         font-size: 24px; | ||||||
|  |         font-family: 'Cal Sans'; | ||||||
|  |       } | ||||||
|  |     `, | ||||||
|  |   ]; | ||||||
|  |   public render() { | ||||||
|  |     return html` | ||||||
|  |       <div class="maincontainer"> | ||||||
|  |         <dees-simple-login name="cloudly v${commitinfo.version}"> | ||||||
|  |           <dees-simple-appdash name="cloudly v${commitinfo.version}" | ||||||
|  |             .viewTabs=${this.viewTabs} | ||||||
|           ></dees-simple-appdash> |           ></dees-simple-appdash> | ||||||
|         </dees-simple-login> |         </dees-simple-login> | ||||||
|       </div> |       </div> | ||||||
|   | |||||||
| @@ -18,12 +18,6 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|   @state() |   @state() | ||||||
|   private data: appstate.IDataState = {}; |   private data: appstate.IDataState = {}; | ||||||
|  |  | ||||||
|   @state() |  | ||||||
|   private tasks: any[] = []; |  | ||||||
|  |  | ||||||
|   @state() |  | ||||||
|   private executions: plugins.interfaces.data.ITaskExecution[] = []; |  | ||||||
|  |  | ||||||
|   @state() |   @state() | ||||||
|   private selectedExecution: plugins.interfaces.data.ITaskExecution | null = null; |   private selectedExecution: plugins.interfaces.data.ITaskExecution | null = null; | ||||||
|  |  | ||||||
| @@ -41,6 +35,18 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|         this.data = dataArg; |         this.data = dataArg; | ||||||
|       }); |       }); | ||||||
|     this.rxSubscriptions.push(subscription); |     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 = [ |   public static styles = [ | ||||||
| @@ -276,41 +282,6 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|     `, |     `, | ||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   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) { |   private async triggerTask(taskName: string) { | ||||||
|     try { |     try { | ||||||
| @@ -319,13 +290,28 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|       ); |       ); | ||||||
|        |        | ||||||
|       // Reload tasks and executions to show the new execution |       // Reload tasks and executions to show the new execution | ||||||
|       await this.loadTasks(); |       await appstate.dataState.dispatchAction(appstate.taskActions.getTasks, {}); | ||||||
|       await this.loadExecutions(); |       await this.loadExecutionsWithFilter(); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       console.error('Failed to trigger task:', 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 { |   private formatDate(timestamp: number): string { | ||||||
|     return new Date(timestamp).toLocaleString(); |     return new Date(timestamp).toLocaleString(); | ||||||
|   } |   } | ||||||
| @@ -359,7 +345,8 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private renderTaskCard(task: any) { |   private renderTaskCard(task: any) { | ||||||
|     const lastExecution = this.executions |     const executions = this.data.taskExecutions || []; | ||||||
|  |     const lastExecution = executions | ||||||
|       .filter(e => e.data.taskName === task.name) |       .filter(e => e.data.taskName === task.name) | ||||||
|       .sort((a, b) => (b.data.startedAt || 0) - (a.data.startedAt || 0))[0]; |       .sort((a, b) => (b.data.startedAt || 0) - (a.data.startedAt || 0))[0]; | ||||||
|      |      | ||||||
| @@ -495,32 +482,32 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|       <div class="filter-bar"> |       <div class="filter-bar"> | ||||||
|         <button |         <button | ||||||
|           class="filter-button ${this.filterStatus === 'all' ? 'active' : ''}" |           class="filter-button ${this.filterStatus === 'all' ? 'active' : ''}" | ||||||
|           @click=${() => { this.filterStatus = 'all'; this.loadExecutions(); }} |           @click=${() => { this.filterStatus = 'all'; this.loadExecutionsWithFilter(); }} | ||||||
|         > |         > | ||||||
|           All |           All | ||||||
|         </button> |         </button> | ||||||
|         <button |         <button | ||||||
|           class="filter-button ${this.filterStatus === 'running' ? 'active' : ''}" |           class="filter-button ${this.filterStatus === 'running' ? 'active' : ''}" | ||||||
|           @click=${() => { this.filterStatus = 'running'; this.loadExecutions(); }} |           @click=${() => { this.filterStatus = 'running'; this.loadExecutionsWithFilter(); }} | ||||||
|         > |         > | ||||||
|           Running |           Running | ||||||
|         </button> |         </button> | ||||||
|         <button |         <button | ||||||
|           class="filter-button ${this.filterStatus === 'completed' ? 'active' : ''}" |           class="filter-button ${this.filterStatus === 'completed' ? 'active' : ''}" | ||||||
|           @click=${() => { this.filterStatus = 'completed'; this.loadExecutions(); }} |           @click=${() => { this.filterStatus = 'completed'; this.loadExecutionsWithFilter(); }} | ||||||
|         > |         > | ||||||
|           Completed |           Completed | ||||||
|         </button> |         </button> | ||||||
|         <button |         <button | ||||||
|           class="filter-button ${this.filterStatus === 'failed' ? 'active' : ''}" |           class="filter-button ${this.filterStatus === 'failed' ? 'active' : ''}" | ||||||
|           @click=${() => { this.filterStatus = 'failed'; this.loadExecutions(); }} |           @click=${() => { this.filterStatus = 'failed'; this.loadExecutionsWithFilter(); }} | ||||||
|         > |         > | ||||||
|           Failed |           Failed | ||||||
|         </button> |         </button> | ||||||
|       </div> |       </div> | ||||||
|        |        | ||||||
|       <div class="task-grid"> |       <div class="task-grid"> | ||||||
|         ${this.tasks.map(task => this.renderTaskCard(task))} |         ${(this.data.tasks || []).map(task => this.renderTaskCard(task))} | ||||||
|       </div> |       </div> | ||||||
|        |        | ||||||
|       <cloudly-sectionheading>Execution History</cloudly-sectionheading> |       <cloudly-sectionheading>Execution History</cloudly-sectionheading> | ||||||
| @@ -528,7 +515,7 @@ export class CloudlyViewTasks extends DeesElement { | |||||||
|       <dees-table |       <dees-table | ||||||
|         .heading1=${'Task Executions'} |         .heading1=${'Task Executions'} | ||||||
|         .heading2=${'History of task runs and their outcomes'} |         .heading2=${'History of task runs and their outcomes'} | ||||||
|         .data=${this.executions} |         .data=${this.data.taskExecutions || []} | ||||||
|         .displayFunction=${(itemArg: plugins.interfaces.data.ITaskExecution) => { |         .displayFunction=${(itemArg: plugins.interfaces.data.ITaskExecution) => { | ||||||
|           return { |           return { | ||||||
|             Task: itemArg.data.taskName, |             Task: itemArg.data.taskName, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user