Replace Puppeteer with Next.js ImageResponse for previews
- Remove puppeteer (2.9GB) in favor of built-in ImageResponse (0 deps) - Preview endpoint generates styled deployment card as PNG - Shows app name, status, branch, commit, duration, domain - Rename route.ts to route.tsx for JSX support - Simplify dashboard to use image URL directly
This commit is contained in:
@@ -47,13 +47,6 @@ interface StatsResponse {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface PreviewResponse {
|
||||
exists: boolean;
|
||||
url?: string;
|
||||
expiresIn?: number;
|
||||
message?: string;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
interface DeploymentDashboardProps {
|
||||
deployment: Deployment;
|
||||
@@ -484,12 +477,8 @@ export function DeploymentDashboard({ deployment }: DeploymentDashboardProps) {
|
||||
{ refreshInterval: 10000 }
|
||||
);
|
||||
|
||||
// Preview image (only fetch once, no refresh needed)
|
||||
const { data: preview, isLoading: previewLoading } = useSWR<PreviewResponse>(
|
||||
`/api/deployments/${deployment.deployment_uuid}/preview`,
|
||||
fetcher,
|
||||
{ revalidateOnFocus: false }
|
||||
);
|
||||
// Preview image URL (ImageResponse generates PNG directly)
|
||||
const previewUrl = `/api/deployments/${deployment.deployment_uuid}/preview`;
|
||||
|
||||
const coolifyUrl = `${clientConfig.coolifyUrl}/project/${clientConfig.coolifyProjectUuid}/production/application/${deployment.application_uuid}/deployment/${deployment.deployment_uuid}`;
|
||||
|
||||
@@ -645,40 +634,14 @@ export function DeploymentDashboard({ deployment }: DeploymentDashboardProps) {
|
||||
{/* Main content: Preview + Metadata Grid */}
|
||||
<div className="p-6">
|
||||
<div className="flex gap-6">
|
||||
{/* Preview thumbnail (left side) */}
|
||||
{/* Preview thumbnail (left side) - generated via ImageResponse */}
|
||||
<div className="flex-shrink-0 w-80">
|
||||
<div className="aspect-[16/10] bg-slate-900 dark:bg-stone-950 rounded-lg flex items-center justify-center overflow-hidden border border-slate-200 dark:border-stone-700 relative">
|
||||
{previewLoading ? (
|
||||
<div className="text-center">
|
||||
<Icon name="loader" size={32} className="text-slate-600 dark:text-stone-600 mx-auto mb-2 animate-spin" />
|
||||
<span className="text-xs text-slate-500 dark:text-stone-500">Loading preview...</span>
|
||||
</div>
|
||||
) : preview?.exists && preview.url ? (
|
||||
<img
|
||||
src={preview.url}
|
||||
alt={`Preview of ${deployment.application_name}`}
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
// Hide image on error and show placeholder
|
||||
e.currentTarget.style.display = 'none';
|
||||
e.currentTarget.nextElementSibling?.classList.remove('hidden');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<Icon name="image" size={48} className="text-slate-600 dark:text-stone-600 mx-auto mb-2" />
|
||||
<span className="text-xs text-slate-500 dark:text-stone-500">
|
||||
{deployment.status === 'in_progress' ? 'Preview after deploy' : 'No preview'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Fallback placeholder (hidden by default, shown on image error) */}
|
||||
<div className="hidden absolute inset-0 flex items-center justify-center bg-slate-900 dark:bg-stone-950">
|
||||
<div className="text-center">
|
||||
<Icon name="image" size={48} className="text-slate-600 dark:text-stone-600 mx-auto mb-2" />
|
||||
<span className="text-xs text-slate-500 dark:text-stone-500">Preview unavailable</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="aspect-[16/10] rounded-lg overflow-hidden border border-slate-200 dark:border-stone-700">
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt={`Preview of ${deployment.application_name}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user