Improve build logs UI: hide controls while building

- Hide Linux Distribution dropdown and Build button during build
- Show only build output and status badges when building
- Properly format size (show GB for sizes >= 1GB)
- Increase log viewer height and center loading state
This commit is contained in:
Thomas Marchand
2026-01-15 11:55:00 +00:00
parent d89423ef4e
commit 38007de1d6

View File

@@ -588,124 +588,119 @@ export default function WorkspacesPage() {
)}
</div>
<div className="p-4 space-y-4">
<div>
<label className="text-xs text-white/40 block mb-2">Linux Distribution</label>
<select
value={selectedDistro}
onChange={(e) => setSelectedDistro(e.target.value as ChrootDistro)}
disabled={building || selectedWorkspace.status === 'building'}
className="w-full px-3 py-2.5 rounded-lg bg-black/20 border border-white/[0.06] text-sm text-white focus:outline-none focus:border-indigo-500/50 disabled:opacity-50 appearance-none cursor-pointer"
style={{
backgroundImage:
"url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\")",
backgroundPosition: 'right 0.75rem center',
backgroundRepeat: 'no-repeat',
backgroundSize: '1.25em 1.25em',
}}
>
{CHROOT_DISTROS.map((distro) => (
<option key={distro.value} value={distro.value} className="bg-[#161618]">
{distro.label}
</option>
))}
</select>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => handleBuildWorkspace(selectedWorkspace.status === 'ready')}
disabled={building || selectedWorkspace.status === 'building'}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-indigo-500 hover:bg-indigo-600 rounded-lg disabled:opacity-50 transition-colors"
>
{building ? (
<>
<Loader className="h-4 w-4 animate-spin" />
{selectedWorkspace.status === 'ready' ? 'Rebuilding...' : 'Building...'}
</>
) : selectedWorkspace.status === 'ready' ? (
<>
<RefreshCw className="h-4 w-4" />
Rebuild
</>
) : (
<>
<Hammer className="h-4 w-4" />
Build
</>
)}
</button>
<p className="text-xs text-white/40 flex-1">
{selectedWorkspace.status === 'ready'
? 'Destroys container and reruns init script'
: 'Creates isolated Linux filesystem'}
</p>
</div>
{/* Show build controls when not building */}
{selectedWorkspace.status !== 'building' && (
<>
<div>
<label className="text-xs text-white/40 block mb-2">Linux Distribution</label>
<select
value={selectedDistro}
onChange={(e) => setSelectedDistro(e.target.value as ChrootDistro)}
disabled={building}
className="w-full px-3 py-2.5 rounded-lg bg-black/20 border border-white/[0.06] text-sm text-white focus:outline-none focus:border-indigo-500/50 disabled:opacity-50 appearance-none cursor-pointer"
style={{
backgroundImage:
"url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\")",
backgroundPosition: 'right 0.75rem center',
backgroundRepeat: 'no-repeat',
backgroundSize: '1.25em 1.25em',
}}
>
{CHROOT_DISTROS.map((distro) => (
<option key={distro.value} value={distro.value} className="bg-[#161618]">
{distro.label}
</option>
))}
</select>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => handleBuildWorkspace(selectedWorkspace.status === 'ready')}
disabled={building}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-indigo-500 hover:bg-indigo-600 rounded-lg disabled:opacity-50 transition-colors"
>
{building ? (
<>
<Loader className="h-4 w-4 animate-spin" />
{selectedWorkspace.status === 'ready' ? 'Rebuilding...' : 'Building...'}
</>
) : selectedWorkspace.status === 'ready' ? (
<>
<RefreshCw className="h-4 w-4" />
Rebuild
</>
) : (
<>
<Hammer className="h-4 w-4" />
Build
</>
)}
</button>
<p className="text-xs text-white/40 flex-1">
{selectedWorkspace.status === 'ready'
? 'Destroys container and reruns init script'
: 'Creates isolated Linux filesystem'}
</p>
</div>
</>
)}
{/* Build Progress Logs */}
{selectedWorkspace.status === 'building' && (buildDebug || buildLog) && (
<div className="mt-4 pt-4 border-t border-white/[0.06]">
<button
onClick={() => setShowBuildLogs(!showBuildLogs)}
className="flex items-center justify-between w-full text-left"
>
{/* Build Progress Logs - shown when building */}
{selectedWorkspace.status === 'building' && (
<div className="space-y-3">
{/* Header with size */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileText className="h-3.5 w-3.5 text-amber-400" />
<span className="text-xs text-white/70 font-medium">Build Logs</span>
<span className="text-xs text-white/70 font-medium">Build Output</span>
</div>
<div className="flex items-center gap-2">
{buildDebug?.size_bytes && (
<span className="text-[10px] text-white/40 font-mono">
{(buildDebug.size_bytes / 1024 / 1024).toFixed(1)} MB
{buildDebug?.size_bytes != null && buildDebug.size_bytes > 0 && (
<span className="text-[10px] text-white/40 font-mono">
{buildDebug.size_bytes >= 1024 * 1024 * 1024
? `${(buildDebug.size_bytes / 1024 / 1024 / 1024).toFixed(2)} GB`
: `${(buildDebug.size_bytes / 1024 / 1024).toFixed(1)} MB`}
</span>
)}
</div>
{/* Container Status Badges */}
{buildDebug && (
<div className="flex flex-wrap gap-2">
{buildDebug.has_bash && (
<span className="px-2 py-0.5 text-[10px] font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 rounded">
bash ready
</span>
)}
{buildDebug.init_script_exists && (
<span className="px-2 py-0.5 text-[10px] font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20 rounded">
init script running
</span>
)}
{buildDebug.distro && (
<span className="px-2 py-0.5 text-[10px] font-mono text-white/40 bg-white/[0.04] border border-white/[0.06] rounded">
{buildDebug.distro}
</span>
)}
<span className="text-xs text-white/40">{showBuildLogs ? '' : '+'}</span>
</div>
</button>
)}
{showBuildLogs && (
<div className="mt-3 space-y-3">
{/* Container Status */}
{buildDebug && (
<div className="flex flex-wrap gap-2">
{buildDebug.has_bash && (
<span className="px-2 py-0.5 text-[10px] font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 rounded">
bash ready
</span>
)}
{buildDebug.init_script_exists && (
<span className="px-2 py-0.5 text-[10px] font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20 rounded">
init script running
</span>
)}
{buildDebug.distro && (
<span className="px-2 py-0.5 text-[10px] font-mono text-white/40 bg-white/[0.04] border border-white/[0.06] rounded">
{buildDebug.distro}
</span>
)}
</div>
)}
{/* Init Log Output */}
{buildLog?.exists && buildLog.content && (
<div className="rounded-lg bg-black/30 border border-white/[0.06] overflow-hidden">
<div className="px-3 py-1.5 border-b border-white/[0.06] flex items-center justify-between">
<span className="text-[10px] text-white/40 font-mono">{buildLog.log_path}</span>
{buildLog.total_lines && (
<span className="text-[10px] text-white/30">{buildLog.total_lines} lines</span>
)}
</div>
<pre className="p-3 text-[11px] font-mono text-white/70 overflow-x-auto max-h-48 overflow-y-auto whitespace-pre-wrap break-all">
{buildLog.content.split('\n').slice(-50).join('\n')}
</pre>
</div>
)}
{/* No logs yet message */}
{(!buildLog?.exists || !buildLog?.content) && (
<div className="flex items-center gap-2 py-3 text-xs text-white/40">
<Loader className="h-3 w-3 animate-spin" />
<span>Waiting for build output...</span>
</div>
)}
{/* Init Log Output */}
{buildLog?.exists && buildLog.content ? (
<div className="rounded-lg bg-black/30 border border-white/[0.06] overflow-hidden">
<div className="px-3 py-1.5 border-b border-white/[0.06] flex items-center justify-between">
<span className="text-[10px] text-white/40 font-mono">{buildLog.log_path}</span>
{buildLog.total_lines && (
<span className="text-[10px] text-white/30">{buildLog.total_lines} lines</span>
)}
</div>
<pre className="p-3 text-[11px] font-mono text-white/70 overflow-x-auto max-h-64 overflow-y-auto whitespace-pre-wrap break-all">
{buildLog.content.split('\n').slice(-50).join('\n')}
</pre>
</div>
) : (
<div className="flex items-center gap-2 py-6 justify-center text-xs text-white/40">
<Loader className="h-3 w-3 animate-spin" />
<span>Waiting for build output...</span>
</div>
)}
</div>