Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
This commit is contained in:
113
portal/src/app/admin/page.tsx
Normal file
113
portal/src/app/admin/page.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Building2, Users, CreditCard, Shield, ArrowRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function AdminPortalPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Admin Portal</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const adminSections = [
|
||||
{
|
||||
title: 'Organization Management',
|
||||
description: 'Manage multi-tenant organizations, tenant isolation, and resource quotas',
|
||||
icon: Building2,
|
||||
href: '/admin/organizations',
|
||||
features: ['Multi-tenant view', 'Tenant isolation', 'Resource quotas'],
|
||||
},
|
||||
{
|
||||
title: 'User Management',
|
||||
description: 'Manage users, roles, and permissions across your organization',
|
||||
icon: Users,
|
||||
href: '/admin/users',
|
||||
features: ['User list', 'Role assignment', 'Permission management'],
|
||||
},
|
||||
{
|
||||
title: 'Billing & Subscriptions',
|
||||
description: 'View subscriptions, usage-based billing, invoices, and payment methods',
|
||||
icon: CreditCard,
|
||||
href: '/admin/billing',
|
||||
features: ['Subscriptions', 'Usage billing', 'Invoice history'],
|
||||
},
|
||||
{
|
||||
title: 'Compliance & Reporting',
|
||||
description: 'Compliance dashboard, audit logs, and export reports',
|
||||
icon: Shield,
|
||||
href: '/admin/compliance',
|
||||
features: ['Compliance dashboard', 'Audit logs', 'Export reports'],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{adminSections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<Card key={section.href} className="bg-gray-800 border-gray-700 hover:border-orange-500 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Icon className="h-6 w-6 text-orange-500" />
|
||||
<CardTitle className="text-white">{section.title}</CardTitle>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">{section.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 mb-4">
|
||||
{section.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<span className="text-orange-500">•</span>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link
|
||||
href={section.href}
|
||||
className="inline-flex items-center gap-2 text-sm text-orange-500 hover:text-orange-400 transition-colors"
|
||||
>
|
||||
Manage <ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
49
portal/src/app/analytics/page.tsx
Normal file
49
portal/src/app/analytics/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { AdvancedAnalytics } from '@/components/analytics/AdvancedAnalytics';
|
||||
|
||||
export default function AnalyticsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Analytics</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Advanced Analytics</h1>
|
||||
<p className="text-gray-400">Comprehensive analytics and insights for your infrastructure</p>
|
||||
</div>
|
||||
|
||||
<AdvancedAnalytics />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
28
portal/src/app/argocd/page.tsx
Normal file
28
portal/src/app/argocd/page.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { redirect } from 'next/navigation'
|
||||
import ArgoCDApplications from '@/components/argocd/ArgoCDApplications'
|
||||
|
||||
export default function ArgoCDPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
redirect('/api/auth/signin')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<ArgoCDApplications />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
28
portal/src/app/crossplane/page.tsx
Normal file
28
portal/src/app/crossplane/page.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { redirect } from 'next/navigation'
|
||||
import CrossplaneResourceBrowser from '@/components/crossplane/CrossplaneResourceBrowser'
|
||||
|
||||
export default function CrossplanePage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
redirect('/api/auth/signin')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<CrossplaneResourceBrowser />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
78
portal/src/app/dashboard/business/page.tsx
Normal file
78
portal/src/app/dashboard/business/page.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { CostOverviewTile } from '@/components/dashboard/CostOverviewTile';
|
||||
import { CostForecastingTile } from '@/components/dashboard/CostForecastingTile';
|
||||
import { ResourceUsageTile } from '@/components/dashboard/ResourceUsageTile';
|
||||
import { BillingTile } from '@/components/dashboard/BillingTile';
|
||||
import { ServiceAdoptionTile } from '@/components/dashboard/ServiceAdoptionTile';
|
||||
import { ComplianceStatusTile } from '@/components/dashboard/ComplianceStatusTile';
|
||||
import { QuickActionsPanel } from '@/components/dashboard/QuickActionsPanel';
|
||||
|
||||
export default function BusinessDashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Nexus Console</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white">Business Owner Dashboard</h1>
|
||||
<p className="text-gray-400 mt-2">Cost, usage, billing, and compliance overview</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<CostOverviewTile />
|
||||
<CostForecastingTile />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<ResourceUsageTile />
|
||||
<BillingTile />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
<BillingTile />
|
||||
<ServiceAdoptionTile />
|
||||
<ComplianceStatusTile />
|
||||
</div>
|
||||
|
||||
<QuickActionsPanel
|
||||
actions={[
|
||||
{ label: 'View Reports', href: '/reports', icon: 'FileText' },
|
||||
{ label: 'Manage Billing', href: '/admin/billing', icon: 'CreditCard' },
|
||||
{ label: 'Contact Support', href: '/support', icon: 'HelpCircle' },
|
||||
{ label: 'Export Data', href: '/reports/export', icon: 'Download' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
70
portal/src/app/dashboard/developer/page.tsx
Normal file
70
portal/src/app/dashboard/developer/page.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { APIUsageTile } from '@/components/dashboard/APIUsageTile';
|
||||
import { DeploymentsTile } from '@/components/dashboard/DeploymentsTile';
|
||||
import { TestEnvironmentsTile } from '@/components/dashboard/TestEnvironmentsTile';
|
||||
import { APIKeysTile } from '@/components/dashboard/APIKeysTile';
|
||||
import { QuickActionsPanel } from '@/components/dashboard/QuickActionsPanel';
|
||||
|
||||
export default function DeveloperDashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Nexus Console</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white">Developer Dashboard</h1>
|
||||
<p className="text-gray-400 mt-2">API usage, deployments, test environments, and developer tools</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<APIUsageTile />
|
||||
<DeploymentsTile />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<TestEnvironmentsTile />
|
||||
<APIKeysTile />
|
||||
</div>
|
||||
|
||||
<QuickActionsPanel
|
||||
actions={[
|
||||
{ label: 'Create API Key', href: '/developer/api-keys/new', icon: 'Key' },
|
||||
{ label: 'Deploy Service', href: '/developer/deploy', icon: 'Rocket' },
|
||||
{ label: 'View Docs', href: '/docs', icon: 'Book' },
|
||||
{ label: 'View Logs', href: '/developer/logs', icon: 'FileText' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
61
portal/src/app/dashboard/page.tsx
Normal file
61
portal/src/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { getDashboardRoute } from '@/lib/roles';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'authenticated' && session) {
|
||||
const route = getDashboardRoute(session);
|
||||
router.push(route);
|
||||
}
|
||||
}, [status, session, router]);
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Nexus Console</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
<p className="text-sm text-gray-500 mt-4">
|
||||
Development mode: Use any email/password
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Redirecting...
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Redirecting to your dashboard...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
79
portal/src/app/dashboard/technical/page.tsx
Normal file
79
portal/src/app/dashboard/technical/page.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { SystemHealthTile } from '@/components/dashboard/SystemHealthTile';
|
||||
import { IntegrationStatusTile } from '@/components/dashboard/IntegrationStatusTile';
|
||||
import { DataPipelineTile } from '@/components/dashboard/DataPipelineTile';
|
||||
import { ResourceUtilizationTile } from '@/components/dashboard/ResourceUtilizationTile';
|
||||
import { OptimizationEngine } from '@/components/ai/OptimizationEngine';
|
||||
import { QuickActionsPanel } from '@/components/dashboard/QuickActionsPanel';
|
||||
|
||||
export default function TechnicalDashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Nexus Console</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white">Technical Admin Dashboard</h1>
|
||||
<p className="text-gray-400 mt-2">Operational view of infrastructure, integrations, and system health</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
<div className="lg:col-span-2">
|
||||
<SystemHealthTile />
|
||||
</div>
|
||||
<div>
|
||||
<IntegrationStatusTile />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<DataPipelineTile />
|
||||
<ResourceUtilizationTile />
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<OptimizationEngine />
|
||||
</div>
|
||||
|
||||
<QuickActionsPanel
|
||||
actions={[
|
||||
{ label: 'Add Integration', href: '/integrations/new', icon: 'Plus' },
|
||||
{ label: 'Create Connection', href: '/connections/new', icon: 'Link' },
|
||||
{ label: 'View Logs', href: '/monitoring/logs', icon: 'FileText' },
|
||||
{ label: 'System Settings', href: '/settings/system', icon: 'Settings' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
59
portal/src/app/dashboards/page.tsx
Normal file
59
portal/src/app/dashboards/page.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
// Metrics dashboard - component to be implemented
|
||||
// import Dashboard from '@/components/dashboards/Dashboard';
|
||||
|
||||
export default function DashboardsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Access Denied</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to view dashboards</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6 text-white">Metrics Dashboards</h1>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">CPU Usage</h2>
|
||||
<p className="text-gray-400">CPU metrics dashboard coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Memory Usage</h2>
|
||||
<p className="text-gray-400">Memory metrics dashboard coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Network Throughput</h2>
|
||||
<p className="text-gray-400">Network metrics dashboard coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
127
portal/src/app/developer/page.tsx
Normal file
127
portal/src/app/developer/page.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Key, Book, TestTube, BarChart3, Webhook, Download, ArrowRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function DeveloperPortalPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Developer Portal</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const developerSections = [
|
||||
{
|
||||
title: 'API Key Management',
|
||||
description: 'Create, manage, and revoke API keys for your applications',
|
||||
icon: Key,
|
||||
href: '/developer/api-keys',
|
||||
features: ['Create API keys', 'View usage', 'Revoke keys'],
|
||||
},
|
||||
{
|
||||
title: 'API Documentation',
|
||||
description: 'Interactive API documentation with examples and code snippets',
|
||||
icon: Book,
|
||||
href: '/developer/docs',
|
||||
features: ['REST API docs', 'GraphQL schema', 'Code examples'],
|
||||
},
|
||||
{
|
||||
title: 'Test Environments',
|
||||
description: 'Provision and manage sandbox environments for testing',
|
||||
icon: TestTube,
|
||||
href: '/developer/environments',
|
||||
features: ['Create environments', 'Sandbox access', 'Auto-cleanup'],
|
||||
},
|
||||
{
|
||||
title: 'Usage Analytics',
|
||||
description: 'Monitor API usage, quotas, and performance metrics',
|
||||
icon: BarChart3,
|
||||
href: '/developer/analytics',
|
||||
features: ['API usage stats', 'Quota monitoring', 'Performance metrics'],
|
||||
},
|
||||
{
|
||||
title: 'Webhook Configuration',
|
||||
description: 'Configure webhooks for real-time event notifications',
|
||||
icon: Webhook,
|
||||
href: '/developer/webhooks',
|
||||
features: ['Create webhooks', 'Event subscriptions', 'Delivery logs'],
|
||||
},
|
||||
{
|
||||
title: 'SDK Downloads',
|
||||
description: 'Download SDKs and client libraries for popular languages',
|
||||
icon: Download,
|
||||
href: '/developer/sdks',
|
||||
features: ['TypeScript SDK', 'Python SDK', 'Go SDK'],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Developer Portal</h1>
|
||||
<p className="text-gray-400">API keys, documentation, test environments, and developer tools</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{developerSections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<Card key={section.href} className="bg-gray-800 border-gray-700 hover:border-orange-500 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Icon className="h-6 w-6 text-orange-500" />
|
||||
<CardTitle className="text-white">{section.title}</CardTitle>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">{section.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 mb-4">
|
||||
{section.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<span className="text-orange-500">•</span>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link
|
||||
href={section.href}
|
||||
className="inline-flex items-center gap-2 text-sm text-orange-500 hover:text-orange-400 transition-colors"
|
||||
>
|
||||
Access <ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
191
portal/src/app/developer/webhooks/test/page.tsx
Normal file
191
portal/src/app/developer/webhooks/test/page.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Send, CheckCircle, XCircle, Clock } from 'lucide-react';
|
||||
|
||||
export default function WebhookTestingPage() {
|
||||
const [url, setUrl] = useState('');
|
||||
const [method, setMethod] = useState('POST');
|
||||
const [headers, setHeaders] = useState('{"Content-Type": "application/json"}');
|
||||
const [payload, setPayload] = useState('{"event": "test", "data": {}}');
|
||||
const [testResult, setTestResult] = useState<any>(null);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
|
||||
const testWebhook = async () => {
|
||||
setIsTesting(true);
|
||||
setTestResult(null);
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await fetch('/api/webhooks/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url,
|
||||
method,
|
||||
headers: JSON.parse(headers),
|
||||
payload: JSON.parse(payload),
|
||||
}),
|
||||
});
|
||||
const endTime = Date.now();
|
||||
const data = await response.json();
|
||||
|
||||
setTestResult({
|
||||
success: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
responseTime: endTime - startTime,
|
||||
response: data,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
setTestResult({
|
||||
success: false,
|
||||
error: error.message,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Webhook Testing Tool</h1>
|
||||
<p className="text-gray-400">Test your webhook endpoints before configuring them</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">Webhook Configuration</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-2">Webhook URL</label>
|
||||
<input
|
||||
type="url"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder="https://example.com/webhook"
|
||||
className="w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-2">HTTP Method</label>
|
||||
<select
|
||||
value={method}
|
||||
onChange={(e) => setMethod(e.target.value)}
|
||||
className="w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white"
|
||||
>
|
||||
<option value="POST">POST</option>
|
||||
<option value="GET">GET</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-2">Headers (JSON)</label>
|
||||
<textarea
|
||||
value={headers}
|
||||
onChange={(e) => setHeaders(e.target.value)}
|
||||
className="w-full h-24 px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white font-mono text-sm"
|
||||
placeholder='{"Content-Type": "application/json"}'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-2">Payload (JSON)</label>
|
||||
<textarea
|
||||
value={payload}
|
||||
onChange={(e) => setPayload(e.target.value)}
|
||||
className="w-full h-32 px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white font-mono text-sm"
|
||||
placeholder='{"event": "test", "data": {}}'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={testWebhook}
|
||||
disabled={!url || isTesting}
|
||||
className="w-full px-6 py-3 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Send className="h-4 w-4" />
|
||||
{isTesting ? 'Testing...' : 'Test Webhook'}
|
||||
</button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">Test Results</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{!testResult ? (
|
||||
<div className="text-center py-12 text-gray-400">
|
||||
<p>Test results will appear here</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{testResult.success ? (
|
||||
<CheckCircle className="h-5 w-5 text-green-400" />
|
||||
) : (
|
||||
<XCircle className="h-5 w-5 text-red-400" />
|
||||
)}
|
||||
<span className={`font-semibold ${testResult.success ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{testResult.success ? 'Success' : 'Failed'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{testResult.status && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-400 mb-1">Status</p>
|
||||
<p className="text-white font-mono">
|
||||
{testResult.status} {testResult.statusText}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testResult.responseTime && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-400 mb-1">Response Time</p>
|
||||
<p className="text-white font-mono">{testResult.responseTime}ms</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testResult.timestamp && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-400 mb-1">Timestamp</p>
|
||||
<p className="text-white text-sm">{new Date(testResult.timestamp).toLocaleString()}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testResult.response && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-400 mb-1">Response</p>
|
||||
<pre className="p-3 bg-gray-900 rounded text-white font-mono text-xs overflow-auto max-h-64">
|
||||
{JSON.stringify(testResult.response, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testResult.error && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-400 mb-1">Error</p>
|
||||
<p className="text-red-400 font-mono text-sm">{testResult.error}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
50
portal/src/app/error.tsx
Normal file
50
portal/src/app/error.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Home, RefreshCw, AlertCircle } from 'lucide-react'
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
reset: () => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log error to error reporting service
|
||||
console.error('Application error:', error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<AlertCircle className="h-16 w-16 text-red-500 mx-auto mb-4" />
|
||||
<h1 className="text-3xl font-bold text-white mb-4">Something went wrong!</h1>
|
||||
<p className="text-gray-400 mb-2">
|
||||
An unexpected error occurred. Please try again.
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p className="text-sm text-gray-500 mb-8">Error ID: {error.digest}</p>
|
||||
)}
|
||||
<div className="flex gap-4 justify-center">
|
||||
<button
|
||||
onClick={reset}
|
||||
className="flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<RefreshCw className="h-5 w-5" />
|
||||
Try Again
|
||||
</button>
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Go Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
28
portal/src/app/kubernetes/page.tsx
Normal file
28
portal/src/app/kubernetes/page.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { redirect } from 'next/navigation'
|
||||
import KubernetesClusters from '@/components/kubernetes/KubernetesClusters'
|
||||
|
||||
export default function KubernetesPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
redirect('/api/auth/signin')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<KubernetesClusters />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
import { Providers } from './providers';
|
||||
'use client'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import { Providers } from './providers'
|
||||
import { PortalHeader } from '@/components/layout/PortalHeader'
|
||||
import { PortalSidebar } from '@/components/layout/PortalSidebar'
|
||||
import { PortalBreadcrumbs } from '@/components/layout/PortalBreadcrumbs'
|
||||
import { MobileNavigation } from '@/components/layout/MobileNavigation'
|
||||
import { KeyboardShortcutsProvider } from '@/components/KeyboardShortcutsProvider'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Hybrid Cloud Control Plane',
|
||||
description: 'Unified management portal for hybrid cloud infrastructure',
|
||||
};
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<Providers>{children}</Providers>
|
||||
<SessionProvider>
|
||||
<Providers>
|
||||
<KeyboardShortcutsProvider>
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<PortalHeader />
|
||||
<PortalBreadcrumbs />
|
||||
<div className="flex flex-1">
|
||||
<PortalSidebar />
|
||||
<main className="flex-1 ml-0 md:ml-64">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<MobileNavigation />
|
||||
</div>
|
||||
</KeyboardShortcutsProvider>
|
||||
</Providers>
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
29
portal/src/app/ml/page.tsx
Normal file
29
portal/src/app/ml/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function MLPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return <div>Please sign in</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6">AI Foundry</h1>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
ML platform for model training, inference, and pipeline management
|
||||
</p>
|
||||
{/* ML platform UI will be implemented here */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="text-sm text-muted-foreground">ML platform interface coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
62
portal/src/app/monitoring/page.tsx
Normal file
62
portal/src/app/monitoring/page.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { redirect } from 'next/navigation'
|
||||
import GrafanaPanel from '@/components/monitoring/GrafanaPanel'
|
||||
import LokiLogViewer from '@/components/monitoring/LokiLogViewer'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/Tabs'
|
||||
|
||||
export default function MonitoringPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
redirect('/api/auth/signin')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-3xl font-bold mb-6">Monitoring</h1>
|
||||
|
||||
<Tabs defaultValue="grafana" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="grafana">Grafana Dashboards</TabsTrigger>
|
||||
<TabsTrigger value="logs">Logs (Loki)</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="grafana" className="space-y-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">System Metrics</h2>
|
||||
<GrafanaPanel
|
||||
dashboardUID="system"
|
||||
panelId={1}
|
||||
height="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">Application Metrics</h2>
|
||||
<GrafanaPanel
|
||||
dashboardUID="application"
|
||||
panelId={2}
|
||||
height="400px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="logs">
|
||||
<LokiLogViewer />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
51
portal/src/app/network/page.tsx
Normal file
51
portal/src/app/network/page.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
// Network topology view - component to be implemented
|
||||
// import { NetworkTopologyView } from '@/components/network/NetworkTopologyView';
|
||||
|
||||
export default function NetworkPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Access Denied</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to view network topology</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6 text-white">Network Topology</h1>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<p className="text-gray-400">Network topology visualization coming soon</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
This will display the network graph showing relationships between resources across Proxmox, Kubernetes, and Cloudflare.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
35
portal/src/app/not-found.tsx
Normal file
35
portal/src/app/not-found.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { Home, ArrowLeft } from 'lucide-react'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-6xl font-bold text-white mb-4">404</h1>
|
||||
<h2 className="text-2xl font-semibold text-gray-300 mb-4">Page Not Found</h2>
|
||||
<p className="text-gray-400 mb-8">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Go Home
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="flex items-center gap-2 px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
37
portal/src/app/onboarding/page.tsx
Normal file
37
portal/src/app/onboarding/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import { OnboardingWizard } from '@/components/onboarding/OnboardingWizard';
|
||||
import { WelcomeStep } from '@/components/onboarding/steps/WelcomeStep';
|
||||
import { ProfileStep } from '@/components/onboarding/steps/ProfileStep';
|
||||
import { PreferencesStep } from '@/components/onboarding/steps/PreferencesStep';
|
||||
|
||||
const onboardingSteps = [
|
||||
{
|
||||
id: 'welcome',
|
||||
title: 'Welcome',
|
||||
description: 'Get started with Nexus Console',
|
||||
component: WelcomeStep,
|
||||
},
|
||||
{
|
||||
id: 'profile',
|
||||
title: 'Profile',
|
||||
description: 'Set up your profile information',
|
||||
component: ProfileStep,
|
||||
},
|
||||
{
|
||||
id: 'preferences',
|
||||
title: 'Preferences',
|
||||
description: 'Configure your preferences',
|
||||
component: PreferencesStep,
|
||||
},
|
||||
];
|
||||
|
||||
export default function OnboardingPage() {
|
||||
const handleComplete = () => {
|
||||
// Mark onboarding as complete
|
||||
localStorage.setItem('onboarding-complete', 'true');
|
||||
};
|
||||
|
||||
return <OnboardingWizard steps={onboardingSteps} onComplete={handleComplete} />;
|
||||
}
|
||||
|
||||
113
portal/src/app/partner/page.tsx
Normal file
113
portal/src/app/partner/page.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Handshake, TrendingUp, BookOpen, Package, ArrowRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function PartnerPortalPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Welcome to Partner Portal</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to continue</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const partnerSections = [
|
||||
{
|
||||
title: 'Co-Sell Deal Management',
|
||||
description: 'Register and manage co-sell deals with Sankofa',
|
||||
icon: Handshake,
|
||||
href: '/partner/deals',
|
||||
features: ['Deal registration', 'Deal tracking', 'Revenue sharing'],
|
||||
},
|
||||
{
|
||||
title: 'Technical Onboarding',
|
||||
description: 'Access technical resources and onboarding materials',
|
||||
icon: BookOpen,
|
||||
href: '/partner/onboarding',
|
||||
features: ['Technical training', 'Architecture guides', 'Best practices'],
|
||||
},
|
||||
{
|
||||
title: 'Solution Registration',
|
||||
description: 'Register your solutions in the marketplace',
|
||||
icon: Package,
|
||||
href: '/partner/solutions',
|
||||
features: ['Solution listing', 'Certification', 'Marketplace access'],
|
||||
},
|
||||
{
|
||||
title: 'Partner Resources',
|
||||
description: 'Marketing materials, sales enablement, and training resources',
|
||||
icon: TrendingUp,
|
||||
href: '/partner/resources',
|
||||
features: ['Marketing assets', 'Sales materials', 'Training programs'],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Partner Portal</h1>
|
||||
<p className="text-gray-400">Co-sell deals, technical onboarding, and solution registration</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{partnerSections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<Card key={section.href} className="bg-gray-800 border-gray-700 hover:border-orange-500 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Icon className="h-6 w-6 text-orange-500" />
|
||||
<CardTitle className="text-white">{section.title}</CardTitle>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">{section.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 mb-4">
|
||||
{section.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<span className="text-orange-500">•</span>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link
|
||||
href={section.href}
|
||||
className="inline-flex items-center gap-2 text-sm text-orange-500 hover:text-orange-400 transition-colors"
|
||||
>
|
||||
Access <ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
29
portal/src/app/policies/page.tsx
Normal file
29
portal/src/app/policies/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function PoliciesPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return <div>Please sign in</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6">Policy Management</h1>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Manage compliance, security, and cost optimization policies
|
||||
</p>
|
||||
{/* Policy management UI will be implemented here */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="text-sm text-muted-foreground">Policy management interface coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
29
portal/src/app/resources/graph/page.tsx
Normal file
29
portal/src/app/resources/graph/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function ResourceGraphPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return <div>Please sign in</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6">Resource Graph</h1>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Visualize resource relationships and dependencies
|
||||
</p>
|
||||
{/* React Flow graph visualization will be implemented here */}
|
||||
<div className="border rounded-lg p-4 h-96">
|
||||
<p className="text-sm text-muted-foreground">Resource graph visualization coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
30
portal/src/app/resources/page.tsx
Normal file
30
portal/src/app/resources/page.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function ResourcesPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return <div>Please sign in</div>
|
||||
}
|
||||
|
||||
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
|
||||
</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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
157
portal/src/app/settings/2fa/page.tsx
Normal file
157
portal/src/app/settings/2fa/page.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||
import { Shield, CheckCircle, XCircle } 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);
|
||||
const [verificationCode, setVerificationCode] = useState('');
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
|
||||
const enable2FA = async () => {
|
||||
try {
|
||||
// In production, this would call your backend API
|
||||
const response = await fetch('/api/auth/2fa/setup', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
setSecret(data.secret);
|
||||
const qr = await QRCode.toDataURL(data.qrCodeUrl);
|
||||
setQrCode(qr);
|
||||
} catch (error) {
|
||||
console.error('Failed to enable 2FA:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const verifyAndEnable = async () => {
|
||||
setIsVerifying(true);
|
||||
try {
|
||||
const response = await fetch('/api/auth/2fa/verify', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code: verificationCode, secret }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setIsEnabled(true);
|
||||
setQrCode(null);
|
||||
setSecret(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Verification failed:', error);
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const disable2FA = async () => {
|
||||
try {
|
||||
await fetch('/api/auth/2fa/disable', { method: 'POST' });
|
||||
setIsEnabled(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to disable 2FA:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Two-Factor Authentication</h1>
|
||||
<p className="text-gray-400">Add an extra layer of security to your account</p>
|
||||
</div>
|
||||
|
||||
<Card className="bg-gray-800 border-gray-700 max-w-2xl">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="h-6 w-6 text-orange-500" />
|
||||
<div>
|
||||
<CardTitle className="text-white">2FA Status</CardTitle>
|
||||
<CardDescription className="text-gray-400">
|
||||
{isEnabled ? 'Two-factor authentication is enabled' : 'Two-factor authentication is disabled'}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{!isEnabled && !qrCode && (
|
||||
<div>
|
||||
<p className="text-gray-300 mb-4">
|
||||
Enable two-factor authentication to protect your account with an additional security layer.
|
||||
</p>
|
||||
<button
|
||||
onClick={enable2FA}
|
||||
className="px-6 py-3 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors"
|
||||
>
|
||||
Enable 2FA
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{qrCode && secret && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-gray-300 mb-2">Scan this QR code with your authenticator app:</p>
|
||||
<div className="flex justify-center p-4 bg-white rounded">
|
||||
<img src={qrCode} alt="2FA QR Code" className="w-48 h-48" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-gray-300 mb-2">Or enter this secret manually:</p>
|
||||
<code className="block p-3 bg-gray-900 rounded text-white font-mono text-sm break-all">
|
||||
{secret}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-2">
|
||||
Enter verification code from your app:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={verificationCode}
|
||||
onChange={(e) => setVerificationCode(e.target.value)}
|
||||
placeholder="000000"
|
||||
maxLength={6}
|
||||
className="w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={verifyAndEnable}
|
||||
disabled={verificationCode.length !== 6 || isVerifying}
|
||||
className="px-6 py-3 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isVerifying ? 'Verifying...' : 'Verify and Enable'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isEnabled && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 text-green-400">
|
||||
<CheckCircle className="h-5 w-5" />
|
||||
<span>Two-factor authentication is active</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={disable2FA}
|
||||
className="px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
|
||||
>
|
||||
Disable 2FA
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
132
portal/src/app/settings/page.tsx
Normal file
132
portal/src/app/settings/page.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
'use client';
|
||||
|
||||
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';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Access Denied</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to access settings</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6 text-white">Settings</h1>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<User className="h-5 w-5" />
|
||||
<CardTitle>Profile Settings</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300">Email</label>
|
||||
<p className="text-white">{session?.user?.email || 'Not available'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300">Name</label>
|
||||
<p className="text-white">{session?.user?.name || 'Not available'}</p>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
||||
Edit Profile
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<Bell className="h-5 w-5" />
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" className="rounded" />
|
||||
<span className="text-white">Email notifications</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" className="rounded" />
|
||||
<span className="text-white">Alert notifications</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" className="rounded" />
|
||||
<span className="text-white">Weekly reports</span>
|
||||
</label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="h-5 w-5" />
|
||||
<CardTitle>Security</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<button className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
||||
Change Password
|
||||
</button>
|
||||
<button className="w-full px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">
|
||||
Enable Two-Factor Authentication
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<Key className="h-5 w-5" />
|
||||
<CardTitle>API Keys</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-400">Manage your API keys and tokens</p>
|
||||
<button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
||||
Create API Key
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
38
portal/src/app/unauthorized.tsx
Normal file
38
portal/src/app/unauthorized.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { Shield, Home, Lock } from 'lucide-react'
|
||||
|
||||
export default function Unauthorized() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<Shield className="h-16 w-16 text-yellow-500 mx-auto mb-4" />
|
||||
<h1 className="text-3xl font-bold text-white mb-4">Access Denied</h1>
|
||||
<p className="text-gray-400 mb-2">
|
||||
You don't have permission to access this resource.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mb-8">
|
||||
Please contact your administrator if you believe this is an error.
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
Go Home
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="flex items-center gap-2 px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<Lock className="h-5 w-5" />
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
29
portal/src/app/vm-scale-sets/page.tsx
Normal file
29
portal/src/app/vm-scale-sets/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export default function VMScaleSetsPage() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return <div>Please sign in</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6">VM Scale Sets</h1>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Manage auto-scaling VM groups with metrics and scaling policies
|
||||
</p>
|
||||
{/* VM Scale Sets UI will be implemented here */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="text-sm text-muted-foreground">VM Scale Sets management interface coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
71
portal/src/app/well-architected/page.tsx
Normal file
71
portal/src/app/well-architected/page.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
// Well-Architected Framework dashboard - component to be implemented
|
||||
// import WAFDashboard from '@/components/well-architected/WAFDashboard';
|
||||
|
||||
export default function WellArchitectedPage() {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600 mx-auto"></div>
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div className="text-center max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-4">Access Denied</h1>
|
||||
<p className="text-gray-400 mb-6">Please sign in to view Well-Architected Framework</p>
|
||||
<button
|
||||
onClick={() => signIn()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold mb-6 text-white">Well-Architected Framework</h1>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Security</h2>
|
||||
<p className="text-gray-400">Security pillar assessment coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Reliability</h2>
|
||||
<p className="text-gray-400">Reliability pillar assessment coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Performance</h2>
|
||||
<p className="text-gray-400">Performance pillar assessment coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Cost Optimization</h2>
|
||||
<p className="text-gray-400">Cost optimization assessment coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Operational Excellence</h2>
|
||||
<p className="text-gray-400">Operational excellence assessment coming soon</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4 bg-gray-800">
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">Sustainability</h2>
|
||||
<p className="text-gray-400">Sustainability assessment coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user