start new in php

main
suvodip ghosh 2025-07-17 13:07:37 +00:00
parent 65a37ad477
commit ac520dfcff
6 changed files with 344 additions and 302 deletions

View File

@ -1,21 +1,27 @@
import React, { useState, useEffect } from "react"; import React, { useState } from "react";
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "../ui/select"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "../ui/select";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { Label } from "../ui/label"; import { Label } from "../ui/label";
import Loader from "../ui/loader";
import { useToast } from "../ui/toast"; import { useToast } from "../ui/toast";
import { useIsLoggedIn } from '../../lib/isLoggedIn'; import { useIsLoggedIn } from '../../lib/isLoggedIn';
import Loader from "../ui/loader";
export default function Kubernetes() { export default function Kubernetes() {
const { isLoggedIn, loading, balance } = useIsLoggedIn(); // Environment variables and hooks
const PUBLIC_DEPLOYMENT_CHANEL_1_URL = import.meta.env.PUBLIC_DEPLOYMENT_CHANEL_1_URL;
const { loading, isLoggedIn, balance } = useIsLoggedIn();
const { showToast } = useToast(); const { showToast } = useToast();
// State management
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [deployError, setDeployError] = useState(null); const [deployError, setDeployError] = useState(null);
const [deployStatus, setDeployStatus] = useState({}); const [deployStatus, setDeployStatus] = useState({});
const [clusterStatus, setClusterStatus] = useState(''); const [productAmount] = useState(100);
const [productAmount, setProductAmount] = useState(100); const [copied, setCopied] = useState(false);
const [downloadClusterId, setDownloadClusterId] = useState('');
// Form states
const [nodePools, setNodePools] = useState([{ const [nodePools, setNodePools] = useState([{
label: `${getRandomString()}`, label: `${getRandomString()}`,
size: '10215', size: '10215',
@ -28,7 +34,7 @@ export default function Kubernetes() {
cluster_label: `${getRandomString()}`, cluster_label: `${getRandomString()}`,
}); });
// Generate random string for IDs // Helper functions
function getRandomString(length = 8) { function getRandomString(length = 8) {
return Math.random().toString(36).substring(2, length+2); return Math.random().toString(36).substring(2, length+2);
} }
@ -40,58 +46,8 @@ export default function Kubernetes() {
{ id: '10218', name: '16 vCPU, 32GB RAM' } { id: '10218', name: '16 vCPU, 32GB RAM' }
]; ];
// Check cluster status periodically after deployment
useEffect(() => {
if (!deployStatus.clusterId || deployStatus.isReady) return;
const checkStatus = async () => {
try {
const response = await fetch(
`https://host-api.cs1.hz.siliconpin.com/v1/kubernetis/?query=status&clusterId=${deployStatus.clusterId}&source=chanel_1`,
{ credentials: "include" }
);
if (!response.ok) throw new Error("Failed to check status");
const data = await response.json();
// Update status
setClusterStatus(data.status || 'pending');
if (data.status === 'active') {
// Cluster is ready for configuration download
setDeployStatus(prev => ({ ...prev, isReady: true }));
showToast({
title: "Cluster Ready",
description: "Your Kubernetes cluster is now fully configured.",
variant: "default"
});
return; // Stop checking when ready
} else if (data.status === 'failed') {
throw new Error("Cluster deployment failed");
}
// Continue checking every 20 seconds if not ready
setTimeout(checkStatus, 20000);
} catch (error) {
console.error("Status check error:", error);
// Retry after delay if not a fatal error
if (!error.message.includes('failed')) {
setTimeout(checkStatus, 30000);
} else {
setDeployError(error.message);
}
}
};
const timer = setTimeout(checkStatus, 15000);
return () => clearTimeout(timer);
}, [deployStatus.clusterId, deployStatus.isReady]);
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
if(balance < productAmount){ if (balance < productAmount) return;
return;
}
e.preventDefault(); e.preventDefault();
setIsLoading(true); setIsLoading(true);
setDeployError(null); setDeployError(null);
@ -114,7 +70,7 @@ export default function Kubernetes() {
}; };
const response = await fetch( const response = await fetch(
"https://host-api.cs1.hz.siliconpin.com/v1/kubernetis/?query=deploy&source=chanel_1", `${PUBLIC_DEPLOYMENT_CHANEL_1_URL}kubernetis/?query=deploy&source=chanel_1`,
{ {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
@ -130,13 +86,11 @@ export default function Kubernetes() {
if (data.status === "success") { if (data.status === "success") {
setDeployStatus({ setDeployStatus({
status: "success", status: "success",
clusterId: data.clusterId, clusterId: data.id
endpoint: data.endpoint,
isReady: false
}); });
showToast({ showToast({
title: "Deployment Started", title: "Deployment Successful",
description: "Your cluster is being provisioned. This may take 10-15 minutes.", description: "Your cluster is being provisioned.",
variant: "default" variant: "default"
}); });
} else { } else {
@ -154,22 +108,26 @@ export default function Kubernetes() {
} }
}; };
const downloadKubeConfig = async () => { const downloadKubeConfig = (clusterId) => {
if (!deployStatus.clusterId) return; if (!clusterId) {
try {
// Trigger download via backend
window.open(
`https://host-api.cs1.hz.siliconpin.com/v1/kubernetis/?query=download&clusterId=${deployStatus.clusterId}&source=chanel_1`,
'_blank'
);
} catch (error) {
showToast({ showToast({
title: "Download Failed", title: "Cluster ID Required",
description: "Could not download configuration. Please try again.", description: "Please enter a valid Cluster ID",
variant: "destructive" variant: "destructive"
}); });
return;
} }
window.open(
`${PUBLIC_DEPLOYMENT_CHANEL_1_URL}kubernetis/?query=download&clusterId=${clusterId}&source=chanel_1`,
'_blank'
);
};
const handleCopy = (text) => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
}; };
// Form field handlers // Form field handlers
@ -220,11 +178,9 @@ export default function Kubernetes() {
setNodePools(updatedPools); setNodePools(updatedPools);
} }
}; };
if (loading) { if (loading) {
return <Loader />; return <Loader />;
} }
if (!isLoggedIn) { if (!isLoggedIn) {
return ( return (
<p className="text-center mt-8"> <p className="text-center mt-8">
@ -235,77 +191,40 @@ export default function Kubernetes() {
if (balance < productAmount) { if (balance < productAmount) {
return ( return (
<p> <p className="text-center mt-8">
You have insufficient balance to deploy this service. Please <a href="/profile" className="text-[#6d9e37]"> click here </a> to go to your profile and add balance, then try again.</p> You have insufficient balance to deploy this service. Please <a href="/profile" className="text-[#6d9e37]">click here</a> to add balance.
</p>
); );
} }
if (deployStatus.status === 'success') {
return ( return (
<Card className="w-full max-w-2xl mx-auto my-4"> <div className="space-y-6 max-w-3xl mx-auto mt-8">
{/* Deployment Card */}
{deployStatus.status === 'success' ? (
<Card>
<CardHeader> <CardHeader>
<CardTitle>Kubernetes Cluster Deployment</CardTitle> <CardTitle>Kubernetes Cluster Deployment</CardTitle>
<CardDescription> <CardDescription>Save your Cluster ID in a safe place to download configuration file</CardDescription>
{deployStatus.isReady
? "Your cluster is ready to use"
: "Your cluster is being provisioned"}
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
<div className="space-y-2"> <div className="flex">
<Label>Current Status</Label> <Input
<div className="p-3 bg-gray-100 rounded-md"> className="rounded-l-md rounded-r-none text-xl font-bold border-[2px] border-[#6d9e37]"
<div className="flex items-center gap-2"> type="text"
{!deployStatus.isReady && <Loader size="sm" />} readOnly
<span className="font-medium"> value={deployStatus.clusterId}
{deployStatus.isReady ? 'Ready' : (clusterStatus || 'Starting deployment')} />
</span> <Button
</div> className="rounded-l-none rounded-r-md"
{!deployStatus.isReady && ( onClick={() => handleCopy(deployStatus.clusterId)}
<p className="text-sm text-muted-foreground mt-2"> >
This process may take several minutes. Please don't close this page. {copied ? 'Copied!' : 'Copy'}
</p>
)}
</div>
</div>
<div className="space-y-4">
<div>
<Label>Cluster ID</Label>
<p className="font-mono p-2 bg-gray-100 rounded text-sm">
{deployStatus.clusterId}
</p>
</div>
<div>
<Label>API Endpoint</Label>
<p className="font-mono p-2 bg-gray-100 rounded text-sm">
{deployStatus.endpoint || 'Pending...'}
</p>
</div>
</div>
{deployStatus.isReady ? (
<div className="flex flex-col gap-4 pt-4">
<Button onClick={downloadKubeConfig}>
Download Configuration File
</Button>
<Button variant="outline" onClick={() => window.location.reload()}>
Deploy Another Cluster
</Button> </Button>
</div> </div>
) : (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader size="sm" />
<span>Preparing your cluster... This may take several minutes</span>
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
); ) : (
} <Card>
return (
<Card className="w-full max-w-2xl mx-auto my-4">
<CardHeader> <CardHeader>
<CardTitle>Create Kubernetes Cluster</CardTitle> <CardTitle>Create Kubernetes Cluster</CardTitle>
<CardDescription> <CardDescription>
@ -433,8 +352,6 @@ export default function Kubernetes() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem selected value="nvme">NVMe SSD</SelectItem> <SelectItem selected value="nvme">NVMe SSD</SelectItem>
{/* <SelectItem value="ssd">Standard SSD</SelectItem>
<SelectItem value="hdd">HDD</SelectItem> */}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -465,18 +382,40 @@ export default function Kubernetes() {
disabled={isLoading} disabled={isLoading}
className="w-full md:w-auto" className="w-full md:w-auto"
> >
{isLoading ? ( {isLoading ? "Deploying..." : "Deploy Cluster"}
<>
<Loader size="sm" className="mr-2" />
Deploying...
</>
) : (
"Deploy Cluster"
)}
</Button> </Button>
</div> </div>
</form> </form>
</CardContent> </CardContent>
</Card> </Card>
)}
{/* Download Config Card - Always visible */}
<Card>
<CardHeader>
<CardTitle>Download Configuration</CardTitle>
<CardDescription>
Enter your Cluster ID to download the kubeconfig file <br /> <span className="text-xs">(Try to download config file after 5 minutes from Deployment!)</span>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="downloadClusterId">Cluster ID</Label>
<Input
id="downloadClusterId"
value={deployStatus.clusterId || downloadClusterId}
onChange={(e) => setDownloadClusterId(e.target.value)}
placeholder="Enter your Cluster ID"
/>
</div>
<Button
className="w-full"
onClick={() => downloadKubeConfig(deployStatus.clusterId || downloadClusterId)}
>
Download Config
</Button>
</CardContent>
</Card>
</div>
); );
} }

View File

@ -0,0 +1,98 @@
import React, { useEffect, useState } from 'react';
import { Heart, Loader2 } from 'lucide-react';
export default function LikeSystem({ postId, siliconId, token }) {
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchLikeStatusAndCount = async () => {
try {
const countRes = await fetch(`https://sp-likes-pocndhvgmcnbacgb.siliconpin.com/posts/${postId}/likes`);
const countData = await countRes.json();
setLikeCount(countData.likes);
const likedRes = await fetch(
`https://sp-likes-pocndhvgmcnbacgb.siliconpin.com/is-liked?post_id=${postId}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'X-SiliconId': siliconId,
},
}
);
const likedData = await likedRes.json();
setLiked(likedData.liked);
} catch (err) {
console.error("Error fetching like data", err);
}
};
fetchLikeStatusAndCount();
}, [postId, token, siliconId]);
const handleLikeToggle = async () => {
setLoading(true);
try {
const res = await fetch(`https://sp-likes-pocndhvgmcnbacgb.siliconpin.com/like`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-SiliconId': siliconId,
},
body: JSON.stringify({ post_id: postId }),
});
const data = await res.json();
if (data.success) {
setLiked(data.liked);
setLikeCount(prev => data.liked ? prev + 1 : prev - 1);
}
} catch (error) {
console.error('Network error:', error);
} finally {
setLoading(false);
}
};
return (
<div style={{ textAlign: 'center' }}>
<button
onClick={handleLikeToggle}
disabled={loading}
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: '6px',
padding: '6px 14px',
backgroundColor: 'transparent',
border: 'none',
cursor: loading ? 'not-allowed' : 'pointer',
transition: 'all 0.2s ease-in-out',
}}
>
{loading ? (
<Loader2 size={22} className="animate-spin" />
) : (
<Heart
size={28}
fill={liked ? '#ff4d4f' : 'none'}
color={liked ? '#ff4d4f' : '#999'}
strokeWidth={2.2}
style={{ transition: '0.2s ease-in-out' }}
/>
)}
<span style={{
fontSize: '15px',
fontWeight: 'bold',
color: liked ? '#ff4d4f' : '#555'
}}>
{likeCount}
</span>
</button>
</div>
);
}

View File

@ -135,8 +135,8 @@ console.log('serviceName', serviceName)
</> </>
) : ( ) : (
<> <>
<h2 className="text-xl font-bold mb-4 text-neutral-950">VPN Credentials</h2> <h2 className="text-xl font-bold mb-2 text-neutral-950">VPN Credentials</h2>
<p className="text-yellow-600 bg-yellow-50 p-3 rounded-md mb-4 text-sm"> Make sure to save these credentials in a safe place. We do not store them.</p> <p className="text-neutral-950 mb-4">Install WireGuard (Available on Android / IOS / Linux / Windows / FreeBSD) Scan this QR code using WireGuard app or import the config file.</p>
</> </>
) )
} }
@ -148,7 +148,6 @@ console.log('serviceName', serviceName)
{creds.qr_code && ( {creds.qr_code && (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<img src={`data:image/png;base64,${creds.qr_code}`} alt="VPN Configuration QR Code" className="w-48 h-48 border border-gray-300 rounded" /> <img src={`data:image/png;base64,${creds.qr_code}`} alt="VPN Configuration QR Code" className="w-48 h-48 border border-gray-300 rounded" />
<p className="text-sm text-neutral-400 mt-2">Scan this QR code with your WireGuard app</p>
</div> </div>
)} )}
@ -161,6 +160,7 @@ console.log('serviceName', serviceName)
</Button> </Button>
</div> </div>
<pre className="whitespace-pre-wrap bg-gray-50 p-3 rounded text-sm overflow-x-auto text-neutral-950">{creds.output}</pre> <pre className="whitespace-pre-wrap bg-gray-50 p-3 rounded text-sm overflow-x-auto text-neutral-950">{creds.output}</pre>
<p className="text-yellow-600 bg-yellow-50 p-3 rounded-md my-4 text-sm"> Make sure to save these credentials in a safe place. We do not store them.</p>
</div> </div>
)} )}
</div> </div>

View File

@ -8,13 +8,16 @@ import { Button } from "./ui/button";
import { useIsLoggedIn } from '../lib/isLoggedIn'; import { useIsLoggedIn } from '../lib/isLoggedIn';
import { token, user_name, pb_id } from '../lib/CookieValues'; import { token, user_name, pb_id } from '../lib/CookieValues';
import CommentSystem from './CommentSystem/CommentSystem'; import CommentSystem from './CommentSystem/CommentSystem';
import '../styles/markdown.css' import '../styles/markdown.css';
import LikeSystem from './CommentSystem/LikeSystem'
const COMMENTS_API_URL = 'https://host-api-sxashuasysagibx.siliconpin.com/v1/comments/'; const COMMENTS_API_URL = 'https://host-api-sxashuasysagibx.siliconpin.com/v1/comments/';
export default function TopicDetail(props) { export default function TopicDetail(props) {
// console.log('coockie data', user_name) // console.log('coockie data', user_name)
const [showCopied, setShowCopied] = useState(false); const [showCopied, setShowCopied] = useState(false);
const [allGalleryImages, setAllGalleryImages] = useState([]); const [allGalleryImages, setAllGalleryImages] = useState([]);
const siliconId = '4uPRIGMm';
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3NTI5MjQxMTQsImlkIjoiZ203NzYyZ2Q2aTNscDAxIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.Aq02zEAcXjyfsFO6MniM0atzzy2rSXx7B_rnilK6OYQ';
if (!props.topic) { if (!props.topic) {
return <div>Topic not found</div>; return <div>Topic not found</div>;
} }
@ -126,10 +129,12 @@ export default function TopicDetail(props) {
<article className="max-w-4xl mx-auto"> <article className="max-w-4xl mx-auto">
<img src={props.topic.img ? props.topic.img : '/assets/images/thumb-place.jpg'} alt={props.topic.title} className="w-full object-cover rounded-lg mb-8 shadow-md" /> <img src={props.topic.img ? props.topic.img : '/assets/images/thumb-place.jpg'} alt={props.topic.title} className="w-full object-cover rounded-lg mb-8 shadow-md" />
<h1 className="text-4xl font-bold text-[#6d9e37] mb-6">{props.topic.title}</h1> <h1 className="text-4xl font-bold text-[#6d9e37] mb-6">{props.topic.title}</h1>
{/* Enhanced Social Share Buttons */} {/* Enhanced Social Share Buttons */}
<div className="mb-8"> <div className="mb-8">
<div className="flex flex-row justify-between">
<p className="text-sm text-gray-500 mb-3 font-medium">Share this Topic:</p> <p className="text-sm text-gray-500 mb-3 font-medium">Share this Topic:</p>
<LikeSystem postId={props.topic.id} siliconId={siliconId} token={token} />
</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<button <button
onClick={() => shareOnSocialMedia('facebook')} onClick={() => shareOnSocialMedia('facebook')}

View File

@ -435,7 +435,7 @@ export default function EditTopic (){
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="slug">URL Slug</Label> {/* <Label htmlFor="slug">URL Slug</Label> */}
<input <input
type="hidden" type="hidden"
name="slug" name="slug"
@ -443,7 +443,7 @@ export default function EditTopic (){
onChange={handleChange} onChange={handleChange}
className="bg-gray-50" className="bg-gray-50"
/> />
<p className="text-xs text-gray-500">This will be used in the topic URL</p> {/* <p className="text-xs text-gray-500">This will be used in the topic URL</p> */}
</div> </div>
{/* Featured Image Upload */} {/* Featured Image Upload */}

View File

@ -43,8 +43,8 @@ const services = [
{ {
imageUrl: '/assets/images/services/kubernetes-edge.png', imageUrl: '/assets/images/services/kubernetes-edge.png',
learnMoreUrl: '/services/kubernetes', learnMoreUrl: '/services/kubernetes',
buyButtonText: '', buyButtonText: 'Get K8s',
buyButtonUrl: '' buyButtonUrl: '/services/kubernetes'
}, },
{ {
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop', imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
@ -233,7 +233,7 @@ const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=8
newSchema1={JSON.stringify(schema1)} newSchema1={JSON.stringify(schema1)}
canonicalURL="/services" canonicalURL="/services"
> >
<main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12"> <main class="container mx-auto max-w-7xl px-4 sm:px-6 py-8 sm:py-12">
<div class="text-center mb-8 sm:mb-12"> <div class="text-center mb-8 sm:mb-12">
<h1 class="text-3xl sm:text-4xl font-bold text-[#6d9e37] mb-3 sm:mb-4">Hosting Services</h1> <h1 class="text-3xl sm:text-4xl font-bold text-[#6d9e37] mb-3 sm:mb-4">Hosting Services</h1>
<p class="text-lg sm:text-xl max-w-3xl mx-auto text-neutral-300"> <p class="text-lg sm:text-xl max-w-3xl mx-auto text-neutral-300">