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:
parent
dfb5d50f73
commit
e82cf7eaaf
5 changed files with 69 additions and 12 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue