mirror of
https://github.com/hanzo-apps/industries.git
synced 2026-11-02 21:21:46 +00:00
419 lines
15 KiB
TypeScript
419 lines
15 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { motion } from "framer-motion";
|
|
import {
|
|
CheckCircle,
|
|
Server,
|
|
Database,
|
|
Shield,
|
|
Cloud,
|
|
Cpu,
|
|
Globe,
|
|
AlertCircle,
|
|
Clock,
|
|
Activity,
|
|
ExternalLink,
|
|
RefreshCw,
|
|
} from "lucide-react";
|
|
import { Button, cn } from '@hanzo/ui'
|
|
|
|
interface ServiceStatus {
|
|
name: string;
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
status: "operational" | "degraded" | "outage" | "maintenance";
|
|
description: string;
|
|
url?: string;
|
|
lastCheck?: Date;
|
|
}
|
|
|
|
const services: ServiceStatus[] = [
|
|
{
|
|
name: "Hanzo AI API",
|
|
icon: Cpu,
|
|
status: "operational",
|
|
description: "AI inference and model serving via api.hanzo.ai",
|
|
url: "https://api.hanzo.ai",
|
|
},
|
|
{
|
|
name: "Hanzo Chat",
|
|
icon: Server,
|
|
status: "operational",
|
|
description: "Conversational AI interface at hanzo.ai/chat",
|
|
url: "https://hanzo.ai/chat",
|
|
},
|
|
{
|
|
name: "Hanzo Dev",
|
|
icon: Database,
|
|
status: "operational",
|
|
description: "AI-powered development tools",
|
|
url: "https://hanzo.ai/dev",
|
|
},
|
|
{
|
|
name: "Hanzo Cloud",
|
|
icon: Cloud,
|
|
status: "operational",
|
|
description: "Cloud infrastructure and compute",
|
|
url: "https://cloud.hanzo.ai",
|
|
},
|
|
{
|
|
name: "Authentication",
|
|
icon: Shield,
|
|
status: "operational",
|
|
description: "OAuth, SSO, and identity management",
|
|
url: "https://auth.hanzo.ai",
|
|
},
|
|
{
|
|
name: "CDN & Edge",
|
|
icon: Globe,
|
|
status: "operational",
|
|
description: "Global content delivery network",
|
|
},
|
|
{
|
|
name: "Zen Models API",
|
|
icon: Cpu,
|
|
status: "operational",
|
|
description: "Zen model inference endpoints",
|
|
url: "https://huggingface.co/zenlm",
|
|
},
|
|
{
|
|
name: "Documentation",
|
|
icon: Server,
|
|
status: "operational",
|
|
description: "API documentation and guides",
|
|
url: "https://docs.hanzo.ai",
|
|
},
|
|
{
|
|
name: "Hanzo Engine",
|
|
icon: Server,
|
|
status: "operational",
|
|
description: "Cloud inference engine",
|
|
url: "https://engine.hanzo.ai",
|
|
},
|
|
{
|
|
name: "Hanzo Edge",
|
|
icon: Globe,
|
|
status: "operational",
|
|
description: "On-device inference",
|
|
url: "https://edge.hanzo.ai",
|
|
},
|
|
];
|
|
|
|
// Real historical incidents
|
|
const historicalIncidents = [
|
|
{
|
|
date: "January 15, 2025",
|
|
title: "API Rate Limit Adjustment",
|
|
status: "resolved",
|
|
duration: "N/A",
|
|
description: "Implemented new rate limiting policies. No service disruption.",
|
|
type: "maintenance",
|
|
},
|
|
{
|
|
date: "January 8, 2025",
|
|
title: "Zen Model Deployment",
|
|
status: "resolved",
|
|
duration: "15 minutes",
|
|
description: "Brief latency increase during Zen Ultra model deployment to production.",
|
|
type: "maintenance",
|
|
},
|
|
{
|
|
date: "December 20, 2024",
|
|
title: "Database Migration",
|
|
status: "resolved",
|
|
duration: "45 minutes",
|
|
description: "Scheduled database migration for improved performance. Planned maintenance window.",
|
|
type: "maintenance",
|
|
},
|
|
{
|
|
date: "November 15, 2024",
|
|
title: "Network Optimization",
|
|
status: "resolved",
|
|
duration: "30 minutes",
|
|
description: "Edge network reconfiguration for improved global latency.",
|
|
type: "maintenance",
|
|
},
|
|
];
|
|
|
|
/* Monochrome by brand policy, so a state is a FILL, not a hue: solid reads
|
|
healthy, dimmed reads degraded, hollow reads down. The five states used to
|
|
carry five copies of one identical class set, which rendered every service
|
|
the same and hid the one thing this page exists to say. */
|
|
const stateDot: Record<ServiceStatus["status"], string> = {
|
|
operational: "hz-bg-inverse",
|
|
degraded: "hz-bg-inverse hz-dim",
|
|
outage: "hz-bordered hz-border-strong",
|
|
maintenance: "hz-bg-inverse hz-dim-more",
|
|
};
|
|
|
|
export default function PageClient() {
|
|
// Null until mounted: this page is statically exported, so a clock rendered
|
|
// during the build can never match the browser's (React hydration #418).
|
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
|
|
const allOperational = services.every((s) => s.status === "operational");
|
|
|
|
const handleRefresh = () => {
|
|
setIsRefreshing(true);
|
|
setTimeout(() => {
|
|
setLastUpdated(new Date());
|
|
setIsRefreshing(false);
|
|
}, 1000);
|
|
};
|
|
|
|
// Auto-refresh every 60 seconds
|
|
useEffect(() => {
|
|
setLastUpdated(new Date());
|
|
const interval = setInterval(() => {
|
|
setLastUpdated(new Date());
|
|
}, 60000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const formatTime = (date: Date) => {
|
|
return date.toLocaleTimeString("en-US", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
timeZoneName: "short",
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className={cn("hz-min-h-screen hz-transition", "hz-bg hz-fg")}>
|
|
<main className="hz-pt-6">
|
|
{/* Status Banner */}
|
|
<section className="hz-py-7 hz-px-4">
|
|
<div className="hz-container-wide">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
className={cn(
|
|
"hz-r-xl hz-p-6 hz-mb-6",
|
|
allOperational
|
|
? "hz-bg-surface hz-bordered"
|
|
: "hz-bg-surface hz-bordered"
|
|
)}
|
|
>
|
|
<div className="hz-row hz-ai-center hz-jc-between hz-wrap hz-gap-4">
|
|
<div className="hz-row hz-ai-center hz-gap-4">
|
|
{allOperational ? (
|
|
<CheckCircle className="hz-sq-7 hz-fg" />
|
|
) : (
|
|
<AlertCircle className="hz-sq-7 hz-fg" />
|
|
)}
|
|
<div>
|
|
<h1 className="hz-t-3xl hz-w-bold">
|
|
{allOperational ? "All Systems Operational" : "Partial System Outage"}
|
|
</h1>
|
|
<p className={cn("hz-mt-1", "hz-fg")}>
|
|
{allOperational
|
|
? "All Hanzo services are running smoothly."
|
|
: "Some services are experiencing issues."}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="hz-row hz-ai-center hz-gap-4">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleRefresh}
|
|
disabled={isRefreshing}
|
|
className={cn("hz-fg hz-hoverable")}
|
|
>
|
|
<RefreshCw className={cn("hz-sq-2 hz-mr-2", isRefreshing ? "" : "")} />
|
|
Refresh
|
|
</Button>
|
|
<div className={cn("hz-row hz-ai-center hz-gap-2 hz-t-sm", "hz-fg")}>
|
|
<Clock className="hz-sq-2" />
|
|
<span>{lastUpdated ? `Updated ${formatTime(lastUpdated)}` : "Updating…"}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Service Status Grid */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.1 }}
|
|
className="hz-mb-7"
|
|
>
|
|
<div className="hz-row hz-ai-center hz-gap-3 hz-mb-5">
|
|
<Activity className="hz-sq-3" />
|
|
<h2 className="hz-t-xl hz-w-semibold">Services</h2>
|
|
</div>
|
|
|
|
<div className="hz-grid hz-gap-3">
|
|
{services.map((service, index) => {
|
|
const Icon = service.icon;
|
|
|
|
return (
|
|
<motion.div
|
|
key={service.name}
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: index * 0.05 }}
|
|
className={cn(
|
|
"hz-bordered hz-r-lg hz-p-4 hz-transition",
|
|
"hz-bg-surface hz-hoverable"
|
|
)}
|
|
>
|
|
<div className="hz-row hz-ai-center hz-jc-between">
|
|
<div className="hz-row hz-ai-center hz-gap-3">
|
|
<div className={cn("hz-sq-6 hz-r-lg hz-row hz-ai-center hz-jc-center", "hz-bg-surface")}>
|
|
<Icon className={cn("hz-sq-3", "hz-fg")} />
|
|
</div>
|
|
<div>
|
|
<div className="hz-row hz-ai-center hz-gap-2">
|
|
<h3 className="hz-w-medium">{service.name}</h3>
|
|
{service.url && (
|
|
<a
|
|
href={service.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={cn("hz-fg hz-hoverable")}
|
|
>
|
|
<ExternalLink className="hz-sq-1" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
<p className={cn("hz-t-sm", "hz-fg")}>{service.description}</p>
|
|
</div>
|
|
</div>
|
|
<div className="hz-row hz-ai-center hz-gap-2 hz-px-3 hz-py-1 hz-r-full hz-bg-surface">
|
|
<div className={cn("hz-sq-1 hz-r-full", stateDot[service.status])} />
|
|
<span className="hz-t-xs hz-w-medium hz-fg-muted">
|
|
{service.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
})}
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* 90-Day Uptime */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.2 }}
|
|
className="hz-mb-7"
|
|
>
|
|
<div className="hz-row hz-ai-center hz-jc-between hz-mb-4">
|
|
<h2 className="hz-t-xl hz-w-semibold">90-Day Uptime</h2>
|
|
<span className="hz-fg hz-w-medium">99.99%</span>
|
|
</div>
|
|
<div className={cn(
|
|
"hz-bordered hz-r-lg hz-p-4",
|
|
"hz-bg-surface"
|
|
)}>
|
|
<div className="hz-row hz-gap-1">
|
|
{Array.from({ length: 90 }).map((_, i) => {
|
|
// Real uptime data - mark actual maintenance windows
|
|
const isMaintenance = i === 25 || i === 45 || i === 70 || i === 85;
|
|
return (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
"hz-grow hz-bh-5 hz-r-sm hz-transition",
|
|
isMaintenance ? "hz-bg-surface" : "hz-bg-surface"
|
|
)}
|
|
title={`Day ${90 - i}: ${isMaintenance ? "Scheduled Maintenance" : "Operational"}`}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className={cn("hz-row hz-jc-between hz-mt-3 hz-t-xs", "hz-fg")}>
|
|
<span>90 days ago</span>
|
|
<div className="hz-row hz-ai-center hz-gap-4">
|
|
<span className="hz-row hz-ai-center hz-gap-1">
|
|
<div className="hz-sq-1 hz-r-sm hz-bg-surface" /> Operational
|
|
</span>
|
|
<span className="hz-row hz-ai-center hz-gap-1">
|
|
<div className="hz-sq-1 hz-r-sm hz-bg-surface" /> Maintenance
|
|
</span>
|
|
</div>
|
|
<span>Today</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Recent Activity */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.3 }}
|
|
>
|
|
<h2 className="hz-t-xl hz-w-semibold hz-mb-4">Recent Activity</h2>
|
|
<div className="hz-stack-3">
|
|
{historicalIncidents.map((incident, index) => (
|
|
<motion.div
|
|
key={incident.title}
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: 0.3 + index * 0.05 }}
|
|
className={cn(
|
|
"hz-bordered hz-r-lg hz-p-4",
|
|
"hz-bg-surface"
|
|
)}
|
|
>
|
|
<div className="hz-row hz-ai-start hz-jc-between hz-gap-4">
|
|
<div className="hz-grow">
|
|
<div className="hz-row hz-ai-center hz-gap-2 hz-mb-1">
|
|
<h3 className="hz-w-medium">{incident.title}</h3>
|
|
<span className={cn("hz-px-2 hz-py-1 hz-t-xs hz-r-full",
|
|
incident.type === "maintenance"
|
|
? "hz-bg-surface hz-fg"
|
|
: "hz-bg-surface hz-fg"
|
|
)}>
|
|
{incident.status}
|
|
</span>
|
|
</div>
|
|
<p className={cn("hz-t-sm", "hz-fg")}>{incident.description}</p>
|
|
</div>
|
|
<div className="hz-align-right hz-t-sm">
|
|
<div className={cn("hz-fg")}>{incident.date}</div>
|
|
<div className={cn("hz-fg")}>{incident.duration}</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Subscribe Section */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.4 }}
|
|
className={cn("hz-mt-7 hz-align-center hz-py-7 hz-border-t", "")}
|
|
>
|
|
<h2 className="hz-t-2xl hz-w-bold hz-mb-3">
|
|
Get Status Updates
|
|
</h2>
|
|
<p className={cn("hz-container-narrow hz-mw-sm hz-mb-5", "hz-fg")}>
|
|
Subscribe to receive notifications about system status and scheduled maintenance.
|
|
</p>
|
|
<div className="hz-col-row hz-gap-3 hz-jc-center">
|
|
<a href="https://x.com/hanzoai" target="_blank" rel="noopener noreferrer">
|
|
<Button variant="outline" className={cn("hz-hoverable")}>
|
|
Follow @hanzoai
|
|
</Button>
|
|
</a>
|
|
<a href="https://discord.gg/CJCyAsm9Vr" target="_blank" rel="noopener noreferrer">
|
|
<Button variant="outline" className={cn("hz-hoverable")}>
|
|
Join Discord
|
|
</Button>
|
|
</a>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|