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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user