portal: Apollo dashboard queries, strict TypeScript build, UI primitives
- Add GraphQL dashboard operations, ApolloProvider, CardDescription, label/checkbox/alert - Fix case-sensitive UI imports, Crossplane VM metadata uid, VMList spec parsing - Extend next-auth session user (id, role); fairness filters as unknown; ESLint relax to warnings - Remove unused session destructure across pages; next.config without skip TS/ESLint api: GraphQL/WebSocket hardening, logger import in websocket service Made-with: Cursor
This commit is contained in:
@@ -73,6 +73,9 @@ export default function AdminPortalPage() {
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Customer / Tenant Admin Portal</h1>
|
||||
<p className="text-gray-400">Manage your organization, users, billing, and compliance</p>
|
||||
{session?.user?.email && (
|
||||
<p className="text-sm text-gray-500 mt-2">Signed in as {session.user.email}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { signIn } from 'next-auth/react';
|
||||
import { AdvancedAnalytics } from '@/components/analytics/AdvancedAnalytics';
|
||||
|
||||
export default function AnalyticsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ComplianceStatusTile } from '@/components/dashboard/ComplianceStatusTil
|
||||
import { QuickActionsPanel } from '@/components/dashboard/QuickActionsPanel';
|
||||
|
||||
export default function BusinessDashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@ import { APIKeysTile } from '@/components/dashboard/APIKeysTile';
|
||||
import { QuickActionsPanel } from '@/components/dashboard/QuickActionsPanel';
|
||||
|
||||
export default function DeveloperDashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,7 @@ import { OptimizationEngine } from '@/components/ai/OptimizationEngine';
|
||||
import { QuickActionsPanel } from '@/components/dashboard/QuickActionsPanel';
|
||||
|
||||
export default function TechnicalDashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { signIn } from 'next-auth/react';
|
||||
// import Dashboard from '@/components/dashboards/Dashboard';
|
||||
|
||||
export default function DashboardsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Key, Book, TestTube, BarChart3, Webhook, Download, ArrowRight } from 'l
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function DeveloperPortalPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Send, CheckCircle, XCircle, Clock } from 'lucide-react';
|
||||
import { Send, CheckCircle, XCircle } from 'lucide-react';
|
||||
|
||||
export default function WebhookTestingPage() {
|
||||
const [url, setUrl] = useState('');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import { Providers } from './providers'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function MLPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const { status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { signIn } from 'next-auth/react';
|
||||
// import { NetworkTopologyView } from '@/components/network/NetworkTopologyView';
|
||||
|
||||
export default function NetworkPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import { signIn } from 'next-auth/react';
|
||||
import Dashboard from '@/components/Dashboard';
|
||||
|
||||
export default function Home() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Handshake, TrendingUp, BookOpen, Package, ArrowRight } from 'lucide-rea
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function PartnerPortalPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function PoliciesPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const { status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache } from '@apollo/client';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
function createApolloClient() {
|
||||
const uri =
|
||||
process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || 'http://localhost:4000/graphql';
|
||||
return new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
link: new HttpLink({ uri, credentials: 'include' }),
|
||||
defaultOptions: {
|
||||
watchQuery: { fetchPolicy: 'cache-and-network' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
@@ -16,10 +29,13 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||
},
|
||||
})
|
||||
);
|
||||
const [apolloClient] = useState(createApolloClient);
|
||||
|
||||
return (
|
||||
<SessionProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
</ApolloProvider>
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function ResourceGraphPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const { status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTenantResources } from '@/hooks/usePhoenixRailing'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
|
||||
export default function ResourcesPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const { status } = useSession()
|
||||
const { data: tenantData, isLoading, error } = useTenantResources()
|
||||
|
||||
if (status === 'loading') {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Shield, CheckCircle } from 'lucide-react';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
export default function TwoFactorAuthPage() {
|
||||
const { data: session } = useSession();
|
||||
const [isEnabled, setIsEnabled] = useState(false);
|
||||
const [qrCode, setQrCode] = useState<string | null>(null);
|
||||
const [secret, setSecret] = useState<string | null>(null);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Settings, User, Bell, Shield, Key } from 'lucide-react';
|
||||
import { User, Bell, Shield, Key } from 'lucide-react';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function VMScaleSetsPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const { status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { signIn } from 'next-auth/react';
|
||||
import VMList from '@/components/vms/VMList';
|
||||
|
||||
export default function VMsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { signIn } from 'next-auth/react';
|
||||
// import WAFDashboard from '@/components/well-architected/WAFDashboard';
|
||||
|
||||
export default function WellArchitectedPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function Dashboard() {
|
||||
const { data: session } = useSession();
|
||||
const crossplane = createCrossplaneClient(session?.accessToken as string);
|
||||
|
||||
const { data: vms = [] } = useQuery({
|
||||
const { data: vms = [], isLoading: vmsLoading } = useQuery({
|
||||
queryKey: ['vms'],
|
||||
queryFn: () => crossplane.getVMs(),
|
||||
});
|
||||
@@ -84,7 +84,7 @@ export default function Dashboard() {
|
||||
<Server className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{isLoading ? '...' : totalVMs}</div>
|
||||
<div className="text-2xl font-bold">{vmsLoading ? '...' : totalVMs}</div>
|
||||
<p className="text-xs text-muted-foreground">Across all sites</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -95,7 +95,7 @@ export default function Dashboard() {
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{isLoading ? '...' : runningVMs}</div>
|
||||
<div className="text-2xl font-bold">{vmsLoading ? '...' : runningVMs}</div>
|
||||
<p className="text-xs text-muted-foreground">Active virtual machines</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -106,7 +106,7 @@ export default function Dashboard() {
|
||||
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{isLoading ? '...' : stoppedVMs}</div>
|
||||
<div className="text-2xl font-bold">{vmsLoading ? '...' : stoppedVMs}</div>
|
||||
<p className="text-xs text-muted-foreground">Inactive virtual machines</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useState, type ChangeEvent } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
@@ -96,7 +96,7 @@ export function ResourceExplorer() {
|
||||
<Input
|
||||
placeholder="Search resources..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setSearch(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Select value={filterProvider} onValueChange={setFilterProvider}>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import axios from 'axios'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Server, Play, Pause, Trash2 } from 'lucide-react'
|
||||
|
||||
interface VM {
|
||||
@@ -15,34 +14,28 @@ interface VM {
|
||||
disk: number
|
||||
}
|
||||
|
||||
function specToNumber(v: string | number | undefined): number {
|
||||
if (v == null) return 0
|
||||
if (typeof v === 'number') return v
|
||||
const m = /^(\d+(?:\.\d+)?)/.exec(String(v).trim())
|
||||
return m ? parseFloat(m[1]) : 0
|
||||
}
|
||||
|
||||
export function VMList() {
|
||||
const { data: vms, isLoading } = useQuery<VM[]>({
|
||||
queryKey: ['vms'],
|
||||
queryFn: async () => {
|
||||
// Use Crossplane client to get VMs
|
||||
const { createCrossplaneClient } = await import('@/lib/crossplane-client')
|
||||
const { useSession } = await import('next-auth/react')
|
||||
|
||||
// Get session token if available
|
||||
const session = typeof window !== 'undefined'
|
||||
? await import('next-auth/react').then(m => {
|
||||
// This is a workaround - in a real component we'd use the hook
|
||||
// For now, we'll use the client without auth or get token from storage
|
||||
return null
|
||||
})
|
||||
: null
|
||||
|
||||
const client = createCrossplaneClient()
|
||||
const vms = await client.getVMs()
|
||||
|
||||
// Transform Crossplane VM format to component format
|
||||
return vms.map((vm: any) => ({
|
||||
id: vm.metadata?.name || vm.metadata?.uid || '',
|
||||
|
||||
return vms.map((vm) => ({
|
||||
id: vm.metadata?.name || 'unknown',
|
||||
name: vm.metadata?.name || 'Unknown',
|
||||
status: vm.status?.state || 'unknown',
|
||||
cpu: vm.spec?.forProvider?.cpu || 0,
|
||||
memory: vm.spec?.forProvider?.memory || 0,
|
||||
disk: vm.spec?.forProvider?.disk || 0,
|
||||
memory: specToNumber(vm.spec?.forProvider?.memory),
|
||||
disk: specToNumber(vm.spec?.forProvider?.disk),
|
||||
}))
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Sparkles, TrendingDown, TrendingUp, AlertCircle } from 'lucide-react';
|
||||
import { Sparkles, TrendingDown, TrendingUp } from 'lucide-react';
|
||||
|
||||
export function OptimizationEngine() {
|
||||
const [recommendations, setRecommendations] = useState([
|
||||
const [recommendations] = useState([
|
||||
{
|
||||
id: '1',
|
||||
type: 'cost',
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/Card'
|
||||
import { Input } from '../ui/Input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
||||
import { Badge } from '../ui/badge'
|
||||
import { Button } from '../ui/Button'
|
||||
import { Server, Database, Network, HardDrive } from 'lucide-react'
|
||||
|
||||
interface CrossplaneResource {
|
||||
@@ -17,7 +16,7 @@ interface CrossplaneResource {
|
||||
metadata: {
|
||||
name: string
|
||||
namespace: string
|
||||
uid: string
|
||||
uid?: string
|
||||
creationTimestamp: string
|
||||
labels?: Record<string, string>
|
||||
}
|
||||
@@ -46,7 +45,12 @@ export default function CrossplaneResourceBrowser() {
|
||||
return vms.map((vm) => ({
|
||||
apiVersion: process.env.NEXT_PUBLIC_CROSSPLANE_API_GROUP || 'proxmox.sankofa.nexus/v1alpha1',
|
||||
kind: 'ProxmoxVM',
|
||||
metadata: vm.metadata,
|
||||
metadata: {
|
||||
...vm.metadata,
|
||||
uid:
|
||||
(vm.metadata as { uid?: string }).uid ??
|
||||
`${vm.metadata.namespace}/${vm.metadata.name}`,
|
||||
},
|
||||
spec: vm.spec,
|
||||
status: vm.status,
|
||||
}))
|
||||
@@ -74,10 +78,10 @@ export default function CrossplaneResourceBrowser() {
|
||||
return <Server className="h-5 w-5 text-gray-500" />
|
||||
}
|
||||
|
||||
const getResourceStatusColor = (status: any) => {
|
||||
const getResourceStatusColor = (status: Record<string, unknown> | undefined) => {
|
||||
if (!status) return 'bg-gray-500'
|
||||
|
||||
const state = status.state || status.phase || 'Unknown'
|
||||
const state = String(status.state ?? status.phase ?? 'Unknown')
|
||||
switch (state.toLowerCase()) {
|
||||
case 'running':
|
||||
case 'ready':
|
||||
@@ -94,7 +98,7 @@ export default function CrossplaneResourceBrowser() {
|
||||
}
|
||||
}
|
||||
|
||||
const filteredResources = resources.filter((resource) => {
|
||||
const filteredResources = resources.filter((resource: CrossplaneResource) => {
|
||||
const matchesSearch = resource.metadata.name.toLowerCase().includes(search.toLowerCase())
|
||||
const matchesKind = filterKind === 'all' || resource.kind === filterKind
|
||||
const matchesNamespace =
|
||||
@@ -124,7 +128,7 @@ export default function CrossplaneResourceBrowser() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Types</SelectItem>
|
||||
{uniqueKinds.map((kind) => (
|
||||
{uniqueKinds.map((kind: string) => (
|
||||
<SelectItem key={kind} value={kind}>
|
||||
{kind}
|
||||
</SelectItem>
|
||||
@@ -137,7 +141,7 @@ export default function CrossplaneResourceBrowser() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Namespaces</SelectItem>
|
||||
{uniqueNamespaces.map((ns) => (
|
||||
{uniqueNamespaces.map((ns: string) => (
|
||||
<SelectItem key={ns} value={ns}>
|
||||
{ns}
|
||||
</SelectItem>
|
||||
@@ -158,8 +162,8 @@ export default function CrossplaneResourceBrowser() {
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredResources.map((resource) => (
|
||||
<Card key={resource.metadata.uid}>
|
||||
{filteredResources.map((resource: CrossplaneResource) => (
|
||||
<Card key={resource.metadata.uid ?? `${resource.metadata.namespace}/${resource.metadata.name}`}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -204,7 +208,7 @@ export default function CrossplaneResourceBrowser() {
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{Object.entries(resource.metadata.labels).map(([key, value]) => (
|
||||
<Badge key={key} variant="outline" className="text-xs">
|
||||
{key}={value}
|
||||
{key}={String(value)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { GripVertical, X, Plus } from 'lucide-react';
|
||||
import { GripVertical, Plus } from 'lucide-react';
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
||||
|
||||
interface DashboardTile {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import Link from 'next/link';
|
||||
import { Plus, Link as LinkIcon, FileText, Settings, Key, Rocket, Book, CreditCard, HelpCircle, Download } from 'lucide-react';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
|
||||
interface QuickAction {
|
||||
label: string;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -11,13 +11,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { InfoIcon, AlertTriangleIcon, CheckCircleIcon } from 'lucide-react';
|
||||
|
||||
// Import orchestration engine (would be from API in production)
|
||||
import type {
|
||||
OrchestrationRequest,
|
||||
OrchestrationResult,
|
||||
OutputType,
|
||||
InputSpec,
|
||||
TimelineSpec
|
||||
} from '@/lib/fairness-orchestration';
|
||||
import type { OrchestrationRequest, InputSpec, TimelineSpec } from '@/lib/fairness-orchestration';
|
||||
import { orchestrate, getAvailableOutputs, getUserMessage } from '@/lib/fairness-orchestration';
|
||||
|
||||
export default function FairnessOrchestrationWizard() {
|
||||
@@ -32,8 +26,6 @@ export default function FairnessOrchestrationWizard() {
|
||||
mode: 'now',
|
||||
sla: '2 hours'
|
||||
});
|
||||
const [orchestrationResult, setOrchestrationResult] = useState<OrchestrationResult | null>(null);
|
||||
|
||||
const availableOutputs = getAvailableOutputs();
|
||||
|
||||
// Calculate orchestration when inputs change
|
||||
@@ -60,9 +52,8 @@ export default function FairnessOrchestrationWizard() {
|
||||
};
|
||||
|
||||
const handleRun = () => {
|
||||
if (result) {
|
||||
setOrchestrationResult(result);
|
||||
}
|
||||
if (!result?.feasible) return;
|
||||
// In production: POST orchestration job to API using `result` + inputSpec
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
import { useState } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Server,
|
||||
Network,
|
||||
Settings,
|
||||
FileText,
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Server,
|
||||
Network,
|
||||
Settings,
|
||||
Activity,
|
||||
Users,
|
||||
CreditCard,
|
||||
Shield,
|
||||
Menu,
|
||||
X
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
|
||||
const navigation = [
|
||||
|
||||
@@ -33,3 +33,7 @@ export function CardContent({ children, className = '' }: CardProps) {
|
||||
return <div className={`p-6 pt-0 ${className}`}>{children}</div>;
|
||||
}
|
||||
|
||||
export function CardDescription({ children, className = '' }: CardProps) {
|
||||
return <p className={`text-sm text-muted-foreground ${className}`}>{children}</p>;
|
||||
}
|
||||
|
||||
|
||||
34
portal/src/components/ui/alert.tsx
Normal file
34
portal/src/components/ui/alert.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface AlertProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
variant?: 'default' | 'destructive'
|
||||
}
|
||||
|
||||
export function Alert({ children, className, variant = 'default' }: AlertProps) {
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
className={cn(
|
||||
'relative w-full rounded-lg border p-4 flex gap-3',
|
||||
variant === 'destructive'
|
||||
? 'border-red-500/50 bg-red-950/30 text-red-200'
|
||||
: 'border-gray-700 bg-gray-800/50 text-gray-200',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface AlertDescriptionProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AlertDescription({ children, className }: AlertDescriptionProps) {
|
||||
return <div className={cn('text-sm flex-1', className)}>{children}</div>
|
||||
}
|
||||
28
portal/src/components/ui/checkbox.tsx
Normal file
28
portal/src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface CheckboxProps {
|
||||
id?: string
|
||||
checked?: boolean
|
||||
onCheckedChange?: () => void
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function Checkbox({ id, checked, onCheckedChange, className, disabled }: CheckboxProps) {
|
||||
return (
|
||||
<input
|
||||
id={id}
|
||||
type="checkbox"
|
||||
checked={!!checked}
|
||||
disabled={disabled}
|
||||
onChange={() => onCheckedChange?.()}
|
||||
className={cn(
|
||||
'h-4 w-4 rounded border border-gray-600 bg-gray-900 text-blue-600 focus:ring-2 focus:ring-blue-500',
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
13
portal/src/components/ui/label.tsx
Normal file
13
portal/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
|
||||
|
||||
export function Label({ className, ...props }: LabelProps) {
|
||||
return (
|
||||
<label
|
||||
className={cn('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { Check, ChevronDown } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
@@ -13,8 +13,6 @@ interface KeyboardShortcut {
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
// Don't trigger shortcuts when typing in inputs
|
||||
@@ -70,8 +68,8 @@ export function useGlobalKeyboardShortcuts() {
|
||||
action: () => {
|
||||
// Show keyboard shortcuts help
|
||||
const helpModal = document.getElementById('keyboard-shortcuts-help');
|
||||
if (helpModal) {
|
||||
(helpModal as any).showModal?.();
|
||||
if (helpModal && 'showModal' in helpModal && typeof (helpModal as HTMLDialogElement).showModal === 'function') {
|
||||
(helpModal as HTMLDialogElement).showModal();
|
||||
}
|
||||
},
|
||||
description: 'Show keyboard shortcuts',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextAuthOptions } from 'next-auth';
|
||||
import KeycloakProvider from 'next-auth/providers/keycloak';
|
||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||
import KeycloakProvider from 'next-auth/providers/keycloak';
|
||||
|
||||
// Check if Keycloak is configured
|
||||
const isKeycloakConfigured =
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface OutputType {
|
||||
export interface InputSpec {
|
||||
dataset: string;
|
||||
dateRange?: { start: string; end: string };
|
||||
filters?: Record<string, any>;
|
||||
filters?: Record<string, unknown>;
|
||||
sensitiveAttributes: string[];
|
||||
estimatedSize?: number;
|
||||
}
|
||||
@@ -95,7 +95,6 @@ export const OUTPUT_TYPES: Record<string, OutputType> = {
|
||||
const INPUT_PASS_MULTIPLIER = 2.0;
|
||||
const TOTAL_LOAD_MULTIPLIER = 3.2;
|
||||
const OUTPUT_TARGET_MULTIPLIER = 1.2;
|
||||
const BASE_PROCESSING_RATE = 10;
|
||||
const INPUT_PROCESSING_RATE = 15;
|
||||
const OUTPUT_PROCESSING_RATE = 8;
|
||||
|
||||
@@ -240,7 +239,7 @@ export function orchestrate(request: OrchestrationRequest): OrchestrationResult
|
||||
};
|
||||
}
|
||||
|
||||
export function getUserMessage(result: OrchestrationResult, request: OrchestrationRequest): string {
|
||||
export function getUserMessage(result: OrchestrationResult, _request: OrchestrationRequest): string {
|
||||
const { inputLoad, outputLoad, estimatedTime, feasible, warnings } = result;
|
||||
|
||||
if (feasible && warnings.length === 0) {
|
||||
|
||||
116
portal/src/lib/graphql/queries/dashboard.ts
Normal file
116
portal/src/lib/graphql/queries/dashboard.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
export const GET_SYSTEM_HEALTH = gql`
|
||||
query SystemHealth {
|
||||
sites {
|
||||
id
|
||||
name
|
||||
status
|
||||
}
|
||||
resources {
|
||||
id
|
||||
name
|
||||
status
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_COST_OVERVIEW = gql`
|
||||
query CostOverview($tenantId: ID!, $timeRange: TimeRangeInput!, $granularity: Granularity!) {
|
||||
usage(tenantId: $tenantId, timeRange: $timeRange, granularity: $granularity) {
|
||||
tenantId
|
||||
totalCost
|
||||
currency
|
||||
breakdown {
|
||||
total
|
||||
byResource {
|
||||
resourceId
|
||||
resourceName
|
||||
cost
|
||||
percentage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_BILLING_INFO = gql`
|
||||
query BillingInfo($tenantId: ID!, $filter: InvoiceFilter!) {
|
||||
invoices(tenantId: $tenantId, filter: $filter) {
|
||||
id
|
||||
invoiceNumber
|
||||
billingPeriodStart
|
||||
billingPeriodEnd
|
||||
subtotal
|
||||
tax
|
||||
total
|
||||
currency
|
||||
status
|
||||
dueDate
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_API_USAGE = gql`
|
||||
query DashboardAPIUsage($timeRange: TimeRangeInput!) {
|
||||
analyticsAPIUsage(timeRange: $timeRange) {
|
||||
totalRequests
|
||||
errorRate
|
||||
byEndpoint {
|
||||
endpoint
|
||||
requests
|
||||
errors
|
||||
}
|
||||
byPeriod {
|
||||
period
|
||||
requests
|
||||
errors
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_DEPLOYMENTS = gql`
|
||||
query DashboardDeployments($filter: DeploymentFilter) {
|
||||
deployments(filter: $filter) {
|
||||
id
|
||||
name
|
||||
tenantId
|
||||
region
|
||||
status
|
||||
deploymentType
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_TEST_ENVIRONMENTS = gql`
|
||||
query DashboardTestEnvironments {
|
||||
testEnvironments {
|
||||
id
|
||||
name
|
||||
userId
|
||||
tenantId
|
||||
region
|
||||
status
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_API_KEYS = gql`
|
||||
query DashboardApiKeys {
|
||||
apiKeys {
|
||||
id
|
||||
name
|
||||
description
|
||||
keyPrefix
|
||||
createdAt
|
||||
expiresAt
|
||||
lastUsedAt
|
||||
revoked
|
||||
}
|
||||
}
|
||||
`
|
||||
7
portal/src/types/next-auth.d.ts
vendored
7
portal/src/types/next-auth.d.ts
vendored
@@ -1,12 +1,19 @@
|
||||
import type { DefaultSession } from 'next-auth';
|
||||
import 'next-auth';
|
||||
|
||||
declare module 'next-auth' {
|
||||
interface Session {
|
||||
accessToken?: string;
|
||||
roles?: string[];
|
||||
user?: DefaultSession['user'] & {
|
||||
id?: string;
|
||||
role?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
id?: string;
|
||||
role?: string;
|
||||
roles?: string[];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user