Files
stack/apps/api/src/widgets/widgets.controller.ts
Jason Woltje 4c3604e85c
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
feat(#52): implement Active Projects & Agent Chains widget
Add HUD widget for tracking active projects and running agent sessions.

Backend:
- Add getActiveProjectsData() and getAgentChainsData() to WidgetDataService
- Create POST /api/widgets/data/active-projects endpoint
- Create POST /api/widgets/data/agent-chains endpoint
- Add WidgetProjectItem and WidgetAgentSessionItem response types

Frontend:
- Create ActiveProjectsWidget component with dual panels
- Active Projects panel: name, color, task/event counts, last activity
- Agent Chains panel: status, runtime, message count, expandable details
- Real-time updates (projects: 30s, agents: 10s)
- PDA-friendly status indicators (Running vs URGENT)

Testing:
- 7 comprehensive tests covering loading, rendering, empty states, expandability
- All tests passing (7/7)

Refs #52

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 19:17:13 -06:00

130 lines
3.8 KiB
TypeScript

import {
Controller,
Get,
Post,
Body,
Param,
UseGuards,
Request,
UnauthorizedException,
} from "@nestjs/common";
import { WidgetsService } from "./widgets.service";
import { WidgetDataService } from "./widget-data.service";
import { AuthGuard } from "../auth/guards/auth.guard";
import type { StatCardQueryDto, ChartQueryDto, ListQueryDto, CalendarPreviewQueryDto } from "./dto";
import type { AuthenticatedRequest } from "../common/types/user.types";
/**
* Controller for widget definition and data endpoints
* All endpoints require authentication
*/
@Controller("widgets")
@UseGuards(AuthGuard)
export class WidgetsController {
constructor(
private readonly widgetsService: WidgetsService,
private readonly widgetDataService: WidgetDataService
) {}
/**
* GET /api/widgets
* List all available widget definitions
* Returns only active widgets
*/
@Get()
async findAll() {
return this.widgetsService.findAll();
}
/**
* GET /api/widgets/:name
* Get a widget definition by name
* Useful for fetching widget configuration schemas
*/
@Get(":name")
async findByName(@Param("name") name: string) {
return this.widgetsService.findByName(name);
}
/**
* POST /api/widgets/data/stat-card
* Get stat card widget data
*/
@Post("data/stat-card")
async getStatCardData(@Request() req: AuthenticatedRequest, @Body() query: StatCardQueryDto) {
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Workspace ID required");
}
return this.widgetDataService.getStatCardData(workspaceId, query);
}
/**
* POST /api/widgets/data/chart
* Get chart widget data
*/
@Post("data/chart")
async getChartData(@Request() req: AuthenticatedRequest, @Body() query: ChartQueryDto) {
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Workspace ID required");
}
return this.widgetDataService.getChartData(workspaceId, query);
}
/**
* POST /api/widgets/data/list
* Get list widget data
*/
@Post("data/list")
async getListData(@Request() req: AuthenticatedRequest, @Body() query: ListQueryDto) {
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Workspace ID required");
}
return this.widgetDataService.getListData(workspaceId, query);
}
/**
* POST /api/widgets/data/calendar-preview
* Get calendar preview widget data
*/
@Post("data/calendar-preview")
async getCalendarPreviewData(
@Request() req: AuthenticatedRequest,
@Body() query: CalendarPreviewQueryDto
) {
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Workspace ID required");
}
return this.widgetDataService.getCalendarPreviewData(workspaceId, query);
}
/**
* POST /api/widgets/data/active-projects
* Get active projects widget data
*/
@Post("data/active-projects")
async getActiveProjectsData(@Request() req: AuthenticatedRequest) {
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Workspace ID required");
}
return this.widgetDataService.getActiveProjectsData(workspaceId);
}
/**
* POST /api/widgets/data/agent-chains
* Get agent chains widget data (active agent sessions)
*/
@Post("data/agent-chains")
async getAgentChainsData(@Request() req: AuthenticatedRequest) {
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Workspace ID required");
}
return this.widgetDataService.getAgentChainsData(workspaceId);
}
}