feat(#94): implement spoke configuration UI

Implements the final piece of M7-Federation - the spoke configuration UI
that allows administrators to configure their local instance's federation
capabilities and settings.

Backend Changes:
- Add UpdateInstanceDto with validation for name, capabilities, and metadata
- Implement FederationService.updateInstanceConfiguration() method
- Add PATCH /api/v1/federation/instance endpoint to FederationController
- Add audit logging for configuration updates
- Add tests for updateInstanceConfiguration (5 new tests, all passing)

Frontend Changes:
- Create SpokeConfigurationForm component with PDA-friendly design
- Create /federation/settings page with configuration management
- Add regenerate keypair functionality with confirmation dialog
- Extend federation API client with updateInstanceConfiguration and regenerateInstanceKeys
- Add comprehensive tests (10 tests, all passing)

Design Decisions:
- Admin-only access via AdminGuard
- Never expose private key in API responses (security)
- PDA-friendly language throughout (no demanding terms)
- Clear visual hierarchy with read-only and editable fields
- Truncated public key with copy button for usability
- Confirmation dialog for destructive key regeneration

All tests passing:
- Backend: 13/13 federation service tests passing
- Frontend: 10/10 SpokeConfigurationForm tests passing
- TypeScript compilation: passing
- Linting: passing
- PDA-friendliness: verified

This completes M7-Federation. All federation features are now implemented.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-03 14:51:59 -06:00
parent 12abdfe81d
commit 0495f979a7
33 changed files with 1660 additions and 8 deletions

View File

@@ -4,7 +4,18 @@
* API endpoints for instance identity and federation management.
*/
import { Controller, Get, Post, UseGuards, Logger, Req, Body, Param, Query } from "@nestjs/common";
import {
Controller,
Get,
Post,
Patch,
UseGuards,
Logger,
Req,
Body,
Param,
Query,
} from "@nestjs/common";
import { FederationService } from "./federation.service";
import { FederationAuditService } from "./audit.service";
import { ConnectionService } from "./connection.service";
@@ -22,6 +33,7 @@ import {
DisconnectConnectionDto,
IncomingConnectionRequestDto,
} from "./dto/connection.dto";
import { UpdateInstanceDto } from "./dto/instance.dto";
import type { SpawnAgentCommandPayload } from "./types/federation-agent.types";
import { FederationConnectionStatus } from "@prisma/client";
@@ -68,6 +80,36 @@ export class FederationController {
return result;
}
/**
* Update instance configuration
* Requires system administrator privileges
* Allows updating name, capabilities, and metadata
* Returns public identity only (private key never exposed in API)
*/
@Patch("instance")
@UseGuards(AuthGuard, AdminGuard)
async updateInstanceConfiguration(
@Req() req: AuthenticatedRequest,
@Body() dto: UpdateInstanceDto
): Promise<PublicInstanceIdentity> {
if (!req.user) {
throw new Error("User not authenticated");
}
this.logger.log(`Admin user ${req.user.id} updating instance configuration`);
const result = await this.federationService.updateInstanceConfiguration(dto);
// Audit log for security compliance
const auditData: Record<string, unknown> = {};
if (dto.name !== undefined) auditData.name = dto.name;
if (dto.capabilities !== undefined) auditData.capabilities = dto.capabilities;
if (dto.metadata !== undefined) auditData.metadata = dto.metadata;
this.auditService.logInstanceConfigurationUpdate(req.user.id, result.instanceId, auditData);
return result;
}
/**
* Initiate a connection to a remote instance
* Requires authentication