cockpit-kata/cockpit-kata/src/components/kata/KernelDetail.tsx

958 lines
38 KiB
TypeScript
Executable File

"use client";
import React, { useState, useEffect } from "react";
import { cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Progress } from "@/components/ui/progress";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Cpu,
CheckCircle2,
XCircle,
AlertTriangle,
FileText,
Terminal,
Info,
Settings,
Download,
ChevronDown,
ChevronRight,
ExternalLink,
RefreshCw,
} from "lucide-react";
import {
fetchQCrowsImages,
KATA_REQUIRED_KERNEL_OPTIONS,
KATA_RECOMMENDED_KERNEL_OPTIONS,
HYPERVISOR_KERNEL_FORMAT,
VMM_MODULE_MATRIX,
COMPRESS_PRIORITY,
INITRD_REGEN_DEFAULTS,
type QCrowsImage,
type QCrowsKernelConfig,
type QCrowsBootParams,
type InitrdRegenConfig,
type InitrdRegenResult,
} from "@/lib/kata-mock";
import { regenInitrd } from "@/lib/backend/adapter";
export function KernelDetail() {
const [images, setImages] = useState<QCrowsImage[]>([]);
const [loading, setLoading] = useState(true);
const [selectedImage, setSelectedImage] = useState<QCrowsImage | null>(null);
useEffect(() => {
fetchQCrowsImages().then((data) => {
setImages(data);
if (data.length > 0) setSelectedImage(data[0]);
setLoading(false);
});
}, []);
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-pulse text-slate-400">Loading kernel images...</div>
</div>
);
}
return (
<div className="p-6 space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
<Cpu className="h-5 w-5 text-emerald-400" />
Kernel Management
</h2>
<p className="text-sm text-slate-400 mt-1">
Inspect guest kernels bundled in QCrows images config options, boot parameters, and hypervisor compatibility
</p>
</div>
<Badge variant="outline" className="border-emerald-700 text-emerald-400 text-[10px]">
QCrows v0.2 Kernel REQUIRED
</Badge>
</div>
<div className="grid grid-cols-12 gap-6">
{/* Image selector sidebar */}
<div className="col-span-3">
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-xs font-medium text-slate-400 uppercase tracking-wider">
QCrows Images
</CardTitle>
</CardHeader>
<CardContent className="px-2 pb-3">
<div className="space-y-1">
{images.map((img) => (
<button
key={img.id}
onClick={() => setSelectedImage(img)}
className={cn(
"w-full text-left rounded-md px-3 py-2.5 text-sm transition-colors",
selectedImage?.id === img.id
? "bg-slate-700/60 text-white"
: "text-slate-400 hover:text-slate-200 hover:bg-slate-800/60"
)}
>
<div className="font-medium truncate">{img.menu.label}</div>
<div className="text-[11px] text-slate-500 mt-0.5 flex items-center gap-2">
<span>{img.metadata.kernelVersion}</span>
<span className="text-slate-600">|</span>
<span>{img.metadata.kernelFormat}</span>
<span className="text-slate-600">|</span>
<span>{img.metadata.kernelSizeMB}MB</span>
</div>
</button>
))}
</div>
</CardContent>
</Card>
</div>
{/* Main kernel detail area */}
<div className="col-span-9 space-y-5">
{selectedImage && (
<KernelDetailView image={selectedImage} />
)}
</div>
</div>
</div>
);
}
function KernelDetailView({ image }: { image: QCrowsImage }) {
const meta = image.metadata;
const kConfig = image.kernelConfig;
const bootParams = image.bootParams;
const [regenOpen, setRegenOpen] = useState(false);
const [regenConfig, setRegenConfig] = useState<Partial<InitrdRegenConfig>>(INITRD_REGEN_DEFAULTS);
const [regening, setRegening] = useState(false);
const [regenResult, setRegenResult] = useState<InitrdRegenResult | null>(null);
const executeRegen = async () => {
setRegening(true);
setRegenResult(null);
try {
const result = await regenInitrd(image, regenConfig);
setRegenResult(result);
} catch {
setRegenResult({
success: false, output: "", sizeMB: 0, vmm: regenConfig.vmm || "qemu",
format: regenConfig.format || "cpio-gzip", initStyle: regenConfig.initStyle || "busybox",
kernelVersion: meta.kernelVersion, moduleCount: 0,
messages: [], errors: ["Initrd regeneration failed unexpectedly"],
});
} finally {
setRegening(false);
}
};
return (
<>
{/* Kernel Overview Cards */}
<div className="grid grid-cols-4 gap-4">
<KernelStatCard
label="Kernel Version"
value={meta.kernelVersion}
icon={<Cpu className="h-4 w-4" />}
color="emerald"
/>
<KernelStatCard
label="Format"
value={meta.kernelFormat === "vmlinuz" ? "vmlinuz (bzImage)" : "vmlinux (ELF)"}
icon={<FileText className="h-4 w-4" />}
color="blue"
/>
<KernelStatCard
label="Size"
value={`${meta.kernelSizeMB} MB`}
icon={<Download className="h-4 w-4" />}
color="amber"
/>
<KernelStatCard
label="Config Valid"
value={meta.kernelConfigValid ? "Pass" : "Fail"}
icon={meta.kernelConfigValid ? <CheckCircle2 className="h-4 w-4" /> : <XCircle className="h-4 w-4" />}
color={meta.kernelConfigValid ? "emerald" : "red"}
/>
</div>
{/* Hypervisor Compatibility */}
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200 flex items-center gap-2">
<Settings className="h-4 w-4 text-blue-400" />
Hypervisor Kernel Format Compatibility
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<div className="grid grid-cols-2 gap-3">
{meta.hypervisors.map((hyp) => {
const requirement = HYPERVISOR_KERNEL_FORMAT[hyp];
const matches = requirement && requirement.format === meta.kernelFormat;
const archMatch = requirement && requirement.arches.includes(meta.arch);
return (
<div
key={hyp}
className={cn(
"flex items-center justify-between rounded-md border px-3 py-2",
matches && archMatch
? "border-emerald-700/50 bg-emerald-900/10"
: "border-amber-700/50 bg-amber-900/10"
)}
>
<div>
<div className="text-sm font-medium text-slate-200">{hyp}</div>
<div className="text-[11px] text-slate-400">
Requires: {requirement?.format || "vmlinux"} ({requirement?.arches.join(", ") || "any"})
</div>
</div>
<div className="flex items-center gap-1.5">
{matches && archMatch ? (
<>
<CheckCircle2 className="h-4 w-4 text-emerald-400" />
<span className="text-[11px] text-emerald-400 font-medium">Compatible</span>
</>
) : (
<>
<AlertTriangle className="h-4 w-4 text-amber-400" />
<span className="text-[11px] text-amber-400 font-medium">Format mismatch</span>
</>
)}
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
{/* Initrd Regen Action */}
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200 flex items-center justify-between">
<div className="flex items-center gap-2">
<RefreshCw className="h-4 w-4 text-violet-400" />
Initrd Regeneration
</div>
<Button
size="sm"
onClick={() => { setRegenOpen(true); setRegenResult(null); }}
className="bg-violet-700 hover:bg-violet-600 text-white text-xs h-7"
>
<RefreshCw className="h-3.5 w-3.5 mr-1.5" />
Regen Initrd
</Button>
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<div className="text-xs text-slate-400 space-y-1.5">
<p>
Rebuild the initrd component with environment-aware module selection. Unlike <code className="text-slate-300 bg-slate-800 px-1 rounded">dracut</code> or{" "}
<code className="text-slate-300 bg-slate-800 px-1 rounded">mkinitramfs</code>, this produces an initrd where kata-agent IS the final process
(no pivot_root) and includes ONLY the modules required by the detected VMM.
</p>
<div className="flex items-center gap-4 mt-2 text-slate-500">
<span>Auto-detects: VMM, compression, init style, kata-agent</span>
<span>|</span>
<span>Module matrix: {Object.keys(VMM_MODULE_MATRIX).length} VMMs</span>
</div>
</div>
{/* Regen Dialog */}
{regenOpen && !regenResult && (
<div className="mt-4 rounded-lg border border-slate-700/50 bg-[#0a0f1a] p-4 space-y-4">
<div className="text-sm font-medium text-slate-200">Configure Initrd Regeneration</div>
<div className="grid grid-cols-2 gap-4">
{/* VMM Selection */}
<div className="space-y-1.5">
<label className="text-xs text-slate-400">Target VMM</label>
<select
value={regenConfig.vmm || ""}
onChange={(e) => setRegenConfig((c) => ({ ...c, vmm: e.target.value as InitrdRegenConfig["vmm"] || undefined }))}
className="w-full rounded-md border border-slate-700 bg-[#111827] text-sm text-slate-200 px-3 py-1.5"
>
<option value="">Auto-detect</option>
<option value="qemu">QEMU (virtio-pci)</option>
<option value="cloud-hypervisor">Cloud Hypervisor (virtio-mmio)</option>
<option value="firecracker">Firecracker (virtio-mmio minimal)</option>
<option value="dragonball">Dragonball (vsock-direct)</option>
</select>
</div>
{/* Compression */}
<div className="space-y-1.5">
<label className="text-xs text-slate-400">Compression</label>
<select
value={regenConfig.format || ""}
onChange={(e) => setRegenConfig((c) => ({ ...c, format: e.target.value as InitrdRegenConfig["format"] || undefined }))}
className="w-full rounded-md border border-slate-700 bg-[#111827] text-sm text-slate-200 px-3 py-1.5"
>
<option value="">Auto-detect from kernel config</option>
{COMPRESS_PRIORITY.map((c) => (
<option key={c.algo} value={c.algo}>
{c.algo.replace("cpio-", "").toUpperCase()} ({c.bootSpeed})
</option>
))}
<option value="cramfs">CRAMFS (read-only, minimal)</option>
</select>
</div>
{/* Init Style */}
<div className="space-y-1.5">
<label className="text-xs text-slate-400">Init Style</label>
<select
value={regenConfig.initStyle || ""}
onChange={(e) => setRegenConfig((c) => ({ ...c, initStyle: e.target.value as InitrdRegenConfig["initStyle"] || undefined }))}
className="w-full rounded-md border border-slate-700 bg-[#111827] text-sm text-slate-200 px-3 py-1.5"
>
<option value="">Auto-detect from rootfs</option>
<option value="busybox">Busybox (kata-agent as PID 1)</option>
<option value="systemd">systemd (via kata-containers.target)</option>
<option value="openrc">OpenRC (via init script)</option>
</select>
</div>
{/* In-place toggle */}
<div className="space-y-1.5">
<label className="text-xs text-slate-400">Output Mode</label>
<div className="flex items-center gap-3 mt-1">
<button
onClick={() => setRegenConfig((c) => ({ ...c, inPlace: true }))}
className={cn(
"px-3 py-1.5 rounded-md text-xs font-medium border transition-colors",
regenConfig.inPlace
? "border-violet-600 bg-violet-900/20 text-violet-300"
: "border-slate-700 bg-[#111827] text-slate-400 hover:border-slate-600"
)}
>
In-place (update bundle)
</button>
<button
onClick={() => setRegenConfig((c) => ({ ...c, inPlace: false }))}
className={cn(
"px-3 py-1.5 rounded-md text-xs font-medium border transition-colors",
!regenConfig.inPlace
? "border-violet-600 bg-violet-900/20 text-violet-300"
: "border-slate-700 bg-[#111827] text-slate-400 hover:border-slate-600"
)}
>
Separate file
</button>
</div>
</div>
</div>
{/* Module preview for selected VMM */}
{regenConfig.vmm && VMM_MODULE_MATRIX[regenConfig.vmm] && (
<div className="text-xs text-slate-400 bg-[#111827] rounded-md p-3">
<div className="font-medium text-slate-300 mb-1.5">
Modules for {regenConfig.vmm}:
</div>
<div className="flex flex-wrap gap-1.5">
{VMM_MODULE_MATRIX[regenConfig.vmm].required.map((m) => (
<Badge key={m} variant="outline" className="text-[9px] border-emerald-700 text-emerald-400 px-1.5 py-0">
{m}
</Badge>
))}
{VMM_MODULE_MATRIX[regenConfig.vmm].optional.map((m) => (
<Badge key={m} variant="outline" className="text-[9px] border-slate-600 text-slate-500 px-1.5 py-0">
{m}?
</Badge>
))}
</div>
</div>
)}
<div className="flex items-center gap-3 pt-2">
<Button
size="sm"
onClick={executeRegen}
disabled={regening}
className="bg-violet-700 hover:bg-violet-600 text-white text-xs h-8"
>
{regening ? (
<>
<RefreshCw className="h-3.5 w-3.5 mr-1.5 animate-spin" />
Regenerating...
</>
) : (
<>
<RefreshCw className="h-3.5 w-3.5 mr-1.5" />
Regenerate Initrd
</>
)}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => setRegenOpen(false)}
className="text-xs h-8 text-slate-400"
>
Cancel
</Button>
</div>
</div>
)}
{/* Regen Result */}
{regenResult && (
<div className={cn(
"mt-4 rounded-lg border p-4 space-y-3",
regenResult.success
? "border-emerald-700/50 bg-emerald-900/10"
: "border-red-700/50 bg-red-900/10"
)}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{regenResult.success ? (
<CheckCircle2 className="h-5 w-5 text-emerald-400" />
) : (
<XCircle className="h-5 w-5 text-red-400" />
)}
<span className={cn("text-sm font-medium", regenResult.success ? "text-emerald-300" : "text-red-300")}>
{regenResult.success ? "Initrd regenerated successfully" : "Initrd regeneration failed"}
</span>
</div>
<Button size="sm" variant="ghost" onClick={() => { setRegenResult(null); setRegenOpen(false); }} className="text-xs text-slate-400 h-6">
Close
</Button>
</div>
{regenResult.success && (
<div className="grid grid-cols-3 gap-3">
<div className="bg-[#0a0f1a] rounded-md p-2.5">
<div className="text-[10px] text-slate-500 uppercase">Output</div>
<div className="text-sm text-slate-200 font-mono mt-0.5">{regenResult.output}</div>
</div>
<div className="bg-[#0a0f1a] rounded-md p-2.5">
<div className="text-[10px] text-slate-500 uppercase">Size</div>
<div className="text-sm text-slate-200 mt-0.5">{regenResult.sizeMB} MB</div>
</div>
<div className="bg-[#0a0f1a] rounded-md p-2.5">
<div className="text-[10px] text-slate-500 uppercase">Modules</div>
<div className="text-sm text-slate-200 mt-0.5">{regenResult.moduleCount} included</div>
</div>
</div>
)}
{regenResult.messages.length > 0 && (
<div className="bg-[#0a0f1a] rounded-md p-3 max-h-40 overflow-y-auto">
<div className="text-[10px] text-slate-500 uppercase mb-1.5">Pipeline Log</div>
{regenResult.messages.map((msg, i) => (
<div key={i} className="text-xs text-slate-400 font-mono py-0.5">{msg}</div>
))}
</div>
)}
{regenResult.errors.length > 0 && (
<div className="bg-red-950/30 rounded-md p-3">
<div className="text-[10px] text-red-400 uppercase mb-1.5">Errors</div>
{regenResult.errors.map((err, i) => (
<div key={i} className="text-xs text-red-300 font-mono py-0.5">{err}</div>
))}
</div>
)}
</div>
)}
</CardContent>
</Card>
{/* Tabs: Config Options / Boot Params / Build Info */}
<Tabs defaultValue="config" className="space-y-4">
<TabsList className="bg-[#111827] border border-slate-700/50">
<TabsTrigger value="config" className="data-[state=active]:bg-slate-700/60">
Kernel Config
</TabsTrigger>
<TabsTrigger value="boot-params" className="data-[state=active]:bg-slate-700/60">
Boot Parameters
</TabsTrigger>
<TabsTrigger value="build" className="data-[state=active]:bg-slate-700/60">
Build Info
</TabsTrigger>
</TabsList>
<TabsContent value="config">
<KernelConfigPanel config={kConfig} kernelVersion={meta.kernelVersion} />
</TabsContent>
<TabsContent value="boot-params">
<BootParamsPanel bootParams={bootParams} kernelVersion={meta.kernelVersion} />
</TabsContent>
<TabsContent value="build">
<BuildInfoPanel image={image} />
</TabsContent>
</Tabs>
</>
);
}
function KernelStatCard({
label,
value,
icon,
color,
}: {
label: string;
value: string;
icon: React.ReactNode;
color: "emerald" | "blue" | "amber" | "red";
}) {
const colorMap = {
emerald: "text-emerald-400",
blue: "text-blue-400",
amber: "text-amber-400",
red: "text-red-400",
};
return (
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-2">
<span className={colorMap[color]}>{icon}</span>
<span className="text-[11px] text-slate-400 uppercase tracking-wider font-medium">{label}</span>
</div>
<div className="text-lg font-semibold text-slate-100">{value}</div>
</CardContent>
</Card>
);
}
function KernelConfigPanel({
config,
kernelVersion,
}: {
config?: QCrowsKernelConfig;
kernelVersion: string;
}) {
const [showRecommended, setShowRecommended] = useState(false);
if (!config) {
return (
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-6 text-center text-slate-400">
No kernel config data available for this image.
</CardContent>
</Card>
);
}
const requiredPass = Object.values(config.requiredOptions).filter(Boolean).length;
const requiredTotal = Object.keys(config.requiredOptions).length;
const optionalPass = Object.values(config.optionalOptions).filter(Boolean).length;
const optionalTotal = Object.keys(config.optionalOptions).length;
const allRequiredPass = requiredPass === requiredTotal;
return (
<div className="space-y-4">
{/* Summary cards */}
<div className="grid grid-cols-3 gap-4">
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-4">
<div className="text-[11px] text-slate-400 uppercase tracking-wider mb-2">Required Options</div>
<div className="flex items-center gap-2">
<span className={cn("text-xl font-bold", allRequiredPass ? "text-emerald-400" : "text-red-400")}>
{requiredPass}/{requiredTotal}
</span>
{allRequiredPass ? (
<CheckCircle2 className="h-5 w-5 text-emerald-400" />
) : (
<XCircle className="h-5 w-5 text-red-400" />
)}
</div>
<Progress
value={(requiredPass / requiredTotal) * 100}
className={cn("mt-2 h-1.5", allRequiredPass ? "[&>div]:bg-emerald-500" : "[&>div]:bg-red-500")}
/>
</CardContent>
</Card>
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-4">
<div className="text-[11px] text-slate-400 uppercase tracking-wider mb-2">Recommended Options</div>
<div className="flex items-center gap-2">
<span className="text-xl font-bold text-blue-400">{optionalPass}/{optionalTotal}</span>
</div>
<Progress
value={(optionalPass / optionalTotal) * 100}
className="mt-2 h-1.5 [&>div]:bg-blue-500"
/>
</CardContent>
</Card>
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-4">
<div className="text-[11px] text-slate-400 uppercase tracking-wider mb-2">Config Statistics</div>
<div className="text-sm space-y-1">
<div className="flex justify-between text-slate-300">
<span>Total options</span>
<span className="font-mono">{config.totalOptions.toLocaleString()}</span>
</div>
<div className="flex justify-between text-slate-300">
<span>Built-in</span>
<span className="font-mono text-emerald-400">{config.builtInCount.toLocaleString()}</span>
</div>
<div className="flex justify-between text-slate-300">
<span>Modules</span>
<span className="font-mono text-blue-400">{config.moduleCount.toLocaleString()}</span>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Required options detail */}
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200 flex items-center gap-2">
<AlertTriangle className="h-4 w-4 text-amber-400" />
Required Kata Kernel Options
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-2">
{Object.entries(config.requiredOptions).map(([opt, enabled]) => {
const desc = KATA_REQUIRED_KERNEL_OPTIONS[opt] || "";
return (
<div
key={opt}
className={cn(
"flex items-center justify-between rounded-md border px-3 py-2",
enabled
? "border-emerald-700/30 bg-emerald-900/5"
: "border-red-700/30 bg-red-900/5"
)}
>
<div className="flex items-center gap-3">
{enabled ? (
<CheckCircle2 className="h-4 w-4 text-emerald-400 shrink-0" />
) : (
<XCircle className="h-4 w-4 text-red-400 shrink-0" />
)}
<code className="text-sm font-mono text-slate-200">{opt}</code>
</div>
<div className="flex items-center gap-3">
<span className="text-[11px] text-slate-400">{desc}</span>
<Badge
variant="outline"
className={cn(
"text-[9px] px-1.5 py-0",
enabled ? "border-emerald-600 text-emerald-400" : "border-red-600 text-red-400"
)}
>
{enabled ? "y" : "MISSING"}
</Badge>
</div>
</div>
);
})}
</CardContent>
</Card>
{/* Recommended options (collapsible) */}
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader
className="py-3 px-4 cursor-pointer"
onClick={() => setShowRecommended(!showRecommended)}
>
<CardTitle className="text-sm font-medium text-slate-200 flex items-center gap-2">
<Info className="h-4 w-4 text-blue-400" />
Recommended Kata Kernel Options
<Badge variant="outline" className="border-blue-700 text-blue-400 text-[9px] px-1.5 py-0 ml-auto mr-2">
{optionalPass}/{optionalTotal}
</Badge>
{showRecommended ? (
<ChevronDown className="h-4 w-4 text-slate-400" />
) : (
<ChevronRight className="h-4 w-4 text-slate-400" />
)}
</CardTitle>
</CardHeader>
{showRecommended && (
<CardContent className="px-4 pb-4 space-y-2">
{Object.entries(config.optionalOptions).map(([opt, enabled]) => {
const desc = KATA_RECOMMENDED_KERNEL_OPTIONS[opt] || "";
return (
<div
key={opt}
className={cn(
"flex items-center justify-between rounded-md border px-3 py-2",
enabled
? "border-blue-700/20 bg-blue-900/5"
: "border-slate-700/30 bg-slate-900/20"
)}
>
<div className="flex items-center gap-3">
{enabled ? (
<CheckCircle2 className="h-4 w-4 text-blue-400 shrink-0" />
) : (
<XCircle className="h-4 w-4 text-slate-500 shrink-0" />
)}
<code className="text-sm font-mono text-slate-200">{opt}</code>
</div>
<div className="flex items-center gap-3">
<span className="text-[11px] text-slate-400">{desc}</span>
<Badge
variant="outline"
className={cn(
"text-[9px] px-1.5 py-0",
enabled ? "border-blue-600 text-blue-400" : "border-slate-600 text-slate-500"
)}
>
{enabled ? "y" : "n/m"}
</Badge>
</div>
</div>
);
})}
</CardContent>
)}
</Card>
</div>
);
}
function BootParamsPanel({
bootParams,
kernelVersion,
}: {
bootParams?: QCrowsBootParams;
kernelVersion: string;
}) {
if (!bootParams) {
return (
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-6 text-center text-slate-400">
No boot parameters data available for this image.
</CardContent>
</Card>
);
}
const dangerousParams = [
"module.sig_enforce=0",
"nokaslr",
"nopti",
"nosmap",
"nosmep",
];
const warnings = bootParams.params.filter((p) =>
dangerousParams.some((d) => p.includes(d.replace("=", "").replace("=0", "")))
);
return (
<div className="space-y-4">
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200 flex items-center gap-2">
<Terminal className="h-4 w-4 text-emerald-400" />
Kernel Command Line
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<div className="rounded-md bg-[#0a0f1a] border border-slate-700/50 p-4 font-mono text-sm text-slate-200 break-all">
<span className="text-slate-500">BOOT_IMAGE=/kernel/vmlinuz</span>{" "}
{bootParams.params.map((param, i) => (
<span key={i}>
{i > 0 && " "}
<span
className={cn(
dangerousParams.some((d) => param.includes(d.split("=")[0]))
? "text-amber-400 underline decoration-amber-600 decoration-wavy"
: "text-emerald-300"
)}
>
{param}
</span>
</span>
))}
</div>
<div className="mt-3 flex items-center gap-2">
<Badge variant="outline" className="border-slate-600 text-slate-400 text-[10px]">
Source: {bootParams.source}
</Badge>
<Badge variant="outline" className="border-slate-600 text-slate-400 text-[10px]">
{bootParams.params.length} parameters
</Badge>
</div>
</CardContent>
</Card>
{/* Parameter breakdown */}
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200">Parameter Breakdown</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-2">
{bootParams.params.map((param, i) => {
const isDangerous = dangerousParams.some((d) => param.includes(d.split("=")[0]));
const [key, ...valParts] = param.split("=");
const val = valParts.join("=");
return (
<div
key={i}
className={cn(
"flex items-center gap-3 rounded-md border px-3 py-2",
isDangerous
? "border-amber-700/30 bg-amber-900/5"
: "border-slate-700/30"
)}
>
{isDangerous ? (
<AlertTriangle className="h-4 w-4 text-amber-400 shrink-0" />
) : (
<CheckCircle2 className="h-4 w-4 text-emerald-400 shrink-0" />
)}
<code className="text-sm font-mono text-slate-200">{key}</code>
{val && (
<>
<span className="text-slate-500">=</span>
<code className="text-sm font-mono text-blue-300">{val}</code>
</>
)}
{isDangerous && (
<Badge variant="outline" className="border-amber-600 text-amber-400 text-[9px] px-1.5 py-0 ml-auto">
Security concern
</Badge>
)}
</div>
);
})}
</CardContent>
</Card>
{warnings.length > 0 && (
<Card className="bg-[#111827] border-amber-700/30">
<CardContent className="p-4">
<div className="flex items-start gap-3">
<AlertTriangle className="h-5 w-5 text-amber-400 shrink-0 mt-0.5" />
<div>
<div className="text-sm font-medium text-amber-300">Security Warnings</div>
<div className="text-sm text-slate-400 mt-1">
The following parameters may reduce security isolation in the guest VM:
</div>
<ul className="mt-2 space-y-1">
{warnings.map((w, i) => (
<li key={i} className="text-sm text-amber-300 font-mono">{w}</li>
))}
</ul>
</div>
</div>
</CardContent>
</Card>
)}
</div>
);
}
function BuildInfoPanel({ image }: { image: QCrowsImage }) {
const build = image.build;
const meta = image.metadata;
if (!build) {
return (
<Card className="bg-[#111827] border-slate-700/50">
<CardContent className="p-6 text-center text-slate-400">
No build information available for this image.
</CardContent>
</Card>
);
}
return (
<div className="space-y-4">
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200 flex items-center gap-2">
<Info className="h-4 w-4 text-blue-400" />
Kernel Build Provenance
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<div className="grid grid-cols-2 gap-x-8 gap-y-3">
<BuildInfoRow label="Build System" value={build.system} />
<BuildInfoRow label="Build ID" value={build.buildId || "N/A"} mono />
<BuildInfoRow label="Host Arch" value={build.hostArch} />
<BuildInfoRow label="Target Arch" value={build.targetArch} />
<BuildInfoRow label="Kernel Compiler" value={build.kernelCompiler || "N/A"} />
<BuildInfoRow label="Kernel Defconfig" value={build.kernelDefconfig || "N/A"} mono />
<BuildInfoRow label="Reproducible" value={build.reproducible ? "Yes" : "No"} />
<BuildInfoRow label="Built At" value={build.timestamp} />
</div>
</CardContent>
</Card>
{/* Kernel file paths after import */}
<Card className="bg-[#111827] border-slate-700/50">
<CardHeader className="py-3 px-4">
<CardTitle className="text-sm font-medium text-slate-200 flex items-center gap-2">
<ExternalLink className="h-4 w-4 text-slate-400" />
Import Paths (after registration)
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-2">
<PathRow
label="Kernel binary"
path={`/usr/share/kata-containers/vmlinuz-${meta.kernelVersion}.container`}
exists={meta.kernelIncluded}
/>
<PathRow
label="Kernel config"
path={`/usr/share/kata-containers/config-${meta.kernelVersion}.container`}
exists={meta.kernelConfigValid}
/>
{meta.initrdIncluded && (
<PathRow
label="Initrd"
path={`/usr/share/kata-containers/kata-${meta.name}-initrd.img`}
exists={true}
/>
)}
<PathRow
label="Rootfs"
path={`/usr/share/kata-containers/kata-${meta.name}-rootfs${meta.rootfsType === "tar-gzip" ? ".tar.gz" : meta.rootfsType === "tar-xz" ? ".tar.xz" : ".tar.zst"}`}
exists={true}
/>
{meta.bootParamsIncluded && (
<PathRow
label="Boot params"
path="(appended to kernel_params in configuration.toml)"
exists={true}
/>
)}
</CardContent>
</Card>
</div>
);
}
function BuildInfoRow({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
return (
<div className="flex items-center justify-between">
<span className="text-sm text-slate-400">{label}</span>
<span className={cn("text-sm text-slate-200", mono && "font-mono")}>{value}</span>
</div>
);
}
function PathRow({ label, path, exists }: { label: string; path: string; exists: boolean }) {
return (
<div className="flex items-center gap-3 text-sm">
{exists ? (
<CheckCircle2 className="h-4 w-4 text-emerald-400 shrink-0" />
) : (
<XCircle className="h-4 w-4 text-red-400 shrink-0" />
)}
<span className="text-slate-400 w-28 shrink-0">{label}</span>
<code className="text-slate-200 font-mono text-xs break-all">{path}</code>
</div>
);
}