dashboard

package
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 13, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

Dashboard Resource Integration Guide

This guide explains how to add a new Kubernetes resource to the Cozystack dashboard. The dashboard provides a unified interface for viewing and managing Kubernetes resources through custom table views, detail pages, and sidebar navigation.

Overview

Adding a new resource to the dashboard requires three main components:

  1. CustomColumnsOverride: Defines how the resource appears in list/table views
  2. Factory: Defines the detail page layout for individual resource instances
  3. Sidebar Entry: Adds navigation to the resource in the sidebar menu

Prerequisites

  • The resource must have a Kubernetes CustomResourceDefinition (CRD) or be a built-in Kubernetes resource
  • Know the resource's API group, version, kind, and plural name
  • Understand the resource's spec structure to display relevant fields

Step-by-Step Guide

Step 1: Add CustomColumnsOverride

The CustomColumnsOverride defines the columns shown in the resource list table and how clicking on a row navigates to the detail page.

Location: internal/controller/dashboard/static_refactored.go in CreateAllCustomColumnsOverrides()

Example:

// Stock namespace backups cozystack io v1alpha1 plans
createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/plans", []any{
    createCustomColumnWithJsonPath("Name", ".metadata.name", "Plan", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/plan-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
    createApplicationRefColumn("Application"),
    createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),

Key Components:

  • ID Format: stock-namespace-/{group}/{version}/{plural}
  • Name Column: Use createCustomColumnWithJsonPath() with:
    • Badge value: Resource kind in PascalCase (e.g., "Plan", "Service")
    • Link href: /openapi-ui/{2}/{namespace}/factory/{resource-details}/{name}
  • Additional Columns: Use helper functions like:
    • createStringColumn(): Simple string values
    • createTimestampColumn(): Timestamp fields
    • createReadyColumn(): Ready status from conditions
    • Custom helpers for complex fields

Helper Functions Available:

  • createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, linkHref): Column with badge and link
  • createStringColumn(name, jsonPath): Simple string column
  • createTimestampColumn(name, jsonPath): Timestamp with formatting
  • createReadyColumn(): Ready status column
  • createBoolColumn(name, jsonPath): Boolean column
  • createArrayColumn(name, jsonPath): Array column
Step 2: Add Factory (Detail Page)

The Factory defines the detail page layout when viewing an individual resource instance.

Location: internal/controller/dashboard/static_refactored.go in CreateAllFactories()

Using Unified Factory Approach (Recommended):

// Resource details factory using unified approach
resourceConfig := UnifiedResourceConfig{
    Name:         "resource-details",  // Must match the href in CustomColumnsOverride
    ResourceType: "factory",
    Kind:         "ResourceKind",      // PascalCase
    Plural:       "resources",         // lowercase plural
    Title:        "resource",          // lowercase singular
}
resourceTabs := []any{
    map[string]any{
        "key":   "details",
        "label": "Details",
        "children": []any{
            contentCard("details-card", map[string]any{
                "marginBottom": "24px",
            }, []any{
                antdText("details-title", true, "Resource details", map[string]any{
                    "fontSize":     20,
                    "marginBottom": "12px",
                }),
                spacer("details-spacer", 16),
                antdRow("details-grid", []any{48, 12}, []any{
                    antdCol("col-left", 12, []any{
                        antdFlexVertical("col-left-stack", 24, []any{
                            // Metadata fields: Name, Namespace, Created, etc.
                        }),
                    }),
                    antdCol("col-right", 12, []any{
                        antdFlexVertical("col-right-stack", 24, []any{
                            // Spec fields
                        }),
                    }),
                }),
            }),
        },
    },
}
resourceSpec := createUnifiedFactory(resourceConfig, resourceTabs, []any{"/api/clusters/{2}/k8s/apis/{group}/{version}/namespaces/{3}/{plural}/{6}"})

// Add to return list
return []*dashboardv1alpha1.Factory{
    // ... other factories
    createFactory("resource-details", resourceSpec),
}

Key Components:

  • Factory Key: Must match the href path segment (e.g., plan-details matches /factory/plan-details/...)
  • API Endpoint: /api/clusters/{2}/k8s/apis/{group}/{version}/namespaces/{3}/{plural}/{6}
    • {2}: Cluster name
    • {3}: Namespace
    • {6}: Resource name
  • Sidebar Tags: Automatically set to ["{lowercase-kind}-sidebar"] by createUnifiedFactory()
  • Tabs: Define the detail page content (Details, YAML, etc.)

UI Helper Functions:

  • contentCard(id, style, children): Container card
  • antdText(id, strong, text, style): Text element
  • antdFlexVertical(id, gap, children): Vertical flex container
  • antdRow(id, gutter, children): Row layout
  • antdCol(id, span, children): Column layout
  • parsedText(id, text, style): Text with JSON path parsing
  • parsedTextWithFormatter(id, text, formatter): Formatted text (e.g., timestamp)
  • spacer(id, space): Spacing element

Displaying Fields:

antdFlexVertical("field-block", 4, []any{
    antdText("field-label", true, "Field Label", nil),
    parsedText("field-value", "{reqsJsonPath[0]['.spec.fieldName']['-']}", nil),
}),
Step 3: Add Sidebar Entry

The sidebar entry adds navigation to the resource in the sidebar menu.

Location: internal/controller/dashboard/sidebar.go in ensureSidebar()

3a. Add to keysAndTags (around line 110-116):

// Add sidebar for {group} {kind} resource
keysAndTags["{plural}"] = []any{"{lowercase-kind}-sidebar"}

3b. Add Sidebar Section (if creating a new section, around line 169):

// Add hardcoded {SectionName} section
menuItems = append(menuItems, map[string]any{
    "key":   "{section-key}",
    "label": "{SectionName}",
    "children": []any{
        map[string]any{
            "key":   "{plural}",
            "label": "{ResourceLabel}",
            "link":  "/openapi-ui/{cluster}/{namespace}/api-table/{group}/{version}/{plural}",
        },
    },
}),

3c. Add Sidebar ID to targetIDs (around line 220):

"stock-project-factory-{lowercase-kind}-details",

3d. Update Category Ordering (if adding a new section):

  • Update the comment around line 29 to include the new section
  • Update orderCategoryLabels() function if needed
  • Add skip condition in the category loop (around line 149)

Important Notes:

  • The sidebar tag ({lowercase-kind}-sidebar) must match what the Factory uses
  • The link format: /openapi-ui/{cluster}/{namespace}/api-table/{group}/{version}/{plural}
  • All sidebars share the same keysAndTags and menuItems, so changes affect all sidebar instances
Step 4: Verify Integration
  1. Check Factory-Sidebar Connection:

    • Factory uses sidebarTags: ["{lowercase-kind}-sidebar"]
    • Sidebar has keysAndTags["{plural}"] = []any{"{lowercase-kind}-sidebar"}
    • Sidebar ID stock-project-factory-{lowercase-kind}-details exists in targetIDs
  2. Check Navigation Flow:

    • Sidebar link → List table (CustomColumnsOverride)
    • List table Name column → Detail page (Factory)
    • All paths use consistent naming
  3. Test:

    • Verify the resource appears in the sidebar
    • Verify the list table displays correctly
    • Verify clicking a resource navigates to the detail page
    • Verify the detail page displays all relevant fields

Common Patterns

Displaying Object References

For fields that reference other resources (e.g., applicationRef, storageRef):

// In CustomColumnsOverride
createApplicationRefColumn("Application"),  // Uses helper function

// In Factory details tab
parsedText("application-ref-value", 
    "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", 
    nil),
Displaying Timestamps
antdFlexVertical("created-block", 4, []any{
    antdText("time-label", true, "Created", nil),
    antdFlex("time-block", 6, []any{
        map[string]any{
            "type": "antdText",
            "data": map[string]any{
                "id":   "time-icon",
                "text": "🌐",
            },
        },
        map[string]any{
            "type": "parsedText",
            "data": map[string]any{
                "formatter": "timestamp",
                "id":        "time-value",
                "text":      "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}",
            },
        },
    }),
}),
antdFlexVertical("meta-namespace-block", 8, []any{
    antdText("meta-namespace-label", true, "Namespace", nil),
    antdFlex("header-row", 6, []any{
        // Badge component
        map[string]any{
            "type": "antdText",
            "data": map[string]any{
                "id":    "header-badge",
                "text":  "NS",
                "title": "namespace",
                "style": map[string]any{
                    "backgroundColor": "#a25792ff",
                    // ... badge styles
                },
            },
        },
        // Link component
        map[string]any{
            "type": "antdLink",
            "data": map[string]any{
                "id":   "namespace-link",
                "text": "{reqsJsonPath[0]['.metadata.namespace']['-']}",
                "href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
            },
        },
    }),
}),

File Reference

  • CustomColumnsOverride: internal/controller/dashboard/static_refactored.goCreateAllCustomColumnsOverrides()
  • Factory: internal/controller/dashboard/static_refactored.goCreateAllFactories()
  • Sidebar: internal/controller/dashboard/sidebar.goensureSidebar()
  • Helper Functions: internal/controller/dashboard/static_helpers.go
  • UI Helpers: internal/controller/dashboard/ui_helpers.go
  • Unified Helpers: internal/controller/dashboard/unified_helpers.go

AI Agent Prompt Template

Use this template when asking an AI agent to add a new resource to the dashboard:

Please add support for the {ResourceKind} resource ({group}/{version}/{plural}) to the Cozystack dashboard.

Resource Details:
- API Group: {group}
- Version: {version}
- Kind: {ResourceKind}
- Plural: {plural}
- Namespaced: {true/false}

Requirements:
1. Add a CustomColumnsOverride in CreateAllCustomColumnsOverrides() with:
   - ID: stock-namespace-/{group}/{version}/{plural}
   - Name column with {ResourceKind} badge linking to /factory/{lowercase-kind}-details/{name}
   - Additional columns: {list relevant columns}
   
2. Add a Factory in CreateAllFactories() with:
   - Key: {lowercase-kind}-details
   - API endpoint: /api/clusters/{2}/k8s/apis/{group}/{version}/namespaces/{3}/{plural}/{6}
   - Details tab showing all spec fields:
     {list spec fields to display}
   - Use createUnifiedFactory() approach
   
3. Add sidebar entry in ensureSidebar():
   - Add keysAndTags entry: keysAndTags["{plural}"] = []any{"{lowercase-kind}-sidebar"}
   - Add sidebar section: {specify section name or "add to existing section"}
   - Add to targetIDs: "stock-project-factory-{lowercase-kind}-details"
   
4. Ensure Factory sidebarTags matches the keysAndTags entry

Please follow the existing patterns in the codebase, particularly the Plan resource implementation as a reference.

Example: Plan Resource

The Plan resource (backups.cozystack.io/v1alpha1/plans) serves as a complete reference implementation:

  • CustomColumnsOverride: Diff in static_refactored.go
  • Factory: Diff in static_refactored.go
  • Sidebar: Diff in sidebar.go
  • Helper Function: createApplicationRefColumn() in static_helpers.go (diff)

Review this implementation for a complete working example.

Troubleshooting

Resource doesn't appear in sidebar
  • Check that keysAndTags["{plural}"] is set correctly
  • Verify the sidebar section is added to menuItems
  • Ensure the sidebar ID is in targetIDs
Clicking resource doesn't navigate to detail page
  • Verify the CustomColumnsOverride href matches the Factory key
  • Check that the Factory key is exactly {lowercase-kind}-details
  • Ensure the Factory is added to the return list in CreateAllFactories()
Detail page shows wrong sidebar
  • Verify Factory sidebarTags matches keysAndTags["{plural}"]
  • Check that the sidebar ID stock-project-factory-{lowercase-kind}-details exists
  • Ensure all sidebars are updated (they share the same keysAndTags)
Fields not displaying correctly
  • Verify JSON paths are correct (use .spec.fieldName format)
  • Check that reqsJsonPath[0] index is used for single resource views
  • Ensure field names match the actual resource spec structure

Additional Resources

Documentation

Index

Constants

View Source
const (
	// Label keys for dashboard resource management
	LabelManagedBy    = "dashboard.cozystack.io/managed-by"
	LabelResourceType = "dashboard.cozystack.io/resource-type"
	LabelCRDName      = "dashboard.cozystack.io/crd-name"
	LabelCRDGroup     = "dashboard.cozystack.io/crd-group"
	LabelCRDVersion   = "dashboard.cozystack.io/crd-version"
	LabelCRDKind      = "dashboard.cozystack.io/crd-kind"
	LabelCRDPlural    = "dashboard.cozystack.io/crd-plural"

	// Label values
	ManagedByValue      = "cozystack-dashboard-controller"
	ResourceTypeStatic  = "static"
	ResourceTypeDynamic = "dynamic"
)

Variables

This section is empty.

Functions

func AddToScheme

func AddToScheme(s *runtime.Scheme) error

AddToScheme exposes dashboard types registration for controller setup.

func CreateAllBreadcrumbs

func CreateAllBreadcrumbs() []*dashboardv1alpha1.Breadcrumb

CreateAllBreadcrumbs creates all breadcrumb resources using helper functions

func CreateAllCustomColumnsOverrides

func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverride

CreateAllCustomColumnsOverrides creates all custom column override resources using helper functions

func CreateAllCustomFormsOverrides

func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride

CreateAllCustomFormsOverrides creates all custom forms override resources using helper functions

func CreateAllFactories

func CreateAllFactories() []*dashboardv1alpha1.Factory

CreateAllFactories creates all factory resources using helper functions

func CreateAllNavigations

func CreateAllNavigations() []*dashboardv1alpha1.Navigation

CreateAllNavigations creates all navigation resources using helper functions

func CreateAllStaticResources

func CreateAllStaticResources() []client.Object

CreateAllStaticResources creates all static dashboard resources using helper functions

func CreateAllTableUriMappings

func CreateAllTableUriMappings() []*dashboardv1alpha1.TableUriMapping

CreateAllTableUriMappings creates all table URI mapping resources using helper functions

func CreateStaticCFOMapping added in v1.0.0

func CreateStaticCFOMapping() *dashboardv1alpha1.CFOMapping

CreateStaticCFOMapping creates the CFOMapping resource with mappings from static CustomFormsOverrides

Types

type BadgeConfig

type BadgeConfig struct {
	Kind  string // Resource kind in PascalCase (e.g., "VirtualMachine") - used for value and auto-generation
	Text  string // Optional abbreviation override (if empty, ResourceBadge auto-generates from Kind)
	Color string // Optional custom backgroundColor override
}

BadgeConfig holds configuration for badge generation

type Manager

type Manager struct {
	client.Client
	Scheme *runtime.Scheme
}

Manager owns logic for creating/updating dashboard resources derived from CRDs. It’s easy to extend: add new ensure* methods and wire them into EnsureForAppDef.

func NewManager

func NewManager(c client.Client, scheme *runtime.Scheme) *Manager

NewManager constructs a dashboard Manager.

func (*Manager) CleanupOrphanedResources

func (m *Manager) CleanupOrphanedResources(ctx context.Context) error

CleanupOrphanedResources removes dashboard resources that are no longer needed This should be called after cache warming to ensure all current resources are known

func (*Manager) EnsureForAppDef added in v1.0.0

EnsureForAppDef is the single entry-point used by the controller. Add more ensure* calls here as you implement support for other resources:

  • ensureBreadcrumb (implemented)
  • ensureCustomColumnsOverride (implemented)
  • ensureCustomFormsOverride (implemented)
  • ensureCustomFormsPrefill (implemented)
  • ensureFactory
  • ensureMarketplacePanel (implemented)
  • ensureSidebar (implemented)
  • ensureTableUriMapping (implemented)

func (*Manager) InitializeStaticResources

func (m *Manager) InitializeStaticResources(ctx context.Context) error

InitializeStaticResources creates all static dashboard resources once during controller startup

func (*Manager) Reconcile added in v0.37.7

func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

func (*Manager) SetupWithManager added in v0.37.7

func (m *Manager) SetupWithManager(mgr ctrl.Manager) error

type ResourceConfig

type ResourceConfig struct {
	SpecID       string
	MetadataName string
	Kind         string
	Title        string
	BadgeConfig  BadgeConfig
}

ResourceConfig holds configuration for resource creation

type UnifiedResourceConfig

type UnifiedResourceConfig struct {
	Name         string
	ResourceType string
	Kind         string
	Plural       string
	Title        string
	Color        string
	BadgeText    string
}

UnifiedResourceConfig holds configuration for unified resource creation

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL