Redesign ServiceCard: card body is informational, links in footer

The card body no longer wraps in an <a> tag. Instead, the footer has
explicit icon buttons: external-link for the web URL, settings gear for
Coolify management, with a divider before start/stop/restart controls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-04 00:52:50 +01:00
parent 5fd10819e4
commit 1a5be27a17

View File

@@ -101,68 +101,50 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
return (
<div className={`group relative p-4 bg-white dark:bg-stone-900 rounded-xl border border-slate-100 dark:border-stone-700/50 border-l-[3px] ${borderColors[status]} shadow-sm hover:shadow-md transition-all duration-200 ${isStopped ? 'opacity-75 hover:opacity-100' : ''}`}>
{/* Top row: badges + links */}
{/* Top row: badge */}
<div className="absolute top-3 right-3 flex items-center gap-1.5">
{resourceBadge && (
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-slate-100 dark:bg-stone-800 text-slate-500 dark:text-stone-400">
{resourceBadge}
</span>
)}
{discovered && (
<a
href={getCoolifyUrl(service as DiscoveredService)}
target="_blank"
rel="noopener noreferrer"
title="Manage in Coolify"
className="p-1 rounded-md text-slate-300 dark:text-stone-600 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
>
<Icon name="settings" size={14} />
</a>
</div>
{/* Service info (non-clickable) */}
<div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-100 dark:bg-stone-800 mb-3">
<Icon
name={service.icon}
size={20}
className="text-slate-600 dark:text-stone-400"
/>
</div>
<h3 className="font-medium text-slate-900 dark:text-stone-100 mb-1">
{service.name}
</h3>
{service.description && (
<p className="text-sm text-slate-500 dark:text-stone-500 line-clamp-2">
{service.description}
</p>
)}
<div className="mt-3 flex items-center gap-2">
{fqdnLabel && (
<span className="text-xs px-1.5 py-0.5 rounded bg-cyan-50 dark:bg-cyan-900/20 text-cyan-600 dark:text-cyan-400 font-mono">
{fqdnLabel}
</span>
)}
{service.port > 0 && (
<span className="text-xs text-slate-400 dark:text-stone-600 font-mono">
:{service.port}
</span>
)}
</div>
{/* Clickable area for opening service URL */}
<a
href={service.url}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-100 dark:bg-stone-800 mb-3 group-hover:bg-slate-200 dark:group-hover:bg-stone-700 transition-colors">
<Icon
name={service.icon}
size={20}
className="text-slate-600 dark:text-stone-400"
/>
</div>
<h3 className="font-medium text-slate-900 dark:text-stone-100 mb-1">
{service.name}
</h3>
{service.description && (
<p className="text-sm text-slate-500 dark:text-stone-500 line-clamp-2">
{service.description}
</p>
)}
<div className="mt-3 flex items-center gap-2">
{fqdnLabel && (
<span className="text-xs px-1.5 py-0.5 rounded bg-cyan-50 dark:bg-cyan-900/20 text-cyan-600 dark:text-cyan-400 font-mono">
{fqdnLabel}
</span>
)}
{service.port > 0 && (
<span className="text-xs text-slate-400 dark:text-stone-600 font-mono">
:{service.port}
</span>
)}
</div>
</a>
{/* Controls footer */}
{discovered && (
<div className="mt-3 pt-3 border-t border-slate-100 dark:border-stone-800 flex items-center justify-between">
{/* Status pill */}
{/* Footer: status + links + controls */}
<div className="mt-3 pt-3 border-t border-slate-100 dark:border-stone-800 flex items-center justify-between">
{/* Left: status pill */}
{discovered ? (
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium ${statusPillStyles[status]}`}>
<Icon
name={loading ? 'loader' : statusIcons[status]}
@@ -171,10 +153,44 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
/>
{loading ? 'Processing...' : statusLabels[status]}
</span>
) : (
<span />
)}
{/* Action buttons */}
{!loading && (
<div className="flex items-center gap-1">
{/* Right: links + action buttons */}
<div className="flex items-center gap-1">
{/* Open website */}
<a
href={service.url}
target="_blank"
rel="noopener noreferrer"
title="Open website"
className="p-1.5 rounded-md text-slate-400 dark:text-stone-500 hover:bg-slate-100 dark:hover:bg-stone-800 hover:text-slate-600 dark:hover:text-stone-300 transition-colors"
>
<Icon name="external-link" size={14} />
</a>
{/* Manage in Coolify */}
{discovered && (
<a
href={getCoolifyUrl(service as DiscoveredService)}
target="_blank"
rel="noopener noreferrer"
title="Manage in Coolify"
className="p-1.5 rounded-md text-slate-400 dark:text-stone-500 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
>
<Icon name="settings" size={14} />
</a>
)}
{/* Divider between links and controls */}
{discovered && !loading && (status === 'running' || isStopped) && (
<span className="w-px h-4 bg-slate-200 dark:bg-stone-700 mx-0.5" />
)}
{/* Control buttons */}
{discovered && !loading && (
<>
{isStopped ? (
<button
onClick={() => controlService('start')}
@@ -205,10 +221,10 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
</button>
</>
) : null}
</div>
</>
)}
</div>
)}
</div>
</div>
);
}