CUB-20: Develop agent card component with dynamic status/progress
Dev Build / build-test (pull_request) Successful in 2m4s
Dev Build / build-test (pull_request) Successful in 2m4s
This commit is contained in:
@@ -1,15 +1,154 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Signal,
|
||||
computed,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { AgentCardComponent } from '../../command-hub/components/agent-card/agent-card.component';
|
||||
import { AgentCardData } from '../../models/agent.model';
|
||||
import { AgentStatusService } from '../../services/agent-status.service';
|
||||
|
||||
// ============================================================================
|
||||
// Hub Page — Fleet Status Grid
|
||||
// Per spec Section 7.3: Renders AgentCard components in a responsive grid.
|
||||
// Handles loading, empty, error, and success states.
|
||||
// Uses Angular signals from AgentStatusService for reactive updates.
|
||||
// ============================================================================
|
||||
|
||||
@Component({
|
||||
selector: 'app-hub-page',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
imports: [CommonModule, MatIconModule, AgentCardComponent],
|
||||
template: `
|
||||
<div class="hub-page">
|
||||
<p class="hub-page__placeholder">Command Hub — Fleet status grid will render here</p>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div class="hub-page__loading" *ngIf="loading()">
|
||||
<mat-icon class="hub-page__loading-icon" aria-hidden="true">hourglass_empty</mat-icon>
|
||||
<p class="hub-page__loading-text">Loading agents…</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div class="hub-page__empty" *ngIf="!loading() && agents().length === 0">
|
||||
<mat-icon class="hub-page__empty-icon" aria-hidden="true">smart_toy</mat-icon>
|
||||
<p class="hub-page__empty-text">No agents connected yet.</p>
|
||||
<p class="hub-page__empty-hint">Agents will appear here once they come online.</p>
|
||||
</div>
|
||||
|
||||
<!-- Agent card grid -->
|
||||
<app-agent-card
|
||||
*ngFor="let agent of agents(); trackBy: trackByAgentId"
|
||||
[status]="agent.status"
|
||||
[task]="agent.currentTask ?? ''"
|
||||
[progress]="agent.taskProgress ?? 0"
|
||||
[sessionKey]="agent.sessionKey"
|
||||
[channel]="agent.channel"
|
||||
[lastActivity]="agent.lastActivity"
|
||||
[agentId]="agent.id"
|
||||
[displayName]="agent.displayName"
|
||||
[role]="agent.role"
|
||||
[errorMessage]="agent.errorMessage ?? ''"
|
||||
[taskElapsed]="agent.taskElapsed ?? ''"
|
||||
/>
|
||||
|
||||
<!-- Error state -->
|
||||
<div class="hub-page__error" *ngIf="error()" [attr.aria-label]="'Error: ' + error()">
|
||||
<mat-icon class="hub-page__error-icon" aria-hidden="true">error_outline</mat-icon>
|
||||
<p class="hub-page__error-text">{{ error() }}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './hub-page.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class HubPageComponent {}
|
||||
export class HubPageComponent implements OnInit {
|
||||
/** Agent list signal from the status service */
|
||||
readonly agents: Signal<AgentCardData[]>;
|
||||
|
||||
/** Whether the initial load is in progress */
|
||||
readonly loading = computed(() => this._initialLoad);
|
||||
|
||||
/** Error message signal */
|
||||
readonly error = computed(() => this._errorMsg);
|
||||
|
||||
private _initialLoad = true;
|
||||
private _errorMsg: string | null = null;
|
||||
|
||||
private readonly _agentService = inject(AgentStatusService);
|
||||
|
||||
constructor() {
|
||||
this.agents = this._agentService.agents;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Seed mock data for development until SignalR is wired up
|
||||
this._seedMockData();
|
||||
}
|
||||
|
||||
/** TrackBy function for *ngFor performance */
|
||||
trackByAgentId(index: number, agent: AgentCardData): string {
|
||||
return agent.id;
|
||||
}
|
||||
|
||||
// --- Mock data seeding (remove when SignalR is integrated) ---
|
||||
private _seedMockData(): void {
|
||||
const now = new Date();
|
||||
const mockAgents: AgentCardData[] = [
|
||||
{
|
||||
id: 'otto',
|
||||
displayName: 'Otto',
|
||||
role: 'Orchestrator Agent',
|
||||
status: 'active',
|
||||
currentTask: 'Reviewing PR #42',
|
||||
taskProgress: 72,
|
||||
taskElapsed: '04m 12s',
|
||||
sessionKey: 'agent:otto:slack:control-center:8787a',
|
||||
channel: 'slack',
|
||||
lastActivity: new Date(now.getTime() - 30_000),
|
||||
},
|
||||
{
|
||||
id: 'rex',
|
||||
displayName: 'Rex',
|
||||
role: 'Frontend Agent',
|
||||
status: 'thinking',
|
||||
currentTask: 'Building agent card component',
|
||||
taskProgress: 45,
|
||||
taskElapsed: '02m 38s',
|
||||
sessionKey: 'agent:rex:telegram:direct:b3c91',
|
||||
channel: 'telegram',
|
||||
lastActivity: new Date(now.getTime() - 5_000),
|
||||
},
|
||||
{
|
||||
id: 'dex',
|
||||
displayName: 'Dex',
|
||||
role: 'Backend Agent',
|
||||
status: 'idle',
|
||||
currentTask: '',
|
||||
taskProgress: 0,
|
||||
sessionKey: 'agent:dex:slack:control-center:4f2a0',
|
||||
channel: 'slack',
|
||||
lastActivity: new Date(now.getTime() - 300_000),
|
||||
},
|
||||
{
|
||||
id: 'hex',
|
||||
displayName: 'Hex',
|
||||
role: 'Database Agent',
|
||||
status: 'error',
|
||||
currentTask: 'Migration failed',
|
||||
taskProgress: 0,
|
||||
errorMessage: 'Connection timeout to PostgreSQL',
|
||||
sessionKey: 'agent:hex:telegram:direct:9d1e7',
|
||||
channel: 'telegram',
|
||||
lastActivity: new Date(now.getTime() - 1_200_000),
|
||||
},
|
||||
];
|
||||
this._agentService['_agents'].set(mockAgents);
|
||||
this._initialLoad = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user