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:
@@ -9,6 +9,7 @@ import { formatUptime } from '@/lib/stats';
|
|||||||
import { STATUS_COLORS, STATUS_LABELS, formatRelativeTime, formatDuration } from '@/lib/deployments';
|
import { STATUS_COLORS, STATUS_LABELS, formatRelativeTime, formatDuration } from '@/lib/deployments';
|
||||||
import type { DeploymentStatus } from '@/lib/deployments';
|
import type { DeploymentStatus } from '@/lib/deployments';
|
||||||
import type { HealthStatus } from '@/lib/PortalContext';
|
import type { HealthStatus } from '@/lib/PortalContext';
|
||||||
|
import { getCoolifyUrl } from '@/lib/services';
|
||||||
import type { Service, DiscoveredService } from '@/lib/services';
|
import type { Service, DiscoveredService } from '@/lib/services';
|
||||||
|
|
||||||
function isDiscoveredService(s: Service): s is DiscoveredService {
|
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"
|
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" />
|
<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 && (
|
{uuid && (
|
||||||
<button
|
<button
|
||||||
onClick={() => controlService(s, 'start')}
|
onClick={() => controlService(s, 'start')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
title={`Start ${s.name}`}
|
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 ? (
|
{loading ? (
|
||||||
<Icon name="loader" size={11} className="animate-spin" />
|
<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"
|
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" />
|
<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 && (
|
{uuid && (
|
||||||
<button
|
<button
|
||||||
onClick={() => controlService(s, 'start')}
|
onClick={() => controlService(s, 'start')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
title={`Start ${s.name}`}
|
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 ? (
|
{loading ? (
|
||||||
<Icon name="loader" size={11} className="animate-spin" />
|
<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"
|
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" />
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 flex-shrink-0" />
|
||||||
{s.name}
|
<a href={s.url} target="_blank" rel="noopener noreferrer" className="hover:underline">{s.name}</a>
|
||||||
{uuid && (
|
<span className="flex items-center gap-0.5 ml-0.5">
|
||||||
<span className="flex items-center gap-0.5 ml-0.5">
|
{isDiscoveredService(s) && (
|
||||||
<button
|
<a
|
||||||
onClick={() => controlService(s, 'restart')}
|
href={getCoolifyUrl(s)}
|
||||||
disabled={loading}
|
target="_blank"
|
||||||
title={`Restart ${s.name}`}
|
rel="noopener noreferrer"
|
||||||
className="p-0.5 rounded hover:bg-emerald-100 dark:hover:bg-emerald-800/30 transition-colors disabled:opacity-50"
|
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="settings" size={11} />
|
||||||
<Icon name="loader" size={11} className="animate-spin" />
|
</a>
|
||||||
) : (
|
)}
|
||||||
<Icon name="refresh-cw" size={11} />
|
{uuid && (
|
||||||
)}
|
<>
|
||||||
</button>
|
<button
|
||||||
<button
|
onClick={() => controlService(s, 'restart')}
|
||||||
onClick={() => controlService(s, 'stop')}
|
disabled={loading}
|
||||||
disabled={loading}
|
title={`Restart ${s.name}`}
|
||||||
title={`Stop ${s.name}`}
|
className="p-0.5 rounded hover:bg-emerald-100 dark:hover:bg-emerald-800/30 transition-colors disabled:opacity-50"
|
||||||
className="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-800/30 hover:text-red-500 transition-colors disabled:opacity-50"
|
>
|
||||||
>
|
{loading ? (
|
||||||
<Icon name="power" size={11} />
|
<Icon name="loader" size={11} className="animate-spin" />
|
||||||
</button>
|
) : (
|
||||||
</span>
|
<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>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { Service, DiscoveredService } from '@/lib/services';
|
import { Service, DiscoveredService, getCoolifyUrl } from '@/lib/services';
|
||||||
import { HealthStatus } from '@/lib/PortalContext';
|
import { HealthStatus } from '@/lib/PortalContext';
|
||||||
import { Icon } from './Icons';
|
import { Icon } from './Icons';
|
||||||
|
|
||||||
@@ -101,13 +101,24 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
|
|||||||
|
|
||||||
return (
|
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' : ''}`}>
|
<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">
|
<div className="absolute top-3 right-3 flex items-center gap-1.5">
|
||||||
{resourceBadge && (
|
{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">
|
<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}
|
{resourceBadge}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Clickable area for opening service URL */}
|
{/* Clickable area for opening service URL */}
|
||||||
|
|||||||
Reference in New Issue
Block a user