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);
|
aiChatStore.removeContextItem(guestId);
|
||||||
// If no items left in context and sidebar is open, keep it open for now
|
// If no items left in context and sidebar is open, keep it open for now
|
||||||
} else {
|
} else {
|
||||||
aiChatStore.addContextItem(guestType, guestId, guest.name, {
|
// Build context with OCI-specific info when applicable
|
||||||
|
const contextData: Record<string, unknown> = {
|
||||||
guestName: guest.name,
|
guestName: guest.name,
|
||||||
name: guest.name,
|
name: guest.name,
|
||||||
type: guest.type === 'qemu' ? 'Virtual Machine' : (guest.type === 'oci' ? 'OCI Container' : 'LXC Container'),
|
type: guest.type === 'qemu' ? 'Virtual Machine' : (guest.type === 'oci' ? 'OCI Container' : 'LXC Container'),
|
||||||
vmid: guest.vmid,
|
vmid: guest.vmid,
|
||||||
node: guest.node,
|
node: guest.node,
|
||||||
status: guest.status,
|
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
|
// Auto-open the sidebar when first item is selected
|
||||||
if (!aiChatStore.isOpen) {
|
if (!aiChatStore.isOpen) {
|
||||||
aiChatStore.open();
|
aiChatStore.open();
|
||||||
|
|
|
||||||
|
|
@ -571,6 +571,18 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
const agentVersion = createMemo(() => props.guest.agentVersion?.trim() ?? '');
|
const agentVersion = createMemo(() => props.guest.agentVersion?.trim() ?? '');
|
||||||
const hasOsInfo = createMemo(() => osName().length > 0 || osVersion().length > 0);
|
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
|
// Update custom URL when prop changes
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const prevUrl = customUrl();
|
const prevUrl = customUrl();
|
||||||
|
|
@ -890,16 +902,16 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<span
|
<span
|
||||||
class={`inline-block px-1 py-0.5 text-[10px] font-medium rounded whitespace-nowrap ${props.guest.type === 'qemu'
|
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'
|
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||||
: props.guest.type === 'oci'
|
: props.guest.type === 'oci'
|
||||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300'
|
? '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-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300'
|
||||||
}`}
|
}`}
|
||||||
title={
|
title={
|
||||||
isVM(props.guest)
|
isVM(props.guest)
|
||||||
? 'Virtual Machine'
|
? 'Virtual Machine'
|
||||||
: props.guest.type === 'oci'
|
: props.guest.type === 'oci'
|
||||||
? 'OCI Container (Docker image)'
|
? `OCI Container${ociImage() ? ` • ${ociImage()}` : ''}`
|
||||||
: 'LXC Container'
|
: 'LXC Container'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -1111,7 +1123,23 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
<Show when={isColVisible('os')}>
|
<Show when={isColVisible('os')}>
|
||||||
<td class="px-2 py-1 align-middle">
|
<td class="px-2 py-1 align-middle">
|
||||||
<div class="flex justify-center">
|
<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
|
<OSInfoCell
|
||||||
osName={osName()}
|
osName={osName()}
|
||||||
osVersion={osVersion()}
|
osVersion={osVersion()}
|
||||||
|
|
|
||||||
|
|
@ -120,17 +120,33 @@ func (b *Builder) BuildForInfrastructure(state models.StateSnapshot) *Infrastruc
|
||||||
ctx.VMs = append(ctx.VMs, resourceCtx)
|
ctx.VMs = append(ctx.VMs, resourceCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process containers
|
// Process containers (LXC and OCI)
|
||||||
for _, ct := range state.Containers {
|
for _, ct := range state.Containers {
|
||||||
if ct.Template {
|
if ct.Template {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
trends := b.computeGuestTrends(ct.ID)
|
trends := b.computeGuestTrends(ct.ID)
|
||||||
|
|
||||||
|
// Determine container type - OCI containers are treated specially
|
||||||
|
containerType := "container"
|
||||||
|
if ct.IsOCI {
|
||||||
|
containerType = "oci_container"
|
||||||
|
}
|
||||||
|
|
||||||
resourceCtx := FormatGuestForContext(
|
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.CPU, ct.Memory.Usage, ct.Disk.Usage,
|
||||||
ct.Uptime, ct.LastBackup, trends,
|
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.enrichWithNotes(&resourceCtx)
|
||||||
b.enrichWithAnomalies(&resourceCtx)
|
b.enrichWithAnomalies(&resourceCtx)
|
||||||
ctx.Containers = append(ctx.Containers, resourceCtx)
|
ctx.Containers = append(ctx.Containers, resourceCtx)
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ func FormatInfrastructureContext(ctx *InfrastructureContext) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ctx.Containers) > 0 {
|
if len(ctx.Containers) > 0 {
|
||||||
sb.WriteString("## LXC Containers\n")
|
sb.WriteString("## LXC/OCI Containers\n")
|
||||||
for _, r := range ctx.Containers {
|
for _, r := range ctx.Containers {
|
||||||
sb.WriteString(FormatResourceContext(r))
|
sb.WriteString(FormatResourceContext(r))
|
||||||
}
|
}
|
||||||
|
|
@ -312,6 +312,8 @@ func formatResourceType(t string) string {
|
||||||
return "VM"
|
return "VM"
|
||||||
case "container":
|
case "container":
|
||||||
return "Container"
|
return "Container"
|
||||||
|
case "oci_container":
|
||||||
|
return "OCI Container"
|
||||||
case "storage":
|
case "storage":
|
||||||
return "Storage"
|
return "Storage"
|
||||||
case "docker_host":
|
case "docker_host":
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ type ResourceTrends struct {
|
||||||
// ResourceContext contains all context for a single resource
|
// ResourceContext contains all context for a single resource
|
||||||
type ResourceContext struct {
|
type ResourceContext struct {
|
||||||
ResourceID string
|
ResourceID string
|
||||||
ResourceType string // "node", "vm", "container", "storage", "docker_host"
|
ResourceType string // "node", "vm", "container", "oci_container", "storage", "docker_host"
|
||||||
ResourceName string
|
ResourceName string
|
||||||
Node string // Parent node (for guests)
|
Node string // Parent node (for guests)
|
||||||
|
|
||||||
|
|
@ -137,6 +137,9 @@ type ResourceContext struct {
|
||||||
PastIssues []string // Summary of past findings
|
PastIssues []string // Summary of past findings
|
||||||
LastRemediation string // What was done last time
|
LastRemediation string // What was done last time
|
||||||
RecentChanges []Change // Recent configuration changes
|
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
|
// InfrastructureContext contains summarized context for the entire infrastructure
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue