tst2
parent
b8e20ab510
commit
22fade091d
|
@ -25,6 +25,7 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"image-resize-compress": "^2.1.1",
|
||||||
"lucide-react": "^0.484.0",
|
"lucide-react": "^0.484.0",
|
||||||
"marked": "^15.0.8",
|
"marked": "^15.0.8",
|
||||||
"menubar": "^9.5.1",
|
"menubar": "^9.5.1",
|
||||||
|
@ -7737,6 +7738,15 @@
|
||||||
],
|
],
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/image-resize-compress": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-resize-compress/-/image-resize-compress-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-aF4O1ZzAM0wDziIQZdqlxvDBe163J1Kiu+hSXbYNaGDcD4ggqAgRLLtRmpspnpJPHa4PkhVjwOshzBMHbOWESQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"image-resize-compress": "^2.1.1",
|
||||||
"lucide-react": "^0.484.0",
|
"lucide-react": "^0.484.0",
|
||||||
"marked": "^15.0.8",
|
"marked": "^15.0.8",
|
||||||
"menubar": "^9.5.1",
|
"menubar": "^9.5.1",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Skipped minification because the original files appears to be already minified.
|
||||||
|
* Original file: /npm/image-resize-compress@2.1.1/dist/index.js
|
||||||
|
*
|
||||||
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
*/
|
||||||
|
var h=e=>({png:"image/png",webp:"image/webp",bmp:"image/bmp",gif:"image/gif",jpeg:"image/jpeg"})[e]||"image/jpeg",w=(e,t="auto",r="auto")=>{let o=t==="auto"||t===0,m=r==="auto"||r===0;if(!o&&!m)return{width:t,height:r};if(!o){let n=e.naturalWidth/t;return{width:t,height:Math.round((e.naturalHeight/n+Number.EPSILON)*100)/100}}if(!m){let n=e.naturalHeight/r;return{width:Math.round((e.naturalWidth/n+Number.EPSILON)*100)/100,height:r}}return{width:e.naturalWidth,height:e.naturalHeight}},E=async(e,t=100,r="auto",o="auto",m=null,n=null)=>{if(!(e instanceof Blob))throw new TypeError(`Expected a Blob or File, but got ${typeof e}.`);if(e.size===0)throw new Error("Failed to load the image. The file might be corrupt or empty.");if(t<=0)throw new RangeError("Quality must be greater than 0.");if(typeof r=="number"&&r<0||typeof o=="number"&&o<0)throw new RangeError("Invalid width or height value!");let a=m?h(m):e.type,g=t<1?t:t/100;return new Promise((p,l)=>{let u=new FileReader;u.onload=()=>{let s=new Image;s.src=u.result,s.onload=()=>{let i=document.createElement("canvas"),d=w(s,r,o);i.width=d.width,i.height=d.height;let f=i.getContext("2d");if(!f){l(new Error("Failed to get canvas context."));return}n&&a==="image/png"&&(f.fillStyle=n,f.fillRect(0,0,i.width,i.height)),f.drawImage(s,0,0,i.width,i.height),i.toBlob(b=>{if(b===null)return l(new Error("Failed to generate image blob."));p(new Blob([b],{type:a}))},a,g)},s.onerror=()=>{l(new Error("Failed to load the image. The file might be corrupt or empty."))}},u.onerror=()=>l(new Error("Failed to read the blob as a Data URL.")),u.readAsDataURL(e)})},c=E;var F=async(e,t=100,r="auto",o="auto",m,n)=>{try{let a=await fetch(e,n);if(!a.ok)throw new Error(`Failed to fetch image: ${a.statusText}`);let g=await a.blob();return await c(g,t,r,o,m)}catch(a){throw new Error(`Failed to process the image from URL. Check CORS or network issues. Error: ${a}`)}},y=F;var I=e=>new Promise((t,r)=>{if(e.size===0)return r(new Error("Cannot convert empty Blob."));if(e.size>10485760)return r(new Error("File size exceeds the maximum allowed limit."));let o=new FileReader;o.onloadend=()=>{o.result?t(o.result):r(new Error("Failed to convert blob to DataURL."))},o.onerror=()=>r(new Error("Error reading blob.")),o.readAsDataURL(e)}),R=I;var L=async(e,t)=>{try{let r=await fetch(e,t);if(!r.ok)throw new Error(`Failed to fetch image: ${r.statusText}`);return await r.blob()}catch(r){throw new Error(`Failed to fetch image from URL. Error: ${r}`)}},T=L;export{R as blobToURL,c as fromBlob,y as fromURL,T as urlToBlob};
|
||||||
|
/*!
|
||||||
|
* image-resize-compress
|
||||||
|
* Copyright(c) 2024 Álef Duarte
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
|
@ -0,0 +1,433 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||||
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "../ui/select";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import Loader from "../ui/loader";
|
||||||
|
import { useToast } from "../ui/toast";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
|
||||||
|
export default function NewKubernetesService() {
|
||||||
|
const { showToast } = useToast();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isFetchingData, setIsFetchingData] = useState(true);
|
||||||
|
const [plans, setPlans] = useState([]);
|
||||||
|
const [vpcs, setVpcs] = useState([]);
|
||||||
|
const UTHO_API_KEY = "Bearer IoNXhkRJsQPyOEqFMceSfzuKaDLrpxUCATgZjiVdvYlBHbwWmGtn";
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
datacenter: "mumbai",
|
||||||
|
plan: "",
|
||||||
|
name: "",
|
||||||
|
k8s_version: "1.27",
|
||||||
|
nodes: 1,
|
||||||
|
storage: "50",
|
||||||
|
public_ip: true,
|
||||||
|
auto_scaling: false,
|
||||||
|
min_nodes: 1,
|
||||||
|
max_nodes: 3,
|
||||||
|
vpc: "",
|
||||||
|
subnet: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchInitialData = async () => {
|
||||||
|
setIsFetchingData(true);
|
||||||
|
try {
|
||||||
|
const [plansResponse, vpcsResponse] = await Promise.all([
|
||||||
|
fetch("https://api.utho.com/v2/plans", {
|
||||||
|
headers: {
|
||||||
|
"Authorization": UTHO_API_KEY,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fetch("https://api.utho.com/v2/vpc", {
|
||||||
|
headers: {
|
||||||
|
"Authorization": UTHO_API_KEY,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
const plansData = await plansResponse.json();
|
||||||
|
const vpcsData = await vpcsResponse.json();
|
||||||
|
console.log('vpcsData.vpc', vpcsData.vpc)
|
||||||
|
setPlans(plansData.plans || []);
|
||||||
|
setVpcs(vpcsData.vpc || []);
|
||||||
|
|
||||||
|
// Set default VPC and subnet if available
|
||||||
|
if (vpcsData.vpc?.length > 0) {
|
||||||
|
const firstVpc = vpcsData.vpc[0];
|
||||||
|
const firstSubnet = firstVpc.subnets?.[0]?.id || "";
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
vpc: firstVpc.id,
|
||||||
|
subnet: firstSubnet
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Initial data fetch error:", error);
|
||||||
|
showToast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Failed to load initial configuration data",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsFetchingData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchInitialData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVpcChange = (vpcId) => {
|
||||||
|
const selectedVpc = vpcs.find(vpc => vpc.id === vpcId);
|
||||||
|
const firstSubnet = selectedVpc?.subnet?.[0]?.id || "";
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
vpc: vpcId,
|
||||||
|
subnet: firstSubnet
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrentSubnets = () => {
|
||||||
|
const selectedVpc = vpcs.find(vpc => vpc.id === formData.vpc);
|
||||||
|
return selectedVpc?.subnets || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!formData.vpc || !formData.subnet) {
|
||||||
|
throw new Error("Please select both VPC and Subnet to proceed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
datacenter: formData.datacenter,
|
||||||
|
plan: formData.plan,
|
||||||
|
name: formData.name,
|
||||||
|
k8s_version: formData.k8s_version,
|
||||||
|
nodes: formData.nodes,
|
||||||
|
storage: formData.storage,
|
||||||
|
public_ip: formData.public_ip,
|
||||||
|
vpc: formData.vpc,
|
||||||
|
subnet: formData.subnet
|
||||||
|
};
|
||||||
|
|
||||||
|
if (formData.auto_scaling) {
|
||||||
|
payload.auto_scaling = true;
|
||||||
|
payload.min_nodes = formData.min_nodes;
|
||||||
|
payload.max_nodes = formData.max_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch("https://api.utho.com/v2/kubernetes/deploy", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": UTHO_API_KEY,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.message || "Failed to deploy Kubernetes cluster");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.status === "success") {
|
||||||
|
showToast({
|
||||||
|
title: "Success",
|
||||||
|
description: `Cluster ${data.cluster_id || data.id} is being deployed`,
|
||||||
|
variant: "success"
|
||||||
|
});
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
name: "",
|
||||||
|
plan: "",
|
||||||
|
nodes: 1
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || "Failed to deploy Kubernetes cluster");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast({
|
||||||
|
title: "Deployment Failed",
|
||||||
|
description: error.message,
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
console.error("Deployment error:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value, type, checked } = e.target;
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: type === "checkbox" ? checked : value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isFetchingData) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-64">
|
||||||
|
<Loader />
|
||||||
|
<span className="ml-2">Loading configuration...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSubnets = getCurrentSubnets();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full max-w-2xl mx-auto my-4">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Deploy New Kubernetes Cluster</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure your Kubernetes cluster with the required parameters
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
{/* Name, Datacenter, and Kubernetes Version fields */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">Cluster Name *</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="my-cluster"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="datacenter">Data Center *</Label>
|
||||||
|
<Select
|
||||||
|
name="datacenter"
|
||||||
|
value={formData.datacenter}
|
||||||
|
onValueChange={(value) => setFormData({...formData, datacenter: value})}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select location" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="mumbai">Mumbai</SelectItem>
|
||||||
|
<SelectItem value="noida">Noida</SelectItem>
|
||||||
|
<SelectItem value="delhi">Delhi</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="k8s_version">Kubernetes Version *</Label>
|
||||||
|
<Select
|
||||||
|
name="k8s_version"
|
||||||
|
value={formData.k8s_version}
|
||||||
|
onValueChange={(value) => setFormData({...formData, k8s_version: value})}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select version" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="1.27">1.27 (Latest)</SelectItem>
|
||||||
|
<SelectItem value="1.26">1.26</SelectItem>
|
||||||
|
<SelectItem value="1.25">1.25</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Node Plan and Count */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="plan">Node Plan *</Label>
|
||||||
|
<Select
|
||||||
|
name="plan"
|
||||||
|
value={formData.plan}
|
||||||
|
onValueChange={(value) => setFormData({...formData, plan: value})}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a plan" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{plans.map(plan => (
|
||||||
|
<SelectItem key={plan.id} value={plan.id}>
|
||||||
|
{`${plan.cpu} vCPU, ${Math.floor(parseInt(plan.ram)/1024)}GB RAM, ${plan.disk}GB Disk`}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nodes">Initial Node Count *</Label>
|
||||||
|
<Input
|
||||||
|
id="nodes"
|
||||||
|
name="nodes"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
value={formData.nodes}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* VPC and Subnet Selection */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="vpc">VPC *</Label>
|
||||||
|
<Select
|
||||||
|
name="vpc"
|
||||||
|
value={formData.vpc}
|
||||||
|
onValueChange={handleVpcChange}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select VPC" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{vpcs.map(vpc => (
|
||||||
|
<SelectItem key={vpc.id} value={vpc.id}>
|
||||||
|
{vpc.name || vpc.id}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="subnet">Subnet *</Label>
|
||||||
|
<Select
|
||||||
|
name="subnet"
|
||||||
|
value={formData.subnet}
|
||||||
|
onValueChange={(value) => setFormData({...formData, subnet: value})}
|
||||||
|
required
|
||||||
|
disabled={!formData.vpc || currentSubnets.length === 0}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={currentSubnets.length === 0 ? "No subnets available" : "Select subnet"}>
|
||||||
|
{formData.subnet || (currentSubnets.length === 0 ? "No subnets available" : "Select subnet")}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{currentSubnets.map(subnet => (
|
||||||
|
<SelectItem key={subnet.id} value={subnet.id}>
|
||||||
|
{subnet.name || subnet.id} ({subnet.network}/{subnet.size})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{currentSubnets.length === 0 && formData.vpc && (
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
No subnets found in this VPC. Please create a subnet first.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Storage and Advanced Options */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="storage">Storage per Node (GB) *</Label>
|
||||||
|
<Select
|
||||||
|
name="storage"
|
||||||
|
value={formData.storage}
|
||||||
|
onValueChange={(value) => setFormData({...formData, storage: value})}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select storage" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="50">50 GB</SelectItem>
|
||||||
|
<SelectItem value="100">100 GB</SelectItem>
|
||||||
|
<SelectItem value="200">200 GB</SelectItem>
|
||||||
|
<SelectItem value="500">500 GB</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-medium">Advanced Options</h3>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch
|
||||||
|
id="public_ip"
|
||||||
|
checked={formData.public_ip}
|
||||||
|
onCheckedChange={(checked) => setFormData({...formData, public_ip: checked})}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="public_ip">Enable Public IP for Nodes</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch
|
||||||
|
id="auto_scaling"
|
||||||
|
checked={formData.auto_scaling}
|
||||||
|
onCheckedChange={(checked) => setFormData({...formData, auto_scaling: checked})}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto_scaling">Enable Auto Scaling</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formData.auto_scaling && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 border rounded-lg">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="min_nodes">Minimum Nodes</Label>
|
||||||
|
<Input
|
||||||
|
id="min_nodes"
|
||||||
|
name="min_nodes"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
value={formData.min_nodes}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="max_nodes">Maximum Nodes</Label>
|
||||||
|
<Input
|
||||||
|
id="max_nodes"
|
||||||
|
name="max_nodes"
|
||||||
|
type="number"
|
||||||
|
min={formData.min_nodes}
|
||||||
|
max="20"
|
||||||
|
value={formData.max_nodes}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end pt-4">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading || !formData.vpc || !formData.subnet}
|
||||||
|
className="w-full md:w-auto"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<>
|
||||||
|
<Loader className="mr-2 h-4 w-4" />
|
||||||
|
Deploying...
|
||||||
|
</>
|
||||||
|
) : "Deploy Kubernetes Cluster"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -57,6 +57,14 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
||||||
hestia: { monthly: 200, yearly: 2000 }
|
hestia: { monthly: 200, yearly: 2000 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeploymentType = (e) => {
|
||||||
|
console.log('setDeploymentType ',e.target.value);
|
||||||
|
setDeploymentType(e.target.value);
|
||||||
|
if(e.target.value === 'vpn'){
|
||||||
|
window.location.href = '/services/buy-vpn';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show toast notification
|
// Show toast notification
|
||||||
const showToast = useCallback((message) => {
|
const showToast = useCallback((message) => {
|
||||||
setToast({ visible: true, message });
|
setToast({ visible: true, message });
|
||||||
|
@ -451,54 +459,54 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'vpn':
|
// case 'vpn':
|
||||||
return (
|
// return (
|
||||||
<>
|
// <>
|
||||||
<div className="space-y-2">
|
// <div className="space-y-2">
|
||||||
<label htmlFor="vpn-continent" className="block text-white font-medium">Select Continent</label>
|
// <label htmlFor="vpn-continent" className="block text-white font-medium">Select Continent</label>
|
||||||
<select
|
// <select
|
||||||
id="vpn-continent"
|
// id="vpn-continent"
|
||||||
value={vpnContinent}
|
// value={vpnContinent}
|
||||||
onChange={(e) => setVpnContinent(e.target.value)}
|
// onChange={(e) => setVpnContinent(e.target.value)}
|
||||||
className="w-full rounded-md py-2 px-3 bg-neutral-700 border border-neutral-600 text-white focus:outline-none focus:ring-2 focus:ring-[#6d9e37]"
|
// className="w-full rounded-md py-2 px-3 bg-neutral-700 border border-neutral-600 text-white focus:outline-none focus:ring-2 focus:ring-[#6d9e37]"
|
||||||
>
|
// >
|
||||||
<option value="">-Select-</option>
|
// <option value="">-Select-</option>
|
||||||
<option value="India">India</option>
|
// <option value="India">India</option>
|
||||||
<option value="America">America</option>
|
// <option value="America">America</option>
|
||||||
<option value="Europe">Europe</option>
|
// <option value="Europe">Europe</option>
|
||||||
</select>
|
// </select>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<ul className="flex justify-between text-sm">
|
// <ul className="flex justify-between text-sm">
|
||||||
{["600 GB bandwidth", "WireGuard® protocol", "Global server locations", "No activity logs"].map((feature, index) => (
|
// {["600 GB bandwidth", "WireGuard® protocol", "Global server locations", "No activity logs"].map((feature, index) => (
|
||||||
<li key={index} className="flex items-start">
|
// <li key={index} className="flex items-start">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-[#6d9e37] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" ><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
|
// <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-[#6d9e37] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" ><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
|
||||||
<span className='text-zinc-400'>{feature}</span>
|
// <span className='text-zinc-400'>{feature}</span>
|
||||||
</li>
|
// </li>
|
||||||
))}
|
// ))}
|
||||||
</ul>
|
// </ul>
|
||||||
<div className='flex flex-row justify-between items-center gap-x-6'>
|
// <div className='flex flex-row justify-between items-center gap-x-6'>
|
||||||
<label className={`border ${selectedCycle === 'monthly' ? 'bg-[#6d9e37]' : ''} border-[#6d9e37] text-white px-4 py-2 text-center rounded-md w-full cursor-pointer`}>
|
// <label className={`border ${selectedCycle === 'monthly' ? 'bg-[#6d9e37]' : ''} border-[#6d9e37] text-white px-4 py-2 text-center rounded-md w-full cursor-pointer`}>
|
||||||
<input type="checkbox" checked={selectedCycle === 'monthly'} onChange={() => handleBillingCycleChange('monthly', PRICE_CONFIG.vpn.monthly)} className="hidden" />
|
// <input type="checkbox" checked={selectedCycle === 'monthly'} onChange={() => handleBillingCycleChange('monthly', PRICE_CONFIG.vpn.monthly)} className="hidden" />
|
||||||
<p className='text-3xl font-bold text-center'>₹{PRICE_CONFIG.vpn.monthly}</p>
|
// <p className='text-3xl font-bold text-center'>₹{PRICE_CONFIG.vpn.monthly}</p>
|
||||||
<span>Monthly</span>
|
// <span>Monthly</span>
|
||||||
</label>
|
// </label>
|
||||||
|
|
||||||
<label className={`border ${selectedCycle === 'yearly' ? 'bg-[#6d9e37]' : ''} border-[#6d9e37] text-white px-4 py-2 text-center rounded-md w-full cursor-pointer`}>
|
// <label className={`border ${selectedCycle === 'yearly' ? 'bg-[#6d9e37]' : ''} border-[#6d9e37] text-white px-4 py-2 text-center rounded-md w-full cursor-pointer`}>
|
||||||
<input type="checkbox" checked={selectedCycle === 'yearly'} onChange={() => handleBillingCycleChange('yearly', PRICE_CONFIG.vpn.yearly)} className="hidden" />
|
// <input type="checkbox" checked={selectedCycle === 'yearly'} onChange={() => handleBillingCycleChange('yearly', PRICE_CONFIG.vpn.yearly)} className="hidden" />
|
||||||
<p className='text-3xl font-bold text-center'>₹{PRICE_CONFIG.vpn.yearly}</p>
|
// <p className='text-3xl font-bold text-center'>₹{PRICE_CONFIG.vpn.yearly}</p>
|
||||||
<span>Yearly</span>
|
// <span>Yearly</span>
|
||||||
</label>
|
// </label>
|
||||||
</div>
|
// </div>
|
||||||
<div className='text-white'>
|
// <div className='text-white'>
|
||||||
{selectedCycle && (
|
// {selectedCycle && (
|
||||||
<p className={`${selectedCycle === 'monthly' ? 'text-left' : 'text-end'}`}>You selected <strong>{selectedCycle}</strong> plan at ₹{selectedPrice}</p>
|
// <p className={`${selectedCycle === 'monthly' ? 'text-left' : 'text-end'}`}>You selected <strong>{selectedCycle}</strong> plan at ₹{selectedPrice}</p>
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<Button onClick={() => handlePurchase('vpn')} className='w-full'>Proceed to Pay</Button>
|
// <Button onClick={() => handlePurchase('vpn')} className='w-full'>Proceed to Pay</Button>
|
||||||
</>
|
// </>
|
||||||
);
|
// );
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
@ -514,7 +522,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
||||||
<select
|
<select
|
||||||
id="deployment-type"
|
id="deployment-type"
|
||||||
value={deploymentType}
|
value={deploymentType}
|
||||||
onChange={(e) => setDeploymentType(e.target.value)}
|
onChange={handleDeploymentType}
|
||||||
className="w-full rounded-md py-2 px-3 bg-neutral-700 border border-neutral-600 text-white focus:outline-none focus:ring-2 focus:ring-[#6d9e37]"
|
className="w-full rounded-md py-2 px-3 bg-neutral-700 border border-neutral-600 text-white focus:outline-none focus:ring-2 focus:ring-[#6d9e37]"
|
||||||
>
|
>
|
||||||
<option value="app">💻 Deploy an App</option>
|
<option value="app">💻 Deploy an App</option>
|
||||||
|
|
|
@ -8,15 +8,18 @@ export interface ServiceCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
features: string[];
|
features: [];
|
||||||
learnMoreUrl: string;
|
learnMoreUrl: string;
|
||||||
buyButtonText: string;
|
buyButtonText: string;
|
||||||
buyButtonUrl: string;
|
buyButtonUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServiceCard({ title, description, imageUrl, features, learnMoreUrl, buyButtonText, buyButtonUrl }: ServiceCardProps) {
|
export function ServiceCard({ title, description, imageUrl, features, learnMoreUrl, buyButtonText, buyButtonUrl }: ServiceCardProps) {
|
||||||
|
// console.log('checkType', typeof features)
|
||||||
|
// console.log('checkType', features)
|
||||||
const { isLoggedIn, loading, sessionData } = useIsLoggedIn();
|
const { isLoggedIn, loading, sessionData } = useIsLoggedIn();
|
||||||
const [showLoginModal, setShowLoginModal] = useState(false);
|
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||||
|
const parsedFeatures = typeof features === 'string' ? JSON.parse(features) : features;
|
||||||
|
|
||||||
const handleBuyClick = () => {
|
const handleBuyClick = () => {
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
|
@ -43,12 +46,16 @@ export function ServiceCard({ title, description, imageUrl, features, learnMoreU
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex-grow">
|
<CardContent className="flex-grow">
|
||||||
<ul className="space-y-2 text-sm">
|
<ul className="space-y-2 text-sm">
|
||||||
{features.map((feature, index) => (
|
{
|
||||||
|
parsedFeatures && (
|
||||||
|
parsedFeatures.map((feature : string, index: number) => (
|
||||||
<li key={index} className="flex items-start">
|
<li key={index} className="flex items-start">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-[#6d9e37] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-[#6d9e37] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
|
||||||
<span className='text-zinc-600'>{feature}</span>
|
<span className='text-zinc-600'>{feature}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { fromBlob, blobToURL } from "image-resize-compress";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
ImageIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
SlidersHorizontal,
|
||||||
|
FileImage,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export default function ImageResize() {
|
||||||
|
const [previewUrl, setPreviewUrl] = useState(null);
|
||||||
|
const [resizedBlob, setResizedBlob] = useState(null);
|
||||||
|
const [imgQuality, setImgQuality] = useState(80);
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const [originalSize, setOriginalSize] = useState(0);
|
||||||
|
const [resizedSize, setResizedSize] = useState(0);
|
||||||
|
const [originalExtension, setOriginalExtension] = useState("webp");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const resizeImage = async () => {
|
||||||
|
if (!selectedFile) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const width = "auto";
|
||||||
|
const height = "auto";
|
||||||
|
const ext = selectedFile.name.split(".").pop().toLowerCase();
|
||||||
|
|
||||||
|
setOriginalExtension(ext);
|
||||||
|
setOriginalSize((selectedFile.size / 1024).toFixed(2)); // KB
|
||||||
|
|
||||||
|
// Only apply quality change for JPEG or WebP
|
||||||
|
const format = ["jpg", "jpeg", "webp"].includes(ext)
|
||||||
|
? ext
|
||||||
|
: "jpeg"; // fallback to jpeg for better quality control
|
||||||
|
|
||||||
|
const resized = await fromBlob(selectedFile, imgQuality, width, height, format);
|
||||||
|
const url = await blobToURL(resized);
|
||||||
|
|
||||||
|
setResizedBlob(resized);
|
||||||
|
setResizedSize((resized.size / 1024).toFixed(2)); // KB
|
||||||
|
setPreviewUrl(url);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Image resizing failed:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resizeImage();
|
||||||
|
}, [selectedFile, imgQuality]);
|
||||||
|
|
||||||
|
const handleFileChange = (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
setSelectedFile(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
if (!resizedBlob) return;
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = URL.createObjectURL(resizedBlob);
|
||||||
|
link.download = `resized-image.${originalExtension}`;
|
||||||
|
link.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen text-white p-6 flex items-center justify-center">
|
||||||
|
<div className="bg-gray-800 rounded-2xl shadow-lg w-full max-w-2xl p-6 space-y-6">
|
||||||
|
<h2 className="text-2xl font-bold flex items-center gap-2 text-[#6d9e37]">
|
||||||
|
<ImageIcon className="w-6 h-6" />
|
||||||
|
Image Resizer
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<label className="flex items-center gap-2 text-sm text-gray-300 font-medium">
|
||||||
|
<FileImage className="w-4 h-4" />
|
||||||
|
Choose an image
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="w-full p-2 bg-gray-700 border border-gray-600 rounded-lg text-sm file:bg-[#6d9e37] file:text-white file:border-0 file:px-3 file:py-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium flex items-center gap-1">
|
||||||
|
<SlidersHorizontal className="w-4 h-4" />
|
||||||
|
Quality: <span className="text-[#6d9e37]">{imgQuality}%</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="1"
|
||||||
|
max="100"
|
||||||
|
value={imgQuality}
|
||||||
|
onChange={(e) => setImgQuality(e.target.value)}
|
||||||
|
className="w-full accent-[#6d9e37]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{previewUrl && (
|
||||||
|
<div className="border-t border-gray-700 pt-4 space-y-3">
|
||||||
|
<div className="text-sm text-gray-300 grid grid-cols-2 gap-2">
|
||||||
|
<p>
|
||||||
|
<strong>Original Size:</strong> {originalSize} KB
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Resized Size:</strong> {resizedSize} KB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-gray-400 mb-2">Preview:</p>
|
||||||
|
<img
|
||||||
|
src={previewUrl}
|
||||||
|
alt="Resized Preview"
|
||||||
|
className="w-full h-auto max-h-96 object-contain rounded-lg border border-gray-700"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleDownload}
|
||||||
|
className="mt-4 w-full gap-2"
|
||||||
|
>
|
||||||
|
<DownloadIcon className="w-5 h-5" />
|
||||||
|
Download Resized Image
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,50 +2,12 @@
|
||||||
import '../styles/global.css';
|
import '../styles/global.css';
|
||||||
import LoginProfile from '../components/LoginOrProfile';
|
import LoginProfile from '../components/LoginOrProfile';
|
||||||
|
|
||||||
interface Props {
|
interface Props { title: string; description?: string; ogImage?: string; canonicalURL?: string; type?: 'website' | 'article'; newSchema1 : string}
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
ogImage?: string;
|
|
||||||
canonicalURL?: string;
|
|
||||||
type?: 'website' | 'article';
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const { title, description, ogImage, canonicalURL = new URL(Astro.url.pathname, Astro.site).href, type = "website", newSchema1} = Astro.props;
|
||||||
title,
|
|
||||||
description = "SiliconPin offers high-performance hosting solutions for PHP, Node.js, Python, K8s, and K3s applications with 24/7 support.",
|
|
||||||
ogImage,
|
|
||||||
canonicalURL = new URL(Astro.url.pathname, Astro.site).href,
|
|
||||||
type = "website",
|
|
||||||
} = Astro.props;
|
|
||||||
|
|
||||||
// Organization schema
|
// Organization schema
|
||||||
const organizationSchema = {
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "Organization",
|
|
||||||
"name": "SiliconPin",
|
|
||||||
"url": "https://siliconpin.com",
|
|
||||||
"logo": "https://siliconpin.com/assets/logo.svg",
|
|
||||||
"contactPoint": {
|
|
||||||
"@type": "ContactPoint",
|
|
||||||
"telephone": "+91-700-160-1485",
|
|
||||||
"contactType": "customer service",
|
|
||||||
"availableLanguage": ["English", "Hindi", "Bengali"]
|
|
||||||
},
|
|
||||||
"address": {
|
|
||||||
"@type": "PostalAddress",
|
|
||||||
"streetAddress": "121 Lalbari, GourBongo Road",
|
|
||||||
"addressLocality": "Habra",
|
|
||||||
"addressRegion": "W.B.",
|
|
||||||
"postalCode": "743271",
|
|
||||||
"addressCountry": "India"
|
|
||||||
},
|
|
||||||
"sameAs": [
|
|
||||||
"https://www.linkedin.com/company/siliconpin",
|
|
||||||
"https://x.com/dwd_consultancy",
|
|
||||||
"https://www.facebook.com/profile.php?id=100088549643337",
|
|
||||||
"https://instagram.com/siliconpin.com_"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -83,7 +45,9 @@ const organizationSchema = {
|
||||||
<link rel="apple-touch-icon" href="https://siliconpin.com/assets/logo.svg" />
|
<link rel="apple-touch-icon" href="https://siliconpin.com/assets/logo.svg" />
|
||||||
|
|
||||||
<!-- Structured Data -->
|
<!-- Structured Data -->
|
||||||
<script type="application/ld+json" set:html={JSON.stringify(organizationSchema)}></script>
|
<script type="application/ld+json" set:html={newSchema1}></script>
|
||||||
|
<!-- <script type="application/ld+json" set:html={JSON.stringify(organizationSchema)}></script>
|
||||||
|
<script type="application/ld+json" set:html={JSON.stringify(organizationSchema)}></script> -->
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen flex flex-col">
|
<body class="min-h-screen flex flex-col">
|
||||||
<!-- Top header - visible on all devices -->
|
<!-- Top header - visible on all devices -->
|
||||||
|
@ -96,12 +60,12 @@ const organizationSchema = {
|
||||||
<!-- Desktop navigation -->
|
<!-- Desktop navigation -->
|
||||||
<nav class="hidden md:flex items-center gap-6" aria-label="Main Navigation">
|
<nav class="hidden md:flex items-center gap-6" aria-label="Main Navigation">
|
||||||
<a href="/" class="hover:text-[#6d9e37] transition-colors">Home</a>
|
<a href="/" class="hover:text-[#6d9e37] transition-colors">Home</a>
|
||||||
<a href="/services" class="hover:text-[#6d9e37] transition-colors">Services</a>
|
<!-- <a href="/services" class="hover:text-[#6d9e37] transition-colors">Services</a> -->
|
||||||
<a href="/topic" class="hover:text-[#6d9e37] transition-colors">Topic</a>
|
<a href="/topic" class="hover:text-[#6d9e37] transition-colors">Topic</a>
|
||||||
<a href="/contact" class="hover:text-[#6d9e37] transition-colors">Contact</a>
|
<a href="/contact" class="hover:text-[#6d9e37] transition-colors">Contact</a>
|
||||||
<LoginProfile deviceType="desktop" client:load />
|
<LoginProfile deviceType="desktop" client:load />
|
||||||
<!-- <a href="/profile" class="hover:text-[#6d9e37] transition-colors"></a> -->
|
<!-- <a href="/profile" class="hover:text-[#6d9e37] transition-colors"></a> -->
|
||||||
<a href="/get-started" class="inline-flex items-center justify-center rounded-md bg-[#6d9e37] px-4 py-2 text-sm font-medium text-white hover:bg-[#598035] transition-colors">Get Started</a>
|
<a href="/services" class="inline-flex items-center justify-center rounded-md bg-[#6d9e37] px-4 py-2 text-sm font-medium text-white hover:bg-[#598035] transition-colors">Services</a>
|
||||||
</nav>
|
</nav>
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<div class="md:hidden relative">
|
<div class="md:hidden relative">
|
||||||
|
@ -112,11 +76,11 @@ const organizationSchema = {
|
||||||
<!-- Mobile dropdown menu -->
|
<!-- Mobile dropdown menu -->
|
||||||
<div id="mobileMenu" class="absolute right-0 mt-2 w-48 bg-neutral-800 rounded-md shadow-lg py-1 z-50 hidden border border-neutral-700">
|
<div id="mobileMenu" class="absolute right-0 mt-2 w-48 bg-neutral-800 rounded-md shadow-lg py-1 z-50 hidden border border-neutral-700">
|
||||||
<a href="/" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Home</a>
|
<a href="/" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Home</a>
|
||||||
<a href="/services" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Services</a>
|
<!-- <a href="/services" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Services</a> -->
|
||||||
<a href="/topic" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Topic</a>
|
<a href="/topic" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Topic</a>
|
||||||
<a href="/contact" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Contact</a>
|
<a href="/contact" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Contact</a>
|
||||||
<a href="/about-us" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">About Us</a>
|
<a href="/about-us" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">About Us</a>
|
||||||
<a href="/get-started" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700 font-medium text-[#6d9e37]">Get Started</a>
|
<a href="/services" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700 font-medium text-[#6d9e37]">Services</a>
|
||||||
<a href="/suggestion-or-report" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Suggestion or Report</a>
|
<a href="/suggestion-or-report" class="block px-4 py-2 text-sm text-white hover:bg-neutral-700">Suggestion or Report</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -170,8 +134,8 @@ const organizationSchema = {
|
||||||
<div class="bg-neutral-800 p-5 rounded-lg border border-neutral-700 shadow-lg">
|
<div class="bg-neutral-800 p-5 rounded-lg border border-neutral-700 shadow-lg">
|
||||||
<h3 class="font-medium text-lg text-[#6d9e37] mb-4 pb-2 border-b border-[#6d9e37]">Services</h3>
|
<h3 class="font-medium text-lg text-[#6d9e37] mb-4 pb-2 border-b border-[#6d9e37]">Services</h3>
|
||||||
<ul class="space-y-2 text-neutral-400">
|
<ul class="space-y-2 text-neutral-400">
|
||||||
<li class="flex items-center gap-2">📦 <a href="/services" class="hover:text-[#6d9e37] transition-colors">All Services</a></li>
|
<!-- <li class="flex items-center gap-2">📦 <a href="/services" class="hover:text-[#6d9e37] transition-colors">All Services</a></li> -->
|
||||||
<li class="flex items-center gap-2">🚀 <a href="/get-started" class="hover:text-[#6d9e37] transition-colors">Get Started</a></li>
|
<li class="flex items-center gap-2">🚀 <a href="/services" class="hover:text-[#6d9e37] transition-colors">Services</a></li>
|
||||||
<li class="flex items-center gap-2">👨💻 <a href="/hire-developer" class="hover:text-[#6d9e37] transition-colors">Hire a Human Developer</a></li>
|
<li class="flex items-center gap-2">👨💻 <a href="/hire-developer" class="hover:text-[#6d9e37] transition-colors">Hire a Human Developer</a></li>
|
||||||
<li class="flex items-center gap-2">🤖 <a href="/hire-ai-agent" class="hover:text-[#6d9e37] transition-colors">Hire an AI Agent</a></li>
|
<li class="flex items-center gap-2">🤖 <a href="/hire-ai-agent" class="hover:text-[#6d9e37] transition-colors">Hire an AI Agent</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -226,7 +190,7 @@ const organizationSchema = {
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
||||||
<span class="text-xs mt-1">Services</span>
|
<span class="text-xs mt-1">Services</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/get-started" class="flex flex-col items-center justify-center w-full h-full text-[#6d9e37] hover:text-white transition-colors">
|
<a href="/services" class="flex flex-col items-center justify-center w-full h-full text-[#6d9e37] hover:text-white transition-colors">
|
||||||
<div class="w-10 h-10 rounded-full bg-[#6d9e37] flex items-center justify-center -mt-5 shadow-lg">
|
<div class="w-10 h-10 rounded-full bg-[#6d9e37] flex items-center justify-center -mt-5 shadow-lg">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,34 @@
|
||||||
---
|
---
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
|
|
||||||
|
const schema1 = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "SiliconPin",
|
||||||
|
"url": "https://siliconpin.com",
|
||||||
|
"logo": "https://siliconpin.com/assets/logo.svg",
|
||||||
|
"contactPoint": {
|
||||||
|
"@type": "ContactPoint",
|
||||||
|
"telephone": "+91-700-160-1485",
|
||||||
|
"contactType": "customer service",
|
||||||
|
"availableLanguage": ["English", "Hindi", "Bengali"]
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
"streetAddress": "121 Lalbari, GourBongo Road",
|
||||||
|
"addressLocality": "Habra",
|
||||||
|
"addressRegion": "W.B.",
|
||||||
|
"postalCode": "743271",
|
||||||
|
"addressCountry": "India"
|
||||||
|
},
|
||||||
|
"sameAs": [
|
||||||
|
"https://www.linkedin.com/company/siliconpin",
|
||||||
|
"https://x.com/dwd_consultancy",
|
||||||
|
"https://www.facebook.com/profile.php?id=100088549643337",
|
||||||
|
"https://instagram.com/siliconpin.com_"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
// Page-specific SEO metadata
|
// Page-specific SEO metadata
|
||||||
const pageTitle = "SiliconPin - Lets create some digital freedom";
|
const pageTitle = "SiliconPin - Lets create some digital freedom";
|
||||||
const pageDescription = "SiliconPin - easy to deploy apps and tools, freedom oriented apps and tools, high-performance, hosting solutions for PHP, Node.js, Python, Kubernetes (K8s), and K3s, and technical support.";
|
const pageDescription = "SiliconPin - easy to deploy apps and tools, freedom oriented apps and tools, high-performance, hosting solutions for PHP, Node.js, Python, Kubernetes (K8s), and K3s, and technical support.";
|
||||||
|
@ -11,6 +39,8 @@ const pageImage = "/assets/logo.svg";
|
||||||
title={pageTitle}
|
title={pageTitle}
|
||||||
description={pageDescription}
|
description={pageDescription}
|
||||||
ogImage={pageImage}
|
ogImage={pageImage}
|
||||||
|
newSchema1={JSON.stringify(schema1)}
|
||||||
|
|
||||||
>
|
>
|
||||||
<!-- Canvas background animation -->
|
<!-- Canvas background animation -->
|
||||||
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
|
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
|
||||||
|
|
|
@ -1,120 +1,229 @@
|
||||||
---
|
---
|
||||||
|
// Astro script block
|
||||||
import Layout from '../../layouts/Layout.astro';
|
import Layout from '../../layouts/Layout.astro';
|
||||||
import {ServiceCard} from '../../components/ServiceCard';
|
import { ServiceCard } from '../../components/ServiceCard';
|
||||||
|
|
||||||
// Page-specific SEO metadata
|
|
||||||
const pageTitle = "Hosting Services | SiliconPin";
|
|
||||||
const pageDescription = "Explore SiliconPin's reliable hosting services for PHP, Node.js, Python, Kubernetes (K8s), and K3s. Scalable solutions for businesses of all sizes with 24/7 support.";
|
|
||||||
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
|
|
||||||
|
|
||||||
// Service
|
|
||||||
const services = [
|
const services = [
|
||||||
{
|
{
|
||||||
title: 'VPN (WireGuard)',
|
|
||||||
description: 'WireGuard is much better than other solutions like open VPN Free VPN. as WireGuard is the best we provide WireGuard VPN',
|
|
||||||
imageUrl: '/assets/images/services/wiregurd-thumb.jpg',
|
imageUrl: '/assets/images/services/wiregurd-thumb.jpg',
|
||||||
features: ["600 GB bandwidth", "WireGuard® protocol", "Global server locations", "No activity logs"],
|
|
||||||
learnMoreUrl: '/services/vpn',
|
learnMoreUrl: '/services/vpn',
|
||||||
buyButtonText: 'Buy VPN',
|
buyButtonText: 'Buy VPN',
|
||||||
buyButtonUrl: '/services/buy-vpn'
|
buyButtonUrl: '/services/buy-vpn'
|
||||||
},{
|
},{
|
||||||
title: 'Deploy an App',
|
|
||||||
description: 'WordPress, Joomla, Drupal, PrestaShop, Wiki, Moodle, Directus, PocketBase, StarAPI and more.',
|
|
||||||
imageUrl: '/assets/images/services/deployapp-thumb.jpg',
|
imageUrl: '/assets/images/services/deployapp-thumb.jpg',
|
||||||
features: [ 'WordPress', 'Joomla', 'Drupal', 'PrestaShop', 'Wiki', 'Moodle', 'Directus', 'PocketBase', 'StarAPI' ],
|
|
||||||
learnMoreUrl: '/services/deploy-an-app',
|
learnMoreUrl: '/services/deploy-an-app',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Deploy From Source Code',
|
|
||||||
description: 'Node.js, Python, Ruby, Go, Rust, and more. Deploy your custom applications with ease.',
|
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1599507593499-a3f7d7d97667?q=80&w=2000&auto=format&fit=crop',
|
imageUrl: 'https://images.unsplash.com/photo-1599507593499-a3f7d7d97667?q=80&w=2000&auto=format&fit=crop',
|
||||||
features: ['Node.js', 'Python', 'Ruby', 'Go', 'Rust', 'Docker', 'Kubernetes', 'JAMstack', 'Serverless'],
|
|
||||||
learnMoreUrl: '/services/deploy-from-source-code',
|
learnMoreUrl: '/services/deploy-from-source-code',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Static Site Hosting',
|
|
||||||
description: 'Secure and scalable hosting for static websites and JAMstack applications.',
|
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1526379879527-8559ecfcaec0?q=80&w=2000&auto=format&fit=crop',
|
imageUrl: 'https://images.unsplash.com/photo-1526379879527-8559ecfcaec0?q=80&w=2000&auto=format&fit=crop',
|
||||||
features: ['JAMstack', 'Gatsby', 'Hugo', 'Next.js', 'Nuxt.js', 'VuePress', 'Eleventy', 'SvelteKit', 'Astro'],
|
|
||||||
learnMoreUrl: '/services/static-site-hosting',
|
learnMoreUrl: '/services/static-site-hosting',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Python Hosting",
|
|
||||||
description: "High-performance hosting for Python applications with support for all major frameworks.",
|
|
||||||
imageUrl: "https://images.unsplash.com/photo-1526379095098-d400fd0bf935?q=80&w=2000&auto=format&fit=crop",
|
imageUrl: "https://images.unsplash.com/photo-1526379095098-d400fd0bf935?q=80&w=2000&auto=format&fit=crop",
|
||||||
features: ["Django", "Flask", "FastAPI", "Pyramid", "Bottle", "WSGI Support", "ASGI Support", "Celery", "Redis"],
|
|
||||||
learnMoreUrl: "/services/python-hosting",
|
learnMoreUrl: "/services/python-hosting",
|
||||||
buyButtonText: "",
|
buyButtonText: "",
|
||||||
buyButtonUrl: ""
|
buyButtonUrl: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Node.js Hosting",
|
|
||||||
description: "Optimized cloud hosting for Node.js applications with automatic scaling.",
|
|
||||||
imageUrl: "/assets/images/services/node-js.jpg",
|
imageUrl: "/assets/images/services/node-js.jpg",
|
||||||
features: ["Express", "NestJS", "Koa", "Socket.io", "PM2", "WebSockets", "Serverless", "TypeScript", "GraphQL"],
|
|
||||||
learnMoreUrl: "/services/nodejs-hosting",
|
learnMoreUrl: "/services/nodejs-hosting",
|
||||||
buyButtonText: "",
|
buyButtonText: "",
|
||||||
buyButtonUrl: ""
|
buyButtonUrl: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'STT Streaming (API Services)',
|
|
||||||
description: 'Real-time Speech-to-Text (STT) streaming that converts spoken words into accurate, readable text. Ideal for live events, webinars, customer support, and accessibility solutions.',
|
|
||||||
imageUrl: '/assets/images/services/stt-streaming.png',
|
imageUrl: '/assets/images/services/stt-streaming.png',
|
||||||
features: ['Setup Charge 5000', '10000 Min INR 2000 in Loose Plan', '10000 Min INR 1000 in Bulk Plan', 'Real-time transcription with high accuracy', 'Supports multiple languages', 'Custom vocabulary for domain-specific terms', 'Integrates easily with video/audio streams', 'Secure and scalable API access', 'Live subtitle overlay options'],
|
|
||||||
learnMoreUrl: '',
|
learnMoreUrl: '',
|
||||||
buyButtonText: 'Purchase',
|
buyButtonText: 'Purchase',
|
||||||
buyButtonUrl: '/services/stt-streaming'
|
buyButtonUrl: '/services/stt-streaming'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Kubernetes (K8s)',
|
|
||||||
description: 'Enterprise-grade Kubernetes clusters for container orchestration at scale.',
|
|
||||||
imageUrl: '/assets/images/services/kubernetes-edge.png',
|
imageUrl: '/assets/images/services/kubernetes-edge.png',
|
||||||
features: [ 'Fully managed K8s clusters', 'Auto-scaling and load balancing', 'Advanced networking options', 'Persistent storage solutions', 'Enterprise support available'],
|
|
||||||
learnMoreUrl: '/services/kubernetes',
|
learnMoreUrl: '/services/kubernetes',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'K3s Lightweight Kubernetes',
|
|
||||||
description: 'Lightweight Kubernetes for edge, IoT, and resource-constrained environments.',
|
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
|
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
|
||||||
features: [ 'Minimal resource requirements', 'Single binary < 100MB', 'Edge computing optimized', 'ARM support (Raspberry Pi compatible)', 'Simplified management'],
|
|
||||||
learnMoreUrl: '/services/k3s',
|
learnMoreUrl: '/services/k3s',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Hire a Human Developer',
|
|
||||||
description: 'Need a custom solution? Our experts can design a tailored App or WebApp for your specific needs.',
|
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
|
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
|
||||||
features: ['Node.js', 'Python', 'Ruby', 'Go', 'Rust', 'Docker', 'Kubernetes', 'JAMstack', 'Serverless'],
|
|
||||||
learnMoreUrl: '/services/hire-a-human-developer',
|
learnMoreUrl: '/services/hire-a-human-developer',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Hire an AI Agent',
|
|
||||||
description: 'Need a custom solution? Our experts can design a tailored AI Agent for your specific needs.',
|
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
|
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
|
||||||
features: ['Reactive Agents (Stateless, Rule-Based)', 'Proactive Agents (Stateful, Machine Learning)', 'Hybrid Agents (Reactive + Proactive)', 'Model-Based Agents (Stateful, Uses Memory)', 'Goal-Based Agents (Optimizes for an Objective)', 'Utility-Based Agents', 'Self Learning Agents', 'Autonomous AI Agents'],
|
|
||||||
learnMoreUrl: '/services/hire-an-ai-agent',
|
learnMoreUrl: '/services/hire-an-ai-agent',
|
||||||
buyButtonText: '',
|
buyButtonText: '',
|
||||||
buyButtonUrl: ''
|
buyButtonUrl: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const SERVICE_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/services/?query=all-services-list';
|
||||||
|
let data = [];
|
||||||
|
try {
|
||||||
|
const response = await fetch(SERVICE_API_URL);
|
||||||
|
const json = await response.json();
|
||||||
|
if (json.success) {
|
||||||
|
// data = [...json.data, ...services];
|
||||||
|
data = json.data;
|
||||||
|
} else {
|
||||||
|
console.error("API error:", json.message);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fetch failed:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const servicesData = services.map((item, index) => {
|
||||||
|
return { ...item, ...data[index] };
|
||||||
|
});
|
||||||
|
|
||||||
|
// SEO
|
||||||
|
let schema1 = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "SiliconPin Hosting & Development Services",
|
||||||
|
"description": "Reliable, scalable, and secure hosting solutions for all your applications, plus custom developer and AI agent hiring.",
|
||||||
|
"provider": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "SiliconPin",
|
||||||
|
"url": "https://siliconpin.com",
|
||||||
|
"telephone": "+91-700-160-1485",
|
||||||
|
"address": {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
"streetAddress": "121 Lalbari, GourBongo Road",
|
||||||
|
"addressLocality": "Habra",
|
||||||
|
"addressRegion": "West Bengal",
|
||||||
|
"postalCode": "743271",
|
||||||
|
"addressCountry": "IN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"areaServed": "Worldwide",
|
||||||
|
"hasOfferCatalog": {
|
||||||
|
"@type": "OfferCatalog",
|
||||||
|
"name": "SiliconPin Services",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "VPN (WireGuard)",
|
||||||
|
"description": "WireGuard VPN with 600 GB bandwidth, no logs, global servers."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "App Deployment",
|
||||||
|
"description": "Deploy WordPress, Joomla, Drupal, Moodle, Directus, PocketBase, StarAPI and more."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Deploy From Source Code",
|
||||||
|
"description": "Deploy custom Node.js, Python, Ruby, Go, Rust, Docker, Kubernetes, JAMstack & serverless apps."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Static Site Hosting",
|
||||||
|
"description": "Hosting for JAMstack static sites via Gatsby, Hugo, Next.js, Nuxt.js, VuePress, SvelteKit, Astro."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Python Hosting",
|
||||||
|
"description": "High‑performance hosting for Django, Flask, FastAPI, Pyramid, Bottle with WSGI/ASGI, Celery, Redis."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Node.js Hosting",
|
||||||
|
"description": "Optimized cloud hosting of Node.js apps with auto‑scaling, WebSockets, PM2, GraphQL support."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "STT Streaming (API)",
|
||||||
|
"description": "Real‑time speech‑to‑text streaming with multi‑language support, custom vocabulary, live subtitle overlay."
|
||||||
|
},
|
||||||
|
"priceCurrency": "INR",
|
||||||
|
"priceSpecification": {
|
||||||
|
"@type": "UnitPriceSpecification",
|
||||||
|
"price": "2000",
|
||||||
|
"billingIncrement": 10000,
|
||||||
|
"description": "Loose plan per 10000 minutes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Kubernetes (K8s)",
|
||||||
|
"description": "Fully managed enterprise‑grade Kubernetes with auto‑scaling, load balancing, persistent storage."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "K3s Lightweight Kubernetes",
|
||||||
|
"description": "Lightweight K3s clusters for edge/IoT—<100 MB binary, ARM support, simplified management."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Hire a Human Developer",
|
||||||
|
"description": "Custom app/webapp development using Node.js, Python, Ruby, Go, Rust, Docker, Kubernetes, JAMstack, serverless."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Hire an AI Agent",
|
||||||
|
"description": "Build reactive, proactive, hybrid, self‑learning or goal‑based AI agents tailored to your needs."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pageTitle = "Hosting Services | SiliconPin";
|
||||||
|
const pageDescription = "Explore SiliconPin's reliable hosting services for PHP, Node.js, Python, Kubernetes (K8s), and K3s. Scalable solutions for businesses of all sizes with 24/7 support.";
|
||||||
|
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title={pageTitle}
|
title={pageTitle}
|
||||||
description={pageDescription}
|
description={pageDescription}
|
||||||
ogImage={pageImage}
|
ogImage={pageImage}
|
||||||
type="website"
|
type="website"
|
||||||
|
newSchema1={JSON.stringify(schema1)}
|
||||||
>
|
>
|
||||||
<main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12">
|
<main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12">
|
||||||
<div class="text-center mb-8 sm:mb-12">
|
<div class="text-center mb-8 sm:mb-12">
|
||||||
|
@ -127,7 +236,7 @@ const services = [
|
||||||
<section aria-labelledby="services-heading">
|
<section aria-labelledby="services-heading">
|
||||||
<h2 id="services-heading" class="sr-only">Our hosting services</h2>
|
<h2 id="services-heading" class="sr-only">Our hosting services</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
|
||||||
{services.map((service, index) => (
|
{servicesData.map((service : any, index: number) => (
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
client:load
|
client:load
|
||||||
title={service.title}
|
title={service.title}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
import NewKubernetisUtho from "../../components/BuyServices/NewKubernetisUtho";
|
||||||
|
---
|
||||||
|
<Layout title="Buy VPN | WireGuard | SiliconPin">
|
||||||
|
<NewKubernetisUtho client:load />
|
||||||
|
</Layout>
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
import ImageResizes from "../../components/Tools/ImageResize"
|
||||||
|
---
|
||||||
|
<Layout title="">
|
||||||
|
<div>
|
||||||
|
<ImageResizes client:load />
|
||||||
|
<!-- <input type="file" id="fileInput" accept="image/*" /> -->
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<!-- <script is:inline type="module">
|
||||||
|
// Import the library directly from CDN (as ES module)
|
||||||
|
import ImageResizeCompress from 'https://cdn.jsdelivr.net/npm/image-resize-compress/dist/index.min.js';
|
||||||
|
</script>
|
||||||
|
<script is:inline>
|
||||||
|
|
||||||
|
document.getElementById('fileInput').addEventListener('change', async function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
console.error('Selected file is not an image');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resizedBlob = await ImageResizeCompress.fromBlob(
|
||||||
|
file,
|
||||||
|
75,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'webp'
|
||||||
|
);
|
||||||
|
console.log('Resized image:', resizedBlob);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error resizing image:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script> -->
|
|
@ -3136,6 +3136,11 @@ ieee754@^1.2.1:
|
||||||
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
|
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
|
||||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||||
|
|
||||||
|
image-resize-compress@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.npmjs.org/image-resize-compress/-/image-resize-compress-2.1.1.tgz"
|
||||||
|
integrity sha512-aF4O1ZzAM0wDziIQZdqlxvDBe163J1Kiu+hSXbYNaGDcD4ggqAgRLLtRmpspnpJPHa4PkhVjwOshzBMHbOWESQ==
|
||||||
|
|
||||||
import-fresh@^3.3.0:
|
import-fresh@^3.3.0:
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
|
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
|
||||||
|
|
Loading…
Reference in New Issue