fix sidebars

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
This commit is contained in:
Andrei Kvapil
2025-09-25 18:02:39 +02:00
parent 907dcb5e8b
commit f130895b30
2 changed files with 94 additions and 47 deletions

View File

@@ -12,6 +12,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@@ -245,29 +246,29 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResource
continue
}
// Skip resources with non-empty spec.dashboard.name (tenant modules)
if strings.TrimSpace(crd.Spec.Dashboard.Name) != "" {
continue
}
// Note: We include ALL resources with dashboard config, regardless of dashboard.name
// because ensureFactory and ensureBreadcrumb create resources for all CRDs with dashboard config
g, v, kind := pickGVK(&crd)
plural := pickPlural(kind, &crd)
// CustomColumnsOverride
// CustomColumnsOverride - created for ALL CRDs with dashboard config
name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
expected["CustomColumnsOverride"][name] = true
// CustomFormsOverride
// CustomFormsOverride - created for ALL CRDs with dashboard config
name = fmt.Sprintf("%s.%s.%s", g, v, plural)
expected["CustomFormsOverride"][name] = true
// CustomFormsPrefill
// CustomFormsPrefill - created for ALL CRDs with dashboard config
expected["CustomFormsPrefill"][name] = true
// MarketplacePanel (name matches CRD name)
expected["MarketplacePanel"][crd.Name] = true
// MarketplacePanel - only created for CRDs WITHOUT dashboard.name
if strings.TrimSpace(crd.Spec.Dashboard.Name) == "" {
expected["MarketplacePanel"][crd.Name] = true
}
// Sidebar resources (multiple per CRD)
// Sidebar resources - created for ALL CRDs with dashboard config
lowerKind := strings.ToLower(kind)
detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
expected["Sidebar"][detailsID] = true
@@ -291,15 +292,15 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResource
expected["Sidebar"][sidebarID] = true
}
// TableUriMapping
// TableUriMapping - created for ALL CRDs with dashboard config
name = fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
expected["TableUriMapping"][name] = true
// Breadcrumb
// Breadcrumb - created for ALL CRDs with dashboard config
detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
expected["Breadcrumb"][detailID] = true
// Factory
// Factory - created for ALL CRDs with dashboard config
factoryName := fmt.Sprintf("%s-details", lowerKind)
expected["Factory"][factoryName] = true
}
@@ -423,22 +424,24 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.BreadcrumbList:
for _, item := range l.Items {
if !expected[item.Name] {
logger := log.FromContext(ctx)
logger.Info("Deleting orphaned Breadcrumb resource", "name", item.Name)
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.FactoryList:
for _, item := range l.Items {
if !expected[item.Name] {
logger := log.FromContext(ctx)
logger.Info("Deleting orphaned Factory resource", "name", item.Name)
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}

View File

@@ -57,6 +57,9 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
categories := map[string][]item{} // category label -> children
keysAndTags := map[string]any{} // plural -> []string{ "<lower(kind)>-sidebar" }
// Collect sidebar names for resources with dashboard.name
var moduleSidebars []any
for i := range all {
def := &all[i]
@@ -65,35 +68,46 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
continue
}
// Skip resources with non-empty spec.dashboard.name
if strings.TrimSpace(def.Spec.Dashboard.Name) != "" {
continue
}
g, v, kind := pickGVK(def)
plural := pickPlural(kind, def)
cat := safeCategory(def) // falls back to "Resources" if empty
lowerKind := strings.ToLower(kind)
// Label: prefer dashboard.Plural if provided
label := titleFromKindPlural(kind, plural)
if def.Spec.Dashboard.Plural != "" {
label = def.Spec.Dashboard.Plural
// Check if this resource has dashboard.name set
if strings.TrimSpace(def.Spec.Dashboard.Name) != "" {
// Add to modules sidebar list
moduleSidebars = append(moduleSidebars, fmt.Sprintf("%s-sidebar", lowerKind))
} else {
// Add to keysAndTags for resources without dashboard.name
keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)}
}
// Weight (default 0)
weight := def.Spec.Dashboard.Weight
// Only add to menu categories if dashboard.name is empty
if strings.TrimSpace(def.Spec.Dashboard.Name) == "" {
cat := safeCategory(def) // falls back to "Resources" if empty
link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", g, v, plural)
// Label: prefer dashboard.Plural if provided
label := titleFromKindPlural(kind, plural)
if def.Spec.Dashboard.Plural != "" {
label = def.Spec.Dashboard.Plural
}
categories[cat] = append(categories[cat], item{
Key: plural,
Label: label,
Link: link,
Weight: weight,
})
// Weight (default 0)
weight := def.Spec.Dashboard.Weight
// keysAndTags: plural -> [ "<lower(kind)>-sidebar" ]
keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", strings.ToLower(kind))}
link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", g, v, plural)
categories[cat] = append(categories[cat], item{
Key: plural,
Label: label,
Link: link,
Weight: weight,
})
}
}
// Add modules to keysAndTags if we have any module sidebars
if len(moduleSidebars) > 0 {
keysAndTags["modules"] = moduleSidebars
}
// 3) Sort items within each category by Weight (desc), then Label (A→Z)
@@ -171,14 +185,8 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
})
// 6) Prepare the list of Sidebar IDs to upsert with the SAME content
_, _, thisKind := pickGVK(crd)
lowerThisKind := strings.ToLower(thisKind)
detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerThisKind)
// Create sidebars for ALL CRDs with dashboard config
targetIDs := []string{
// original details sidebar
detailsID,
// stock-instance sidebars
"stock-instance-api-form",
"stock-instance-api-table",
@@ -196,6 +204,18 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
"stock-project-crd-table",
}
// Add details sidebars for all CRDs with dashboard config
for i := range all {
def := &all[i]
if def.Spec.Dashboard == nil {
continue
}
_, _, kind := pickGVK(def)
lowerKind := strings.ToLower(kind)
detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
targetIDs = append(targetIDs, detailsID)
}
// 7) Upsert all target sidebars with identical menuItems and keysAndTags
return m.upsertMultipleSidebars(ctx, crd, targetIDs, keysAndTags, menuItems)
}
@@ -219,11 +239,35 @@ func (m *Manager) upsertMultipleSidebars(
obj.SetName(id)
if _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
// Only set owner reference for dynamic sidebars (stock-project-factory-{kind}-details)
// Static sidebars (stock-instance-*, stock-project-*) should not have owner references
if strings.HasPrefix(id, "stock-project-factory-") && strings.HasSuffix(id, "-details") {
// This is a dynamic sidebar, set owner reference only if it matches the current CRD
_, _, kind := pickGVK(crd)
lowerKind := strings.ToLower(kind)
expectedID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
if id == expectedID {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
} else {
// This is a different CRD's sidebar, don't modify owner references or labels
// Just update the spec
}
} else {
// This is a static sidebar, don't set owner references
// Add static labels
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
labels[LabelManagedBy] = ManagedByValue
labels[LabelResourceType] = ResourceTypeStatic
obj.SetLabels(labels)
}
// Add dashboard labels to dynamic resources
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
b, err := json.Marshal(spec)
if err != nil {
return err