81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState, ReactNode } from 'react';
|
|
|
|
export default function ServiceButton({ url, checkUrl, className, icon, name, desc, access, disabled }: { url: string, checkUrl?: string, className: string, icon: ReactNode, name: string, desc: string, access?: 'public' | 'private' | 'on_request', disabled?: boolean }) {
|
|
const [status, setStatus] = useState<'pending' | 'up' | 'down'>('pending');
|
|
|
|
useEffect(() => {
|
|
// Disabled buttons are decorative: skip the health check.
|
|
if (disabled) return;
|
|
|
|
const checkStatus = async () => {
|
|
try {
|
|
const targetUrl = checkUrl || url;
|
|
const res = await fetch(`/api/ping?url=${encodeURIComponent(targetUrl)}`);
|
|
const data = await res.json();
|
|
setStatus(data.status);
|
|
} catch {
|
|
setStatus('down');
|
|
}
|
|
};
|
|
checkStatus();
|
|
}, [url, checkUrl, disabled]);
|
|
|
|
// Disabled buttons always read as green without a state update in the effect.
|
|
const effectiveStatus = disabled ? 'up' : status;
|
|
const statusColor = effectiveStatus === 'up' ? '#4ade80' : effectiveStatus === 'down' ? '#ef4444' : '#9ca3af';
|
|
const statusGlow = effectiveStatus === 'up' ? 'rgba(74, 222, 128, 0.9)' : effectiveStatus === 'down' ? 'rgba(239, 68, 68, 0.9)' : 'transparent';
|
|
const statusLabel = effectiveStatus === 'up' ? 'Active' : effectiveStatus === 'down' ? 'Dead' : 'Checking...';
|
|
|
|
const content = (
|
|
<>
|
|
<span className="service-icon-wrap">
|
|
<span className="service-status-dot" />
|
|
<span className="service-icon">{icon}</span>
|
|
</span>
|
|
<span className="service-text">
|
|
<span className="service-name">
|
|
{name}
|
|
{access === 'public' && <span style={{ color: 'var(--accent)', fontSize: '0.7em', marginLeft: '6px' }}>(public)</span>}
|
|
{access === 'private' && <span style={{ color: 'var(--red)', fontSize: '0.7em', marginLeft: '6px' }}>(private)</span>}
|
|
{access === 'on_request' && <span style={{ color: 'var(--accent2)', fontSize: '0.7em', marginLeft: '6px' }}>(on request)</span>}
|
|
</span>
|
|
<span className="service-desc">{desc}</span>
|
|
</span>
|
|
</>
|
|
);
|
|
|
|
const sharedStyle = {
|
|
position: 'relative',
|
|
'--status-color': statusColor,
|
|
'--status-glow': statusGlow,
|
|
...(disabled ? { cursor: 'default' } : {}),
|
|
} as React.CSSProperties;
|
|
|
|
if (disabled) {
|
|
return (
|
|
<span
|
|
className={`service-button ${className} disabled`}
|
|
style={sharedStyle}
|
|
title={statusLabel}
|
|
aria-disabled="true"
|
|
>
|
|
{content}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<a
|
|
href={url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={`service-button ${className}`}
|
|
style={sharedStyle}
|
|
title={statusLabel}
|
|
>
|
|
{content}
|
|
</a>
|
|
);
|
|
}
|