feat(#91): implement Connection Manager UI for federation
Implemented comprehensive UI for managing federation connections: Features: - View existing federation connections grouped by status - Initiate new connections to remote instances - Accept/reject pending connection requests - Disconnect active connections - Display connection status, metadata, and capabilities - PDA-friendly design throughout (no demanding language) Components: - ConnectionCard: Display individual connections with actions - ConnectionList: Grouped list view with status sections - InitiateConnectionDialog: Modal for connecting to new instances - Connections page: Main management interface Implementation: - Full test coverage (42 tests, 100% passing) - TypeScript strict mode compliance - ESLint passing with no warnings - Mock data for development (ready for backend integration) - Proper error handling and loading states - PDA-friendly language (calm, supportive, stress-free) Status indicators: - 🟢 Active (soft green) - 🔵 Pending (soft blue) - ⏸️ Disconnected (soft yellow) - ⚪ Rejected (light gray) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
152
apps/web/src/components/federation/ConnectionCard.tsx
Normal file
152
apps/web/src/components/federation/ConnectionCard.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* ConnectionCard Component
|
||||
* Displays a single federation connection with PDA-friendly design
|
||||
*/
|
||||
|
||||
import { FederationConnectionStatus, type ConnectionDetails } from "@/lib/api/federation";
|
||||
|
||||
interface ConnectionCardProps {
|
||||
connection: ConnectionDetails;
|
||||
onAccept?: (connectionId: string) => void;
|
||||
onReject?: (connectionId: string) => void;
|
||||
onDisconnect?: (connectionId: string) => void;
|
||||
showDetails?: boolean;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PDA-friendly status text and color
|
||||
*/
|
||||
function getStatusDisplay(status: FederationConnectionStatus): {
|
||||
text: string;
|
||||
colorClass: string;
|
||||
icon: string;
|
||||
} {
|
||||
switch (status) {
|
||||
case FederationConnectionStatus.ACTIVE:
|
||||
return {
|
||||
text: "Active",
|
||||
colorClass: "text-green-600 bg-green-50",
|
||||
icon: "🟢",
|
||||
};
|
||||
case FederationConnectionStatus.PENDING:
|
||||
return {
|
||||
text: "Pending",
|
||||
colorClass: "text-blue-600 bg-blue-50",
|
||||
icon: "🔵",
|
||||
};
|
||||
case FederationConnectionStatus.DISCONNECTED:
|
||||
return {
|
||||
text: "Disconnected",
|
||||
colorClass: "text-yellow-600 bg-yellow-50",
|
||||
icon: "⏸️",
|
||||
};
|
||||
case FederationConnectionStatus.REJECTED:
|
||||
return {
|
||||
text: "Rejected",
|
||||
colorClass: "text-gray-600 bg-gray-50",
|
||||
icon: "⚪",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function ConnectionCard({
|
||||
connection,
|
||||
onAccept,
|
||||
onReject,
|
||||
onDisconnect,
|
||||
showDetails = false,
|
||||
compact = false,
|
||||
}: ConnectionCardProps): React.JSX.Element {
|
||||
const status = getStatusDisplay(connection.status);
|
||||
const name =
|
||||
typeof connection.metadata.name === "string" ? connection.metadata.name : "Remote Instance";
|
||||
const description = connection.metadata.description as string | undefined;
|
||||
|
||||
const paddingClass = compact ? "p-3" : "p-4";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-200 rounded-lg ${paddingClass} hover:border-gray-300 transition-colors`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-medium text-gray-900">{name}</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">{connection.remoteUrl}</p>
|
||||
{description && <p className="text-sm text-gray-500 mt-1">{description}</p>}
|
||||
</div>
|
||||
|
||||
{/* Status Badge */}
|
||||
<div
|
||||
className={`flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium ${status.colorClass}`}
|
||||
>
|
||||
<span>{status.icon}</span>
|
||||
<span>{status.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Capabilities (when showDetails is true) */}
|
||||
{showDetails && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-100">
|
||||
<p className="text-xs font-medium text-gray-700 mb-2">Capabilities</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{connection.remoteCapabilities.supportsQuery && (
|
||||
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">Query</span>
|
||||
)}
|
||||
{connection.remoteCapabilities.supportsCommand && (
|
||||
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">Command</span>
|
||||
)}
|
||||
{connection.remoteCapabilities.supportsEvent && (
|
||||
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">Events</span>
|
||||
)}
|
||||
{connection.remoteCapabilities.supportsAgentSpawn && (
|
||||
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">
|
||||
Agent Spawn
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
{connection.status === FederationConnectionStatus.PENDING && (onAccept ?? onReject) && (
|
||||
<div className="mt-4 flex gap-2">
|
||||
{onAccept && (
|
||||
<button
|
||||
onClick={() => {
|
||||
onAccept(connection.id);
|
||||
}}
|
||||
className="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-md hover:bg-green-700 transition-colors"
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
)}
|
||||
{onReject && (
|
||||
<button
|
||||
onClick={() => {
|
||||
onReject(connection.id);
|
||||
}}
|
||||
className="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-300 transition-colors"
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{connection.status === FederationConnectionStatus.ACTIVE && onDisconnect && (
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
onDisconnect(connection.id);
|
||||
}}
|
||||
className="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-300 transition-colors"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user