Add managed databases and services with billing-aligned upgrades.
Introduce product types for managed PostgreSQL, Redis, and RabbitMQ with a dedicated dashboard, Helm-only deploy pipeline, external access, snapshots with progress, and prorated resource or storage upgrades matching application billing rules. PVCs use an expandable StorageClass with automatic migration when legacy disks cannot resize in place. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import api from '@/lib/api';
|
||||
import { FileText, ChevronDown, Monitor, Hammer, Pin } from 'lucide-react';
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
running: 'badge-green',
|
||||
pending: 'badge-yellow',
|
||||
building: 'badge-blue',
|
||||
deploying: 'badge-blue',
|
||||
failed: 'badge-red',
|
||||
build_failed: 'badge-red',
|
||||
cancelled: 'badge-gray',
|
||||
stopped: 'badge-gray',
|
||||
};
|
||||
|
||||
export function WorkloadLogsPanel({
|
||||
appId,
|
||||
showBuildLogs = true,
|
||||
isRunning = false,
|
||||
isStopped = false,
|
||||
emptyPodMessage,
|
||||
}: {
|
||||
appId: string;
|
||||
showBuildLogs?: boolean;
|
||||
isRunning?: boolean;
|
||||
isStopped?: boolean;
|
||||
emptyPodMessage?: string;
|
||||
}) {
|
||||
const [showLogs, setShowLogs] = useState(false);
|
||||
const [logTab, setLogTab] = useState<'pod' | 'build'>('pod');
|
||||
const logsEndRef = useRef<HTMLPreElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showBuildLogs && logTab === 'build') {
|
||||
setLogTab('pod');
|
||||
}
|
||||
}, [showBuildLogs, logTab]);
|
||||
|
||||
const { data: logsData } = useQuery<{ logs: string }>({
|
||||
queryKey: ['logs', appId],
|
||||
queryFn: () => api.get(`/deployments/applications/${appId}/logs`).then((r) => r.data),
|
||||
enabled: showLogs && logTab === 'pod',
|
||||
refetchInterval: showLogs && logTab === 'pod' ? 3000 : false,
|
||||
});
|
||||
|
||||
const { data: buildLogsData } = useQuery<{
|
||||
buildLog: string | null;
|
||||
status: string;
|
||||
version: string | null;
|
||||
}>({
|
||||
queryKey: ['build-logs', appId],
|
||||
queryFn: () => api.get(`/deployments/applications/${appId}/build-logs`).then((r) => r.data),
|
||||
enabled: showBuildLogs && showLogs && logTab === 'build',
|
||||
refetchInterval: showBuildLogs && showLogs && logTab === 'build' ? 5000 : false,
|
||||
});
|
||||
|
||||
const podPlaceholder =
|
||||
emptyPodMessage ||
|
||||
(isRunning
|
||||
? 'Loading logs...'
|
||||
: isStopped
|
||||
? 'Service is stopped. Start it to see logs.'
|
||||
: 'Waiting for workload pods to be ready...');
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||
<FileText className="w-5 h-5" /> Logs
|
||||
</h2>
|
||||
<div className="flex items-center space-x-3">
|
||||
{showLogs && logTab === 'pod' && (
|
||||
<span className="text-xs text-gray-400 flex items-center space-x-1">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||
<span>Live (every 3s)</span>
|
||||
</span>
|
||||
)}
|
||||
{showBuildLogs && showLogs && logTab === 'build' && (
|
||||
<span className="text-xs text-gray-400 flex items-center space-x-1">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
|
||||
<span>Auto-refresh (every 5s)</span>
|
||||
</span>
|
||||
)}
|
||||
<button type="button" onClick={() => setShowLogs(!showLogs)} className="btn-secondary text-sm">
|
||||
{showLogs ? (
|
||||
<>
|
||||
<ChevronDown className="w-4 h-4 inline" /> Hide logs
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FileText className="w-4 h-4 inline" /> Show logs
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showLogs && (
|
||||
<div className="space-y-3">
|
||||
{showBuildLogs ? (
|
||||
<div className="flex gap-1 bg-gray-100 rounded-xl p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setLogTab('pod')}
|
||||
className={`flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-all ${
|
||||
logTab === 'pod' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Monitor className="w-4 h-4 inline" /> Pod logs
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setLogTab('build')}
|
||||
className={`flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-all ${
|
||||
logTab === 'build' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Hammer className="w-4 h-4 inline" /> Build logs
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-gray-500">Workload pod output (no image build for this service).</p>
|
||||
)}
|
||||
|
||||
{logTab === 'pod' && (
|
||||
<pre
|
||||
ref={logsEndRef}
|
||||
className="bg-gray-900 text-green-400 p-4 rounded-xl text-xs font-mono overflow-x-auto max-h-[500px] overflow-y-auto whitespace-pre-wrap break-words"
|
||||
>
|
||||
{logsData?.logs || podPlaceholder}
|
||||
</pre>
|
||||
)}
|
||||
|
||||
{showBuildLogs && logTab === 'build' && (
|
||||
<div>
|
||||
{buildLogsData?.version && (
|
||||
<div className="flex items-center gap-3 mb-2 text-xs text-gray-500">
|
||||
<span>
|
||||
<Pin className="w-3 h-3 inline" /> {buildLogsData.version}
|
||||
</span>
|
||||
<span className={`badge ${statusColors[buildLogsData.status] || 'badge-gray'}`}>
|
||||
{buildLogsData.status}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<pre className="bg-gray-900 text-blue-300 p-4 rounded-xl text-xs font-mono overflow-x-auto max-h-[500px] overflow-y-auto whitespace-pre-wrap break-words">
|
||||
{buildLogsData?.buildLog ||
|
||||
(buildLogsData?.status === 'building'
|
||||
? 'Build in progress... Logs will appear when complete.'
|
||||
: buildLogsData?.status === 'pending'
|
||||
? 'Build is pending...'
|
||||
: buildLogsData?.status === 'no_deployment'
|
||||
? 'No deployments yet. Deploy your app to see build logs.'
|
||||
: 'No build logs available for this deployment.')}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user