Add Coolify management links to service cards and overview chips

Each discovered service now shows a settings gear icon linking to its
Coolify management page alongside the existing web URL link.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-04 00:51:42 +01:00
parent 9e683eba22
commit 5fd10819e4
2 changed files with 77 additions and 30 deletions

View File

@@ -9,6 +9,7 @@ import { formatUptime } from '@/lib/stats';
import { STATUS_COLORS, STATUS_LABELS, formatRelativeTime, formatDuration } from '@/lib/deployments';
import type { DeploymentStatus } from '@/lib/deployments';
import type { HealthStatus } from '@/lib/PortalContext';
import { getCoolifyUrl } from '@/lib/services';
import type { Service, DiscoveredService } from '@/lib/services';
function isDiscoveredService(s: Service): s is DiscoveredService {
@@ -208,13 +209,24 @@ export function OverviewTab() {
className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 border border-red-100 dark:border-red-800/30"
>
<span className="w-1.5 h-1.5 rounded-full bg-red-500 flex-shrink-0" />
{s.name}
<a href={s.url} target="_blank" rel="noopener noreferrer" className="hover:underline">{s.name}</a>
{isDiscoveredService(s) && (
<a
href={getCoolifyUrl(s)}
target="_blank"
rel="noopener noreferrer"
title={`Manage ${s.name} in Coolify`}
className="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-800/30 transition-colors"
>
<Icon name="settings" size={11} />
</a>
)}
{uuid && (
<button
onClick={() => controlService(s, 'start')}
disabled={loading}
title={`Start ${s.name}`}
className="ml-0.5 p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-800/30 transition-colors disabled:opacity-50"
className="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-800/30 transition-colors disabled:opacity-50"
>
{loading ? (
<Icon name="loader" size={11} className="animate-spin" />
@@ -243,13 +255,24 @@ export function OverviewTab() {
className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-slate-50 dark:bg-stone-800/50 text-slate-500 dark:text-stone-500 border border-slate-200 dark:border-stone-700/50"
>
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-stone-600 flex-shrink-0" />
{s.name}
<a href={s.url} target="_blank" rel="noopener noreferrer" className="hover:underline">{s.name}</a>
{isDiscoveredService(s) && (
<a
href={getCoolifyUrl(s)}
target="_blank"
rel="noopener noreferrer"
title={`Manage ${s.name} in Coolify`}
className="p-0.5 rounded hover:bg-slate-200 dark:hover:bg-stone-700 transition-colors"
>
<Icon name="settings" size={11} />
</a>
)}
{uuid && (
<button
onClick={() => controlService(s, 'start')}
disabled={loading}
title={`Start ${s.name}`}
className="ml-0.5 p-0.5 rounded hover:bg-slate-200 dark:hover:bg-stone-700 transition-colors disabled:opacity-50"
className="p-0.5 rounded hover:bg-slate-200 dark:hover:bg-stone-700 transition-colors disabled:opacity-50"
>
{loading ? (
<Icon name="loader" size={11} className="animate-spin" />
@@ -278,31 +301,44 @@ export function OverviewTab() {
className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border border-emerald-100 dark:border-emerald-800/30"
>
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 flex-shrink-0" />
{s.name}
{uuid && (
<span className="flex items-center gap-0.5 ml-0.5">
<button
onClick={() => controlService(s, 'restart')}
disabled={loading}
title={`Restart ${s.name}`}
className="p-0.5 rounded hover:bg-emerald-100 dark:hover:bg-emerald-800/30 transition-colors disabled:opacity-50"
<a href={s.url} target="_blank" rel="noopener noreferrer" className="hover:underline">{s.name}</a>
<span className="flex items-center gap-0.5 ml-0.5">
{isDiscoveredService(s) && (
<a
href={getCoolifyUrl(s)}
target="_blank"
rel="noopener noreferrer"
title={`Manage ${s.name} in Coolify`}
className="p-0.5 rounded hover:bg-emerald-100 dark:hover:bg-emerald-800/30 transition-colors"
>
{loading ? (
<Icon name="loader" size={11} className="animate-spin" />
) : (
<Icon name="refresh-cw" size={11} />
)}
</button>
<button
onClick={() => controlService(s, 'stop')}
disabled={loading}
title={`Stop ${s.name}`}
className="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-800/30 hover:text-red-500 transition-colors disabled:opacity-50"
>
<Icon name="power" size={11} />
</button>
</span>
)}
<Icon name="settings" size={11} />
</a>
)}
{uuid && (
<>
<button
onClick={() => controlService(s, 'restart')}
disabled={loading}
title={`Restart ${s.name}`}
className="p-0.5 rounded hover:bg-emerald-100 dark:hover:bg-emerald-800/30 transition-colors disabled:opacity-50"
>
{loading ? (
<Icon name="loader" size={11} className="animate-spin" />
) : (
<Icon name="refresh-cw" size={11} />
)}
</button>
<button
onClick={() => controlService(s, 'stop')}
disabled={loading}
title={`Stop ${s.name}`}
className="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-800/30 hover:text-red-500 transition-colors disabled:opacity-50"
>
<Icon name="power" size={11} />
</button>
</>
)}
</span>
</span>
);
})}

View File

@@ -1,7 +1,7 @@
'use client';
import { useState, useCallback } from 'react';
import { Service, DiscoveredService } from '@/lib/services';
import { Service, DiscoveredService, getCoolifyUrl } from '@/lib/services';
import { HealthStatus } from '@/lib/PortalContext';
import { Icon } from './Icons';
@@ -101,13 +101,24 @@ 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 */}
{/* Top row: badges + links */}
<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>
{/* Clickable area for opening service URL */}