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 data from '../data/index.js';
|
||||
import * as userInterfaces from '../data/user.js';
|
||||
|
||||
// Get all tasks
|
||||
export interface IRequest_Any_Cloudly_GetTasks
|
||||
@@ -8,7 +9,9 @@ export interface IRequest_Any_Cloudly_GetTasks
|
||||
IRequest_Any_Cloudly_GetTasks
|
||||
> {
|
||||
method: 'getTasks';
|
||||
request: {};
|
||||
request: {
|
||||
identity: userInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
tasks: Array<{
|
||||
name: string;
|
||||
@@ -29,6 +32,7 @@ export interface IRequest_Any_Cloudly_GetTaskExecutions
|
||||
> {
|
||||
method: 'getTaskExecutions';
|
||||
request: {
|
||||
identity: userInterfaces.IIdentity;
|
||||
filter?: {
|
||||
taskName?: string;
|
||||
status?: string;
|
||||
@@ -49,6 +53,7 @@ export interface IRequest_Any_Cloudly_GetTaskExecutionById
|
||||
> {
|
||||
method: 'getTaskExecutionById';
|
||||
request: {
|
||||
identity: userInterfaces.IIdentity;
|
||||
executionId: string;
|
||||
};
|
||||
response: {
|
||||
@@ -64,6 +69,7 @@ export interface IRequest_Any_Cloudly_TriggerTask
|
||||
> {
|
||||
method: 'triggerTask';
|
||||
request: {
|
||||
identity: userInterfaces.IIdentity;
|
||||
taskName: string;
|
||||
userId?: string;
|
||||
};
|
||||
@@ -80,6 +86,7 @@ export interface IRequest_Any_Cloudly_CancelTask
|
||||
> {
|
||||
method: 'cancelTask';
|
||||
request: {
|
||||
identity: userInterfaces.IIdentity;
|
||||
executionId: string;
|
||||
};
|
||||
response: {
|
||||
|
@@ -53,6 +53,8 @@ export interface IDataState {
|
||||
deployments?: plugins.interfaces.data.IDeployment[];
|
||||
domains?: plugins.interfaces.data.IDomain[];
|
||||
dnsEntries?: plugins.interfaces.data.IDnsEntry[];
|
||||
tasks?: any[];
|
||||
taskExecutions?: plugins.interfaces.data.ITaskExecution[];
|
||||
mails?: any[];
|
||||
logs?: any[];
|
||||
s3?: any[];
|
||||
@@ -71,6 +73,8 @@ export const dataState = await appstate.getStatePart<IDataState>(
|
||||
deployments: [],
|
||||
domains: [],
|
||||
dnsEntries: [],
|
||||
tasks: [],
|
||||
taskExecutions: [],
|
||||
mails: [],
|
||||
logs: [],
|
||||
s3: [],
|
||||
@@ -676,7 +680,10 @@ export const taskActions = {
|
||||
const response = await trGetTasks.fire({
|
||||
identity: loginStatePart.getState().identity,
|
||||
});
|
||||
return response as any;
|
||||
return {
|
||||
...currentState,
|
||||
tasks: response.tasks,
|
||||
};
|
||||
}
|
||||
),
|
||||
|
||||
@@ -692,7 +699,10 @@ export const taskActions = {
|
||||
identity: loginStatePart.getState().identity,
|
||||
filter: payloadArg.filter,
|
||||
});
|
||||
return response as any;
|
||||
return {
|
||||
...currentState,
|
||||
taskExecutions: response.executions,
|
||||
};
|
||||
}
|
||||
),
|
||||
|
||||
@@ -708,7 +718,7 @@ export const taskActions = {
|
||||
identity: loginStatePart.getState().identity,
|
||||
executionId: payloadArg.executionId,
|
||||
});
|
||||
return response as any;
|
||||
return currentState;
|
||||
}
|
||||
),
|
||||
|
||||
|
@@ -44,6 +44,105 @@ export class CloudlyDashboard extends DeesElement {
|
||||
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() {
|
||||
super();
|
||||
document.title = `cloudly v${commitinfo.version}`;
|
||||
@@ -76,103 +175,7 @@ export class CloudlyDashboard extends DeesElement {
|
||||
<div class="maincontainer">
|
||||
<dees-simple-login name="cloudly v${commitinfo.version}">
|
||||
<dees-simple-appdash name="cloudly v${commitinfo.version}"
|
||||
.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[]}
|
||||
.viewTabs=${this.viewTabs}
|
||||
></dees-simple-appdash>
|
||||
</dees-simple-login>
|
||||
</div>
|
||||
|
@@ -18,12 +18,6 @@ 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;
|
||||
|
||||
@@ -41,6 +35,18 @@ export class CloudlyViewTasks extends DeesElement {
|
||||
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 = [
|
||||
@@ -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) {
|
||||
try {
|
||||
@@ -319,13 +290,28 @@ export class CloudlyViewTasks extends DeesElement {
|
||||
);
|
||||
|
||||
// Reload tasks and executions to show the new execution
|
||||
await this.loadTasks();
|
||||
await this.loadExecutions();
|
||||
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();
|
||||
}
|
||||
@@ -359,7 +345,8 @@ export class CloudlyViewTasks extends DeesElement {
|
||||
}
|
||||
|
||||
private renderTaskCard(task: any) {
|
||||
const lastExecution = this.executions
|
||||
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];
|
||||
|
||||
@@ -495,32 +482,32 @@ export class CloudlyViewTasks extends DeesElement {
|
||||
<div class="filter-bar">
|
||||
<button
|
||||
class="filter-button ${this.filterStatus === 'all' ? 'active' : ''}"
|
||||
@click=${() => { this.filterStatus = 'all'; this.loadExecutions(); }}
|
||||
@click=${() => { this.filterStatus = 'all'; this.loadExecutionsWithFilter(); }}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filterStatus === 'running' ? 'active' : ''}"
|
||||
@click=${() => { this.filterStatus = 'running'; this.loadExecutions(); }}
|
||||
@click=${() => { this.filterStatus = 'running'; this.loadExecutionsWithFilter(); }}
|
||||
>
|
||||
Running
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filterStatus === 'completed' ? 'active' : ''}"
|
||||
@click=${() => { this.filterStatus = 'completed'; this.loadExecutions(); }}
|
||||
@click=${() => { this.filterStatus = 'completed'; this.loadExecutionsWithFilter(); }}
|
||||
>
|
||||
Completed
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filterStatus === 'failed' ? 'active' : ''}"
|
||||
@click=${() => { this.filterStatus = 'failed'; this.loadExecutions(); }}
|
||||
@click=${() => { this.filterStatus = 'failed'; this.loadExecutionsWithFilter(); }}
|
||||
>
|
||||
Failed
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="task-grid">
|
||||
${this.tasks.map(task => this.renderTaskCard(task))}
|
||||
${(this.data.tasks || []).map(task => this.renderTaskCard(task))}
|
||||
</div>
|
||||
|
||||
<cloudly-sectionheading>Execution History</cloudly-sectionheading>
|
||||
@@ -528,7 +515,7 @@ export class CloudlyViewTasks extends DeesElement {
|
||||
<dees-table
|
||||
.heading1=${'Task Executions'}
|
||||
.heading2=${'History of task runs and their outcomes'}
|
||||
.data=${this.executions}
|
||||
.data=${this.data.taskExecutions || []}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ITaskExecution) => {
|
||||
return {
|
||||
Task: itemArg.data.taskName,
|
||||
|
Reference in New Issue
Block a user