feat: Enhance OCI container display and AI context

- Frontend: Add ociImage memo to extract clean image name from osTemplate
- Frontend: Show OCI image name in type badge tooltip
- Frontend: Display OCI image in OS column when no guest agent info available
- Frontend: Include ociImage in AI context data for selected OCI containers
- Backend: Differentiate OCI containers as 'oci_container' type in AI context
- Backend: Add Metadata field to ResourceContext for extensibility
- Backend: Include oci_image in container metadata for AI analysis
- Backend: Update section heading to 'LXC/OCI Containers' in AI context

This follows Docker container patterns to avoid duplicating work.
This commit is contained in:
rcourtman 2025-12-12 18:00:09 +00:00
parent dfb5d50f73
commit e82cf7eaaf
5 changed files with 69 additions and 12 deletions

View file

@ -914,14 +914,22 @@ export function Dashboard(props: DashboardProps) {
aiChatStore.removeContextItem(guestId);
// If no items left in context and sidebar is open, keep it open for now
} else {
aiChatStore.addContextItem(guestType, guestId, guest.name, {
// Build context with OCI-specific info when applicable
const contextData: Record<string, unknown> = {
guestName: guest.name,
name: guest.name,
type: guest.type === 'qemu' ? 'Virtual Machine' : (guest.type === 'oci' ? 'OCI Container' : 'LXC Container'),
vmid: guest.vmid,
node: guest.node,
status: guest.status,
});
};
// Add OCI image info if available
if (guest.type === 'oci' && 'osTemplate' in guest && guest.osTemplate) {
contextData.ociImage = guest.osTemplate;
}
aiChatStore.addContextItem(guestType, guestId, guest.name, contextData);
// Auto-open the sidebar when first item is selected
if (!aiChatStore.isOpen) {
aiChatStore.open();

View file

@ -571,6 +571,18 @@ export function GuestRow(props: GuestRowProps) {
const agentVersion = createMemo(() => props.guest.agentVersion?.trim() ?? '');
const hasOsInfo = createMemo(() => osName().length > 0 || osVersion().length > 0);
// OCI image info - extract clean image name from osTemplate (similar to Docker container image display)
const ociImage = createMemo(() => {
if (props.guest.type !== 'oci') return null;
const template = (props.guest as Container).osTemplate;
if (!template) return null;
// Strip common prefixes to get clean image reference
let image = template;
if (image.startsWith('oci:')) image = image.slice(4);
if (image.startsWith('docker:')) image = image.slice(7);
return image;
});
// Update custom URL when prop changes
createEffect(() => {
const prevUrl = customUrl();
@ -890,16 +902,16 @@ export function GuestRow(props: GuestRowProps) {
<div class="flex justify-center">
<span
class={`inline-block px-1 py-0.5 text-[10px] font-medium rounded whitespace-nowrap ${props.guest.type === 'qemu'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
: props.guest.type === 'oci'
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300'
: 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
: props.guest.type === 'oci'
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300'
: 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300'
}`}
title={
isVM(props.guest)
? 'Virtual Machine'
: props.guest.type === 'oci'
? 'OCI Container (Docker image)'
? `OCI Container${ociImage() ? `${ociImage()}` : ''}`
: 'LXC Container'
}
>
@ -1111,7 +1123,23 @@ export function GuestRow(props: GuestRowProps) {
<Show when={isColVisible('os')}>
<td class="px-2 py-1 align-middle">
<div class="flex justify-center">
<Show when={hasOsInfo()} fallback={<span class="text-xs text-gray-400">-</span>}>
<Show
when={hasOsInfo()}
fallback={
<Show
when={ociImage()}
fallback={<span class="text-xs text-gray-400">-</span>}
>
{/* For OCI containers without guest agent, show image name in OS column */}
<span
class="text-xs text-purple-600 dark:text-purple-400 truncate max-w-[100px]"
title={`OCI Image: ${ociImage()}`}
>
{ociImage()}
</span>
</Show>
}
>
<OSInfoCell
osName={osName()}
osVersion={osVersion()}

View file

@ -120,17 +120,33 @@ func (b *Builder) BuildForInfrastructure(state models.StateSnapshot) *Infrastruc
ctx.VMs = append(ctx.VMs, resourceCtx)
}
// Process containers
// Process containers (LXC and OCI)
for _, ct := range state.Containers {
if ct.Template {
continue
}
trends := b.computeGuestTrends(ct.ID)
// Determine container type - OCI containers are treated specially
containerType := "container"
if ct.IsOCI {
containerType = "oci_container"
}
resourceCtx := FormatGuestForContext(
ct.ID, ct.Name, ct.Node, "container", ct.Status,
ct.ID, ct.Name, ct.Node, containerType, ct.Status,
ct.CPU, ct.Memory.Usage, ct.Disk.Usage,
ct.Uptime, ct.LastBackup, trends,
)
// Add OCI image info for AI context
if ct.IsOCI && ct.OSTemplate != "" {
if resourceCtx.Metadata == nil {
resourceCtx.Metadata = make(map[string]interface{})
}
resourceCtx.Metadata["oci_image"] = ct.OSTemplate
}
b.enrichWithNotes(&resourceCtx)
b.enrichWithAnomalies(&resourceCtx)
ctx.Containers = append(ctx.Containers, resourceCtx)

View file

@ -212,7 +212,7 @@ func FormatInfrastructureContext(ctx *InfrastructureContext) string {
}
if len(ctx.Containers) > 0 {
sb.WriteString("## LXC Containers\n")
sb.WriteString("## LXC/OCI Containers\n")
for _, r := range ctx.Containers {
sb.WriteString(FormatResourceContext(r))
}
@ -312,6 +312,8 @@ func formatResourceType(t string) string {
return "VM"
case "container":
return "Container"
case "oci_container":
return "OCI Container"
case "storage":
return "Storage"
case "docker_host":

View file

@ -113,7 +113,7 @@ type ResourceTrends struct {
// ResourceContext contains all context for a single resource
type ResourceContext struct {
ResourceID string
ResourceType string // "node", "vm", "container", "storage", "docker_host"
ResourceType string // "node", "vm", "container", "oci_container", "storage", "docker_host"
ResourceName string
Node string // Parent node (for guests)
@ -137,6 +137,9 @@ type ResourceContext struct {
PastIssues []string // Summary of past findings
LastRemediation string // What was done last time
RecentChanges []Change // Recent configuration changes
// Additional metadata (e.g., OCI image for OCI containers)
Metadata map[string]interface{}
}
// InfrastructureContext contains summarized context for the entire infrastructure