suvodip ghosh 2025-06-12 06:42:01 +00:00
parent b8e20ab510
commit 22fade091d
14 changed files with 914 additions and 156 deletions

10
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

12
public/assets/js/index.min.js vendored Normal file
View File

@ -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
*/

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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">

View File

@ -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 600GB 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": "Highperformance 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 autoscaling, WebSockets, PM2, GraphQL support."
}
},
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "STT Streaming (API)",
"description": "Realtime speechtotext streaming with multilanguage 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 enterprisegrade Kubernetes with autoscaling, load balancing, persistent storage."
}
},
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "K3s Lightweight Kubernetes",
"description": "Lightweight K3s clusters for edge/IoT—<100MB 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, selflearning or goalbased 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>

View File

@ -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>

View File

@ -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);
}
---

View File

@ -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> -->

View File

@ -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"