initial commit
This commit is contained in:
719
app/services/cloud-instance/page.tsx
Normal file
719
app/services/cloud-instance/page.tsx
Normal file
@@ -0,0 +1,719 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Header } from '@/components/header'
|
||||
import { Footer } from '@/components/footer'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import {
|
||||
Cloud,
|
||||
Server,
|
||||
HardDrive,
|
||||
Shield,
|
||||
AlertCircle,
|
||||
Check,
|
||||
Cpu,
|
||||
MapPin,
|
||||
ExternalLink,
|
||||
Terminal,
|
||||
} from 'lucide-react'
|
||||
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { checkSufficientBalance, formatCurrency } from '@/lib/balance-service'
|
||||
|
||||
interface Plan {
|
||||
id: string
|
||||
name: string
|
||||
price: {
|
||||
daily: number
|
||||
monthly: number
|
||||
}
|
||||
}
|
||||
|
||||
interface OSImage {
|
||||
id: string
|
||||
name: string
|
||||
price?: {
|
||||
daily: number
|
||||
monthly: number
|
||||
}
|
||||
}
|
||||
|
||||
interface DataCenter {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
interface DeploymentResult {
|
||||
status: string
|
||||
cloudid?: string
|
||||
message?: string
|
||||
password?: string
|
||||
ipv4?: string
|
||||
server_id?: string
|
||||
error?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export default function CloudInstancePage() {
|
||||
const router = useRouter()
|
||||
const { user } = useAuth()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [success, setSuccess] = useState('')
|
||||
const [deploymentResult, setDeploymentResult] = useState<DeploymentResult | null>(null)
|
||||
const [billingCycle, setBillingCycle] = useState<'daily' | 'monthly'>('monthly')
|
||||
|
||||
// Form state
|
||||
const [formData, setFormData] = useState({
|
||||
hostname: '',
|
||||
planId: '',
|
||||
image: '',
|
||||
password: '',
|
||||
dcslug: '',
|
||||
enableBackup: false,
|
||||
enablePublicIp: true,
|
||||
})
|
||||
|
||||
// Available plans
|
||||
const plans: Plan[] = [
|
||||
{ id: '10027', name: '2 vCPU, 4GB RAM, 80GB SSD', price: { daily: 60, monthly: 1600 } },
|
||||
{ id: '10028', name: '4 vCPU, 8GB RAM, 160GB SSD', price: { daily: 80, monthly: 2200 } },
|
||||
{ id: '10029', name: '8 vCPU, 32GB RAM, 480GB SSD', price: { daily: 100, monthly: 2800 } },
|
||||
{ id: '10030', name: '16 vCPU, 64GB RAM, 960GB SSD', price: { daily: 120, monthly: 3200 } },
|
||||
]
|
||||
|
||||
// Available OS images
|
||||
const images: OSImage[] = [
|
||||
{ id: 'almalinux-9.2-x86_64', name: 'Alma Linux 9.2' },
|
||||
{ id: 'centos-7.9-x86_64', name: 'CentOS 7.9' },
|
||||
{ id: 'debian-12-x86_64', name: 'Debian 12' },
|
||||
{ id: 'ubuntu-22.04-x86_64', name: 'Ubuntu 22.04 LTS' },
|
||||
{ id: 'windows-2022', name: 'Windows Server 2022', price: { daily: 200, monthly: 5000 } },
|
||||
]
|
||||
|
||||
// Data centers
|
||||
const dataCenters: DataCenter[] = [
|
||||
{ id: 'inmumbaizone2', name: 'Mumbai, India' },
|
||||
{ id: 'inbangalore', name: 'Bangalore, India' },
|
||||
]
|
||||
|
||||
// Get user balance from auth context
|
||||
const userBalance = user?.balance || 0
|
||||
|
||||
// Calculate total price
|
||||
const calculatePrice = () => {
|
||||
const selectedPlan = plans.find((p) => p.id === formData.planId)
|
||||
const selectedImage = images.find((i) => i.id === formData.image)
|
||||
|
||||
if (!selectedPlan) return 0
|
||||
|
||||
let total = selectedPlan.price[billingCycle]
|
||||
|
||||
// Add Windows license cost if applicable
|
||||
if (selectedImage?.price) {
|
||||
total += selectedImage.price[billingCycle]
|
||||
}
|
||||
|
||||
// Add backup cost (20% of plan price)
|
||||
if (formData.enableBackup) {
|
||||
total += selectedPlan.price[billingCycle] * 0.2
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
const handleDeploy = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setSuccess('')
|
||||
setDeploymentResult(null)
|
||||
|
||||
if (!user) {
|
||||
router.push('/auth?redirect=/services/cloud-instance')
|
||||
return
|
||||
}
|
||||
|
||||
// Validate form
|
||||
if (
|
||||
!formData.hostname ||
|
||||
!formData.planId ||
|
||||
!formData.image ||
|
||||
!formData.password ||
|
||||
!formData.dcslug
|
||||
) {
|
||||
setError('All fields are required')
|
||||
return
|
||||
}
|
||||
|
||||
// Check password strength
|
||||
if (formData.password.length < 8) {
|
||||
setError('Password must be at least 8 characters long')
|
||||
return
|
||||
}
|
||||
|
||||
// Check balance
|
||||
const totalPrice = calculatePrice()
|
||||
const balanceCheck = await checkSufficientBalance(totalPrice)
|
||||
|
||||
if (!balanceCheck.sufficient) {
|
||||
setError(
|
||||
balanceCheck.error ||
|
||||
`Insufficient balance. You need ${formatCurrency(totalPrice)} but only have ${formatCurrency(balanceCheck.currentBalance || 0)}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/services/deploy-cloude', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
hostname: formData.hostname,
|
||||
planid: formData.planId,
|
||||
image: formData.image,
|
||||
dclocation: formData.dcslug,
|
||||
password: formData.password,
|
||||
cycle: billingCycle,
|
||||
amount: totalPrice,
|
||||
backup: formData.enableBackup,
|
||||
publicip: formData.enablePublicIp,
|
||||
}),
|
||||
})
|
||||
|
||||
const result: DeploymentResult = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.message || `Deployment failed with status ${response.status}`)
|
||||
}
|
||||
|
||||
if (result.status === 'success') {
|
||||
setDeploymentResult(result)
|
||||
|
||||
// Create success message with server details
|
||||
let successMessage = 'Cloud instance deployed successfully!'
|
||||
if (result.cloudid) {
|
||||
successMessage += ` Server ID: ${result.cloudid}`
|
||||
}
|
||||
if (result.ipv4) {
|
||||
successMessage += ` | IP: ${result.ipv4}`
|
||||
}
|
||||
|
||||
setSuccess(successMessage)
|
||||
|
||||
// Reset form after successful deployment
|
||||
setFormData({
|
||||
hostname: '',
|
||||
planId: '',
|
||||
image: '',
|
||||
password: '',
|
||||
dcslug: '',
|
||||
enableBackup: false,
|
||||
enablePublicIp: true,
|
||||
})
|
||||
} else {
|
||||
throw new Error(result.message || 'Deployment failed')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Deployment error:', err)
|
||||
setError(err instanceof Error ? err.message : 'Failed to deploy cloud instance')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
setDeploymentResult(null)
|
||||
setSuccess('')
|
||||
setError('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 pt-24 pb-16">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="text-center mb-12">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full mb-4">
|
||||
<Cloud className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold mb-4">Deploy Cloud Instance</h1>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
High-performance cloud servers with instant deployment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!user && (
|
||||
<Alert className="mb-8">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
You need to be logged in to deploy a cloud instance.{' '}
|
||||
<a
|
||||
href="/auth?redirect=/services/cloud-instance"
|
||||
className="text-primary underline"
|
||||
>
|
||||
Click here to login
|
||||
</a>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<Alert className="mb-8 bg-green-50 border-green-200">
|
||||
<Check className="h-4 w-4 text-green-600" />
|
||||
<AlertDescription className="text-green-800">
|
||||
<div className="space-y-3">
|
||||
<div className="font-semibold">{success}</div>
|
||||
|
||||
{deploymentResult?.ipv4 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3">
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-green-700">
|
||||
Server IP Address
|
||||
</Label>
|
||||
<div className="flex items-center mt-1">
|
||||
<Input
|
||||
className="font-mono text-sm bg-green-100 border-green-300"
|
||||
value={deploymentResult.ipv4}
|
||||
readOnly
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(deploymentResult.ipv4!)
|
||||
// You can add a toast notification here
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-green-700">Server ID</Label>
|
||||
<p className="text-sm font-mono bg-green-100 p-2 rounded border border-green-300 mt-1">
|
||||
{deploymentResult.cloudid || deploymentResult.server_id}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{deploymentResult?.password && (
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-medium text-green-700">Root Password</Label>
|
||||
<div className="flex items-center mt-1">
|
||||
<Input
|
||||
type="password"
|
||||
className="font-mono text-sm bg-green-100 border-green-300"
|
||||
value={deploymentResult.password}
|
||||
readOnly
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(deploymentResult.password!)
|
||||
// You can add a toast notification here
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-green-600 mt-1">
|
||||
Please change this password after first login
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{deploymentResult?.message && (
|
||||
<div className="mt-3 p-3 bg-green-100 rounded-md">
|
||||
<p className="text-sm text-green-700">{deploymentResult.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 mt-4 pt-3 border-t border-green-200">
|
||||
<Button
|
||||
onClick={() => router.push('/dashboard/instances')}
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
<Server className="w-4 h-4 mr-2" />
|
||||
View All Instances
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
// SSH connection button - you can implement this
|
||||
if (deploymentResult?.ipv4) {
|
||||
const sshCommand = `ssh root@${deploymentResult.ipv4}`
|
||||
navigator.clipboard.writeText(sshCommand)
|
||||
// Show toast notification
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Terminal className="w-4 h-4 mr-2" />
|
||||
Copy SSH Command
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={resetForm}>
|
||||
Deploy Another
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-xs text-green-600">
|
||||
<p>✅ Deployment successful! Your server is being provisioned.</p>
|
||||
{/* <p>📧 Login details will be sent to your email once ready.</p> */}
|
||||
</div>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{deploymentResult ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Deployment Successful!</CardTitle>
|
||||
<CardDescription>
|
||||
Your cloud instance has been deployed successfully.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>Server ID</Label>
|
||||
<p className="text-sm font-mono bg-muted p-2 rounded">
|
||||
{deploymentResult.server_id}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Status</Label>
|
||||
<p className="text-sm capitalize">{deploymentResult.status}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button onClick={() => router.push('/dashboard/instances')}>
|
||||
View Instances
|
||||
</Button>
|
||||
<Button variant="outline" onClick={resetForm}>
|
||||
Deploy Another
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<form onSubmit={handleDeploy}>
|
||||
<div className="space-y-8">
|
||||
{/* Billing Cycle Selection */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Billing Cycle</CardTitle>
|
||||
<CardDescription>Choose your preferred billing period</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs
|
||||
value={billingCycle}
|
||||
onValueChange={(v) => setBillingCycle(v as 'daily' | 'monthly')}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="daily">Daily</TabsTrigger>
|
||||
<TabsTrigger value="monthly">Monthly (Save 20%)</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Server Configuration */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Server Configuration</CardTitle>
|
||||
<CardDescription>Configure your cloud instance specifications</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="hostname">Hostname</Label>
|
||||
<Input
|
||||
id="hostname"
|
||||
placeholder="my-server.example.com"
|
||||
value={formData.hostname}
|
||||
onChange={(e) => setFormData({ ...formData, hostname: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="plan">Server Plan</Label>
|
||||
<Select
|
||||
value={formData.planId}
|
||||
onValueChange={(value) => setFormData({ ...formData, planId: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a plan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{plans.map((plan) => (
|
||||
<SelectItem key={plan.id} value={plan.id}>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span>{plan.name}</span>
|
||||
<span className="ml-4 text-primary">
|
||||
₹{plan.price[billingCycle]}/
|
||||
{billingCycle === 'daily' ? 'day' : 'month'}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="image">Operating System</Label>
|
||||
<Select
|
||||
value={formData.image}
|
||||
onValueChange={(value) => setFormData({ ...formData, image: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an OS" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{images.map((image) => (
|
||||
<SelectItem key={image.id} value={image.id}>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span>{image.name}</span>
|
||||
{image.price && (
|
||||
<span className="ml-4 text-primary">
|
||||
+₹{image.price[billingCycle]}/
|
||||
{billingCycle === 'daily' ? 'day' : 'month'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="datacenter">Data Center</Label>
|
||||
<Select
|
||||
value={formData.dcslug}
|
||||
onValueChange={(value) => setFormData({ ...formData, dcslug: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select location" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{dataCenters.map((dc) => (
|
||||
<SelectItem key={dc.id} value={dc.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4" />
|
||||
{dc.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="password">Root Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Strong password (min 8 characters)"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Minimum 8 characters required
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="backup"
|
||||
checked={formData.enableBackup}
|
||||
onCheckedChange={(checked) =>
|
||||
setFormData({ ...formData, enableBackup: checked as boolean })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="backup" className="cursor-pointer">
|
||||
Enable automatic backups (+20% of plan cost)
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="publicip"
|
||||
checked={formData.enablePublicIp}
|
||||
onCheckedChange={(checked) =>
|
||||
setFormData({ ...formData, enablePublicIp: checked as boolean })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="publicip" className="cursor-pointer">
|
||||
Enable public IP address
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Pricing Summary */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Order Summary</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Base Plan</span>
|
||||
<span>
|
||||
₹{plans.find((p) => p.id === formData.planId)?.price[billingCycle] || 0}
|
||||
</span>
|
||||
</div>
|
||||
{images.find((i) => i.id === formData.image)?.price && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Windows License</span>
|
||||
<span>
|
||||
₹
|
||||
{images.find((i) => i.id === formData.image)?.price?.[billingCycle] ||
|
||||
0}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{formData.enableBackup && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Backup Service</span>
|
||||
<span>
|
||||
₹
|
||||
{(plans.find((p) => p.id === formData.planId)?.price[billingCycle] ||
|
||||
0) * 0.2}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t pt-2">
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span>Total</span>
|
||||
<span className="text-primary">
|
||||
₹{calculatePrice()}/{billingCycle === 'daily' ? 'day' : 'month'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Your Balance</span>
|
||||
<span
|
||||
className={
|
||||
userBalance >= calculatePrice() ? 'text-green-600' : 'text-red-600'
|
||||
}
|
||||
>
|
||||
₹{userBalance.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Deploy Button */}
|
||||
<Button type="submit" className="w-full" size="lg" disabled={loading || !user}>
|
||||
{loading ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
Deploying...
|
||||
</>
|
||||
) : (
|
||||
'Deploy Cloud Instance'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* Features */}
|
||||
{!deploymentResult && (
|
||||
<div className="mt-16 grid md:grid-cols-3 gap-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Server className="w-8 h-8 text-primary mb-2" />
|
||||
<CardTitle>High Performance</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enterprise-grade hardware with NVMe SSD storage
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Shield className="w-8 h-8 text-primary mb-2" />
|
||||
<CardTitle>Secure & Reliable</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
DDoS protection and 99.99% uptime SLA
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<HardDrive className="w-8 h-8 text-primary mb-2" />
|
||||
<CardTitle>Instant Deployment</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your server ready in less than 60 seconds
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// jRIfuQspDJlWdgXxLmqVFSUthwBOkezibPCyoTvHYcEANraGKMnZ
|
||||
// {
|
||||
// "status": "success",
|
||||
// "cloudid": "1645972",
|
||||
// "message": "Cloud Server deploy in process and as soon it get ready to use system will send you login detail over the email.",
|
||||
// "password": "00000000",
|
||||
// "ipv4": "103.189.89.32"
|
||||
// }
|
||||
Reference in New Issue
Block a user