feat(tasks): Enhance task management with identity handling and initial data loading

This commit is contained in:
2025-09-10 17:04:18 +00:00
parent 5b37bb5b11
commit 5d281d9b6c
4 changed files with 158 additions and 151 deletions

View File

@@ -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: {

View File

@@ -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;
} }
), ),

View File

@@ -44,6 +44,105 @@ export class CloudlyDashboard extends DeesElement {
clusters: [], clusters: [],
}; };
// Keep view tabs stable across renders to preserve active selection
private readonly viewTabs: plugins.deesCatalog.IView[] = [
{
name: 'Overview',
iconName: 'lucide:LayoutDashboard',
element: CloudlyViewOverview,
},
{
name: 'Settings',
iconName: 'lucide:Settings',
element: CloudlyViewSettings,
},
{
name: 'SecretGroups',
iconName: 'lucide:ShieldCheck',
element: CloudlyViewSecretGroups,
},
{
name: 'SecretBundles',
iconName: 'lucide:LockKeyhole',
element: CloudlyViewSecretBundles,
},
{
name: 'Clusters',
iconName: 'lucide:Network',
element: CloudlyViewClusters,
},
{
name: 'ExternalRegistries',
iconName: 'lucide:Package',
element: CloudlyViewExternalRegistries,
},
{
name: 'Images',
iconName: 'lucide:Image',
element: CloudlyViewImages,
},
{
name: 'Services',
iconName: 'lucide:Layers',
element: CloudlyViewServices,
},
{
name: 'Testing & Building',
iconName: 'lucide:HardHat',
element: CloudlyViewServices,
},
{
name: 'Deployments',
iconName: 'lucide:Rocket',
element: CloudlyViewDeployments,
},
{
name: 'Tasks',
iconName: 'lucide:ListChecks',
element: CloudlyViewTasks,
},
{
name: 'Domains',
iconName: 'lucide:Globe2',
element: CloudlyViewDomains,
},
{
name: 'DNS',
iconName: 'lucide:Globe',
element: CloudlyViewDns,
},
{
name: 'Mails',
iconName: 'lucide:Mail',
element: CloudlyViewMails,
},
{
name: 'Logs',
iconName: 'lucide:FileText',
element: CloudlyViewLogs,
},
{
name: 's3',
iconName: 'lucide:Cloud',
element: CloudlyViewS3,
},
{
name: 'DBs',
iconName: 'lucide:Database',
element: CloudlyViewDbs,
},
{
name: 'Backups',
iconName: 'lucide:Save',
element: CloudlyViewBackups,
},
{
name: 'Fleet',
iconName: 'lucide:Truck',
element: CloudlyViewBackups,
},
];
constructor() { constructor() {
super(); super();
document.title = `cloudly v${commitinfo.version}`; document.title = `cloudly v${commitinfo.version}`;
@@ -76,103 +175,7 @@ export class CloudlyDashboard extends DeesElement {
<div class="maincontainer"> <div class="maincontainer">
<dees-simple-login name="cloudly v${commitinfo.version}"> <dees-simple-login name="cloudly v${commitinfo.version}">
<dees-simple-appdash name="cloudly v${commitinfo.version}" <dees-simple-appdash name="cloudly v${commitinfo.version}"
.viewTabs=${[ .viewTabs=${this.viewTabs}
{
name: 'Overview',
iconName: 'lucide:LayoutDashboard',
element: CloudlyViewOverview,
},
{
name: 'Settings',
iconName: 'lucide:Settings',
element: CloudlyViewSettings,
},
{
name: 'SecretGroups',
iconName: 'lucide:ShieldCheck',
element: CloudlyViewSecretGroups,
},
{
name: 'SecretBundles',
iconName: 'lucide:LockKeyhole',
element: CloudlyViewSecretBundles,
},
{
name: 'Clusters',
iconName: 'lucide:Network',
element: CloudlyViewClusters,
},
{
name: 'ExternalRegistries',
iconName: 'lucide:Package',
element: CloudlyViewExternalRegistries,
},
{
name: 'Images',
iconName: 'lucide:Image',
element: CloudlyViewImages,
},
{
name: 'Services',
iconName: 'lucide:Layers',
element: CloudlyViewServices,
},
{
name: 'Testing & Building',
iconName: 'lucide:HardHat',
element: CloudlyViewServices,
},
{
name: 'Deployments',
iconName: 'lucide:Rocket',
element: CloudlyViewDeployments,
},
{
name: 'Tasks',
iconName: 'lucide:ListChecks',
element: CloudlyViewTasks,
},
{
name: 'Domains',
iconName: 'lucide:Globe2',
element: CloudlyViewDomains,
},
{
name: 'DNS',
iconName: 'lucide:Globe',
element: CloudlyViewDns,
},
{
name: 'Mails',
iconName: 'lucide:Mail',
element: CloudlyViewMails,
},
{
name: 'Logs',
iconName: 'lucide:FileText',
element: CloudlyViewLogs,
},
{
name: 's3',
iconName: 'lucide:Cloud',
element: CloudlyViewS3,
},
{
name: 'DBs',
iconName: 'lucide:Database',
element: CloudlyViewDbs,
},
{
name: 'Backups',
iconName: 'lucide:Save',
element: CloudlyViewBackups,
},
{
name: 'Fleet',
iconName: 'lucide:Truck',
element: CloudlyViewBackups,
}
] as plugins.deesCatalog.IView[]}
></dees-simple-appdash> ></dees-simple-appdash>
</dees-simple-login> </dees-simple-login>
</div> </div>

View File

@@ -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,