Portal: Phoenix API Railing wiring, env example, per-tenant rate limit
- Portal: phoenix-api-client, usePhoenixRailing hooks, /infrastructure page - Portal: PhoenixHealthTile on dashboard, resources page uses tenant me/resources - Sidebar: Infrastructure link; Keycloak token used for API calls (BFF) - api/.env.example: PHOENIX_RAILING_URL, PHOENIX_RAILING_API_KEY - rate-limit: key by tenant when tenantContext present Made-with: Cursor
This commit is contained in:
66
portal/src/app/infrastructure/page.tsx
Normal file
66
portal/src/app/infrastructure/page.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import { usePhoenixInfraNodes, usePhoenixInfraStorage } from '@/hooks/usePhoenixRailing';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Server, HardDrive } from 'lucide-react';
|
||||
|
||||
export default function InfrastructurePage() {
|
||||
const { data: nodesData, isLoading: nodesLoading, error: nodesError } = usePhoenixInfraNodes();
|
||||
const { data: storageData, isLoading: storageLoading, error: storageError } = usePhoenixInfraStorage();
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6">Infrastructure</h1>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
Cluster nodes and storage from Phoenix API Railing (GET /api/v1/infra/nodes, /api/v1/infra/storage).
|
||||
</p>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<Server className="h-5 w-5" />
|
||||
Cluster Nodes
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{nodesLoading && <p className="text-gray-400">Loading...</p>}
|
||||
{nodesError && <p className="text-red-400">Error loading nodes</p>}
|
||||
{nodesData?.nodes && (
|
||||
<ul className="space-y-2">
|
||||
{nodesData.nodes.map((n: any) => (
|
||||
<li key={n.node || n.name} className="flex justify-between text-sm">
|
||||
<span>{n.node ?? n.name ?? n.id}</span>
|
||||
<span className={n.status === 'online' ? 'text-green-400' : 'text-gray-400'}>{n.status ?? '—'}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{nodesData?.stub && <p className="text-xs text-gray-500 mt-2">Stub data (set PROXMOX_* on railing)</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<HardDrive className="h-5 w-5" />
|
||||
Storage
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{storageLoading && <p className="text-gray-400">Loading...</p>}
|
||||
{storageError && <p className="text-red-400">Error loading storage</p>}
|
||||
{storageData?.storage && (
|
||||
<ul className="space-y-2">
|
||||
{storageData.storage.slice(0, 10).map((s: any, i: number) => (
|
||||
<li key={s.storage || i} className="text-sm">{s.storage ?? s.name ?? s.id}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{storageData?.stub && <p className="text-xs text-gray-500 mt-2">Stub data (set PROXMOX_* on railing)</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { useTenantResources } from '@/hooks/usePhoenixRailing'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
|
||||
export default function ResourcesPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const { data: tenantData, isLoading, error } = useTenantResources()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
@@ -14,16 +16,37 @@ export default function ResourcesPage() {
|
||||
return <div>Please sign in</div>
|
||||
}
|
||||
|
||||
const resources = tenantData?.resources ?? []
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6">Resource Inventory</h1>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Unified view of all resources across Proxmox, Kubernetes, and Cloudflare
|
||||
Tenant-scoped resources from Phoenix API (GET /api/v1/tenants/me/resources)
|
||||
</p>
|
||||
{/* Resource inventory UI will be implemented here */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="text-sm text-muted-foreground">Resource inventory table coming soon</p>
|
||||
</div>
|
||||
{isLoading && <p className="text-gray-400">Loading...</p>}
|
||||
{error && <p className="text-red-400">Error loading resources</p>}
|
||||
{tenantData && (
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">Tenant: {tenantData.tenantId}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{resources.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No resources</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{resources.map((r: any) => (
|
||||
<li key={r.id} className="flex justify-between text-sm">
|
||||
<span>{r.name}</span>
|
||||
<span className="text-gray-400">{r.resource_type ?? r.provider}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user