diff --git a/.gitignore b/.gitignore
index e2b5290..d470200 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,12 @@
-# Created by https://www.toptal.com/developers/gitignore/api/go
-# Edit at https://www.toptal.com/developers/gitignore?templates=go
+# Created by https://www.toptal.com/developers/gitignore/api/go,nextjs
+# Edit at https://www.toptal.com/developers/gitignore?templates=go,nextjs
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
+*.exe
*.exe~
*.dll
*.so
@@ -23,4 +24,40 @@
# Go workspace file
go.work
-# End of https://www.toptal.com/developers/gitignore/api/go
+### NextJS ###
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# End of https://www.toptal.com/developers/gitignore/api/go,nextjs
diff --git a/console/app/globals.css b/console/app/globals.css
new file mode 100644
index 0000000..02687bd
--- /dev/null
+++ b/console/app/globals.css
@@ -0,0 +1,123 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/console/app/layout.tsx b/console/app/layout.tsx
new file mode 100644
index 0000000..a262171
--- /dev/null
+++ b/console/app/layout.tsx
@@ -0,0 +1,31 @@
+import type { Metadata } from 'next'
+import { GeistSans } from 'geist/font/sans'
+import { GeistMono } from 'geist/font/mono'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'v0 App',
+ description: 'Created with v0',
+ generator: 'v0.app',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/console/app/page.tsx b/console/app/page.tsx
new file mode 100644
index 0000000..d5ed09f
--- /dev/null
+++ b/console/app/page.tsx
@@ -0,0 +1,5 @@
+import { HomecloudConsole } from "@/components/homecloud-console"
+
+export default function HomePage() {
+ return
+}
diff --git a/console/components.json b/console/components.json
new file mode 100644
index 0000000..335484f
--- /dev/null
+++ b/console/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/console/components/homecloud-console.tsx b/console/components/homecloud-console.tsx
new file mode 100644
index 0000000..7b8b1e1
--- /dev/null
+++ b/console/components/homecloud-console.tsx
@@ -0,0 +1,504 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import {
+ Search,
+ Bell,
+ HelpCircle,
+ Settings,
+ User,
+ ChevronDown,
+ Cloud,
+ Plus,
+ MoreHorizontal,
+ Database,
+ Server,
+ Shield,
+ Zap,
+ BarChart3,
+ ExternalLink,
+ Network,
+ Users,
+} from "lucide-react"
+
+import { S3ServicePage } from "./services/s3-service"
+import { EC2ServicePage } from "./services/ec2-service"
+import { VPCServicePage } from "./services/vpc-service"
+import { RDSServicePage } from "./services/rds-service"
+import { LambdaServicePage } from "./services/lambda-service"
+import { CloudWatchServicePage } from "./services/cloudwatch-service"
+import { IAMServicePage } from "./services/iam-service"
+
+export function HomecloudConsole() {
+ const [searchQuery, setSearchQuery] = useState("")
+ const [currentView, setCurrentView] = useState("home")
+
+ const accountData = {
+ accountId: "123456789012",
+ accountName: "Production Account",
+ userName: "john.smith@company.com",
+ region: "us-east-1",
+ regionName: "US East (N. Virginia)",
+ organizationId: "o-abc123def456",
+ billingCurrency: "USD",
+ }
+
+ const recentlyVisited = [
+ { name: "S3", icon: Database, color: "bg-green-500", id: "s3" },
+ { name: "VPC", icon: Shield, color: "bg-purple-500", id: "vpc" },
+ { name: "EC2", icon: Server, color: "bg-orange-500", id: "ec2" },
+ { name: "RDS", icon: Database, color: "bg-blue-500", id: "rds" },
+ { name: "Lambda", icon: Zap, color: "bg-yellow-500", id: "lambda" },
+ { name: "CloudWatch", icon: BarChart3, color: "bg-indigo-500", id: "cloudwatch" },
+ { name: "IAM", icon: Users, color: "bg-red-500", id: "iam" },
+ ]
+
+ const serviceCategories = [
+ {
+ category: "Compute",
+ services: [
+ { name: "EC2", description: "Virtual Servers in the Cloud", id: "ec2", icon: Server },
+ { name: "Lambda", description: "Run Code without Thinking about Servers", id: "lambda", icon: Zap },
+ ],
+ },
+ {
+ category: "Storage",
+ services: [{ name: "S3", description: "Scalable Storage in the Cloud", id: "s3", icon: Database }],
+ },
+ {
+ category: "Database",
+ services: [{ name: "RDS", description: "Managed Relational Database Service", id: "rds", icon: Database }],
+ },
+ {
+ category: "Networking & Content Delivery",
+ services: [{ name: "VPC", description: "Isolated Cloud Resources", id: "vpc", icon: Network }],
+ },
+ {
+ category: "Management & Governance",
+ services: [
+ { name: "CloudWatch", description: "Monitoring and Observability", id: "cloudwatch", icon: BarChart3 },
+ ],
+ },
+ {
+ category: "Security, Identity, & Compliance",
+ services: [{ name: "IAM", description: "Identity and Access Management", id: "iam", icon: Users }],
+ },
+ ]
+
+ const handleServiceClick = (serviceId: string) => {
+ setCurrentView(serviceId)
+ }
+
+ const handleBackToHome = () => {
+ setCurrentView("home")
+ }
+
+ const renderCurrentView = () => {
+ switch (currentView) {
+ case "s3":
+ return
+ case "ec2":
+ return
+ case "vpc":
+ return
+ case "rds":
+ return
+ case "lambda":
+ return
+ case "cloudwatch":
+ return
+ case "iam":
+ return
+ default:
+ return renderDashboard()
+ }
+ }
+
+ const renderDashboard = () => (
+ <>
+ {/* Console Home Header */}
+
+
+
+
Console Home
+
+ beta
+
+
+
+
+
+
+
+
+
+ {/* Main Dashboard Grid */}
+
+
+ {/* Recently visited */}
+
+
+
+
+
Recently visited
+
+ info
+
+
+
+
+
+ {recentlyVisited.slice(0, 4).map((service, index) => (
+ handleServiceClick(service.id)}
+ >
+
+
+
+
{service.name}
+
+ ))}
+
+
+
+
+
+
+ {/* Applications */}
+
+
+
+
+
Applications (3)
+
+ info
+
+
+
+
+
+
+
+
+
+
+
+
web-app-prod
+
Running • 3 resources
+
+
+ Healthy
+
+
+
+
+
api-service
+
Running • 5 resources
+
+
+ Healthy
+
+
+
+
+
data-pipeline
+
Stopped • 2 resources
+
+
+ Stopped
+
+
+
+
+
+
+
+
+
+ {/* Quick Start */}
+
+
+
+
+
+
+
+
+
+
Launch a virtual server
+
Get started with EC2 instances for your applications.
+
+
+
+
+
+
Store and retrieve data
+
Use S3 for scalable object storage solutions.
+
+
+
+
+
+ {/* System Health */}
+
+
+
+
+
System Health
+
+ info
+
+
+
+
+
+
+
+ Open issues
+ Past 7 days
+
+
0
+
+
+
+ Scheduled maintenance
+ Upcoming
+
+
1
+
Network upgrade - Dec 15, 2024
+
+
+
+
+
+
+
+ {/* Cost and usage */}
+
+
+
+
+
Cost and usage
+
+ info
+
+
+
+
+
+
+
+
+ This month
+ Dec 2024
+
+
$247.83
+
↓ 12% from last month
+
+
+
+ Forecasted
+ End of month
+
+
$312.45
+
+
+
+
+
+
+
+
+ {/* Security Overview */}
+
+
+
+
+
Security Overview
+
+ info
+
+
+
+
+
+
+ Security alerts
+
+ All clear
+
+
+
+ Active policies
+ 12
+
+
+ MFA enabled users
+ 8/10
+
+
+
+
+
+
+
+
+ >
+ )
+
+ return (
+
+ {/* Top Navigation Bar */}
+
+
+
+
+
+ homecloud
+
+
+
+
+
+
+ All services
+
+ {serviceCategories.map((category, categoryIndex) => (
+
+
+ {category.category}
+
+ {category.services.map((service, serviceIndex) => (
+
handleServiceClick(service.id)}
+ >
+
+
+
{service.name}
+
{service.description}
+
+
+ ))}
+ {categoryIndex < serviceCategories.length - 1 &&
}
+
+ ))}
+
+
+
+
+
setSearchQuery(e.target.value)}
+ className="pl-10 w-80 bg-gray-800 border-gray-700 text-white placeholder-gray-400"
+ />
+
[Alt+S]
+
+
+
+
+
+
+
+
+
+
+
+
+ Select Region
+
+ US East (N. Virginia) - us-east-1
+ US West (Oregon) - us-west-2
+ Europe (Ireland) - eu-west-1
+ Asia Pacific (Tokyo) - ap-northeast-1
+
+
+
+
+
+
+
+ Account Information
+
+
+
{accountData.accountName}
+
Account ID: {accountData.accountId}
+
User: {accountData.userName}
+
Organization: {accountData.organizationId}
+
+
+ Switch Account
+ Account Settings
+ Sign Out
+
+
+
+
+
+
+
+ {renderCurrentView()}
+
+ )
+}
diff --git a/console/components/services/cloudwatch-service.tsx b/console/components/services/cloudwatch-service.tsx
new file mode 100644
index 0000000..53bc65a
--- /dev/null
+++ b/console/components/services/cloudwatch-service.tsx
@@ -0,0 +1,272 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ MoreHorizontal,
+ BarChart3,
+ Bell,
+ AlertTriangle,
+ Activity,
+ RefreshCw,
+ Eye,
+ Settings,
+} from "lucide-react"
+
+interface CloudWatchServicePageProps {
+ onBack: () => void
+}
+
+export function CloudWatchServicePage({ onBack }: CloudWatchServicePageProps) {
+ const alarms = [
+ {
+ name: "High CPU Usage",
+ status: "OK",
+ metric: "EC2 CPU Utilization",
+ threshold: "> 80%",
+ state: "OK",
+ updated: "2 hours ago",
+ },
+ {
+ name: "Database Connections",
+ status: "ALARM",
+ metric: "RDS DatabaseConnections",
+ threshold: "> 90",
+ state: "ALARM",
+ updated: "15 minutes ago",
+ },
+ {
+ name: "Lambda Errors",
+ status: "OK",
+ metric: "Lambda Errors",
+ threshold: "> 5",
+ state: "OK",
+ updated: "1 hour ago",
+ },
+ ]
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "OK":
+ return "bg-green-100 text-green-800"
+ case "ALARM":
+ return "bg-red-100 text-red-800"
+ case "INSUFFICIENT_DATA":
+ return "bg-yellow-100 text-yellow-800"
+ default:
+ return "bg-gray-100 text-gray-800"
+ }
+ }
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
CloudWatch
+
+
+
+
+
+
+
+
+
+ {/* CloudWatch Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Create alarm
+ Monitor metrics
+
+
+
+
+
+ Create dashboard
+ Visualize metrics
+
+
+
+
+
+ View logs
+ Browse log groups
+
+
+
+
+
+ Insights
+ Query and analyze
+
+
+
+
+ {/* Alarms List */}
+
+
+
+
Alarms
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Alarm name |
+ Status |
+ Metric |
+ Threshold |
+ Updated |
+ Actions |
+
+
+
+ {alarms.map((alarm, index) => (
+
+ |
+ {alarm.name}
+ |
+
+ {alarm.status}
+ |
+ {alarm.metric} |
+ {alarm.threshold} |
+ {alarm.updated} |
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Monitoring Summary */}
+
+
+
+
+
+ Alarm Summary
+
+
+
+
+
+ Total alarms
+ 3
+
+
+ In alarm
+ 1
+
+
+ OK
+ 2
+
+
+
+
+
+
+
+
+
+ Dashboards
+
+
+
+
+
+ Custom dashboards
+ 2
+
+
+ Widgets
+ 12
+
+
+ Automatic dashboards
+ 5
+
+
+
+
+
+
+
+
+
+ Logs
+
+
+
+
+
+ Log groups
+ 8
+
+
+ Log streams
+ 24
+
+
+ Stored data
+ 2.1 GB
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/services/ec2-service.tsx b/console/components/services/ec2-service.tsx
new file mode 100644
index 0000000..5aaa995
--- /dev/null
+++ b/console/components/services/ec2-service.tsx
@@ -0,0 +1,289 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ Server,
+ Play,
+ Square,
+ RotateCcw,
+ Settings,
+ RefreshCw,
+ Monitor,
+ HardDrive,
+ Cpu,
+} from "lucide-react"
+
+interface EC2ServicePageProps {
+ onBack: () => void
+}
+
+export function EC2ServicePage({ onBack }: EC2ServicePageProps) {
+ const instances = [
+ {
+ id: "i-1234567890abcdef0",
+ name: "Web Server 1",
+ type: "t3.medium",
+ state: "running",
+ az: "ap-northeast-1a",
+ publicIp: "54.123.45.67",
+ privateIp: "10.0.1.15",
+ launched: "2024-01-15 10:30",
+ },
+ {
+ id: "i-0987654321fedcba0",
+ name: "Database Server",
+ type: "r5.large",
+ state: "stopped",
+ az: "ap-northeast-1b",
+ publicIp: "-",
+ privateIp: "10.0.2.20",
+ launched: "2024-01-10 14:20",
+ },
+ {
+ id: "i-abcdef1234567890",
+ name: "Load Balancer",
+ type: "t3.small",
+ state: "running",
+ az: "ap-northeast-1c",
+ publicIp: "52.198.76.54",
+ privateIp: "10.0.3.10",
+ launched: "2024-01-20 09:15",
+ },
+ ]
+
+ const getStateColor = (state: string) => {
+ switch (state) {
+ case "running":
+ return "bg-green-100 text-green-800"
+ case "stopped":
+ return "bg-red-100 text-red-800"
+ case "pending":
+ return "bg-yellow-100 text-yellow-800"
+ default:
+ return "bg-gray-100 text-gray-800"
+ }
+ }
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
EC2
+
+
+
+
+
+
+
+
+
+ {/* EC2 Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Launch instance
+ Create a new EC2 instance
+
+
+
+
+
+ Create volume
+ Create EBS volume
+
+
+
+
+
+ View metrics
+ Monitor performance
+
+
+
+
+
+ Security groups
+ Manage firewall rules
+
+
+
+
+ {/* Instances List */}
+
+
+
+
Instances
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Instance ID |
+ Name |
+ Instance type |
+ Instance state |
+ Availability Zone |
+ Public IPv4 |
+ Actions |
+
+
+
+ {instances.map((instance, index) => (
+
+ |
+
+ {instance.id}
+
+ |
+ {instance.name} |
+ {instance.type} |
+
+ {instance.state}
+ |
+ {instance.az} |
+ {instance.publicIp} |
+
+
+ {instance.state === "running" ? (
+
+ ) : (
+
+ )}
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Resource Summary */}
+
+
+
+
+
+ Instance Summary
+
+
+
+
+
+ Running instances
+ 2
+
+
+ Stopped instances
+ 1
+
+
+ Total instances
+ 3
+
+
+
+
+
+
+
+
+
+ Storage
+
+
+
+
+
+ EBS volumes
+ 5
+
+
+ Total storage
+ 250 GB
+
+
+ Snapshots
+ 12
+
+
+
+
+
+
+
+
+
+ Monitoring
+
+
+
+
+
+ CPU utilization
+ 23%
+
+
+ Network in
+ 1.2 MB/s
+
+
+ Network out
+ 0.8 MB/s
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/services/iam-service.tsx b/console/components/services/iam-service.tsx
new file mode 100644
index 0000000..03a183a
--- /dev/null
+++ b/console/components/services/iam-service.tsx
@@ -0,0 +1,324 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ MoreHorizontal,
+ Shield,
+ Users,
+ Key,
+ FileText,
+ RefreshCw,
+ Settings,
+ Lock,
+ UserCheck,
+} from "lucide-react"
+
+interface IAMServicePageProps {
+ onBack: () => void
+}
+
+export function IAMServicePage({ onBack }: IAMServicePageProps) {
+ const users = [
+ {
+ name: "admin-user",
+ groups: ["Administrators"],
+ policies: 3,
+ accessKeys: 1,
+ lastActivity: "2024-01-15 14:30",
+ mfa: true,
+ },
+ {
+ name: "developer-john",
+ groups: ["Developers"],
+ policies: 2,
+ accessKeys: 2,
+ lastActivity: "2024-01-14 16:45",
+ mfa: false,
+ },
+ {
+ name: "readonly-user",
+ groups: ["ReadOnly"],
+ policies: 1,
+ accessKeys: 0,
+ lastActivity: "2024-01-10 09:20",
+ mfa: true,
+ },
+ ]
+
+ const roles = [
+ { name: "EC2-S3-Access-Role", type: "Service Role", policies: 2, lastUsed: "2024-01-15" },
+ { name: "Lambda-Execution-Role", type: "Service Role", policies: 1, lastUsed: "2024-01-14" },
+ { name: "Cross-Account-Role", type: "Cross-account", policies: 3, lastUsed: "2024-01-12" },
+ ]
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
IAM
+
+
+
+
+
+
+
+
+
+ {/* IAM Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Create user
+ Add new IAM user
+
+
+
+
+
+ Create role
+ Define service permissions
+
+
+
+
+
+ Create policy
+ Define permissions
+
+
+
+
+
+ Access analyzer
+ Review permissions
+
+
+
+
+ {/* Users List */}
+
+
+
+
Users
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | User name |
+ Groups |
+ Policies |
+ Access keys |
+ MFA |
+ Last activity |
+ Actions |
+
+
+
+ {users.map((user, index) => (
+
+ |
+ {user.name}
+ |
+ {user.groups.join(", ")} |
+ {user.policies} |
+ {user.accessKeys} |
+
+
+ {user.mfa ? "Enabled" : "Disabled"}
+
+ |
+ {user.lastActivity} |
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Roles List */}
+
+
+
+
Roles
+
+
+
+
+
+
+
+
+ | Role name |
+ Type |
+ Policies |
+ Last used |
+ Actions |
+
+
+
+ {roles.map((role, index) => (
+
+ |
+ {role.name}
+ |
+ {role.type} |
+ {role.policies} |
+ {role.lastUsed} |
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* IAM Summary */}
+
+
+
+
+
+ Users & Groups
+
+
+
+
+
+ Total users
+ 3
+
+
+ Groups
+ 3
+
+
+ MFA enabled
+ 2/3
+
+
+
+
+
+
+
+
+
+ Roles & Policies
+
+
+
+
+
+ Total roles
+ 3
+
+
+ Custom policies
+ 8
+
+
+ AWS managed
+ 12
+
+
+
+
+
+
+
+
+
+ Security
+
+
+
+
+
+ Access keys
+ 3
+
+
+ Unused keys
+ 0
+
+
+ Password policy
+ Strong
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/services/lambda-service.tsx b/console/components/services/lambda-service.tsx
new file mode 100644
index 0000000..8ec5fb2
--- /dev/null
+++ b/console/components/services/lambda-service.tsx
@@ -0,0 +1,278 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ MoreHorizontal,
+ Zap,
+ Play,
+ Settings,
+ RefreshCw,
+ Code,
+ Clock,
+ Activity,
+ FileText,
+} from "lucide-react"
+
+interface LambdaServicePageProps {
+ onBack: () => void
+}
+
+export function LambdaServicePage({ onBack }: LambdaServicePageProps) {
+ const functions = [
+ {
+ name: "user-authentication",
+ runtime: "Node.js 18.x",
+ status: "Active",
+ lastModified: "2024-01-15 14:30",
+ timeout: "30s",
+ memory: "256 MB",
+ invocations: "1,247",
+ },
+ {
+ name: "image-processor",
+ runtime: "Python 3.11",
+ status: "Active",
+ lastModified: "2024-01-12 09:15",
+ timeout: "5m",
+ memory: "1024 MB",
+ invocations: "89",
+ },
+ {
+ name: "data-sync",
+ runtime: "Node.js 18.x",
+ status: "Inactive",
+ lastModified: "2024-01-08 16:45",
+ timeout: "15m",
+ memory: "512 MB",
+ invocations: "0",
+ },
+ ]
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "Active":
+ return "bg-green-100 text-green-800"
+ case "Inactive":
+ return "bg-gray-100 text-gray-800"
+ case "Failed":
+ return "bg-red-100 text-red-800"
+ default:
+ return "bg-gray-100 text-gray-800"
+ }
+ }
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
Lambda
+
+
+
+
+
+
+
+
+
+ {/* Lambda Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Create function
+ Build serverless function
+
+
+
+
+
+ Browse blueprints
+ Use pre-built templates
+
+
+
+
+
+ View metrics
+ Monitor performance
+
+
+
+
+
+ Layers
+ Manage shared code
+
+
+
+
+ {/* Functions List */}
+
+
+
+
Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Function name |
+ Runtime |
+ Status |
+ Last modified |
+ Memory |
+ Invocations |
+ Actions |
+
+
+
+ {functions.map((func, index) => (
+
+ |
+ {func.name}
+ |
+ {func.runtime} |
+
+ {func.status}
+ |
+ {func.lastModified} |
+ {func.memory} |
+ {func.invocations} |
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Lambda Summary */}
+
+
+
+
+
+ Function Summary
+
+
+
+
+
+ Total functions
+ 3
+
+
+ Active functions
+ 2
+
+
+ Total invocations
+ 1,336
+
+
+
+
+
+
+
+
+
+ Performance
+
+
+
+
+
+ Avg duration
+ 245ms
+
+
+ Error rate
+ 0.2%
+
+
+ Throttles
+ 0
+
+
+
+
+
+
+
+
+
+ Resources
+
+
+
+
+
+ Layers
+ 2
+
+
+ Event sources
+ 5
+
+
+ Destinations
+ 1
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/services/rds-service.tsx b/console/components/services/rds-service.tsx
new file mode 100644
index 0000000..6ecffb2
--- /dev/null
+++ b/console/components/services/rds-service.tsx
@@ -0,0 +1,286 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ Database,
+ Play,
+ Square,
+ RotateCcw,
+ Settings,
+ RefreshCw,
+ Activity,
+ HardDrive,
+} from "lucide-react"
+
+interface RDSServicePageProps {
+ onBack: () => void
+}
+
+export function RDSServicePage({ onBack }: RDSServicePageProps) {
+ const databases = [
+ {
+ id: "myapp-prod-db",
+ engine: "MySQL",
+ version: "8.0.35",
+ status: "available",
+ instanceClass: "db.t3.medium",
+ storage: "100 GB",
+ endpoint: "myapp-prod-db.cluster-xyz.ap-northeast-1.rds.amazonaws.com",
+ created: "2024-01-15",
+ },
+ {
+ id: "analytics-db",
+ engine: "PostgreSQL",
+ version: "15.4",
+ status: "stopped",
+ instanceClass: "db.r5.large",
+ storage: "500 GB",
+ endpoint: "analytics-db.xyz.ap-northeast-1.rds.amazonaws.com",
+ created: "2024-01-10",
+ },
+ {
+ id: "cache-cluster",
+ engine: "Redis",
+ version: "7.0",
+ status: "available",
+ instanceClass: "cache.t3.micro",
+ storage: "1 GB",
+ endpoint: "cache-cluster.xyz.cache.amazonaws.com",
+ created: "2024-01-20",
+ },
+ ]
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "available":
+ return "bg-green-100 text-green-800"
+ case "stopped":
+ return "bg-red-100 text-red-800"
+ case "creating":
+ return "bg-yellow-100 text-yellow-800"
+ default:
+ return "bg-gray-100 text-gray-800"
+ }
+ }
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
RDS
+
+
+
+
+
+
+
+
+
+ {/* RDS Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Create database
+ Launch new RDS instance
+
+
+
+
+
+ Create snapshot
+ Backup your database
+
+
+
+
+
+ Performance insights
+ Monitor performance
+
+
+
+
+
+ Parameter groups
+ Manage configurations
+
+
+
+
+ {/* Databases List */}
+
+
+
+
Databases
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | DB identifier |
+ Engine |
+ Status |
+ Instance class |
+ Storage |
+ Actions |
+
+
+
+ {databases.map((db, index) => (
+
+ |
+ {db.id}
+ |
+
+ {db.engine} {db.version}
+ |
+
+ {db.status}
+ |
+ {db.instanceClass} |
+ {db.storage} |
+
+
+ {db.status === "available" ? (
+
+ ) : (
+
+ )}
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Database Summary */}
+
+
+
+
+
+ Database Summary
+
+
+
+
+
+ Running databases
+ 2
+
+
+ Stopped databases
+ 1
+
+
+ Total storage
+ 601 GB
+
+
+
+
+
+
+
+
+
+ Backups
+
+
+
+
+
+ Automated backups
+ Enabled
+
+
+ Manual snapshots
+ 5
+
+
+ Backup retention
+ 7 days
+
+
+
+
+
+
+
+
+
+ Performance
+
+
+
+
+
+ CPU utilization
+ 15%
+
+
+ Connections
+ 12/100
+
+
+ Read IOPS
+ 245/sec
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/services/s3-service.tsx b/console/components/services/s3-service.tsx
new file mode 100644
index 0000000..427c431
--- /dev/null
+++ b/console/components/services/s3-service.tsx
@@ -0,0 +1,212 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ Database,
+ Folder,
+ FileText,
+ Download,
+ Upload,
+ Trash2,
+ Settings,
+ RefreshCw,
+} from "lucide-react"
+
+interface S3ServicePageProps {
+ onBack: () => void
+}
+
+export function S3ServicePage({ onBack }: S3ServicePageProps) {
+ const buckets = [
+ { name: "my-app-assets", region: "ap-northeast-1", created: "2024-01-15", objects: 1247, size: "2.3 GB" },
+ { name: "backup-storage", region: "ap-northeast-1", created: "2024-02-01", objects: 89, size: "456 MB" },
+ { name: "logs-archive", region: "ap-northeast-1", created: "2024-01-20", objects: 3421, size: "8.7 GB" },
+ ]
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
S3
+
+
+
+
+
+
+
+
+
+ {/* S3 Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Upload files
+ Upload files to S3
+
+
+
+
+
+ Create bucket
+ Create a new S3 bucket
+
+
+
+
+
+ Bucket policies
+ Manage access policies
+
+
+
+
+
+ Access logs
+ View access logs
+
+
+
+
+ {/* Buckets List */}
+
+
+
+
Buckets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ Region |
+ Date created |
+ Objects |
+ Size |
+ Actions |
+
+
+
+ {buckets.map((bucket, index) => (
+
+ |
+
+
+
+ {bucket.name}
+
+
+ |
+ {bucket.region} |
+ {bucket.created} |
+ {bucket.objects.toLocaleString()} |
+ {bucket.size} |
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Storage Analytics */}
+
+
+
+ Storage usage
+
+
+
+
+ Total storage
+ 11.5 GB
+
+
+ Number of objects
+ 4,757
+
+
+ Number of buckets
+ 3
+
+
+
+
+
+
+
+ Recent activity
+
+
+
+
+
+
Uploaded 15 files to my-app-assets
+
2h ago
+
+
+
+
Created bucket backup-storage
+
1d ago
+
+
+
+
Updated bucket policy for logs-archive
+
3d ago
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/services/vpc-service.tsx b/console/components/services/vpc-service.tsx
new file mode 100644
index 0000000..be32764
--- /dev/null
+++ b/console/components/services/vpc-service.tsx
@@ -0,0 +1,342 @@
+"use client"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import {
+ ArrowLeft,
+ Search,
+ Plus,
+ MoreHorizontal,
+ Shield,
+ Network,
+ Router,
+ Lock,
+ Settings,
+ RefreshCw,
+} from "lucide-react"
+
+interface VPCServicePageProps {
+ onBack: () => void
+}
+
+export function VPCServicePage({ onBack }: VPCServicePageProps) {
+ const vpcs = [
+ {
+ id: "vpc-1234567890abcdef0",
+ name: "Production VPC",
+ cidr: "10.0.0.0/16",
+ state: "available",
+ subnets: 4,
+ routeTables: 2,
+ igw: "Yes",
+ },
+ {
+ id: "vpc-0987654321fedcba0",
+ name: "Development VPC",
+ cidr: "172.16.0.0/16",
+ state: "available",
+ subnets: 2,
+ routeTables: 1,
+ igw: "Yes",
+ },
+ {
+ id: "vpc-abcdef1234567890",
+ name: "Testing VPC",
+ cidr: "192.168.0.0/16",
+ state: "available",
+ subnets: 3,
+ routeTables: 1,
+ igw: "No",
+ },
+ ]
+
+ const subnets = [
+ {
+ id: "subnet-1a2b3c4d",
+ name: "Public Subnet 1",
+ vpc: "Production VPC",
+ cidr: "10.0.1.0/24",
+ az: "ap-northeast-1a",
+ type: "Public",
+ },
+ {
+ id: "subnet-5e6f7g8h",
+ name: "Private Subnet 1",
+ vpc: "Production VPC",
+ cidr: "10.0.2.0/24",
+ az: "ap-northeast-1b",
+ type: "Private",
+ },
+ {
+ id: "subnet-9i0j1k2l",
+ name: "Public Subnet 2",
+ vpc: "Development VPC",
+ cidr: "172.16.1.0/24",
+ az: "ap-northeast-1a",
+ type: "Public",
+ },
+ ]
+
+ return (
+ <>
+ {/* Service Header */}
+
+
+
+
+
+
+
+
VPC
+
+
+
+
+
+
+
+
+
+ {/* VPC Dashboard */}
+
+ {/* Quick Actions */}
+
+
+
+
+ Create VPC
+ Create a new VPC
+
+
+
+
+
+ Create subnet
+ Add subnet to VPC
+
+
+
+
+
+ Route tables
+ Manage routing
+
+
+
+
+
+ Security groups
+ Configure firewall
+
+
+
+
+ {/* VPCs List */}
+
+
+
+
Your VPCs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | VPC ID |
+ Name |
+ IPv4 CIDR |
+ State |
+ Subnets |
+ Internet Gateway |
+ Actions |
+
+
+
+ {vpcs.map((vpc, index) => (
+
+ |
+ {vpc.id}
+ |
+ {vpc.name} |
+ {vpc.cidr} |
+
+ {vpc.state}
+ |
+ {vpc.subnets} |
+ {vpc.igw} |
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Subnets List */}
+
+
+
+
Subnets
+
+
+
+
+
+
+
+
+ | Subnet ID |
+ Name |
+ VPC |
+ IPv4 CIDR |
+ Availability Zone |
+ Type |
+
+
+
+ {subnets.map((subnet, index) => (
+
+ |
+
+ {subnet.id}
+
+ |
+ {subnet.name} |
+ {subnet.vpc} |
+ {subnet.cidr} |
+ {subnet.az} |
+
+
+ {subnet.type}
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Network Summary */}
+
+
+
+
+
+ VPC Summary
+
+
+
+
+
+ Total VPCs
+ 3
+
+
+ Total subnets
+ 9
+
+
+ Internet gateways
+ 2
+
+
+
+
+
+
+
+
+
+ Security
+
+
+
+
+
+ Security groups
+ 8
+
+
+ Network ACLs
+ 3
+
+
+ NAT gateways
+ 2
+
+
+
+
+
+
+
+
+
+ Routing
+
+
+
+
+
+ Route tables
+ 4
+
+
+ VPC endpoints
+ 1
+
+
+ Peering connections
+ 0
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/console/components/theme-provider.tsx b/console/components/theme-provider.tsx
new file mode 100644
index 0000000..55c2f6e
--- /dev/null
+++ b/console/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/console/components/ui/accordion.tsx b/console/components/ui/accordion.tsx
new file mode 100644
index 0000000..4a8cca4
--- /dev/null
+++ b/console/components/ui/accordion.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDownIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/console/components/ui/alert-dialog.tsx b/console/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..0863e40
--- /dev/null
+++ b/console/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+"use client"
+
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/console/components/ui/alert.tsx b/console/components/ui/alert.tsx
new file mode 100644
index 0000000..1421354
--- /dev/null
+++ b/console/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/console/components/ui/aspect-ratio.tsx b/console/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..3df3fd0
--- /dev/null
+++ b/console/components/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+export { AspectRatio }
diff --git a/console/components/ui/avatar.tsx b/console/components/ui/avatar.tsx
new file mode 100644
index 0000000..71e428b
--- /dev/null
+++ b/console/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/console/components/ui/badge.tsx b/console/components/ui/badge.tsx
new file mode 100644
index 0000000..0205413
--- /dev/null
+++ b/console/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span"
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/console/components/ui/breadcrumb.tsx b/console/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..eb88f32
--- /dev/null
+++ b/console/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/console/components/ui/button.tsx b/console/components/ui/button.tsx
new file mode 100644
index 0000000..a2df8dc
--- /dev/null
+++ b/console/components/ui/button.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/console/components/ui/calendar.tsx b/console/components/ui/calendar.tsx
new file mode 100644
index 0000000..4d7c46a
--- /dev/null
+++ b/console/components/ui/calendar.tsx
@@ -0,0 +1,213 @@
+"use client"
+
+import * as React from "react"
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "lucide-react"
+import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"]
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString("default", { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "flex gap-4 flex-col md:flex-row relative",
+ defaultClassNames.months
+ ),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn(
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ "absolute bg-popover inset-0 opacity-0",
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn(
+ "select-none w-(--cell-size)",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] select-none text-muted-foreground",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "rounded-l-md bg-accent",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-muted-foreground aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "text-muted-foreground opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ )
+ }
+
+ if (orientation === "right") {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+