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:
defiQUG
2026-03-25 20:46:57 -07:00
parent e123f407d3
commit 85fe29adc1
51 changed files with 548 additions and 109 deletions

View File

@@ -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">

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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('');

View File

@@ -1,6 +1,5 @@
'use client'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { Providers } from './providers'

View File

@@ -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>

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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') {

View File

@@ -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);

View File

@@ -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();

View File

@@ -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>

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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>

View File

@@ -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}>

View File

@@ -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),
}))
},
})

View File

@@ -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',

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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 (

View File

@@ -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 = [

View File

@@ -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>;
}

View 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>
}

View 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
)}
/>
)
}

View 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}
/>
)
}

View File

@@ -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

View File

@@ -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',

View File

@@ -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 =

View File

@@ -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) {

View 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
}
}
`

View File

@@ -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[];
}
}