tst2
parent
b8e20ab510
commit
22fade091d
|
@ -25,6 +25,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"image-resize-compress": "^2.1.1",
|
||||
"lucide-react": "^0.484.0",
|
||||
"marked": "^15.0.8",
|
||||
"menubar": "^9.5.1",
|
||||
|
@ -7737,6 +7738,15 @@
|
|||
],
|
||||
"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": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"image-resize-compress": "^2.1.1",
|
||||
"lucide-react": "^0.484.0",
|
||||
"marked": "^15.0.8",
|
||||
"menubar": "^9.5.1",
|
||||
|
@ -48,4 +49,4 @@
|
|||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
||||
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
|
||||
const showToast = useCallback((message) => {
|
||||
setToast({ visible: true, message });
|
||||
|
@ -451,54 +459,54 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||
</div>
|
||||
);
|
||||
|
||||
case 'vpn':
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="vpn-continent" className="block text-white font-medium">Select Continent</label>
|
||||
<select
|
||||
id="vpn-continent"
|
||||
value={vpnContinent}
|
||||
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]"
|
||||
>
|
||||
<option value="">-Select-</option>
|
||||
<option value="India">India</option>
|
||||
<option value="America">America</option>
|
||||
<option value="Europe">Europe</option>
|
||||
</select>
|
||||
</div>
|
||||
// case 'vpn':
|
||||
// return (
|
||||
// <>
|
||||
// <div className="space-y-2">
|
||||
// <label htmlFor="vpn-continent" className="block text-white font-medium">Select Continent</label>
|
||||
// <select
|
||||
// id="vpn-continent"
|
||||
// value={vpnContinent}
|
||||
// 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]"
|
||||
// >
|
||||
// <option value="">-Select-</option>
|
||||
// <option value="India">India</option>
|
||||
// <option value="America">America</option>
|
||||
// <option value="Europe">Europe</option>
|
||||
// </select>
|
||||
// </div>
|
||||
|
||||
<ul className="flex justify-between text-sm">
|
||||
{["600 GB bandwidth", "WireGuard® protocol", "Global server locations", "No activity logs"].map((feature, index) => (
|
||||
<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>
|
||||
<span className='text-zinc-400'>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<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`}>
|
||||
<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>
|
||||
<span>Monthly</span>
|
||||
</label>
|
||||
// <ul className="flex justify-between text-sm">
|
||||
// {["600 GB bandwidth", "WireGuard® protocol", "Global server locations", "No activity logs"].map((feature, index) => (
|
||||
// <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>
|
||||
// <span className='text-zinc-400'>{feature}</span>
|
||||
// </li>
|
||||
// ))}
|
||||
// </ul>
|
||||
// <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`}>
|
||||
// <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>
|
||||
// <span>Monthly</span>
|
||||
// </label>
|
||||
|
||||
<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" />
|
||||
<p className='text-3xl font-bold text-center'>₹{PRICE_CONFIG.vpn.yearly}</p>
|
||||
<span>Yearly</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className='text-white'>
|
||||
{selectedCycle && (
|
||||
<p className={`${selectedCycle === 'monthly' ? 'text-left' : 'text-end'}`}>You selected <strong>{selectedCycle}</strong> plan at ₹{selectedPrice}</p>
|
||||
)}
|
||||
</div>
|
||||
// <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" />
|
||||
// <p className='text-3xl font-bold text-center'>₹{PRICE_CONFIG.vpn.yearly}</p>
|
||||
// <span>Yearly</span>
|
||||
// </label>
|
||||
// </div>
|
||||
// <div className='text-white'>
|
||||
// {selectedCycle && (
|
||||
// <p className={`${selectedCycle === 'monthly' ? 'text-left' : 'text-end'}`}>You selected <strong>{selectedCycle}</strong> plan at ₹{selectedPrice}</p>
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
<Button onClick={() => handlePurchase('vpn')} className='w-full'>Proceed to Pay</Button>
|
||||
</>
|
||||
);
|
||||
// <Button onClick={() => handlePurchase('vpn')} className='w-full'>Proceed to Pay</Button>
|
||||
// </>
|
||||
// );
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
@ -514,7 +522,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||
<select
|
||||
id="deployment-type"
|
||||
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]"
|
||||
>
|
||||
<option value="app">💻 Deploy an App</option>
|
||||
|
|
|
@ -8,15 +8,18 @@ export interface ServiceCardProps {
|
|||
title: string;
|
||||
description: string;
|
||||
imageUrl: string;
|
||||
features: string[];
|
||||
features: [];
|
||||
learnMoreUrl: string;
|
||||
buyButtonText: string;
|
||||
buyButtonUrl: string;
|
||||
}
|
||||
|
||||
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 [showLoginModal, setShowLoginModal] = useState(false);
|
||||
const parsedFeatures = typeof features === 'string' ? JSON.parse(features) : features;
|
||||
|
||||
const handleBuyClick = () => {
|
||||
if (!isLoggedIn) {
|
||||
|
@ -43,12 +46,16 @@ export function ServiceCard({ title, description, imageUrl, features, learnMoreU
|
|||
</CardHeader>
|
||||
<CardContent className="flex-grow">
|
||||
<ul className="space-y-2 text-sm">
|
||||
{features.map((feature, index) => (
|
||||
<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>
|
||||
<span className='text-zinc-600'>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
{
|
||||
parsedFeatures && (
|
||||
parsedFeatures.map((feature : string, index: number) => (
|
||||
<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>
|
||||
<span className='text-zinc-600'>{feature}</span>
|
||||
</li>
|
||||
))
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</CardContent>
|
||||
<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 LoginProfile from '../components/LoginOrProfile';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
ogImage?: string;
|
||||
canonicalURL?: string;
|
||||
type?: 'website' | 'article';
|
||||
}
|
||||
interface Props { title: string; description?: string; ogImage?: string; canonicalURL?: string; type?: 'website' | 'article'; newSchema1 : string}
|
||||
|
||||
const {
|
||||
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;
|
||||
const { title, description, ogImage, canonicalURL = new URL(Astro.url.pathname, Astro.site).href, type = "website", newSchema1} = Astro.props;
|
||||
|
||||
// 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>
|
||||
|
@ -83,7 +45,9 @@ const organizationSchema = {
|
|||
<link rel="apple-touch-icon" href="https://siliconpin.com/assets/logo.svg" />
|
||||
|
||||
<!-- 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>
|
||||
<body class="min-h-screen flex flex-col">
|
||||
<!-- Top header - visible on all devices -->
|
||||
|
@ -96,12 +60,12 @@ const organizationSchema = {
|
|||
<!-- Desktop 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="/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="/contact" class="hover:text-[#6d9e37] transition-colors">Contact</a>
|
||||
<LoginProfile deviceType="desktop" client:load />
|
||||
<!-- <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>
|
||||
<!-- Mobile menu button -->
|
||||
<div class="md:hidden relative">
|
||||
|
@ -112,11 +76,11 @@ const organizationSchema = {
|
|||
<!-- 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">
|
||||
<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="/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="/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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -170,8 +134,8 @@ const organizationSchema = {
|
|||
<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>
|
||||
<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="/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">All Services</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-ai-agent" class="hover:text-[#6d9e37] transition-colors">Hire an AI Agent</a></li>
|
||||
</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>
|
||||
<span class="text-xs mt-1">Services</span>
|
||||
</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">
|
||||
<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>
|
||||
|
|
|
@ -1,6 +1,34 @@
|
|||
---
|
||||
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
|
||||
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.";
|
||||
|
@ -11,6 +39,8 @@ const pageImage = "/assets/logo.svg";
|
|||
title={pageTitle}
|
||||
description={pageDescription}
|
||||
ogImage={pageImage}
|
||||
newSchema1={JSON.stringify(schema1)}
|
||||
|
||||
>
|
||||
<!-- Canvas background animation -->
|
||||
<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 {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
|
||||
import { ServiceCard } from '../../components/ServiceCard';
|
||||
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',
|
||||
features: ["600 GB bandwidth", "WireGuard® protocol", "Global server locations", "No activity logs"],
|
||||
learnMoreUrl: '/services/vpn',
|
||||
buyButtonText: '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',
|
||||
features: [ 'WordPress', 'Joomla', 'Drupal', 'PrestaShop', 'Wiki', 'Moodle', 'Directus', 'PocketBase', 'StarAPI' ],
|
||||
learnMoreUrl: '/services/deploy-an-app',
|
||||
buyButtonText: '',
|
||||
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',
|
||||
features: ['Node.js', 'Python', 'Ruby', 'Go', 'Rust', 'Docker', 'Kubernetes', 'JAMstack', 'Serverless'],
|
||||
learnMoreUrl: '/services/deploy-from-source-code',
|
||||
buyButtonText: '',
|
||||
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',
|
||||
features: ['JAMstack', 'Gatsby', 'Hugo', 'Next.js', 'Nuxt.js', 'VuePress', 'Eleventy', 'SvelteKit', 'Astro'],
|
||||
learnMoreUrl: '/services/static-site-hosting',
|
||||
buyButtonText: '',
|
||||
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",
|
||||
features: ["Django", "Flask", "FastAPI", "Pyramid", "Bottle", "WSGI Support", "ASGI Support", "Celery", "Redis"],
|
||||
learnMoreUrl: "/services/python-hosting",
|
||||
buyButtonText: "",
|
||||
buyButtonUrl: ""
|
||||
},
|
||||
{
|
||||
title: "Node.js Hosting",
|
||||
description: "Optimized cloud hosting for Node.js applications with automatic scaling.",
|
||||
imageUrl: "/assets/images/services/node-js.jpg",
|
||||
features: ["Express", "NestJS", "Koa", "Socket.io", "PM2", "WebSockets", "Serverless", "TypeScript", "GraphQL"],
|
||||
learnMoreUrl: "/services/nodejs-hosting",
|
||||
buyButtonText: "",
|
||||
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',
|
||||
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: '',
|
||||
buyButtonText: 'Purchase',
|
||||
buyButtonUrl: '/services/stt-streaming'
|
||||
},
|
||||
{
|
||||
title: 'Kubernetes (K8s)',
|
||||
description: 'Enterprise-grade Kubernetes clusters for container orchestration at scale.',
|
||||
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',
|
||||
buyButtonText: '',
|
||||
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',
|
||||
features: [ 'Minimal resource requirements', 'Single binary < 100MB', 'Edge computing optimized', 'ARM support (Raspberry Pi compatible)', 'Simplified management'],
|
||||
learnMoreUrl: '/services/k3s',
|
||||
buyButtonText: '',
|
||||
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',
|
||||
features: ['Node.js', 'Python', 'Ruby', 'Go', 'Rust', 'Docker', 'Kubernetes', 'JAMstack', 'Serverless'],
|
||||
learnMoreUrl: '/services/hire-a-human-developer',
|
||||
buyButtonText: '',
|
||||
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',
|
||||
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',
|
||||
buyButtonText: '',
|
||||
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
|
||||
title={pageTitle}
|
||||
description={pageDescription}
|
||||
ogImage={pageImage}
|
||||
type="website"
|
||||
newSchema1={JSON.stringify(schema1)}
|
||||
>
|
||||
<main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12">
|
||||
<div class="text-center mb-8 sm:mb-12">
|
||||
|
@ -127,17 +236,17 @@ const services = [
|
|||
<section aria-labelledby="services-heading">
|
||||
<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">
|
||||
{services.map((service, index) => (
|
||||
<ServiceCard
|
||||
client:load
|
||||
title={service.title}
|
||||
description={service.description}
|
||||
imageUrl={service.imageUrl}
|
||||
features={service.features}
|
||||
learnMoreUrl={service.learnMoreUrl}
|
||||
buyButtonText={service.buyButtonText}
|
||||
buyButtonUrl={service.buyButtonUrl}
|
||||
/>
|
||||
{servicesData.map((service : any, index: number) => (
|
||||
<ServiceCard
|
||||
client:load
|
||||
title={service.title}
|
||||
description={service.description}
|
||||
imageUrl={service.imageUrl}
|
||||
features={service.features}
|
||||
learnMoreUrl={service.learnMoreUrl}
|
||||
buyButtonText={service.buyButtonText}
|
||||
buyButtonUrl={service.buyButtonUrl}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -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>
|
|
@ -19,7 +19,7 @@ try {
|
|||
// console.log('Topic Data', data)
|
||||
topics = data.data || [];
|
||||
} catch (error) {
|
||||
console.error('An error occurred', error);
|
||||
console.error('An error occurred', error);
|
||||
}
|
||||
---
|
||||
|
||||
|
|
|
@ -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"
|
||||
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:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
|
||||
|
|
Loading…
Reference in New Issue