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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user