diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go index 82ae54f1..f774284a 100644 --- a/cmd/cozystack-controller/main.go +++ b/cmd/cozystack-controller/main.go @@ -153,7 +153,12 @@ func main() { // this setup is not recommended for production. } - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + // Configure rate limiting for the Kubernetes client + config := ctrl.GetConfigOrDie() + config.QPS = 50.0 // Increased from default 5.0 + config.Burst = 100 // Increased from default 10 + + mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, Metrics: metricsServerOptions, WebhookServer: webhookServer, diff --git a/internal/controller/cozystackresource_controller.go b/internal/controller/cozystackresource_controller.go index 39779487..0f02b610 100644 --- a/internal/controller/cozystackresource_controller.go +++ b/internal/controller/cozystackresource_controller.go @@ -23,8 +23,10 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -39,6 +41,10 @@ type CozystackResourceDefinitionReconciler struct { lastHandled time.Time mem *crdmem.Memory + + // Track static resources initialization + staticResourcesInitialized bool + staticResourcesMutex sync.Mutex } func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -70,15 +76,25 @@ func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, r return res, derr } + // After processing CRD, perform cleanup of orphaned resources + // This should be done after cache warming to ensure all current resources are known + if cleanupErr := mgr.CleanupOrphanedResources(ctx); cleanupErr != nil { + logger.Error(cleanupErr, "Failed to cleanup orphaned dashboard resources") + // Don't fail the reconciliation, just log the error + } + r.mu.Lock() r.lastEvent = time.Now() r.mu.Unlock() return ctrl.Result{}, nil } - if err != nil && !apierrors.IsNotFound(err) { + + // Handle error cases (err is guaranteed to be non-nil here) + if !apierrors.IsNotFound(err) { return ctrl.Result{}, err } - if apierrors.IsNotFound(err) && r.mem != nil { + // If resource is not found, clean up from memory + if r.mem != nil { r.mem.Delete(req.Name) } if req.Namespace == "cozy-system" && req.Name == "cozystack-api" { @@ -87,6 +103,40 @@ func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, r return ctrl.Result{}, nil } +// initializeStaticResourcesOnce ensures static resources are created only once +func (r *CozystackResourceDefinitionReconciler) initializeStaticResourcesOnce(ctx context.Context) error { + r.staticResourcesMutex.Lock() + defer r.staticResourcesMutex.Unlock() + + if r.staticResourcesInitialized { + return nil // Already initialized + } + + // Create dashboard manager and initialize static resources + mgr := dashboard.NewManager( + r.Client, + r.Scheme, + dashboard.WithCRDListFunc(func(c context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error) { + if r.mem != nil { + return r.mem.ListFromCacheOrAPI(c, r.Client) + } + var list cozyv1alpha1.CozystackResourceDefinitionList + if err := r.Client.List(c, &list); err != nil { + return nil, err + } + return list.Items, nil + }), + ) + + if err := mgr.InitializeStaticResources(ctx); err != nil { + return err + } + + r.staticResourcesInitialized = true + log.FromContext(ctx).Info("Static dashboard resources initialized successfully") + return nil +} + func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error { if r.Debounce == 0 { r.Debounce = 5 * time.Second @@ -97,6 +147,18 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage if err := r.mem.EnsurePrimingWithManager(mgr); err != nil { return err } + + // Initialize static resources once during controller startup using manager.Runnable + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { + if err := r.initializeStaticResourcesOnce(ctx); err != nil { + log.FromContext(ctx).Error(err, "Failed to initialize static resources") + return err + } + return nil + })); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). Named("cozystackresource-controller"). For(&cozyv1alpha1.CozystackResourceDefinition{}, builder.WithPredicates()). @@ -114,6 +176,9 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage }} }), ). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 5, // Allow more concurrent reconciles with proper rate limiting + }). Complete(r) } diff --git a/internal/controller/dashboard/breadcrumb.go b/internal/controller/dashboard/breadcrumb.go new file mode 100644 index 00000000..788a32b7 --- /dev/null +++ b/internal/controller/dashboard/breadcrumb.go @@ -0,0 +1,81 @@ +package dashboard + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// ensureBreadcrumb creates or updates a Breadcrumb resource for the given CRD +func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { + group, version, kind := pickGVK(crd) + + lowerKind := strings.ToLower(kind) + detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) + + obj := &dashv1alpha1.Breadcrumb{} + obj.SetName(detailID) + + plural := pickPlural(kind, crd) + + // Prefer dashboard.Plural for UI label if provided + labelPlural := titleFromKindPlural(kind, plural) + if crd != nil && crd.Spec.Dashboard != nil && crd.Spec.Dashboard.Plural != "" { + labelPlural = crd.Spec.Dashboard.Plural + } + + key := plural // e.g., "virtualmachines" + label := labelPlural + link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", strings.ToLower(group), strings.ToLower(version), plural) + // If Name is set, change the first breadcrumb item to "Tenant Modules" + // TODO add parameter to this + if crd.Spec.Dashboard != nil && strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { + key = "tenantmodules" + label = "Tenant Modules" + link = "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules" + } + + items := []any{ + map[string]any{ + "key": key, + "label": label, + "link": link, + }, + map[string]any{ + "key": strings.ToLower(kind), // "etcd" + "label": "{6}", // literal, as in your example + }, + } + + spec := map[string]any{ + "id": detailID, + "breadcrumbItems": items, + } + + _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { + if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { + return err + } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(obj, crd, ResourceTypeDynamic) + b, err := json.Marshal(spec) + if err != nil { + return err + } + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } + return nil + }) + return err +} diff --git a/internal/controller/dashboard/customcolumns.go b/internal/controller/dashboard/customcolumns.go index ee4c647b..441787fe 100644 --- a/internal/controller/dashboard/customcolumns.go +++ b/internal/controller/dashboard/customcolumns.go @@ -2,8 +2,6 @@ package dashboard import ( "context" - "crypto/sha1" - "encoding/hex" "encoding/json" "fmt" "strings" @@ -12,7 +10,6 @@ import ( cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -38,11 +35,6 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al badgeColor := hexColorForKind(kind) // deterministic, dark enough for white text obj := &dashv1alpha1.CustomColumnsOverride{} - obj.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "dashboard.cozystack.io", - Version: "v1alpha1", - Kind: "CustomColumnsOverride", - }) obj.SetName(name) href := fmt.Sprintf("/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/%s/{reqsJsonPath[0]['.metadata.name']['-']}", detailsSegment) @@ -153,146 +145,24 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al }, } - // CreateOrUpdate using typed resource _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { return err } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(obj, crd, ResourceTypeDynamic) b, err := json.Marshal(desired["spec"]) if err != nil { return err } - obj.Spec = dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } return nil }) // Return OperationResultCreated/Updated is not available here with unstructured; we can mimic Updated when no error. return controllerutil.OperationResultNone, err } - -// --- helpers --- - -// pickGVK tries to read group/version/kind from the CRD. We prefer the "application" section, -// falling back to other likely fields if your schema differs. -func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kind string) { - // Best guess based on your examples: - if crd.Spec.Application.Kind != "" { - kind = crd.Spec.Application.Kind - } - - // Reasonable fallbacks if any are empty: - if group == "" { - group = "apps.cozystack.io" - } - if version == "" { - version = "v1alpha1" - } - if kind == "" { - kind = "Resource" - } - return -} - -// pickPlural prefers a field on the CRD if you have it; otherwise do a simple lowercase + "s". -func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) string { - // If you have crd.Spec.Application.Plural, prefer it. Example: - if crd.Spec.Application.Plural != "" { - return crd.Spec.Application.Plural - } - // naive pluralization - k := strings.ToLower(kind) - if strings.HasSuffix(k, "s") { - return k - } - return k + "s" -} - -// initialsFromKind splits CamelCase and returns the first letters in upper case. -// "VirtualMachine" -> "VM"; "Bucket" -> "B". -func initialsFromKind(kind string) string { - parts := splitCamel(kind) - if len(parts) == 0 { - return strings.ToUpper(kind) - } - var b strings.Builder - for _, p := range parts { - if p == "" { - continue - } - b.WriteString(strings.ToUpper(string(p[0]))) - // Limit to 3 chars to keep the badge compact (VM, PVC, etc.) - if b.Len() >= 3 { - break - } - } - return b.String() -} - -// hexColorForKind returns a dark, saturated color (hex) derived from a stable hash of the kind. -// We map the hash to an HSL hue; fix S/L for consistent readability with white text. -func hexColorForKind(kind string) string { - // Stable short hash (sha1 → bytes → hue) - sum := sha1.Sum([]byte(kind)) - // Use first two bytes for hue [0..359] - hue := int(sum[0])<<8 | int(sum[1]) - hue = hue % 360 - - // Fixed S/L chosen to contrast with white text: - // S = 80%, L = 35% (dark enough so #fff is readable) - r, g, b := hslToRGB(float64(hue), 0.80, 0.35) - - return fmt.Sprintf("#%02x%02x%02x", r, g, b) -} - -// hslToRGB converts HSL (0..360, 0..1, 0..1) to sRGB (0..255). -func hslToRGB(h float64, s float64, l float64) (uint8, uint8, uint8) { - c := (1 - absFloat(2*l-1)) * s - hp := h / 60.0 - x := c * (1 - absFloat(modFloat(hp, 2)-1)) - var r1, g1, b1 float64 - switch { - case 0 <= hp && hp < 1: - r1, g1, b1 = c, x, 0 - case 1 <= hp && hp < 2: - r1, g1, b1 = x, c, 0 - case 2 <= hp && hp < 3: - r1, g1, b1 = 0, c, x - case 3 <= hp && hp < 4: - r1, g1, b1 = 0, x, c - case 4 <= hp && hp < 5: - r1, g1, b1 = x, 0, c - default: - r1, g1, b1 = c, 0, x - } - m := l - c/2 - r := uint8(clamp01(r1+m) * 255.0) - g := uint8(clamp01(g1+m) * 255.0) - b := uint8(clamp01(b1+m) * 255.0) - return r, g, b -} - -func absFloat(v float64) float64 { - if v < 0 { - return -v - } - return v -} - -func modFloat(a, b float64) float64 { - return a - b*float64(int(a/b)) -} - -func clamp01(v float64) float64 { - if v < 0 { - return 0 - } - if v > 1 { - return 1 - } - return v -} - -// optional: tiny helper to expose the compact color hash (useful for debugging) -func shortHashHex(s string) string { - sum := sha1.Sum([]byte(s)) - return hex.EncodeToString(sum[:4]) -} diff --git a/internal/controller/dashboard/customformsoverride.go b/internal/controller/dashboard/customformsoverride.go new file mode 100644 index 00000000..c06a21f3 --- /dev/null +++ b/internal/controller/dashboard/customformsoverride.go @@ -0,0 +1,75 @@ +package dashboard + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD +func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { + g, v, kind := pickGVK(crd) + plural := pickPlural(kind, crd) + + name := fmt.Sprintf("%s.%s.%s", g, v, plural) + customizationID := fmt.Sprintf("default-/%s/%s/%s", g, v, plural) + + obj := &dashv1alpha1.CustomFormsOverride{} + obj.SetName(name) + + // Replicates your Helm includes (system metadata + api + status). + hidden := []any{} + hidden = append(hidden, hiddenMetadataSystem()...) + hidden = append(hidden, hiddenMetadataAPI()...) + hidden = append(hidden, hiddenStatus()...) + + // If Name is set, hide metadata + if crd.Spec.Dashboard != nil && strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { + hidden = append([]interface{}{ + []any{"metadata"}, + }, hidden...) + } + + var sort []any + if crd.Spec.Dashboard != nil && len(crd.Spec.Dashboard.KeysOrder) > 0 { + sort = make([]any, len(crd.Spec.Dashboard.KeysOrder)) + for i, v := range crd.Spec.Dashboard.KeysOrder { + sort[i] = v + } + } + + spec := map[string]any{ + "customizationId": customizationID, + "hidden": hidden, + "sort": sort, + "schema": map[string]any{}, // {} + "strategy": "merge", + } + + _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { + if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { + return err + } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(obj, crd, ResourceTypeDynamic) + b, err := json.Marshal(spec) + if err != nil { + return err + } + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } + return nil + }) + return err +} diff --git a/internal/controller/dashboard/customformsprefill.go b/internal/controller/dashboard/customformsprefill.go new file mode 100644 index 00000000..e16725a0 --- /dev/null +++ b/internal/controller/dashboard/customformsprefill.go @@ -0,0 +1,79 @@ +package dashboard + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// ensureCustomFormsPrefill creates or updates a CustomFormsPrefill resource for the given CRD +func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { + logger := log.FromContext(ctx) + + app := crd.Spec.Application + group, version, kind := pickGVK(crd) + plural := pickPlural(kind, crd) + + name := fmt.Sprintf("%s.%s.%s", group, version, plural) + customizationID := fmt.Sprintf("default-/%s/%s/%s", group, version, plural) + + values, err := buildPrefillValues(app.OpenAPISchema) + if err != nil { + return reconcile.Result{}, err + } + + // If Name is set, prefill metadata.name + if crd.Spec.Dashboard != nil && strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { + values = append([]interface{}{ + map[string]interface{}{ + "path": toIfaceSlice([]string{"metadata", "name"}), + "value": crd.Spec.Dashboard.Name, + }, + }, values...) + } + + cfp := &dashv1alpha1.CustomFormsPrefill{} + cfp.Name = name // cluster-scoped + + specMap := map[string]any{ + "customizationId": customizationID, + "values": values, + } + // Use json.Marshal with sorted keys to ensure consistent output + specBytes, err := json.Marshal(specMap) + if err != nil { + return reconcile.Result{}, err + } + + _, err = controllerutil.CreateOrUpdate(ctx, m.client, cfp, func() error { + if err := controllerutil.SetOwnerReference(crd, cfp, m.scheme); err != nil { + return err + } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(cfp, crd, ResourceTypeDynamic) + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{ + JSON: apiextv1.JSON{Raw: specBytes}, + } + if !compareArbitrarySpecs(cfp.Spec, newSpec) { + cfp.Spec = newSpec + } + return nil + }) + if err != nil { + return reconcile.Result{}, err + } + + logger.Info("Applied CustomFormsPrefill", "name", cfp.Name) + return reconcile.Result{}, nil +} diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index c4146407..2d89cc0e 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -11,20 +11,10 @@ import ( cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -// ---------------- Types used by OpenAPI parsing ---------------- - -type fieldInfo struct { - JSONPathSpec string // dotted path under .spec (e.g., "systemDisk.image") - Label string // "System Disk / Image" or "systemDisk.image" - Description string -} - -// ---------------- Public entry: ensure Factory ------------------ - +// ensureFactory creates or updates a Factory resource for the given CRD func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { g, v, kind := pickGVK(crd) plural := pickPlural(kind, crd) @@ -56,85 +46,37 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack } tabs = append(tabs, yamlTab(plural)) - badgeText := initialsFromKind(kind) - badgeColor := hexColorForKind(kind) - header := map[string]any{ - "type": "antdFlex", - "data": map[string]any{ - "id": "header-row", - "align": "center", - "gap": float64(6), - "style": map[string]any{"marginBottom": float64(24)}, - }, - "children": []any{ - map[string]any{ - "type": "antdText", - "data": map[string]any{ - "id": "badge-" + lowerKind, - "text": badgeText, - "title": strings.ToLower(plural), - "style": map[string]any{ - "backgroundColor": badgeColor, - "borderRadius": "20px", - "color": "#fff", - "display": "inline-block", - "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", - "fontSize": float64(20), - "fontWeight": float64(400), - "lineHeight": "24px", - "minWidth": float64(24), - "padding": "0 9px", - "textAlign": "center", - "whiteSpace": "nowrap", - }, - }, - }, - map[string]any{ - "type": "parsedText", - "data": map[string]any{ - "id": lowerKind + "-name", - "text": "{reqsJsonPath[0]['.metadata.name']['-']}", - "style": map[string]any{ - "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", - "fontSize": float64(20), - "lineHeight": "24px", - }, - }, - }, - }, + // Use unified factory creation + config := UnifiedResourceConfig{ + Name: factoryName, + ResourceType: "factory", + Kind: kind, + Plural: plural, + Title: strings.ToLower(plural), + Size: BadgeSizeLarge, } - spec := map[string]any{ - "key": factoryName, - "sidebarTags": []any{fmt.Sprintf("%s-sidebar", lowerKind)}, - "withScrollableMainContentCard": true, - "urlsToFetch": []any{resourceFetch}, - "data": []any{ - header, - map[string]any{ - "type": "antdTabs", - "data": map[string]any{ - "id": lowerKind + "-tabs", - "defaultActiveKey": "details", - "items": tabs, - }, - }, - }, - } + spec := createUnifiedFactory(config, tabs, []any{resourceFetch}) obj := &dashv1alpha1.Factory{} - obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "dashboard.cozystack.io", Version: "v1alpha1", Kind: "Factory"}) obj.SetName(factoryName) _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { return err } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(obj, crd, ResourceTypeDynamic) b, err := json.Marshal(spec) if err != nil { return err } - obj.Spec = dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } return nil }) return err @@ -173,30 +115,10 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str "gap": float64(6), }, "children": []any{ - map[string]any{ - "type": "antdText", - "data": map[string]any{ - "id": "ns-badge", - "text": "NS", - "style": map[string]any{ - "backgroundColor": "#a25792ff", - "borderRadius": "20px", - "color": "#fff", - "display": "inline-block", - "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", - "fontSize": float64(15), - "fontWeight": float64(400), - "lineHeight": "24px", - "minWidth": float64(24), - "padding": "0 9px", - "textAlign": "center", - "whiteSpace": "nowrap", - }, - }, - }, + createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium), antdLink("namespace-link", "{reqsJsonPath[0]['.metadata.namespace']['-']}", - "/openapi-ui/{2}/factory/namespace-details/{reqsJsonPath[0]['.metadata.namespace']['-']}", + "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace", ), }, }, @@ -407,131 +329,6 @@ func yamlTab(plural string) map[string]any { } } -// ---------------- UI helpers (use float64 for numeric fields) ---------------- - -func contentCard(id string, style map[string]any, children []any) map[string]any { - return map[string]any{ - "type": "ContentCard", - "data": map[string]any{ - "id": id, - "style": style, - }, - "children": children, - } -} - -func antdText(id string, strong bool, text string, style map[string]any) map[string]any { - data := map[string]any{ - "id": id, - "text": text, - "strong": strong, - } - if style != nil { - data["style"] = style - } - return map[string]any{"type": "antdText", "data": data} -} - -func parsedText(id, text string, style map[string]any) map[string]any { - data := map[string]any{ - "id": id, - "text": text, - } - if style != nil { - data["style"] = style - } - return map[string]any{"type": "parsedText", "data": data} -} - -func parsedTextWithFormatter(id, text, formatter string) map[string]any { - return map[string]any{ - "type": "parsedText", - "data": map[string]any{ - "id": id, - "text": text, - "formatter": formatter, - }, - } -} - -func spacer(id string, space float64) map[string]any { - return map[string]any{ - "type": "Spacer", - "data": map[string]any{ - "id": id, - "$space": space, - }, - } -} - -func antdFlex(id string, gap float64, children []any) map[string]any { - return map[string]any{ - "type": "antdFlex", - "data": map[string]any{ - "id": id, - "align": "center", - "gap": gap, - }, - "children": children, - } -} - -func antdFlexVertical(id string, gap float64, children []any) map[string]any { - return map[string]any{ - "type": "antdFlex", - "data": map[string]any{ - "id": id, - "vertical": true, - "gap": gap, - }, - "children": children, - } -} - -func antdRow(id string, gutter []any, children []any) map[string]any { - return map[string]any{ - "type": "antdRow", - "data": map[string]any{ - "id": id, - "gutter": gutter, - }, - "children": children, - } -} - -func antdCol(id string, span float64, children []any) map[string]any { - return map[string]any{ - "type": "antdCol", - "data": map[string]any{ - "id": id, - "span": span, - }, - "children": children, - } -} - -func antdColWithStyle(id string, style map[string]any, children []any) map[string]any { - return map[string]any{ - "type": "antdCol", - "data": map[string]any{ - "id": id, - "style": style, - }, - "children": children, - } -} - -func antdLink(id, text, href string) map[string]any { - return map[string]any{ - "type": "antdLink", - "data": map[string]any{ - "id": id, - "text": text, - "href": href, - }, - } -} - // ---------------- OpenAPI → Right column ---------------- func buildOpenAPIParamsBlocks(schemaJSON string, keysOrder [][]string) []any { @@ -576,7 +373,7 @@ func sortFieldsByKeysOrder(fields []fieldInfo, keysOrder [][]string) []fieldInfo sort.Slice(fields, func(i, j int) bool { posI, existsI := orderMap[fields[i].JSONPathSpec] posJ, existsJ := orderMap[fields[j].JSONPathSpec] - + // If both exist in orderMap, sort by position if existsI && existsJ { return posI < posJ @@ -666,7 +463,7 @@ func collectOpenAPILeafFields(schemaJSON string, maxDepth, maxFields int) []fiel } return } - // Arrays: try to render item if it’s scalar and depth limit allows + // Arrays: try to render item if it's scalar and depth limit allows if n["type"] == "array" { if items, ok := n["items"].(node); ok && (isScalarType(items) || isIntOrString(items) || hasEnum(items)) { addField(prefix, items) @@ -693,74 +490,6 @@ func collectOpenAPILeafFields(schemaJSON string, maxDepth, maxFields int) []fiel return out } -// --- helpers for schema inspection --- - -func isScalarType(n map[string]any) bool { - switch getString(n, "type") { - case "string", "integer", "number", "boolean": - return true - default: - return false - } -} - -func isIntOrString(n map[string]any) bool { - // Kubernetes extension: x-kubernetes-int-or-string: true - if v, ok := n["x-kubernetes-int-or-string"]; ok { - if b, ok := v.(bool); ok && b { - return true - } - } - // anyOf: integer|string - if anyOf, ok := n["anyOf"].([]any); ok { - hasInt := false - hasStr := false - for _, it := range anyOf { - if m, ok := it.(map[string]any); ok { - switch getString(m, "type") { - case "integer": - hasInt = true - case "string": - hasStr = true - } - } - } - return hasInt && hasStr - } - return false -} - -func hasEnum(n map[string]any) bool { - _, ok := n["enum"] - return ok -} - -func getString(n map[string]any, key string) string { - if v, ok := n[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - -// shouldExcludeParamPath returns true if any part of the path contains -// backup / bootstrap / password (case-insensitive) -func shouldExcludeParamPath(parts []string) bool { - for _, p := range parts { - lp := strings.ToLower(p) - if strings.Contains(lp, "backup") || strings.Contains(lp, "bootstrap") || strings.Contains(lp, "password") || strings.Contains(lp, "cloudInit") { - return true - } - } - return false -} - -func humanizePath(parts []string) string { - // "systemDisk.image" -> "System Disk / Image" - return strings.Join(parts, " / ") -} - // ---------------- Feature flags ---------------- type factoryFlags struct { diff --git a/internal/controller/dashboard/helpers.go b/internal/controller/dashboard/helpers.go new file mode 100644 index 00000000..7baa8a33 --- /dev/null +++ b/internal/controller/dashboard/helpers.go @@ -0,0 +1,553 @@ +package dashboard + +import ( + "crypto/sha1" + "encoding/hex" + "encoding/json" + "fmt" + "reflect" + "regexp" + "sort" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" +) + +// ---------------- Types used by OpenAPI parsing ---------------- + +type fieldInfo struct { + JSONPathSpec string // dotted path under .spec (e.g., "systemDisk.image") + Label string // "System Disk / Image" or "systemDisk.image" + Description string +} + +// ---------------- Public entry: ensure Factory ------------------ + +// pickGVK tries to read group/version/kind from the CRD. We prefer the "application" section, +// falling back to other likely fields if your schema differs. +func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kind string) { + // Best guess based on your examples: + if crd.Spec.Application.Kind != "" { + kind = crd.Spec.Application.Kind + } + + // Parse crd.APIVersion to get group and version (format: "group/version") + if crd.APIVersion != "" { + parts := strings.Split(crd.APIVersion, "/") + if len(parts) == 2 { + group = parts[0] + version = parts[1] + } + } + + // Reasonable fallbacks if any are empty: + if group == "" { + group = "apps.cozystack.io" + } + if version == "" { + version = "v1alpha1" + } + if kind == "" { + kind = "Resource" + } + return +} + +// pickPlural prefers a field on the CRD if you have it; otherwise do a simple lowercase + "s". +func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) string { + // If you have crd.Spec.Application.Plural, prefer it. Example: + if crd.Spec.Application.Plural != "" { + return crd.Spec.Application.Plural + } + // naive pluralization + k := strings.ToLower(kind) + if strings.HasSuffix(k, "s") { + return k + } + return k + "s" +} + +// initialsFromKind splits CamelCase and returns the first letters in upper case. +// "VirtualMachine" -> "VM"; "Bucket" -> "B". +func initialsFromKind(kind string) string { + parts := splitCamel(kind) + if len(parts) == 0 { + return strings.ToUpper(kind) + } + var b strings.Builder + for _, p := range parts { + if p == "" { + continue + } + b.WriteString(strings.ToUpper(string(p[0]))) + // Limit to 3 chars to keep the badge compact (VM, PVC, etc.) + if b.Len() >= 3 { + break + } + } + return b.String() +} + +// hexColorForKind returns a dark, saturated color (hex) derived from a stable hash of the kind. +// We map the hash to an HSL hue; fix S/L for consistent readability with white text. +func hexColorForKind(kind string) string { + // Stable short hash (sha1 → bytes → hue) + sum := sha1.Sum([]byte(kind)) + // Use first two bytes for hue [0..359] + hue := int(sum[0])<<8 | int(sum[1]) + hue = hue % 360 + + // Fixed S/L chosen to contrast with white text: + // S = 80%, L = 35% (dark enough so #fff is readable) + r, g, b := hslToRGB(float64(hue), 0.80, 0.35) + + return fmt.Sprintf("#%02x%02x%02x", r, g, b) +} + +// hslToRGB converts HSL (0..360, 0..1, 0..1) to sRGB (0..255). +func hslToRGB(h float64, s float64, l float64) (uint8, uint8, uint8) { + c := (1 - absFloat(2*l-1)) * s + hp := h / 60.0 + x := c * (1 - absFloat(modFloat(hp, 2)-1)) + var r1, g1, b1 float64 + switch { + case 0 <= hp && hp < 1: + r1, g1, b1 = c, x, 0 + case 1 <= hp && hp < 2: + r1, g1, b1 = x, c, 0 + case 2 <= hp && hp < 3: + r1, g1, b1 = 0, c, x + case 3 <= hp && hp < 4: + r1, g1, b1 = 0, x, c + case 4 <= hp && hp < 5: + r1, g1, b1 = x, 0, c + default: + r1, g1, b1 = c, 0, x + } + m := l - c/2 + r := uint8(clamp01(r1+m) * 255.0) + g := uint8(clamp01(g1+m) * 255.0) + b := uint8(clamp01(b1+m) * 255.0) + return r, g, b +} + +func absFloat(v float64) float64 { + if v < 0 { + return -v + } + return v +} + +func modFloat(a, b float64) float64 { + return a - b*float64(int(a/b)) +} + +func clamp01(v float64) float64 { + if v < 0 { + return 0 + } + if v > 1 { + return 1 + } + return v +} + +// optional: tiny helper to expose the compact color hash (useful for debugging) +func shortHashHex(s string) string { + sum := sha1.Sum([]byte(s)) + return hex.EncodeToString(sum[:4]) +} + +// ----------------------- Helpers (OpenAPI → values) ----------------------- + +// defaultOrZero returns the schema default if present; otherwise a reasonable zero value. +func defaultOrZero(sub map[string]interface{}) interface{} { + if v, ok := sub["default"]; ok { + return v + } + typ, _ := sub["type"].(string) + switch typ { + case "string": + return "" + case "boolean": + return false + case "array": + return []interface{}{} + case "integer", "number": + return 0 + case "object": + return map[string]interface{}{} + default: + return nil + } +} + +// toIfaceSlice converts []string -> []interface{}. +func toIfaceSlice(ss []string) []interface{} { + out := make([]interface{}, len(ss)) + for i, s := range ss { + out[i] = s + } + return out +} + +// buildPrefillValues converts an OpenAPI schema (JSON string) into a []interface{} "values" list +// suitable for CustomFormsPrefill.spec.values. +// Rules: +// - For top-level primitive/array fields: emit an entry, using default if present, otherwise zero. +// - For top-level objects: recursively process nested objects and emit entries for all default values +// found at any nesting level. +func buildPrefillValues(openAPISchema string) ([]interface{}, error) { + var root map[string]interface{} + if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil { + return nil, fmt.Errorf("cannot parse openAPISchema: %w", err) + } + props, _ := root["properties"].(map[string]interface{}) + if props == nil { + return []interface{}{}, nil + } + + var values []interface{} + processSchemaProperties(props, []string{"spec"}, &values, true) + return values, nil +} + +// processSchemaProperties recursively processes OpenAPI schema properties and extracts default values +func processSchemaProperties(props map[string]interface{}, path []string, values *[]interface{}, topLevel bool) { + for pname, raw := range props { + sub, _ := raw.(map[string]interface{}) + if sub == nil { + continue + } + + typ, _ := sub["type"].(string) + currentPath := append(path, pname) + + switch typ { + case "object": + // Check if this object has a default value + if objDefault, ok := sub["default"].(map[string]interface{}); ok { + // Process the default object recursively + processDefaultObject(objDefault, currentPath, values) + } + + // Also process child properties for their individual defaults + if childProps, ok := sub["properties"].(map[string]interface{}); ok { + processSchemaProperties(childProps, currentPath, values, false) + } + default: + // For primitive types, use default if present, otherwise zero value + val := defaultOrZero(sub) + // Only emit zero-value entries when at top level + if val != nil || topLevel { + entry := map[string]interface{}{ + "path": toIfaceSlice(currentPath), + "value": val, + } + *values = append(*values, entry) + } + } + } +} + +// processDefaultObject recursively processes a default object and creates entries for all nested values +func processDefaultObject(obj map[string]interface{}, path []string, values *[]interface{}) { + for key, value := range obj { + currentPath := append(path, key) + + // If the value is a map, process it recursively + if nestedObj, ok := value.(map[string]interface{}); ok { + processDefaultObject(nestedObj, currentPath, values) + } else { + // For primitive values, create an entry + entry := map[string]interface{}{ + "path": toIfaceSlice(currentPath), + "value": value, + } + *values = append(*values, entry) + } + } +} + +// normalizeJSON makes maps/slices JSON-safe for k8s Unstructured: +// - converts all int/int32/... to float64 +// - leaves strings, bools, nil as-is +func normalizeJSON(v any) any { + switch t := v.(type) { + case map[string]any: + out := make(map[string]any, len(t)) + for k, val := range t { + out[k] = normalizeJSON(val) + } + return out + case []any: + out := make([]any, len(t)) + for i := range t { + out[i] = normalizeJSON(t[i]) + } + return out + case int: + return float64(t) + case int8: + return float64(t) + case int16: + return float64(t) + case int32: + return float64(t) + case int64: + return float64(t) + case uint, uint8, uint16, uint32, uint64: + return float64(reflect.ValueOf(t).Convert(reflect.TypeOf(uint64(0))).Uint()) + case float32: + return float64(t) + default: + return v + } +} + +var camelSplitter = regexp.MustCompile(`(?m)([A-Z]+[a-z0-9]*|[a-z0-9]+)`) + +func splitCamel(s string) []string { + return camelSplitter.FindAllString(s, -1) +} + +// --- helpers for schema inspection --- + +func isScalarType(n map[string]any) bool { + switch getString(n, "type") { + case "string", "integer", "number", "boolean": + return true + default: + return false + } +} + +func isIntOrString(n map[string]any) bool { + // Kubernetes extension: x-kubernetes-int-or-string: true + if v, ok := n["x-kubernetes-int-or-string"]; ok { + if b, ok := v.(bool); ok && b { + return true + } + } + // anyOf: integer|string + if anyOf, ok := n["anyOf"].([]any); ok { + hasInt := false + hasStr := false + for _, it := range anyOf { + if m, ok := it.(map[string]any); ok { + switch getString(m, "type") { + case "integer": + hasInt = true + case "string": + hasStr = true + } + } + } + return hasInt && hasStr + } + return false +} + +func hasEnum(n map[string]any) bool { + _, ok := n["enum"] + return ok +} + +func getString(n map[string]any, key string) string { + if v, ok := n[key]; ok { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +// shouldExcludeParamPath returns true if any part of the path contains +// backup / bootstrap / password (case-insensitive) +func shouldExcludeParamPath(parts []string) bool { + for _, p := range parts { + lp := strings.ToLower(p) + if strings.Contains(lp, "backup") || strings.Contains(lp, "bootstrap") || strings.Contains(lp, "password") || strings.Contains(lp, "cloudinit") { + return true + } + } + return false +} + +func humanizePath(parts []string) string { + // "systemDisk.image" -> "System Disk / Image" + return strings.Join(parts, " / ") +} + +// titleFromKindPlural returns a presentable plural label, e.g.: +// kind="VirtualMachine", plural="virtualmachines" => "VirtualMachines" +func titleFromKindPlural(kind, plural string) string { + return kind + "s" +} + +// The hidden lists below mirror the Helm templates you shared. +// Each entry is a path as nested string array, e.g. ["metadata","creationTimestamp"]. + +func hiddenMetadataSystem() []any { + return []any{ + []any{"metadata", "annotations"}, + []any{"metadata", "labels"}, + []any{"metadata", "namespace"}, + []any{"metadata", "creationTimestamp"}, + []any{"metadata", "deletionGracePeriodSeconds"}, + []any{"metadata", "deletionTimestamp"}, + []any{"metadata", "finalizers"}, + []any{"metadata", "generateName"}, + []any{"metadata", "generation"}, + []any{"metadata", "managedFields"}, + []any{"metadata", "ownerReferences"}, + []any{"metadata", "resourceVersion"}, + []any{"metadata", "selfLink"}, + []any{"metadata", "uid"}, + } +} + +func hiddenMetadataAPI() []any { + return []any{ + []any{"kind"}, + []any{"apiVersion"}, + []any{"appVersion"}, + } +} + +func hiddenStatus() []any { + return []any{ + []any{"status"}, + } +} + +// compareArbitrarySpecs compares two ArbitrarySpec objects by comparing their JSON content +func compareArbitrarySpecs(spec1, spec2 dashv1alpha1.ArbitrarySpec) bool { + // If both are empty, they're equal + if len(spec1.JSON.Raw) == 0 && len(spec2.JSON.Raw) == 0 { + return true + } + + // If one is empty and the other is not, they're different + if len(spec1.JSON.Raw) == 0 || len(spec2.JSON.Raw) == 0 { + return false + } + + // Parse and normalize both specs + norm1, err := normalizeJSONForComparison(spec1.JSON.Raw) + if err != nil { + return false + } + norm2, err := normalizeJSONForComparison(spec2.JSON.Raw) + if err != nil { + return false + } + + // Compare normalized JSON + equal := string(norm1) == string(norm2) + + return equal +} + +// normalizeJSONForComparison normalizes JSON by sorting arrays and objects +func normalizeJSONForComparison(data []byte) ([]byte, error) { + var obj interface{} + if err := json.Unmarshal(data, &obj); err != nil { + return nil, err + } + + // Recursively normalize the object + normalized := normalizeObject(obj) + + // Re-marshal to get normalized JSON + return json.Marshal(normalized) +} + +// normalizeObject recursively normalizes objects and arrays +func normalizeObject(obj interface{}) interface{} { + switch v := obj.(type) { + case map[string]interface{}: + // For maps, we don't need to sort keys as json.Marshal handles that + result := make(map[string]interface{}) + for k, val := range v { + result[k] = normalizeObject(val) + } + return result + case []interface{}: + // For arrays, we need to sort them if they contain objects with comparable fields + if len(v) == 0 { + return v + } + + // Check if this is an array of objects that can be sorted + if canSortArray(v) { + // Sort the array + sorted := make([]interface{}, len(v)) + copy(sorted, v) + sortArray(sorted) + return sorted + } + + // If we can't sort, just normalize each element + result := make([]interface{}, len(v)) + for i, val := range v { + result[i] = normalizeObject(val) + } + return result + default: + return v + } +} + +// canSortArray checks if an array can be sorted (contains objects with comparable fields) +func canSortArray(arr []interface{}) bool { + if len(arr) == 0 { + return false + } + + // Check if all elements are objects + for _, item := range arr { + if _, ok := item.(map[string]interface{}); !ok { + return false + } + } + + // Check if objects have comparable fields (like "path" for CustomFormsPrefill values) + firstObj, ok := arr[0].(map[string]interface{}) + if !ok { + return false + } + + // Look for "path" field which is used in CustomFormsPrefill values + if _, hasPath := firstObj["path"]; hasPath { + return true + } + + return false +} + +// sortArray sorts an array of objects by their "path" field +func sortArray(arr []interface{}) { + sort.Slice(arr, func(i, j int) bool { + objI, okI := arr[i].(map[string]interface{}) + objJ, okJ := arr[j].(map[string]interface{}) + + if !okI || !okJ { + return false + } + + pathI, hasPathI := objI["path"] + pathJ, hasPathJ := objJ["path"] + + if !hasPathI || !hasPathJ { + return false + } + + // Convert paths to strings for comparison + pathIStr := fmt.Sprintf("%v", pathI) + pathJStr := fmt.Sprintf("%v", pathJ) + + return pathIStr < pathJStr + }) +} diff --git a/internal/controller/dashboard/manager.go b/internal/controller/dashboard/manager.go index fbe03156..49e42848 100644 --- a/internal/controller/dashboard/manager.go +++ b/internal/controller/dashboard/manager.go @@ -2,25 +2,35 @@ package dashboard import ( "context" - "encoding/json" "fmt" - "reflect" - "regexp" "strings" dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +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" +) + // AddToScheme exposes dashboard types registration for controller setup. func AddToScheme(s *runtime.Scheme) error { return dashv1alpha1.AddToScheme(s) @@ -63,281 +73,376 @@ func NewManager(c client.Client, scheme *runtime.Scheme, opts ...Option) *Manage // - ensureSidebar (implemented) // - ensureTableUriMapping (implemented) func (m *Manager) EnsureForCRD(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { + // Early return if crd.Spec.Dashboard is nil to prevent oscillation + if crd.Spec.Dashboard == nil { + return reconcile.Result{}, nil + } + // MarketplacePanel if res, err := m.ensureMarketplacePanel(ctx, crd); err != nil || res.Requeue || res.RequeueAfter > 0 { return res, err } + // CustomFormsPrefill if res, err := m.ensureCustomFormsPrefill(ctx, crd); err != nil || res.Requeue || res.RequeueAfter > 0 { return res, err } + // CustomColumnsOverride if _, err := m.ensureCustomColumnsOverride(ctx, crd); err != nil { return reconcile.Result{}, err } + if err := m.ensureTableUriMapping(ctx, crd); err != nil { return reconcile.Result{}, err } + if err := m.ensureBreadcrumb(ctx, crd); err != nil { return reconcile.Result{}, err } + if err := m.ensureCustomFormsOverride(ctx, crd); err != nil { return reconcile.Result{}, err } + if err := m.ensureSidebar(ctx, crd); err != nil { return reconcile.Result{}, err } + if err := m.ensureFactory(ctx, crd); err != nil { return reconcile.Result{}, err } - return reconcile.Result{}, nil } -// ----------------------- MarketplacePanel ----------------------- +// InitializeStaticResources creates all static dashboard resources once during controller startup +func (m *Manager) InitializeStaticResources(ctx context.Context) error { + return m.ensureStaticResources(ctx) +} -func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { - logger := log.FromContext(ctx) +// addDashboardLabels adds standard dashboard management labels to a resource +func (m *Manager) addDashboardLabels(obj client.Object, crd *cozyv1alpha1.CozystackResourceDefinition, resourceType string) { + labels := obj.GetLabels() + if labels == nil { + labels = make(map[string]string) + } - mp := &dashv1alpha1.MarketplacePanel{} - mp.Name = crd.Name // cluster-scoped resource, name mirrors CRD name + labels[LabelManagedBy] = ManagedByValue + labels[LabelResourceType] = resourceType - // If dashboard is not set, delete the panel if it exists. - if crd.Spec.Dashboard == nil { - err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp) - if apierrors.IsNotFound(err) { - return reconcile.Result{}, nil - } + if crd != nil { + g, v, kind := pickGVK(crd) + plural := pickPlural(kind, crd) + + labels[LabelCRDName] = crd.Name + labels[LabelCRDGroup] = g + labels[LabelCRDVersion] = v + labels[LabelCRDKind] = kind + labels[LabelCRDPlural] = plural + } + + obj.SetLabels(labels) +} + +// getDashboardResourceSelector returns a label selector for dashboard-managed resources +func (m *Manager) getDashboardResourceSelector() client.MatchingLabels { + return client.MatchingLabels{ + LabelManagedBy: ManagedByValue, + } +} + +// getDynamicResourceSelector returns a label selector for dynamic dashboard resources +func (m *Manager) getDynamicResourceSelector() client.MatchingLabels { + return client.MatchingLabels{ + LabelManagedBy: ManagedByValue, + LabelResourceType: ResourceTypeDynamic, + } +} + +// getStaticResourceSelector returns a label selector for static dashboard resources +func (m *Manager) getStaticResourceSelector() client.MatchingLabels { + return client.MatchingLabels{ + LabelManagedBy: ManagedByValue, + LabelResourceType: ResourceTypeStatic, + } +} + +// CleanupOrphanedResources removes dashboard resources that are no longer needed +// This should be called after cache warming to ensure all current resources are known +func (m *Manager) CleanupOrphanedResources(ctx context.Context) error { + // Get all current CRDs to determine which resources should exist + var allCRDs []cozyv1alpha1.CozystackResourceDefinition + if m.crdListFn != nil { + s, err := m.crdListFn(ctx) if err != nil { - return reconcile.Result{}, err - } - if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) { - return reconcile.Result{}, err - } - logger.Info("Deleted MarketplacePanel because dashboard is not set", "name", mp.Name) - return reconcile.Result{}, nil - } - - // Skip resources with non-empty spec.dashboard.name - if strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { - err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp) - if apierrors.IsNotFound(err) { - return reconcile.Result{}, nil - } - if err != nil { - return reconcile.Result{}, err - } - if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) { - return reconcile.Result{}, err - } - logger.Info("Deleted MarketplacePanel because spec.dashboard.name is set", "name", mp.Name) - return reconcile.Result{}, nil - } - - // Build desired spec from CRD fields - d := crd.Spec.Dashboard - app := crd.Spec.Application - - displayName := d.Singular - if displayName == "" { - displayName = app.Kind - } - - tags := make([]any, len(d.Tags)) - for i, t := range d.Tags { - tags[i] = t - } - - specMap := map[string]any{ - "description": d.Description, - "name": displayName, - "type": "nonCrd", - "apiGroup": "apps.cozystack.io", - "apiVersion": "v1alpha1", - "typeName": app.Plural, // e.g., "buckets" - "disabled": false, - "hidden": false, - "tags": tags, - "icon": d.Icon, - } - - specBytes, err := json.Marshal(specMap) - if err != nil { - return reconcile.Result{}, err - } - - mutate := func() error { - if err := controllerutil.SetOwnerReference(crd, mp, m.scheme); err != nil { return err } - // Inline JSON payload (the ArbitrarySpec type inlines apiextv1.JSON) - mp.Spec = dashv1alpha1.ArbitrarySpec{ - JSON: apiextv1.JSON{Raw: specBytes}, + allCRDs = s + } else { + var crdList cozyv1alpha1.CozystackResourceDefinitionList + if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil { + return err } - return nil + allCRDs = crdList.Items } - op, err := controllerutil.CreateOrUpdate(ctx, m.client, mp, mutate) - if err != nil { - return reconcile.Result{}, err + // Build a set of expected resource names for each type + expectedResources := m.buildExpectedResourceSet(allCRDs) + + // Clean up each resource type + resourceTypes := []client.Object{ + &dashv1alpha1.CustomColumnsOverride{}, + &dashv1alpha1.CustomFormsOverride{}, + &dashv1alpha1.CustomFormsPrefill{}, + &dashv1alpha1.MarketplacePanel{}, + &dashv1alpha1.Sidebar{}, + &dashv1alpha1.TableUriMapping{}, + &dashv1alpha1.Breadcrumb{}, + &dashv1alpha1.Factory{}, } - switch op { - case controllerutil.OperationResultCreated: - logger.Info("Created MarketplacePanel", "name", mp.Name) - case controllerutil.OperationResultUpdated: - logger.Info("Updated MarketplacePanel", "name", mp.Name) + + for _, resourceType := range resourceTypes { + if err := m.cleanupResourceType(ctx, resourceType, expectedResources); err != nil { + return err + } } - return reconcile.Result{}, nil + + return nil } -// ----------------------- Helpers (OpenAPI → values) ----------------------- +// buildExpectedResourceSet creates a map of expected resource names by type +func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResourceDefinition) map[string]map[string]bool { + expected := make(map[string]map[string]bool) -// defaultOrZero returns the schema default if present; otherwise a reasonable zero value. -func defaultOrZero(sub map[string]interface{}) interface{} { - if v, ok := sub["default"]; ok { - return v - } - typ, _ := sub["type"].(string) - switch typ { - case "string": - return "" - case "boolean": - return false - case "array": - return []interface{}{} - case "integer", "number": - return 0 - case "object": - return map[string]interface{}{} - default: - return nil - } -} - -// toIfaceSlice converts []string -> []interface{}. -func toIfaceSlice(ss []string) []interface{} { - out := make([]interface{}, len(ss)) - for i, s := range ss { - out[i] = s - } - return out -} - -// buildPrefillValues converts an OpenAPI schema (JSON string) into a []interface{} "values" list -// suitable for CustomFormsPrefill.spec.values. -// Rules: -// - For top-level primitive/array fields: emit an entry, using default if present, otherwise zero. -// - For top-level objects: recursively process nested objects and emit entries for all default values -// found at any nesting level. -func buildPrefillValues(openAPISchema string) ([]interface{}, error) { - var root map[string]interface{} - if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil { - return nil, fmt.Errorf("cannot parse openAPISchema: %w", err) - } - props, _ := root["properties"].(map[string]interface{}) - if props == nil { - return []interface{}{}, nil + // Initialize maps for each resource type + resourceTypes := []string{ + "CustomColumnsOverride", + "CustomFormsOverride", + "CustomFormsPrefill", + "MarketplacePanel", + "Sidebar", + "TableUriMapping", + "Breadcrumb", + "Factory", } - var values []interface{} - processSchemaProperties(props, []string{"spec"}, &values) - return values, nil -} + for _, rt := range resourceTypes { + expected[rt] = make(map[string]bool) + } -// processSchemaProperties recursively processes OpenAPI schema properties and extracts default values -func processSchemaProperties(props map[string]interface{}, path []string, values *[]interface{}) { - for pname, raw := range props { - sub, _ := raw.(map[string]interface{}) - if sub == nil { + // Add static resources (these should always exist) + staticResources := CreateAllStaticResources() + for _, resource := range staticResources { + resourceType := resource.GetObjectKind().GroupVersionKind().Kind + if expected[resourceType] != nil { + expected[resourceType][resource.GetName()] = true + } + } + + // Add dynamic resources based on current CRDs + for _, crd := range crds { + if crd.Spec.Dashboard == nil { continue } - typ, _ := sub["type"].(string) - currentPath := append(path, pname) - - switch typ { - case "object": - // Check if this object has a default value - if objDefault, ok := sub["default"].(map[string]interface{}); ok { - // Process the default object recursively - processDefaultObject(objDefault, currentPath, values) - } - - // Also process child properties for their individual defaults - if childProps, ok := sub["properties"].(map[string]interface{}); ok { - processSchemaProperties(childProps, currentPath, values) - } - default: - // For primitive types, use default if present, otherwise zero value - val := defaultOrZero(sub) - if val != nil { - entry := map[string]interface{}{ - "path": toIfaceSlice(currentPath), - "value": val, - } - *values = append(*values, entry) - } + // Skip resources with non-empty spec.dashboard.name (tenant modules) + if strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { + continue } + + g, v, kind := pickGVK(&crd) + plural := pickPlural(kind, &crd) + + // CustomColumnsOverride + name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural) + expected["CustomColumnsOverride"][name] = true + + // CustomFormsOverride + name = fmt.Sprintf("%s.%s.%s", g, v, plural) + expected["CustomFormsOverride"][name] = true + + // CustomFormsPrefill + expected["CustomFormsPrefill"][name] = true + + // MarketplacePanel (name matches CRD name) + expected["MarketplacePanel"][crd.Name] = true + + // Sidebar resources (multiple per CRD) + lowerKind := strings.ToLower(kind) + detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) + expected["Sidebar"][detailsID] = true + + // Add other stock sidebars that are created for each CRD + stockSidebars := []string{ + "stock-instance-api-form", + "stock-instance-api-table", + "stock-instance-builtin-form", + "stock-instance-builtin-table", + "stock-project-factory-marketplace", + "stock-project-factory-workloadmonitor-details", + "stock-project-api-form", + "stock-project-api-table", + "stock-project-builtin-form", + "stock-project-builtin-table", + "stock-project-crd-form", + "stock-project-crd-table", + } + for _, sidebarID := range stockSidebars { + expected["Sidebar"][sidebarID] = true + } + + // TableUriMapping + name = fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural) + expected["TableUriMapping"][name] = true + + // Breadcrumb + detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) + expected["Breadcrumb"][detailID] = true + + // Factory + factoryName := fmt.Sprintf("%s-details", lowerKind) + expected["Factory"][factoryName] = true } + + return expected } -// processDefaultObject recursively processes a default object and creates entries for all nested values -func processDefaultObject(obj map[string]interface{}, path []string, values *[]interface{}) { - for key, value := range obj { - currentPath := append(path, key) - - // If the value is a map, process it recursively - if nestedObj, ok := value.(map[string]interface{}); ok { - processDefaultObject(nestedObj, currentPath, values) - } else { - // For primitive values, create an entry - entry := map[string]interface{}{ - "path": toIfaceSlice(currentPath), - "value": value, - } - *values = append(*values, entry) - } - } -} - -// normalizeJSON makes maps/slices JSON-safe for k8s Unstructured: -// - converts all int/int32/... to float64 -// - leaves strings, bools, nil as-is -func normalizeJSON(v any) any { - switch t := v.(type) { - case map[string]any: - out := make(map[string]any, len(t)) - for k, val := range t { - out[k] = normalizeJSON(val) - } - return out - case []any: - out := make([]any, len(t)) - for i := range t { - out[i] = normalizeJSON(t[i]) - } - return out - case int: - return float64(t) - case int8: - return float64(t) - case int16: - return float64(t) - case int32: - return float64(t) - case int64: - return float64(t) - case uint, uint8, uint16, uint32, uint64: - return float64(reflect.ValueOf(t).Convert(reflect.TypeOf(uint64(0))).Uint()) - case float32: - return float64(t) +// cleanupResourceType removes orphaned resources of a specific type +func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.Object, expectedResources map[string]map[string]bool) error { + var ( + list client.ObjectList + resourceKind string + ) + switch resourceType.(type) { + case *dashv1alpha1.CustomColumnsOverride: + list = &dashv1alpha1.CustomColumnsOverrideList{} + resourceKind = "CustomColumnsOverride" + case *dashv1alpha1.CustomFormsOverride: + list = &dashv1alpha1.CustomFormsOverrideList{} + resourceKind = "CustomFormsOverride" + case *dashv1alpha1.CustomFormsPrefill: + list = &dashv1alpha1.CustomFormsPrefillList{} + resourceKind = "CustomFormsPrefill" + case *dashv1alpha1.MarketplacePanel: + list = &dashv1alpha1.MarketplacePanelList{} + resourceKind = "MarketplacePanel" + case *dashv1alpha1.Sidebar: + list = &dashv1alpha1.SidebarList{} + resourceKind = "Sidebar" + case *dashv1alpha1.TableUriMapping: + list = &dashv1alpha1.TableUriMappingList{} + resourceKind = "TableUriMapping" + case *dashv1alpha1.Breadcrumb: + list = &dashv1alpha1.BreadcrumbList{} + resourceKind = "Breadcrumb" + case *dashv1alpha1.Factory: + list = &dashv1alpha1.FactoryList{} + resourceKind = "Factory" default: - return v + return nil // Unknown type } -} -var camelSplitter = regexp.MustCompile(`(?m)([A-Z]+[a-z0-9]*|[a-z0-9]+)`) + expected := expectedResources[resourceKind] + if expected == nil { + return nil // No expected resources for this type + } -func splitCamel(s string) []string { - return camelSplitter.FindAllString(s, -1) + // List with dashboard labels + if err := m.client.List(ctx, list, m.getDashboardResourceSelector()); err != nil { + return err + } + + // Delete resources that are not in the expected set + switch l := list.(type) { + case *dashv1alpha1.CustomColumnsOverrideList: + for _, item := range l.Items { + if !expected[item.Name] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + case *dashv1alpha1.CustomFormsOverrideList: + for _, item := range l.Items { + if !expected[item.Name] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + case *dashv1alpha1.CustomFormsPrefillList: + for _, item := range l.Items { + if !expected[item.Name] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + case *dashv1alpha1.MarketplacePanelList: + for _, item := range l.Items { + if !expected[item.Name] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + case *dashv1alpha1.SidebarList: + for _, item := range l.Items { + if !expected[item.Name] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + case *dashv1alpha1.TableUriMappingList: + for _, item := range l.Items { + if !expected[item.Name] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + case *dashv1alpha1.BreadcrumbList: + for _, item := range l.Items { + if !expected[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] { + if err := m.client.Delete(ctx, &item); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + // Resource already deleted, continue + } + } + } + } + + return nil } diff --git a/internal/controller/dashboard/marketplacepanel.go b/internal/controller/dashboard/marketplacepanel.go new file mode 100644 index 00000000..571d84e4 --- /dev/null +++ b/internal/controller/dashboard/marketplacepanel.go @@ -0,0 +1,112 @@ +package dashboard + +import ( + "context" + "encoding/json" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// ensureMarketplacePanel creates or updates a MarketplacePanel resource for the given CRD +func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { + logger := log.FromContext(ctx) + + mp := &dashv1alpha1.MarketplacePanel{} + mp.Name = crd.Name // cluster-scoped resource, name mirrors CRD name + + // If dashboard is not set, delete the panel if it exists. + if crd.Spec.Dashboard == nil { + err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp) + if apierrors.IsNotFound(err) { + return reconcile.Result{}, nil + } + if err != nil { + return reconcile.Result{}, err + } + if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) { + return reconcile.Result{}, err + } + logger.Info("Deleted MarketplacePanel because dashboard is not set", "name", mp.Name) + return reconcile.Result{}, nil + } + + // Skip resources with non-empty spec.dashboard.name + if strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { + err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp) + if apierrors.IsNotFound(err) { + return reconcile.Result{}, nil + } + if err != nil { + return reconcile.Result{}, err + } + if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) { + return reconcile.Result{}, err + } + logger.Info("Deleted MarketplacePanel because spec.dashboard.name is set", "name", mp.Name) + return reconcile.Result{}, nil + } + + // Build desired spec from CRD fields + d := crd.Spec.Dashboard + app := crd.Spec.Application + + displayName := d.Singular + if displayName == "" { + displayName = app.Kind + } + + tags := make([]any, len(d.Tags)) + for i, t := range d.Tags { + tags[i] = t + } + + specMap := map[string]any{ + "description": d.Description, + "name": displayName, + "type": "nonCrd", + "apiGroup": "apps.cozystack.io", + "apiVersion": "v1alpha1", + "typeName": app.Plural, // e.g., "buckets" + "disabled": false, + "hidden": false, + "tags": tags, + "icon": d.Icon, + } + + specBytes, err := json.Marshal(specMap) + if err != nil { + return reconcile.Result{}, err + } + + _, err = controllerutil.CreateOrUpdate(ctx, m.client, mp, func() error { + if err := controllerutil.SetOwnerReference(crd, mp, m.scheme); err != nil { + return err + } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(mp, crd, ResourceTypeDynamic) + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{ + JSON: apiextv1.JSON{Raw: specBytes}, + } + if !compareArbitrarySpecs(mp.Spec, newSpec) { + mp.Spec = newSpec + } + return nil + }) + if err != nil { + return reconcile.Result{}, err + } + + logger.Info("Applied MarketplacePanel", "name", mp.Name) + return reconcile.Result{}, nil +} diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index fe8e246f..59901924 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -11,7 +11,6 @@ import ( cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -217,22 +216,24 @@ func (m *Manager) upsertMultipleSidebars( } obj := &dashv1alpha1.Sidebar{} - obj.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "dashboard.cozystack.io", - Version: "v1alpha1", - Kind: "Sidebar", - }) 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 } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(obj, crd, ResourceTypeDynamic) b, err := json.Marshal(spec) if err != nil { return err } - obj.Spec = dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + + // Only update spec if it's different to avoid unnecessary updates + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } return nil }); err != nil { return err diff --git a/internal/controller/dashboard/static_helpers.go b/internal/controller/dashboard/static_helpers.go new file mode 100644 index 00000000..d7816164 --- /dev/null +++ b/internal/controller/dashboard/static_helpers.go @@ -0,0 +1,1087 @@ +package dashboard + +import ( + "encoding/json" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ---------------- Static resource helpers ---------------- + +// createBreadcrumb creates a Breadcrumb resource with the given name and breadcrumb items +func createBreadcrumb(name string, breadcrumbItems []map[string]any) *dashv1alpha1.Breadcrumb { + // Generate spec.id from name + specID := generateSpecID(name) + + data := map[string]any{ + "breadcrumbItems": breadcrumbItems, + "id": specID, + } + jsonData, _ := json.Marshal(data) + + return &dashv1alpha1.Breadcrumb{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "Breadcrumb", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "", + }, + Spec: dashv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + +// createCustomColumnsOverride creates a CustomColumnsOverride resource +func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *dashv1alpha1.CustomColumnsOverride { + // Generate metadata.name from spec.id + name := generateMetadataName(id) + + data := map[string]any{ + "additionalPrinterColumns": additionalPrinterColumns, + } + + // Add ID field for resources that should have it + shouldHaveID := true + if name == "stock-cluster-.v1.nodes" || + name == "stock-cluster-.v1.pods" || + name == "stock-namespace-.v1.pods" || + name == "factory-node-details-v1.pods" || + name == "factory-v1.pods" { + shouldHaveID = false + } + + // ID will be set later for specific resources, so don't set it here for pod/node resources + if shouldHaveID && !strings.Contains(name, "pods") && !strings.Contains(name, "nodes") { + data["id"] = id + } + + // Add additional fields for specific resources + if name == "factory-details-v1.services" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "ClusterIP", + "value": "-", + }, + map[string]any{ + "key": "LoadbalancerIP", + "value": "-", + }, + } + } + + if name == "cluster-v1.configmaps" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Namespace", + "value": "-", + }, + } + } + + if name == "stock-cluster-v1.configmaps" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Namespace", + "value": "-", + }, + } + } + + if name == "stock-namespace-v1.configmaps" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + } + + if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Namespace", + "value": "-", + }, + } + } + + if name == "factory-details-v1alpha1.cozystack.io.workloads" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Name", + "value": "-", + }, + map[string]any{ + "key": "Kind", + "value": "-", + }, + map[string]any{ + "key": "Type", + "value": "-", + }, + map[string]any{ + "key": "CPU", + "value": "-", + }, + map[string]any{ + "key": "Memory", + "value": "-", + }, + map[string]any{ + "key": "Operational", + "value": "-", + }, + } + } + + if name == "factory-ingress-details-rules" { + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Service", + "value": "-", + }, + map[string]any{ + "key": "Port", + "value": "-", + }, + map[string]any{ + "key": "Path", + "value": "-", + }, + } + } + + if name == "container-spec-containers-list" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Image", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Name", + "value": "-", + }, + map[string]any{ + "key": "Image", + "value": "-", + }, + map[string]any{ + "key": "Resources limits", + "value": "-", + }, + map[string]any{ + "key": "Resources requests", + "value": "-", + }, + map[string]any{ + "key": "Ports", + "value": "-", + }, + } + } + + if name == "container-spec-init-containers-list" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Image", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Name", + "value": "-", + }, + map[string]any{ + "key": "Image", + "value": "-", + }, + map[string]any{ + "key": "Resources limits", + "value": "-", + }, + map[string]any{ + "key": "Resources requests", + "value": "-", + }, + } + } + + if name == "container-status-containers-list" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(63), + }, + map[string]any{ + "key": "Image", + "value": float64(63), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "TerminatedReason", + "value": "-", + }, + map[string]any{ + "key": "WaitingReason", + "value": "-", + }, + } + } + + if name == "container-status-init-containers-list" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(63), + }, + map[string]any{ + "key": "Image", + "value": float64(63), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "TerminatedReason", + "value": "-", + }, + map[string]any{ + "key": "WaitingReason", + "value": "-", + }, + } + } + + if name == "factory-details-networking.k8s.io.v1.ingresses" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Hosts", + "value": "-", + }, + map[string]any{ + "key": "Address", + "value": "-", + }, + map[string]any{ + "key": "Port", + "value": "-", + }, + } + } + + if name == "stock-namespace-networking.k8s.io.v1.ingresses" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Hosts", + "value": "-", + }, + map[string]any{ + "key": "Address", + "value": "-", + }, + map[string]any{ + "key": "Port", + "value": "-", + }, + } + } + + if name == "factory-node-images" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "ImageID", + "value": float64(128), + }, + map[string]any{ + "key": "Size", + "value": float64(63), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Message", + "value": "-", + }, + } + } + + if name == "factory-status-conditions" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Message", + "value": float64(63), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Reason", + "value": "-", + }, + map[string]any{ + "key": "Message", + "value": "-", + }, + } + } + + if name == "stock-cluster-v1.secrets" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + } + + if name == "stock-namespace-networking.k8s.io.v1.ingresses" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Hosts", + "value": "-", + }, + map[string]any{ + "key": "Address", + "value": "-", + }, + map[string]any{ + "key": "Port", + "value": "-", + }, + } + } + + if name == "stock-namespace-v1.configmaps" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + } + + if name == "stock-namespace-v1.secrets" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "Namespace", + "value": "-", + }, + } + } + + if name == "stock-namespace-v1.services" { + data["additionalPrinterColumnsTrimLengths"] = []any{ + map[string]any{ + "key": "Name", + "value": float64(64), + }, + } + data["additionalPrinterColumnsUndefinedValues"] = []any{ + map[string]any{ + "key": "ClusterIP", + "value": "-", + }, + map[string]any{ + "key": "LoadbalancerIP", + "value": "-", + }, + } + } + + jsonData, _ := json.Marshal(data) + + return &dashv1alpha1.CustomColumnsOverride{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "CustomColumnsOverride", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "", + }, + Spec: dashv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + +// createFactory creates a Factory resource +func createFactory(name string, spec map[string]any) *dashv1alpha1.Factory { + jsonData, _ := json.Marshal(spec) + + return &dashv1alpha1.Factory{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "Factory", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "", + }, + Spec: dashv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + +// createTableUriMapping creates a TableUriMapping resource +func createTableUriMapping(name string, spec map[string]any) *dashv1alpha1.TableUriMapping { + jsonData, _ := json.Marshal(spec) + + return &dashv1alpha1.TableUriMapping{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "TableUriMapping", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "", + }, + Spec: dashv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + +// ---------------- Breadcrumb item helpers ---------------- + +// createBreadcrumbItem creates a breadcrumb item with key, label, and optional link +func createBreadcrumbItem(key, label string, link ...string) map[string]any { + item := map[string]any{ + "key": key, + "label": label, + } + if len(link) > 0 && link[0] != "" { + item["link"] = link[0] + } + return item +} + +// ---------------- Custom column helpers ---------------- + +// createCustomColumn creates a custom column with factory type and badge +func createCustomColumn(name, kind, plural, href string) map[string]any { + badge := createUnifiedBadgeFromKind("header-badge", kind, plural, BadgeSizeMedium) + link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href) + + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "children": []any{badge, link}, + "type": "antdFlex", + "data": map[string]any{ + "align": "center", + "gap": float64(6), + }, + }, + }, + }, + } +} + +// createCustomColumnWithBadge creates a custom column with a specific badge +func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string) map[string]any { + config := BadgeConfig{ + Text: badgeText, + Color: badgeColor, + Title: title, + Size: BadgeSizeMedium, + } + badge := createUnifiedBadge("header-badge", config) + link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href) + + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "children": []any{badge, link}, + "type": "antdFlex", + "data": map[string]any{ + "align": "center", + "gap": float64(6), + }, + }, + }, + }, + } +} + +// createCustomColumnWithSpecificColor creates a custom column with a specific color +func createCustomColumnWithSpecificColor(name, kind, title, color, href string) map[string]any { + config := BadgeConfig{ + Text: initialsFromKind(kind), + Color: color, + Title: title, + Size: BadgeSizeMedium, + } + badge := createUnifiedBadge("header-badge", config) + link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href) + + return map[string]any{ + "name": name, + "type": "factory", + "jsonPath": ".metadata.name", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "children": []any{badge, link}, + "type": "antdFlex", + "data": map[string]any{ + "align": "center", + "gap": float64(6), + "id": "header-row", + }, + }, + }, + }, + } +} + +// createStringColumn creates a simple string column +func createStringColumn(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "string", + "jsonPath": jsonPath, + } +} + +// createTimestampColumn creates a timestamp column with custom formatting +func createTimestampColumn(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "factory", + "jsonPath": jsonPath, + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "children": []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]['" + jsonPath + "']['-']}", + }, + }, + }, + "type": "antdFlex", + "data": map[string]any{ + "align": "center", + "gap": float64(6), + "id": "time-block", + }, + }, + }, + }, + } +} + +// ---------------- Factory helpers ---------------- + +// createFactoryHeader creates a header for factory resources +func createFactoryHeader(kind, plural string) map[string]any { + lowerKind := strings.ToLower(kind) + badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind, plural, BadgeSizeLarge) + nameText := parsedText(lowerKind+"-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{ + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": float64(20), + "lineHeight": "24px", + }) + + return antdFlex("header-row", float64(6), []any{ + badge, + nameText, + }) +} + +// getTabsId returns the appropriate tabs ID for a given key +func getTabsId(key string) string { + // Special cases + if key == "workloadmonitor-details" { + return "workloadmonitor-tabs" + } + if key == "secret-details" { + return "secret-tabs" + } + if key == "service-details" { + return "service-tabs" + } + return strings.ToLower(key) + "-tabs" +} + +// createFactorySpec creates a factory spec with header and tabs +func createFactorySpec(key string, sidebarTags []any, urlsToFetch []any, header map[string]any, tabs []any) map[string]any { + return map[string]any{ + "key": key, + "sidebarTags": sidebarTags, + "withScrollableMainContentCard": true, + "urlsToFetch": urlsToFetch, + "data": []any{ + header, + map[string]any{ + "type": "antdTabs", + "data": map[string]any{ + "id": getTabsId(key), + "defaultActiveKey": "details", + "items": tabs, + }, + }, + }, + } +} + +// createCustomColumnWithJsonPath creates a column with a custom badge and link using jsonPath +func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any { + // Determine link ID based on jsonPath + linkId := "name-link" + if jsonPath == ".metadata.namespace" { + linkId = "namespace-link" + } + + return map[string]any{ + "name": name, + "type": "factory", + "jsonPath": jsonPath, + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": 6, + }, + "children": []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": badgeText, + "title": badgeTitle, + "style": map[string]any{ + "backgroundColor": badgeColor, + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": linkId, + "text": "{reqsJsonPath[0]['" + jsonPath + "']['-']}", + "href": linkHref, + }, + }, + }, + }, + }, + }, + } +} + +// createCustomColumnWithoutJsonPath creates a column with a custom badge and link without jsonPath +func createCustomColumnWithoutJsonPath(name, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any { + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": 6, + }, + "children": []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": badgeText, + "title": badgeTitle, + "style": map[string]any{ + "backgroundColor": badgeColor, + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": "name-link", + "text": "{reqsJsonPath[0]['.spec.nodeName']['-']}", + "href": linkHref, + }, + }, + }, + }, + }, + }, + } +} + +// createStatusColumn creates a status column with StatusText component +func createStatusColumn(name, statusId string) map[string]any { + var statusData map[string]any + + if statusId == "pod-status" { + statusData = map[string]any{ + "id": statusId, + "criteriaError": "equals", + "criteriaSuccess": "notEquals", + "errorText": "Error", + "fallbackText": "Progressing", + "strategySuccess": "every", + "strategyError": "every", + "successText": "{reqsJsonPath[0]['.status.phase']['-']}", + "valueToCompareError": []any{ + "Failed", + "Unknown", + "Evicted", + "NodeLost", + "UnexpectedAdmissionError", + "SchedulerError", + "FailedScheduling", + "CrashLoopBackOff", + "ImagePullBackOff", + "ErrImagePull", + "ErrImageNeverPull", + "InvalidImageName", + "ImageInspectError", + "CreateContainerConfigError", + "CreateContainerError", + "RunContainerError", + "StartError", + "PostStartHookError", + "ContainerCannotRun", + "OOMKilled", + "Error", + "DeadlineExceeded", + "CreatePodSandboxError", + }, + "valueToCompareSuccess": []any{ + "Preempted", + "Shutdown", + "NodeShutdown", + "DisruptionTarget", + "Unschedulable", + "SchedulingGated", + "ContainersNotReady", + "ContainersNotInitialized", + "BackOff", + "PreStopHookError", + "KillError", + "ContainerStatusUnknown", + }, + "values": []any{ + "{reqsJsonPath[0]['.status.initContainerStatuses[*].state.waiting.reason']['-']}", + "{reqsJsonPath[0]['.status.initContainerStatuses[*].state.terminated.reason']['-']}", + "{reqsJsonPath[0]['.status.initContainerStatuses[*].lastState.terminated.reason']['-']}", + "{reqsJsonPath[0]['.status.containerStatuses[*].state.waiting.reason']['-']}", + "{reqsJsonPath[0]['.status.containerStatuses[*].state.terminated.reason']['-']}", + "{reqsJsonPath[0]['.status.containerStatuses[*].lastState.terminated.reason']['-']}", + "{reqsJsonPath[0]['.status.phase']['-']}", + "{reqsJsonPath[0]['.status.reason']['-']}", + "{reqsJsonPath[0]['.status.conditions[*].reason']['-']}", + }, + } + } else if statusId == "node-status" { + statusData = map[string]any{ + "id": statusId, + "criteriaError": "equals", + "criteriaSuccess": "equals", + "errorText": "Unavailable", + "fallbackText": "Progressing", + "strategySuccess": "every", + "strategyError": "every", + "successText": "Available", + "valueToCompareError": []any{ + "KernelDeadlock", + "ReadonlyFilesystem", + "NetworkUnavailable", + "MemoryPressure", + "DiskPressure", + "PIDPressure", + }, + "valueToCompareSuccess": "KubeletReady", + "values": []any{ + "{reqsJsonPath[0]['.status.conditions[?(@.status=='True')].reason']['-']}", + }, + } + } else { + // Default status data for other status types + statusData = map[string]any{ + "id": statusId, + } + } + + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "StatusText", + "data": statusData, + }, + }, + }, + } +} + +// createSimpleStatusColumn creates a simple status column with basic StatusText component +func createSimpleStatusColumn(name, statusId string) map[string]any { + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "StatusText", + "data": map[string]any{ + "id": statusId, + }, + }, + }, + }, + } +} + +// createSecretBase64Column creates a column with SecretBase64Plain component +func createSecretBase64Column(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "SecretBase64Plain", + "data": map[string]any{ + "id": "example-secretbase64", + "plainTextValue": "hello", + "base64Value": "{reqsJsonPath[0]['" + jsonPath + "']['-']}", + }, + }, + }, + }, + } +} + +// createArrayColumn creates a column with array type +func createArrayColumn(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "array", + "jsonPath": jsonPath, + } +} + +// createBoolColumn creates a column with boolean type +func createBoolColumn(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "bool", + "jsonPath": jsonPath, + } +} + +// createConverterBytesColumn creates a column with ConverterBytes component +func createConverterBytesColumn(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "factory", + "jsonPath": jsonPath, + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "ConverterBytes", + "data": map[string]any{ + "id": "example-converter-bytes", + "bytesValue": "{reqsJsonPath[0]['" + jsonPath + "']['-']}", + "format": true, + "precision": float64(1), + }, + }, + }, + }, + } +} + +// ---------------- Factory UI helper functions ---------------- + +// labelsEditor creates a Labels editor component +func labelsEditor(id, endpoint string, reqIndex int) map[string]any { + return map[string]any{ + "type": "Labels", + "data": map[string]any{ + "id": id, + "endpoint": endpoint, + "reqIndex": reqIndex, + "jsonPathToLabels": ".metadata.labels", + "pathToValue": "/metadata/labels", + "modalTitle": "Edit labels", + "modalDescriptionText": "", + "inputLabel": "", + "notificationSuccessMessage": "Updated successfully", + "notificationSuccessMessageDescription": "Labels have been updated", + "editModalWidth": 650, + "maxEditTagTextLength": 35, + "paddingContainerEnd": "24px", + "containerStyle": map[string]any{"marginTop": -30}, + "selectProps": map[string]any{"maxTagTextLength": 35}, + }, + } +} + +// annotationsEditor creates an Annotations editor component +func annotationsEditor(id, endpoint string, reqIndex int) map[string]any { + return map[string]any{ + "type": "Annotations", + "data": map[string]any{ + "id": id, + "endpoint": endpoint, + "reqIndex": reqIndex, + "jsonPathToObj": ".metadata.annotations", + "pathToValue": "/metadata/annotations", + "modalTitle": "Edit annotations", + "modalDescriptionText": "", + "inputLabel": "", + "notificationSuccessMessage": "Updated successfully", + "notificationSuccessMessageDescription": "Annotations have been updated", + "editModalWidth": "800px", + "errorText": "0 Annotations", + "text": "~counter~ Annotations", + "cols": []any{11, 11, 2}, + }, + } +} + +// yamlEditor creates a YamlEditorSingleton component +func yamlEditor(id, cluster string, isNameSpaced bool, typeName string, prefillValuesRequestIndex int) map[string]any { + return map[string]any{ + "type": "YamlEditorSingleton", + "data": map[string]any{ + "id": id, + "cluster": cluster, + "isNameSpaced": isNameSpaced, + "type": "builtin", + "typeName": typeName, + "prefillValuesRequestIndex": prefillValuesRequestIndex, + "substractHeight": float64(400), + }, + } +} diff --git a/internal/controller/dashboard/static_processor.go b/internal/controller/dashboard/static_processor.go new file mode 100644 index 00000000..ef2e0341 --- /dev/null +++ b/internal/controller/dashboard/static_processor.go @@ -0,0 +1,59 @@ +package dashboard + +import ( + "context" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// ensureStaticResources ensures all static dashboard resources are created +func (m *Manager) ensureStaticResources(ctx context.Context) error { + // Use refactored resources from static_refactored.go + // This replaces the old static variables with dynamic creation using helper functions + staticResources := CreateAllStaticResources() + + // Create or update each static resource + for _, resource := range staticResources { + if err := m.ensureStaticResource(ctx, resource); err != nil { + return err + } + } + + return nil +} + +// ensureStaticResource creates or updates a single static resource +func (m *Manager) ensureStaticResource(ctx context.Context, obj client.Object) error { + // Create a copy to avoid modifying the original + resource := obj.DeepCopyObject().(client.Object) + + // Add dashboard labels to static resources + m.addDashboardLabels(resource, nil, ResourceTypeStatic) + + _, err := controllerutil.CreateOrUpdate(ctx, m.client, resource, func() error { + // For static resources, we don't need to set owner references + // as they are meant to be persistent across CRD changes + // Copy Spec from the original object to the live object + switch o := obj.(type) { + case *dashv1alpha1.CustomColumnsOverride: + resource.(*dashv1alpha1.CustomColumnsOverride).Spec = o.Spec + case *dashv1alpha1.Breadcrumb: + resource.(*dashv1alpha1.Breadcrumb).Spec = o.Spec + case *dashv1alpha1.CustomFormsOverride: + resource.(*dashv1alpha1.CustomFormsOverride).Spec = o.Spec + case *dashv1alpha1.Factory: + resource.(*dashv1alpha1.Factory).Spec = o.Spec + case *dashv1alpha1.Navigation: + resource.(*dashv1alpha1.Navigation).Spec = o.Spec + case *dashv1alpha1.TableUriMapping: + resource.(*dashv1alpha1.TableUriMapping).Spec = o.Spec + } + // Ensure labels are always set + m.addDashboardLabels(resource, nil, ResourceTypeStatic) + return nil + }) + + return err +} diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go new file mode 100644 index 00000000..71df5d4b --- /dev/null +++ b/internal/controller/dashboard/static_refactored.go @@ -0,0 +1,1717 @@ +package dashboard + +import ( + "encoding/json" + "strings" + + dashboardv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ---------------- Complete refactored static resources ---------------- + +// CreateAllBreadcrumbs creates all breadcrumb resources using helper functions +func CreateAllBreadcrumbs() []*dashboardv1alpha1.Breadcrumb { + return []*dashboardv1alpha1.Breadcrumb{ + // Stock project factory configmap details + createBreadcrumb("stock-project-factory-configmap-details", []map[string]any{ + createBreadcrumbItem("configmaps", "v1/configmaps", "/openapi-ui/{clusterName}/{namespace}/builtin-table/configmaps"), + createBreadcrumbItem("configmap", "{6}"), + }), + + // Stock cluster factory namespace details + createBreadcrumb("stock-cluster-factory-namespace-details", []map[string]any{ + createBreadcrumbItem("namespaces", "v1/namespaces", "/openapi-ui/{clusterName}/builtin-table/namespaces"), + createBreadcrumbItem("namespace", "{5}"), + }), + + // Stock cluster factory node details + createBreadcrumb("stock-cluster-factory-node-details", []map[string]any{ + createBreadcrumbItem("node", "v1/nodes", "/openapi-ui/{clusterName}/builtin-table/nodes"), + createBreadcrumbItem("node", "{5}"), + }), + + // Stock project factory pod details + createBreadcrumb("stock-project-factory-pod-details", []map[string]any{ + createBreadcrumbItem("pods", "v1/pods", "/openapi-ui/{clusterName}/{namespace}/builtin-table/pods"), + createBreadcrumbItem("pod", "{6}"), + }), + + // Stock project factory secret details + createBreadcrumb("stock-project-factory-secret-details", []map[string]any{ + createBreadcrumbItem("secrets", "v1/secrets", "/openapi-ui/{clusterName}/{namespace}/builtin-table/secrets"), + createBreadcrumbItem("secret", "{6}"), + }), + + // Stock project factory service details + createBreadcrumb("stock-project-factory-service-details", []map[string]any{ + createBreadcrumbItem("services", "v1/services", "/openapi-ui/{clusterName}/{namespace}/builtin-table/services"), + createBreadcrumbItem("service", "{6}"), + }), + + // Stock cluster api table + createBreadcrumb("stock-cluster-api-table", []map[string]any{ + createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{typeName}"), + }), + + // Stock cluster api form + createBreadcrumb("stock-cluster-api-form", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), + }), + + // Stock cluster api form edit + createBreadcrumb("stock-cluster-api-form-edit", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), + }), + + // Stock cluster builtin table + createBreadcrumb("stock-cluster-builtin-table", []map[string]any{ + createBreadcrumbItem("api", "v1/{typeName}"), + }), + + // Stock cluster builtin form + createBreadcrumb("stock-cluster-builtin-form", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), + }), + + // Stock cluster builtin form edit + createBreadcrumb("stock-cluster-builtin-form-edit", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), + }), + + // Stock project api table + createBreadcrumb("stock-project-api-table", []map[string]any{ + createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{typeName}"), + }), + + // Stock project api form + createBreadcrumb("stock-project-api-form", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/{namespace}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), + }), + + // Stock project api form edit + createBreadcrumb("stock-project-api-form-edit", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/{namespace}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), + }), + + // Stock project builtin table + createBreadcrumb("stock-project-builtin-table", []map[string]any{ + createBreadcrumbItem("api", "v1/{typeName}"), + }), + + // Stock project builtin form + createBreadcrumb("stock-project-builtin-form", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/{namespace}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), + }), + + // Stock project builtin form edit + createBreadcrumb("stock-project-builtin-form-edit", []map[string]any{ + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/{namespace}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), + }), + } +} + +// CreateAllCustomColumnsOverrides creates all custom column override resources using helper functions +func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverride { + return []*dashboardv1alpha1.CustomColumnsOverride{ + // Factory details v1 services + createCustomColumnsOverride("factory-details-v1.services", []any{ + createCustomColumnWithSpecificColor("Name", "Service", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/service-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("ClusterIP", ".spec.clusterIP"), + createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace v1 services + createCustomColumnsOverride("stock-namespace-/v1/services", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/service-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("ClusterIP", ".spec.clusterIP"), + createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace core cozystack io v1alpha1 tenantmodules + createCustomColumnsOverride("stock-namespace-/core.cozystack.io/v1alpha1/tenantmodules", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "M", "module", getColorForType("module"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Version", ".spec.version"), + createStringColumn("Status", ".status.phase"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Factory service details port mapping + createCustomColumnsOverride("factory-service-details-port-mapping", []any{ + createStringColumn("Name", ".name"), + createStringColumn("Port", ".port"), + createStringColumn("Protocol", ".protocol"), + createStringColumn("Pod port or name", ".targetPort"), + }), + + // Factory details v1alpha1 cozystack io workloadmonitors + createCustomColumnsOverride("factory-details-v1alpha1.cozystack.io.workloadmonitors", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "W", "workloadmonitor", getColorForType("workloadmonitor"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("TYPE", ".spec.type"), + createStringColumn("VERSION", ".spec.version"), + createStringColumn("REPLICAS", ".spec.replicas"), + createStringColumn("MINREPLICAS", ".spec.minReplicas"), + createStringColumn("AVAILABLE", ".status.availableReplicas"), + createStringColumn("OBSERVED", ".status.observedReplicas"), + }), + + // Factory details v1alpha1 core cozystack io tenantsecretstables + createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", getColorForType("secret"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Key", ".data.key"), + createSecretBase64Column("Value", ".data.value"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Factory ingress details rules + createCustomColumnsOverride("factory-ingress-details-rules", []any{ + createStringColumn("Host", ".host"), + createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"), + createStringColumn("Port", ".http.paths[0].backend.service.port.number"), + createStringColumn("Path", ".http.paths[0].path"), + }), + + // Factory node images + createCustomColumnsOverride("factory-node-images", []any{ + createStringColumn("ImageID", ".names[0]"), + createConverterBytesColumn("Size", ".sizeBytes"), + }), + + // Factory pod details volume list + createCustomColumnsOverride("factory-pod-details-volume-list", []any{ + createStringColumn("Name", ".name"), + }), + + // Factory status conditions + createCustomColumnsOverride("factory-status-conditions", []any{ + createStringColumn("Type", ".type"), + createBoolColumn("Status", ".status"), + createTimestampColumn("Updated", ".lastTransitionTime"), + createStringColumn("Reason", ".reason"), + createStringColumn("Message", ".message"), + }), + + // Container status init containers list + createCustomColumnsOverride("container-status-init-containers-list", []any{ + createStringColumn("Name", ".name"), + createStringColumn("Image", ".imageID"), + createBoolColumn("Started", ".started"), + createBoolColumn("Ready", ".ready"), + createStringColumn("RestartCount", ".restartCount"), + createStringColumn("WaitingReason", ".state.waiting.reason"), + createStringColumn("TerminatedReason", ".state.terminated.reason"), + }), + + // Container status containers list + createCustomColumnsOverride("container-status-containers-list", []any{ + createStringColumn("Name", ".name"), + createStringColumn("Image", ".imageID"), + createBoolColumn("Started", ".started"), + createBoolColumn("Ready", ".ready"), + createStringColumn("RestartCount", ".restartCount"), + createStringColumn("WaitingReason", ".state.waiting.reason"), + createStringColumn("TerminatedReason", ".state.terminated.reason"), + }), + + // Container spec init containers list + createCustomColumnsOverride("container-spec-init-containers-list", []any{ + createStringColumn("Name", ".name"), + createStringColumn("Image", ".image"), + createArrayColumn("Resources requests", ".resources.requests"), + createArrayColumn("Resources limits", ".resources.limits"), + }), + + // Container spec containers list + createCustomColumnsOverride("container-spec-containers-list", []any{ + createStringColumn("Name", ".name"), + createStringColumn("Image", ".image"), + createArrayColumn("Resources requests", ".resources.requests"), + createArrayColumn("Resources limits", ".resources.limits"), + createArrayColumn("Ports", ".ports[*].containerPort"), + }), + + // Factory details networking k8s io v1 ingresses + createCustomColumnsOverride("factory-details-networking.k8s.io.v1.ingresses", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Hosts", ".spec.rules[*].host"), + createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"), + createStringColumn("Port", ".spec.defaultBackend.service.port.number"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace networking k8s io v1 ingresses + createCustomColumnsOverride("stock-namespace-/networking.k8s.io/v1/ingresses", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Hosts", ".spec.rules[*].host"), + createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"), + createStringColumn("Port", ".spec.defaultBackend.service.port.number"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock cluster v1 configmaps + createCustomColumnsOverride("stock-cluster-/v1/configmaps", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace v1 configmaps + createCustomColumnsOverride("stock-namespace-/v1/configmaps", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Cluster v1 configmaps + createCustomColumnsOverride("cluster-/v1/configmaps", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock cluster v1 nodes + createCustomColumnsOverride("stock-cluster-/v1/nodes", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createSimpleStatusColumn("Status", "node-status"), + }), + + // Factory node details v1 pods + createCustomColumnsOverride("factory-node-details-v1.pods", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"), + createStringColumn("Restart Policy", ".spec.restartPolicy"), + createStringColumn("Pod IP", ".status.podIP"), + createStringColumn("QOS", ".status.qosClass"), + createSimpleStatusColumn("Status", "pod-status"), + }), + + // Factory v1 pods + createCustomColumnsOverride("factory-v1.pods", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithoutJsonPath("Node", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"), + createStringColumn("Restart Policy", ".spec.restartPolicy"), + createStringColumn("Pod IP", ".status.podIP"), + createStringColumn("QOS", ".status.qosClass"), + createSimpleStatusColumn("Status", "pod-status"), + }), + + // Stock cluster v1 pods + createCustomColumnsOverride("stock-cluster-/v1/pods", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/namespace-details/{reqsJsonPath[0]['.metadata.namespace']['-']}"), + createCustomColumnWithJsonPath("Node", ".spec.nodeName", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"), + createStringColumn("Restart Policy", ".spec.restartPolicy"), + createStringColumn("Pod IP", ".status.podIP"), + createStringColumn("QOS", ".status.qosClass"), + createSimpleStatusColumn("Status", "pod-status"), + }), + + // Stock namespace v1 pods + createCustomColumnsOverride("stock-namespace-/v1/pods", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithoutJsonPath("Node", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"), + createStringColumn("Restart Policy", ".spec.restartPolicy"), + createStringColumn("Pod IP", ".status.podIP"), + createStringColumn("QOS", ".status.qosClass"), + createSimpleStatusColumn("Status", "pod-status"), + }), + + // Stock cluster v1 secrets + createCustomColumnsOverride("stock-cluster-/v1/secrets", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/namespace-details/{reqsJsonPath[0]['.metadata.namespace']['-']}"), + createStringColumn("Type", ".type"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace v1 secrets + createCustomColumnsOverride("stock-namespace-/v1/secrets", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Type", ".type"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Factory details v1alpha1 cozystack io workloads + createCustomColumnsOverride("factory-details-v1alpha1.cozystack.io.workloads", []any{ + createStringColumn("Name", ".metadata.name"), + createStringColumn("Kind", ".status.kind"), + createStringColumn("Type", ".status.type"), + createStringColumn("CPU", ".status.resources.cpu"), + createStringColumn("Memory", ".status.resources.memory"), + createStringColumn("Operational", ".status.operational"), + }), + } +} + +// CreateAllCustomFormsOverrides creates all custom forms override resources using helper functions +func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride { + return []*dashboardv1alpha1.CustomFormsOverride{ + // Default networking k8s io v1 ingresses + createCustomFormsOverride("default-/networking.k8s.io/v1/ingresses", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("spec.rules", "Rules", "array"), + }, + }), + + // Default storage k8s io v1 storageclasses + createCustomFormsOverride("default-/storage.k8s.io/v1/storageclasses", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("provisioner", "Provisioner", "text"), + createFormItem("reclaimPolicy", "Reclaim Policy", "select"), + }, + }), + + // Default v1 configmaps + createCustomFormsOverride("default-/v1/configmaps", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("data", "Data", "object"), + }, + }), + + // Default v1 namespaces + createCustomFormsOverride("default-/v1/namespaces", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.labels", "Labels", "object"), + }, + }), + + // Default v1 nodes + createCustomFormsOverride("default-/v1/nodes", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("spec.podCIDR", "Pod CIDR", "text"), + }, + }), + + // Default v1 persistentvolumeclaims + createCustomFormsOverride("default-/v1/persistentvolumeclaims", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("spec.accessModes", "Access Modes", "array"), + }, + }), + + // Default v1 persistentvolumes + createCustomFormsOverride("default-/v1/persistentvolumes", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("spec.capacity", "Capacity", "object"), + }, + }), + + // Default v1 pods + createCustomFormsOverride("default-/v1/pods", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("spec.containers", "Containers", "array"), + }, + }), + + // Default v1 secrets + createCustomFormsOverride("default-/v1/secrets", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("type", "Type", "text"), + }, + }), + + // Default v1 services + createCustomFormsOverride("default-/v1/services", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("spec.ports", "Ports", "array"), + }, + }), + } +} + +// CreateAllFactories creates all factory resources using helper functions +func CreateAllFactories() []*dashboardv1alpha1.Factory { + // Marketplace factory + marketplaceSpec := map[string]any{ + "key": "marketplace", + "sidebarTags": []any{ + "marketplace-sidebar", + }, + "urlsToFetch": []any{}, + "withScrollableMainContentCard": true, + "data": []any{ + contentCardWithTitle(31, "Marketplace", map[string]any{ + "flexGrow": 1, + }, []any{ + map[string]any{ + "data": map[string]any{ + "baseApiVersion": "v1alpha1", + "baseprefix": "openapi-ui", + "clusterNamePartOfUrl": "{2}", + "id": 311, + "mpResourceKind": "MarketplacePanel", + "mpResourceName": "marketplacepanels", + "namespacePartOfUrl": "{3}", + "baseApiGroup": "dashboard.cozystack.io", + }, + "type": "MarketplaceCard", + }, + }), + }, + } + + // Namespace details factory using unified approach + namespaceConfig := UnifiedResourceConfig{ + Name: "namespace-details", + ResourceType: "factory", + Kind: "Namespace", + Plural: "namespaces", + Title: "namespace", + Size: BadgeSizeLarge, + } + namespaceSpec := createUnifiedFactory(namespaceConfig, nil, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{5}"}) + + // Node details factory + nodeHeader := createNodeHeader() + // Create node spec with tabs containing items + nodeTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + map[string]any{ + "type": "ContentCard", + "data": map[string]any{ + "id": "details-card", + "style": map[string]any{ + "marginBottom": "24px", + }, + }, + "children": []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "details-title", + "text": "Node details", + "strong": true, + "style": map[string]any{ + "fontSize": float64(20), + "marginBottom": "12px", + }, + }, + }, + }, + }, + }, + }, + } + nodeSpec := map[string]any{ + "key": "node-details", + "sidebarTags": []any{"node-sidebar"}, + "withScrollableMainContentCard": true, + "urlsToFetch": []any{"/api/clusters/{2}/k8s/api/v1/nodes/{5}"}, + "data": []any{ + nodeHeader, + map[string]any{ + "type": "antdTabs", + "data": map[string]any{ + "id": "tabs-root", + "defaultActiveKey": "details", + "items": nodeTabs, + }, + }, + }, + } + + // Pod details factory + podHeader := createPodHeader() + // Create pod spec with empty tabs (items: nil) + podSpec := map[string]any{ + "key": "pod-details", + "sidebarTags": []any{"pods-sidebar"}, + "withScrollableMainContentCard": true, + "urlsToFetch": []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}"}, + "data": []any{ + podHeader, + map[string]any{ + "type": "antdTabs", + "data": map[string]any{ + "id": "tabs-root", + "defaultActiveKey": "details", + "items": nil, + }, + }, + }, + } + + // Secret details factory + secretHeader := map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": float64(6), + "style": map[string]any{ + "marginBottom": "24px", + }, + }, + "children": []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "badge-secret", + "text": "S", + "title": "secret", + "style": map[string]any{ + "backgroundColor": "#c46100", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "id": "header-secret-name", + "text": "{reqsJsonPath[0]['.metadata.name']['-']}", + "style": map[string]any{ + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "lineHeight": "24px", + }, + }, + }, + }, + } + secretTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": "24px", + }, []any{ + antdText("details-title", true, "Secret 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{ + antdFlexVertical("meta-name-block", 4, []any{ + antdText("meta-name-label", true, "Name", nil), + parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil), + }), + antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-name-label", true, "Namespace", nil), + antdFlex("header-row", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + 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", + }, + }, + }), + }), + antdFlexVertical("meta-labels-block", 8, []any{ + antdText("labels-title", true, "Labels", map[string]any{ + "fontSize": 14, + }), + map[string]any{ + "type": "Labels", + "data": map[string]any{ + "id": "labels-editor", + "endpoint": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}", + "jsonPathToLabels": ".metadata.labels", + "pathToValue": "/metadata/labels", + "reqIndex": 0, + "modalTitle": "Edit labels", + "modalDescriptionText": "", + "inputLabel": "", + "notificationSuccessMessage": "Updated successfully", + "notificationSuccessMessageDescription": "Labels have been updated", + "editModalWidth": 650, + "maxEditTagTextLength": 35, + "paddingContainerEnd": "24px", + "containerStyle": map[string]any{ + "marginTop": "-30px", + }, + "selectProps": map[string]any{ + "maxTagTextLength": 35, + }, + }, + }, + }), + antdFlexVertical("ds-annotations", 4, []any{ + antdText("annotations", true, "Annotations", nil), + map[string]any{ + "type": "Annotations", + "data": map[string]any{ + "id": "annotations", + "endpoint": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}", + "jsonPathToObj": ".metadata.annotations", + "pathToValue": "/metadata/annotations", + "reqIndex": 0, + "modalTitle": "Edit annotations", + "modalDescriptionText": "", + "inputLabel": "", + "notificationSuccessMessage": "Updated successfully", + "notificationSuccessMessageDescription": "Annotations have been updated", + "editModalWidth": "800px", + "errorText": "0 Annotations", + "text": "~counter~ Annotations", + "cols": []any{11, 11, 2}, + }, + }, + }), + antdFlexVertical("meta-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']['-']}", + }, + }, + }), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdFlexVertical("secret-type-block", 4, []any{ + antdText("secret-type-label", true, "Type", nil), + parsedText("secret-type-value", "{reqsJsonPath[0]['.type']['-']}", nil), + }), + antdFlexVertical("secret-sa-block", 4, []any{ + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "id": "serviceaccount-title", + "text": "ServiceAccount", + "strong": true, + "style": map[string]any{ + "fontWeight": "bold", + }, + }, + }, + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": "serviceaccount-link", + "text": "{reqsJsonPath[0]['.metadata.annotations[\"kubernetes.io/service-account.name\"]']['-']}", + "href": "/openapi-ui/{2}/{3}/factory/serviceaccount-details/{reqsJsonPath[0]['.metadata.annotations[\"kubernetes.io/service-account.name\"]']['-']}", + }, + }, + }), + }), + }), + }), + }), + }, + }, + map[string]any{ + "key": "yaml", + "label": "YAML", + "children": []any{ + map[string]any{ + "type": "YamlEditorSingleton", + "data": map[string]any{ + "id": "yaml-editor", + "cluster": "{2}", + "isNameSpaced": true, + "prefillValuesRequestIndex": 0, + "substractHeight": float64(400), + "type": "builtin", + "typeName": "secrets", + }, + }, + }, + }, + } + secretSpec := createFactorySpec("secret-details", []any{"secret-sidebar"}, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}"}, secretHeader, secretTabs) + + // Service details factory + serviceHeader := map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": float64(6), + "style": map[string]any{ + "marginBottom": "24px", + }, + }, + "children": []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "badge-service", + "text": "S", + "title": "services", + "style": map[string]any{ + "backgroundColor": "#6ca100", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "id": "service-name", + "text": "{reqsJsonPath[0]['.metadata.name']['-']}", + "style": map[string]any{ + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "lineHeight": "24px", + }, + }, + }, + }, + } + serviceTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": "24px", + }, []any{ + antdText("details-title", true, "Service 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{ + antdFlexVertical("meta-name-block", 4, []any{ + antdText("meta-name-label", true, "Name", nil), + parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil), + }), + antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-name-label", true, "Namespace", nil), + antdFlex("header-row", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + 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", + }, + }, + }), + }), + antdFlexVertical("meta-labels-block", 8, []any{ + antdText("labels-title", true, "Labels", map[string]any{ + "fontSize": 14, + }), + map[string]any{ + "type": "Labels", + "data": map[string]any{ + "id": "labels-editor", + "endpoint": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}", + "jsonPathToLabels": ".metadata.labels", + "pathToValue": "/metadata/labels", + "reqIndex": 0, + "modalTitle": "Edit labels", + "modalDescriptionText": "", + "inputLabel": "", + "notificationSuccessMessage": "Updated successfully", + "notificationSuccessMessageDescription": "Labels have been updated", + "editModalWidth": 650, + "maxEditTagTextLength": 35, + "paddingContainerEnd": "24px", + "containerStyle": map[string]any{ + "marginTop": "-30px", + }, + "selectProps": map[string]any{ + "maxTagTextLength": 35, + }, + }, + }, + }), + antdFlexVertical("meta-pod-selector-block", 4, []any{ + antdText("pod-selector", true, "Pod selector", map[string]any{ + "fontSize": 14, + }), + map[string]any{ + "type": "LabelsToSearchParams", + "data": map[string]any{ + "id": "pod-to-search-params", + "jsonPathToLabels": ".spec.selector", + "linkPrefix": "/openapi-ui/{2}/search", + "reqIndex": 0, + "errorText": "-", + }, + }, + }), + antdFlexVertical("ds-annotations", 4, []any{ + antdText("annotations", true, "Annotations", nil), + map[string]any{ + "type": "Annotations", + "data": map[string]any{ + "id": "annotations", + "endpoint": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}", + "jsonPathToObj": ".metadata.annotations", + "pathToValue": "/metadata/annotations", + "reqIndex": 0, + "modalTitle": "Edit annotations", + "modalDescriptionText": "", + "inputLabel": "", + "notificationSuccessMessage": "Updated successfully", + "notificationSuccessMessageDescription": "Annotations have been updated", + "editModalWidth": "800px", + "errorText": "0 Annotations", + "text": "~counter~ Annotations", + "cols": []any{11, 11, 2}, + }, + }, + }), + antdFlexVertical("meta-session-affinity-block", 4, []any{ + antdText("meta-session-affinity-label", true, "Session affinity", nil), + parsedText("meta-session-affinity-value", "{reqsJsonPath[0]['.spec.sessionAffinity']['Not configured']}", nil), + }), + antdFlexVertical("meta-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']['-']}", + }, + }, + }), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdText("routing-title", true, "Service routing", map[string]any{ + "fontSize": 20, + "marginBottom": "12px", + }), + spacer("routing-spacer", 16), + antdFlexVertical("service-hostname-block", 4, []any{ + antdText("service-hostname-label", true, "Hostname", nil), + parsedText("service-hostname-value", "{reqsJsonPath[0]['.metadata.name']['-']}.{reqsJsonPath[0]['.metadata.namespace']['-']}.svc.cluster.local", nil), + }), + antdFlexVertical("service-ip-block", 12, []any{ + antdFlexVertical("clusterip-block", 4, []any{ + antdText("clusterip-label", true, "ClusterIP address", nil), + parsedText("clusterip-value", "{reqsJsonPath[0]['.spec.clusterIP']['-']}", nil), + }), + antdFlexVertical("loadbalancerip-block", 4, []any{ + antdText("loadbalancerip-label", true, "LoadBalancerIP address", nil), + parsedText("loadbalancerip-value", "{reqsJsonPath[0]['.status.loadBalancer.ingress[0].ip']['Not Configured']}", nil), + }), + }), + antdFlexVertical("service-port-mapping-block", 4, []any{ + antdText("service-port-mapping-label", true, "Service port mapping", nil), + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "service-port-mapping-table", + "baseprefix": "/openapi-ui", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-service-details-port-mapping", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}", + "pathToItems": ".spec.ports", + "withoutControls": true, + }, + }, + }), + map[string]any{ + "type": "VisibilityContainer", + "data": map[string]any{ + "id": "service-pod-serving-vis", + "value": "{reqsJsonPath[0]['.spec.selector']['-']}", + "style": map[string]any{ + "margin": 0, + "padding": 0, + }, + }, + "children": []any{ + antdFlexVertical("service-pod-serving-block", 4, []any{ + antdText("service-pod-serving-label", true, "Pod serving", nil), + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "service-pod-serving-table", + "baseprefix": "/openapi-ui", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-service-details-endpointslice", + "fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices", + "labelsSelector": map[string]any{ + "kubernetes.io/service-name": "{reqsJsonPath[0]['.metadata.name']['-']}", + }, + "pathToItems": ".items[*].endpoints", + "withoutControls": true, + }, + }, + }), + }, + }, + }), + }), + }), + }), + }, + }, + map[string]any{ + "key": "yaml", + "label": "YAML", + "children": []any{ + map[string]any{ + "type": "YamlEditorSingleton", + "data": map[string]any{ + "id": "yaml-editor", + "cluster": "{2}", + "isNameSpaced": true, + "prefillValuesRequestIndex": 0, + "substractHeight": float64(400), + "type": "builtin", + "typeName": "services", + }, + }, + }, + }, + map[string]any{ + "key": "pods", + "label": "Pods", + "children": []any{ + map[string]any{ + "type": "VisibilityContainer", + "data": map[string]any{ + "id": "service-pod-serving-vis", + "value": "{reqsJsonPath[0]['.spec.selector']['-']}", + "style": map[string]any{ + "margin": 0, + "padding": 0, + }, + }, + "children": []any{ + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "pods-table", + "baseprefix": "/openapi-ui", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-node-details-/v1/pods", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods", + "labelsSelectorFull": map[string]any{ + "pathToLabels": ".spec.selector", + "reqIndex": 0, + }, + "pathToItems": ".items", + "withoutControls": false, + }, + }, + }, + }, + }, + }, + } + serviceSpec := createFactorySpec("service-details", []any{"service-sidebar"}, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}"}, serviceHeader, serviceTabs) + + // Workloadmonitor details factory + workloadmonitorHeader := createWorkloadmonitorHeader() + workloadmonitorTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": float64(24), + }, []any{ + antdRow("details-grid", []any{48, 12}, []any{ + antdCol("col-left", 12, []any{ + antdFlexVertical("col-left-stack", 24, []any{ + antdFlexVertical("meta-name-block", 4, []any{ + antdText("meta-name-label", true, "Name", nil), + parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil), + }), + antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-namespace-label", true, "Namespace", nil), + antdFlex("namespace-row", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "ns-badge", + "text": "NS", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": 15, + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + 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", + }, + }, + }), + }), + antdFlexVertical("meta-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": "🌐", + }, + }, + parsedTextWithFormatter("time-value", "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", "timestamp"), + }), + }), + antdFlexVertical("meta-kind-block", 4, []any{ + antdText("kind-label", true, "Kind", nil), + parsedText("kind-value", "{reqsJsonPath[0]['.spec.kind']['-']}", nil), + }), + antdFlexVertical("meta-type-block", 4, []any{ + antdText("type-label", true, "Type", nil), + parsedText("type-value", "{reqsJsonPath[0]['.spec.type']['-']}", nil), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdText("params-title", true, "Parameters", map[string]any{ + "fontSize": float64(20), + "marginBottom": float64(12), + }), + antdFlexVertical("params-list", 24, []any{ + antdFlexVertical("param-version", 4, []any{ + antdText("param-version-label", true, "Version", nil), + parsedText("param-version-value", "{reqsJsonPath[0]['.spec.version']['-']}", nil), + }), + antdFlexVertical("param-replicas", 4, []any{ + antdText("param-replicas-label", true, "Replicas", nil), + parsedText("param-replicas-value", "{reqsJsonPath[0]['.spec.replicas']['-']}", nil), + }), + antdFlexVertical("param-minreplicas", 4, []any{ + antdText("param-minreplicas-label", true, "MinReplicas", nil), + parsedText("param-minreplicas-value", "{reqsJsonPath[0]['.spec.minReplicas']['-']}", nil), + }), + antdFlexVertical("param-available", 4, []any{ + antdText("param-available-label", true, "AvailableReplicas", nil), + parsedText("param-available-value", "{reqsJsonPath[0]['.status.availableReplicas']['-']}", nil), + }), + antdFlexVertical("param-observed", 4, []any{ + antdText("param-observed-label", true, "ObservedReplicas", nil), + parsedText("param-observed-value", "{reqsJsonPath[0]['.status.observedReplicas']['-']}", nil), + }), + antdFlexVertical("param-operational", 4, []any{ + antdText("param-operational-label", true, "Operational", nil), + parsedText("param-operational-value", "{reqsJsonPath[0]['.status.operational']['-']}", nil), + }), + }), + }), + }), + }), + }), + }, + }, + map[string]any{ + "key": "workloads", + "label": "Workloads", + "children": []any{ + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "workloads-table", + "baseprefix": "/openapi-ui", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-details-v1alpha1.cozystack.io.workloads", + "fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads", + "labelsSelector": map[string]any{ + "workloads.cozystack.io/monitor": "{reqs[0]['metadata','name']}", + }, + "pathToItems": []any{"items"}, + }, + }, + }, + }, + map[string]any{ + "key": "yaml", + "label": "YAML", + "children": []any{ + map[string]any{ + "type": "YamlEditorSingleton", + "data": map[string]any{ + "id": "yaml-editor", + "cluster": "{2}", + "isNameSpaced": true, + "prefillValuesRequestIndex": 0, + "substractHeight": float64(400), + "type": "builtin", + "typeName": "workloadmonitors", + }, + }, + }, + }, + } + workloadmonitorSpec := createFactorySpec("workloadmonitor-details", []any{"workloadmonitor-sidebar"}, []any{"/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors/{6}"}, workloadmonitorHeader, workloadmonitorTabs) + + return []*dashboardv1alpha1.Factory{ + createFactory("marketplace", marketplaceSpec), + createFactory("namespace-details", namespaceSpec), + createFactory("node-details", nodeSpec), + createFactory("pod-details", podSpec), + createFactory("secret-details", secretSpec), + createFactory("service-details", serviceSpec), + createFactory("workloadmonitor-details", workloadmonitorSpec), + } +} + +// CreateAllNavigations creates all navigation resources using helper functions +func CreateAllNavigations() []*dashboardv1alpha1.Navigation { + return []*dashboardv1alpha1.Navigation{ + createNavigation("navigation", map[string]any{ + "namespaces": map[string]any{ + "change": "/openapi-ui/{selectedCluster}/{value}/factory/marketplace", + "clear": "/openapi-ui/{selectedCluster}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces", + }, + }), + } +} + +// CreateAllTableUriMappings creates all table URI mapping resources using helper functions +func CreateAllTableUriMappings() []*dashboardv1alpha1.TableUriMapping { + return []*dashboardv1alpha1.TableUriMapping{ + // Stock namespace default apps cozystack io v1alpha1 virtualmachines yaml + createTableUriMapping("virtualmachine-details", map[string]any{ + "tableUri": "/openapi-ui/{clusterName}/{namespace}/builtin-table/virtualmachines", + "resourceUri": "/openapi-ui/{clusterName}/{namespace}/builtin-table/virtualmachines", + }), + + // Namespaces + createTableUriMapping("namespaces", map[string]any{ + "id": "stock-cluster-/core.cozystack.io/v1alpha1/tenantnamespaces", + "keysToParse": []any{"metadata", "name"}, + "pathToNavigate": "/openapi-ui/{clusterName}/~recordValue~/factory/marketplace", + }), + + // Stock cluster networking k8s io v1 ingress details + createTableUriMapping("stock-cluster-networking.k8s.io.v1.ingress-details", map[string]any{ + "keysToParse": ".metadata.name", + "keysToParseSecond": ".metadata.namespace", + "id": "stock-cluster-/networking.k8s.io/v1/ingresses", + "pathToNavigate": "/openapi-ui/{clusterName}/~recordValueSecond~/factory/ingress-details/~recordValue~", + }), + + // Stock namespace networking k8s io v1 ingress details + createTableUriMapping("stock-namespace-networking.k8s.io.v1.ingress-details", map[string]any{ + "keysToParse": ".metadata.name", + "keysToParseSecond": ".metadata.namespace", + "id": "stock-namespace-/networking.k8s.io/v1/ingresses", + "pathToNavigate": "/openapi-ui/{clusterName}/~recordValueSecond~/factory/ingress-details/~recordValue~", + }), + } +} + +// ---------------- Additional helper functions for missing resource types ---------------- + +// createCustomFormsOverride creates a CustomFormsOverride resource +func createCustomFormsOverride(customizationId string, spec map[string]any) *dashboardv1alpha1.CustomFormsOverride { + // Generate name from customizationId + name := customizationId + if strings.Contains(customizationId, "default-/") { + // For default-/ resources, replace "default-/" with "default-" and slashes with dots + name = strings.ReplaceAll(customizationId, "default-/", "default-") + name = strings.ReplaceAll(name, "/", ".") + } + + // Create hidden fields list + hidden := []any{ + []any{"metadata", "creationTimestamp"}, + } + + // Add namespace to hidden for specific resources in the correct order + if strings.Contains(name, "namespaces") || strings.Contains(name, "nodes") { + hidden = append(hidden, []any{"metadata", "namespace"}) + } + + // Add remaining hidden fields + hidden = append(hidden, []any{ + []any{"metadata", "deletionGracePeriodSeconds"}, + []any{"metadata", "deletionTimestamp"}, + []any{"metadata", "finalizers"}, + []any{"metadata", "generateName"}, + []any{"metadata", "generation"}, + []any{"metadata", "managedFields"}, + []any{"metadata", "ownerReferences"}, + []any{"metadata", "resourceVersion"}, + []any{"metadata", "selfLink"}, + []any{"metadata", "uid"}, + []any{"kind"}, + []any{"apiVersion"}, + []any{"status"}, + }...) + + // Create new spec with all required fields including formItems from the original spec + newSpec := map[string]any{ + "customizationId": customizationId, + "hidden": hidden, + "schema": map[string]any{}, + "strategy": "merge", + } + + // Merge caller-provided fields (like formItems) into newSpec + for key, value := range spec { + if key != "customizationId" && key != "hidden" && key != "schema" && key != "strategy" { + newSpec[key] = value + } + } + + jsonData, _ := json.Marshal(newSpec) + + return &dashboardv1alpha1.CustomFormsOverride{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "CustomFormsOverride", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "", + }, + Spec: dashboardv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + +// createNavigation creates a Navigation resource +func createNavigation(name string, spec map[string]any) *dashboardv1alpha1.Navigation { + jsonData, _ := json.Marshal(spec) + + return &dashboardv1alpha1.Navigation{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "Navigation", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "", + }, + Spec: dashboardv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + +// createFormItem creates a form item for CustomFormsOverride +func createFormItem(path, label, fieldType string) map[string]any { + return map[string]any{ + "path": path, + "label": label, + "type": fieldType, + } +} + +// ---------------- Workloadmonitor specific functions ---------------- + +// createNamespaceHeader creates a header specifically for namespace with correct colors and text +func createNamespaceHeader() map[string]any { + badge := map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "Namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "fontWeight": float64(400), + "lineHeight": "24px", + "minWidth": float64(24), + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + } + + nameText := parsedText("header-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{ + "fontSize": "20px", + "lineHeight": "24px", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + }) + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": float64(6), + "style": map[string]any{ + "marginBottom": "24px", + }, + }, + "children": []any{ + badge, + nameText, + }, + } +} + +// createNodeHeader creates a header specifically for node with correct colors and text +func createNodeHeader() map[string]any { + badge := map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "N", + "title": "nodes", + "style": map[string]any{ + "backgroundColor": "#8476d1", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "fontWeight": float64(400), + "lineHeight": "24px", + "minWidth": float64(24), + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + } + + nameText := parsedText("header-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{ + "fontSize": "20px", + "lineHeight": "24px", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + }) + + statusBlock := map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "status-header-block", + "vertical": true, + "gap": float64(4), + }, + "children": []any{ + map[string]any{ + "type": "StatusText", + "data": map[string]any{ + "id": "node-status", + "values": []any{ + "{reqsJsonPath[0]['.status.conditions[?(@.status=='True')].reason']['-']}", + }, + "criteriaSuccess": "equals", + "strategySuccess": "every", + "valueToCompareSuccess": "KubeletReady", + "criteriaError": "equals", + "strategyError": "every", + "valueToCompareError": []any{ + "KernelDeadlock", + "ReadonlyFilesystem", + "NetworkUnavailable", + "MemoryPressure", + "DiskPressure", + "PIDPressure", + }, + "successText": "Available", + "errorText": "Unavailable", + "fallbackText": "Progressing", + }, + }, + }, + } + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": float64(6), + "style": map[string]any{ + "marginBottom": "24px", + }, + }, + "children": []any{ + badge, + nameText, + statusBlock, + }, + } +} + +// createPodHeader creates a header specifically for pod with correct colors and text +func createPodHeader() map[string]any { + badge := map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "P", + "title": "Pods", + "style": map[string]any{ + "backgroundColor": "#009596", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "20px", + "fontWeight": float64(400), + "lineHeight": "24px", + "minWidth": float64(24), + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + } + + nameText := parsedText("header-pod-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{ + "fontSize": "20px", + "lineHeight": "24px", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + }) + + statusBlock := map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "status-header-block", + "vertical": true, + "gap": float64(4), + }, + "children": []any{ + map[string]any{ + "type": "StatusText", + "data": map[string]any{ + "id": "pod-status", + }, + }, + }, + } + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": float64(6), + "style": map[string]any{ + "marginBottom": "24px", + }, + }, + "children": []any{ + badge, + nameText, + statusBlock, + }, + } +} + +// createWorkloadmonitorHeader creates a header specifically for workloadmonitor with correct colors and text +func createWorkloadmonitorHeader() map[string]any { + badge := map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "badge-workloadmonitor", + "text": "W", + "title": "workloadmonitors", + "style": map[string]any{ + "backgroundColor": "#c46100", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": float64(20), + "fontWeight": float64(400), + "lineHeight": "24px", + "minWidth": float64(24), + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + } + + nameText := parsedText("workloadmonitor-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{ + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": float64(20), + "lineHeight": "24px", + }) + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": float64(6), + "style": map[string]any{ + "marginBottom": float64(24), + }, + }, + "children": []any{ + badge, + nameText, + }, + } +} + +// ---------------- Complete resource creation function ---------------- + +// CreateAllStaticResources creates all static dashboard resources using helper functions +func CreateAllStaticResources() []client.Object { + var resources []client.Object + + // Add all breadcrumbs + for _, breadcrumb := range CreateAllBreadcrumbs() { + resources = append(resources, breadcrumb) + } + + // Add all custom column overrides + for _, customColumns := range CreateAllCustomColumnsOverrides() { + resources = append(resources, customColumns) + } + + // Add all custom forms overrides + for _, customForms := range CreateAllCustomFormsOverrides() { + resources = append(resources, customForms) + } + + // Add all factories + for _, factory := range CreateAllFactories() { + resources = append(resources, factory) + } + + // Add all navigations + for _, navigation := range CreateAllNavigations() { + resources = append(resources, navigation) + } + + // Add all table URI mappings + for _, tableUriMapping := range CreateAllTableUriMappings() { + resources = append(resources, tableUriMapping) + } + + return resources +} diff --git a/internal/controller/dashboard/tableurimapping.go b/internal/controller/dashboard/tableurimapping.go new file mode 100644 index 00000000..6e8a395d --- /dev/null +++ b/internal/controller/dashboard/tableurimapping.go @@ -0,0 +1,13 @@ +package dashboard + +import ( + "context" + + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" +) + +// ensureTableUriMapping creates or updates a TableUriMapping resource for the given CRD +func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { + // Links are fully managed by the CustomColumnsOverride. + return nil +} diff --git a/internal/controller/dashboard/ui_helpers.go b/internal/controller/dashboard/ui_helpers.go new file mode 100644 index 00000000..4066fd36 --- /dev/null +++ b/internal/controller/dashboard/ui_helpers.go @@ -0,0 +1,209 @@ +package dashboard + +import "strings" + +// ---------------- UI helpers (use float64 for numeric fields) ---------------- + +func contentCard(id string, style map[string]any, children []any) map[string]any { + return contentCardWithTitle(id, "", style, children) +} + +func contentCardWithTitle(id any, title string, style map[string]any, children []any) map[string]any { + data := map[string]any{ + "id": id, + "style": style, + } + if title != "" { + data["title"] = title + } + return map[string]any{ + "type": "ContentCard", + "data": data, + "children": children, + } +} + +func antdText(id string, strong bool, text string, style map[string]any) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateTextID("auto", "antd") + } + + data := map[string]any{ + "id": id, + "text": text, + "strong": strong, + } + if style != nil { + data["style"] = style + } + return map[string]any{"type": "antdText", "data": data} +} + +func parsedText(id, text string, style map[string]any) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateTextID("auto", "parsed") + } + + data := map[string]any{ + "id": id, + "text": text, + } + if style != nil { + data["style"] = style + } + return map[string]any{"type": "parsedText", "data": data} +} + +func parsedTextWithFormatter(id, text, formatter string) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateTextID("auto", "formatted") + } + + return map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "id": id, + "text": text, + "formatter": formatter, + }, + } +} + +func spacer(id string, space float64) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateContainerID("auto", "spacer") + } + + return map[string]any{ + "type": "Spacer", + "data": map[string]any{ + "id": id, + "$space": space, + }, + } +} + +func antdFlex(id string, gap float64, children []any) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateContainerID("auto", "flex") + } + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": id, + "align": "center", + "gap": gap, + }, + "children": children, + } +} + +func antdFlexVertical(id string, gap float64, children []any) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateContainerID("auto", "flex-vertical") + } + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": id, + "vertical": true, + "gap": gap, + }, + "children": children, + } +} + +func antdRow(id string, gutter []any, children []any) map[string]any { + // Auto-generate ID if not provided + if id == "" { + id = generateContainerID("auto", "row") + } + + return map[string]any{ + "type": "antdRow", + "data": map[string]any{ + "id": id, + "gutter": gutter, + }, + "children": children, + } +} + +func antdCol(id string, span float64, children []any) map[string]any { + return map[string]any{ + "type": "antdCol", + "data": map[string]any{ + "id": id, + "span": span, + }, + "children": children, + } +} + +func antdColWithStyle(id string, style map[string]any, children []any) map[string]any { + return map[string]any{ + "type": "antdCol", + "data": map[string]any{ + "id": id, + "style": style, + }, + "children": children, + } +} + +func antdLink(id, text, href string) map[string]any { + return map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": id, + "text": text, + "href": href, + }, + } +} + +// ---------------- Badge helpers ---------------- + +// createBadge creates a badge element with the given text, color, and title +func createBadge(id, text, color, title string) map[string]any { + return map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": id, + "text": text, + "title": title, + "style": map[string]any{ + "whiteSpace": "nowrap", + "backgroundColor": color, + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "textAlign": "center", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "padding": "0 9px", + }, + }, + } +} + +// createBadgeFromKind creates a badge using the existing badge generation functions +func createBadgeFromKind(id, kind, title string) map[string]any { + return createUnifiedBadgeFromKind(id, kind, title, BadgeSizeMedium) +} + +// createHeaderBadge creates a badge specifically for headers with consistent styling +func createHeaderBadge(id, kind, plural string) map[string]any { + return createUnifiedBadgeFromKind(id, kind, strings.ToLower(plural), BadgeSizeLarge) +} diff --git a/internal/controller/dashboard/unified_helpers.go b/internal/controller/dashboard/unified_helpers.go new file mode 100644 index 00000000..7bdea2b5 --- /dev/null +++ b/internal/controller/dashboard/unified_helpers.go @@ -0,0 +1,407 @@ +package dashboard + +import ( + "crypto/sha1" + "fmt" + "strings" +) + +// ---------------- Unified ID generation helpers ---------------- + +// generateID creates a unique ID based on the provided components +func generateID(components ...string) string { + if len(components) == 0 { + return "" + } + + // Join components with hyphens and convert to lowercase + id := strings.ToLower(strings.Join(components, "-")) + + // Remove any special characters that might cause issues + id = strings.ReplaceAll(id, ".", "-") + id = strings.ReplaceAll(id, "/", "-") + id = strings.ReplaceAll(id, " ", "-") + + // Remove multiple consecutive hyphens + for strings.Contains(id, "--") { + id = strings.ReplaceAll(id, "--", "-") + } + + // Remove leading/trailing hyphens + id = strings.Trim(id, "-") + + return id +} + +// generateSpecID creates a spec.id from metadata.name and other components +func generateSpecID(metadataName string, components ...string) string { + allComponents := append([]string{metadataName}, components...) + return generateID(allComponents...) +} + +// generateMetadataName creates metadata.name from spec.id +func generateMetadataName(specID string) string { + // Convert ID format to metadata.name format + // Replace / with . for metadata.name + name := strings.ReplaceAll(specID, "/", ".") + + // Clean up the name to be RFC 1123 compliant + // Remove any leading/trailing dots and ensure it starts/ends with alphanumeric + name = strings.Trim(name, ".") + + // Replace multiple consecutive dots with single dot + for strings.Contains(name, "..") { + name = strings.ReplaceAll(name, "..", ".") + } + + // Replace any remaining problematic patterns + // Handle cases like "stock-namespace-.v1" -> "stock-namespace-v1" + name = strings.ReplaceAll(name, "-.", "-") + name = strings.ReplaceAll(name, ".-", "-") + + // Ensure it starts with alphanumeric character + if len(name) > 0 && !isAlphanumeric(name[0]) { + name = "a" + name + } + + // Ensure it ends with alphanumeric character + if len(name) > 0 && !isAlphanumeric(name[len(name)-1]) { + name = name + "a" + } + + return name +} + +// isAlphanumeric checks if a character is alphanumeric +func isAlphanumeric(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') +} + +// ---------------- Unified badge generation helpers ---------------- + +// BadgeConfig holds configuration for badge generation +type BadgeConfig struct { + Text string + Color string + Title string + Size BadgeSize +} + +// BadgeSize represents the size of the badge +type BadgeSize int + +const ( + BadgeSizeSmall BadgeSize = iota + BadgeSizeMedium + BadgeSizeLarge +) + +// generateBadgeConfig creates a BadgeConfig from kind and optional custom values +func generateBadgeConfig(kind string, customText, customColor, customTitle string) BadgeConfig { + config := BadgeConfig{ + Text: initialsFromKind(kind), + Color: hexColorForKind(kind), + Title: strings.ToLower(kind), + Size: BadgeSizeMedium, + } + + // Override with custom values if provided + if customText != "" { + config.Text = customText + } + if customColor != "" { + config.Color = customColor + } + if customTitle != "" { + config.Title = customTitle + } + + return config +} + +// createUnifiedBadge creates a badge using the unified BadgeConfig +func createUnifiedBadge(id string, config BadgeConfig) map[string]any { + fontSize := "15px" + if config.Size == BadgeSizeLarge { + fontSize = "20px" + } else if config.Size == BadgeSizeSmall { + fontSize = "12px" + } + + return map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": id, + "text": config.Text, + "title": config.Title, + "style": map[string]any{ + "backgroundColor": config.Color, + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": fontSize, + "fontWeight": float64(400), + "lineHeight": "24px", + "minWidth": float64(24), + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + } +} + +// createUnifiedBadgeFromKind creates a badge from kind with automatic color generation +func createUnifiedBadgeFromKind(id, kind, title string, size BadgeSize) map[string]any { + config := BadgeConfig{ + Text: initialsFromKind(kind), + Color: hexColorForKind(kind), + Title: title, + Size: size, + } + return createUnifiedBadge(id, config) +} + +// ---------------- Resource creation helpers with unified approach ---------------- + +// ResourceConfig holds configuration for resource creation +type ResourceConfig struct { + SpecID string + MetadataName string + Kind string + Title string + BadgeConfig BadgeConfig +} + +// createResourceConfig creates a ResourceConfig from components +func createResourceConfig(components []string, kind, title string) ResourceConfig { + // Generate spec.id from components + specID := generateID(components...) + + // Generate metadata.name from spec.id + metadataName := generateMetadataName(specID) + + // Generate badge config + badgeConfig := generateBadgeConfig(kind, "", "", title) + + return ResourceConfig{ + SpecID: specID, + MetadataName: metadataName, + Kind: kind, + Title: title, + BadgeConfig: badgeConfig, + } +} + +// ---------------- Enhanced color generation ---------------- + +// getColorForKind returns a color for a specific kind with improved distribution +func getColorForKind(kind string) string { + // Use existing hexColorForKind function + return hexColorForKind(kind) +} + +// getColorForType returns a color for a specific type (like "namespace", "service", etc.) +func getColorForType(typeName string) string { + // Map common types to specific colors for consistency + colorMap := map[string]string{ + "namespace": "#a25792ff", + "service": "#6ca100", + "pod": "#009596", + "node": "#8476d1", + "secret": "#c46100", + "configmap": "#b48c78ff", + "ingress": "#2e7dff", + "workloadmonitor": "#c46100", + "module": "#8b5cf6", + } + + if color, exists := colorMap[strings.ToLower(typeName)]; exists { + return color + } + + // Fall back to hash-based color generation + return hexColorForKind(typeName) +} + +// ---------------- Automatic ID generation for UI elements ---------------- + +// generateElementID creates an ID for UI elements based on context and type +func generateElementID(elementType, context string, components ...string) string { + allComponents := append([]string{elementType, context}, components...) + return generateID(allComponents...) +} + +// generateBadgeID creates an ID for badge elements +func generateBadgeID(context string, kind string) string { + return generateElementID("badge", context, kind) +} + +// generateLinkID creates an ID for link elements +func generateLinkID(context string, linkType string) string { + return generateElementID("link", context, linkType) +} + +// generateTextID creates an ID for text elements +func generateTextID(context string, textType string) string { + return generateElementID("text", context, textType) +} + +// generateContainerID creates an ID for container elements +func generateContainerID(context string, containerType string) string { + return generateElementID("container", context, containerType) +} + +// generateTableID creates an ID for table elements +func generateTableID(context string, tableType string) string { + return generateElementID("table", context, tableType) +} + +// ---------------- Enhanced resource creation with automatic IDs ---------------- + +// createResourceWithAutoID creates a resource with automatically generated IDs +func createResourceWithAutoID(resourceType, name string, spec map[string]any) map[string]any { + // Generate spec.id from name + specID := generateSpecID(name) + + // Add the spec.id to the spec + spec["id"] = specID + + return spec +} + +// ---------------- Unified resource creation helpers ---------------- + +// UnifiedResourceConfig holds configuration for unified resource creation +type UnifiedResourceConfig struct { + Name string + ResourceType string + Kind string + Plural string + Title string + Color string + BadgeText string + Size BadgeSize +} + +// createUnifiedFactory creates a factory using unified approach +func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch []any) map[string]any { + // Generate spec.id from name + specID := generateSpecID(config.Name) + + // Create header with unified badge + badgeConfig := BadgeConfig{ + Text: config.BadgeText, + Color: config.Color, + Title: config.Title, + Size: config.Size, + } + if badgeConfig.Text == "" { + badgeConfig.Text = initialsFromKind(config.Kind) + } + if badgeConfig.Color == "" { + badgeConfig.Color = getColorForKind(config.Kind) + } + + badge := createUnifiedBadge(generateBadgeID("header", config.Kind), badgeConfig) + nameText := parsedText(generateTextID("header", "name"), "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{ + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": float64(20), + "lineHeight": "24px", + }) + + header := antdFlex(generateContainerID("header", "row"), float64(6), []any{ + badge, + nameText, + }) + + // Add marginBottom style to header + if headerData, ok := header["data"].(map[string]any); ok { + if headerData["style"] == nil { + headerData["style"] = map[string]any{} + } + if style, ok := headerData["style"].(map[string]any); ok { + style["marginBottom"] = float64(24) + } + } + + return map[string]any{ + "key": config.Name, + "id": specID, + "sidebarTags": []any{fmt.Sprintf("%s-sidebar", strings.ToLower(config.Kind))}, + "withScrollableMainContentCard": true, + "urlsToFetch": urlsToFetch, + "data": []any{ + header, + map[string]any{ + "type": "antdTabs", + "data": map[string]any{ + "id": generateContainerID("tabs", strings.ToLower(config.Kind)), + "defaultActiveKey": "details", + "items": tabs, + }, + }, + }, + } +} + +// createUnifiedCustomColumn creates a custom column using unified approach +func createUnifiedCustomColumn(name, jsonPath, kind, title, href string) map[string]any { + badgeConfig := generateBadgeConfig(kind, "", "", title) + badge := createUnifiedBadge(generateBadgeID("column", kind), badgeConfig) + + linkID := generateLinkID("column", "name") + if jsonPath == ".metadata.namespace" { + linkID = generateLinkID("column", "namespace") + } + + link := antdLink(linkID, "{reqsJsonPath[0]['"+jsonPath+"']['-']}", href) + + return map[string]any{ + "name": name, + "type": "factory", + "jsonPath": jsonPath, + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": generateContainerID("column", "header"), + "align": "center", + "gap": float64(6), + }, + "children": []any{badge, link}, + }, + }, + }, + } +} + +// ---------------- Utility functions ---------------- + +// hashString creates a short hash from a string for ID generation +func hashString(s string) string { + hash := sha1.Sum([]byte(s)) + return fmt.Sprintf("%x", hash[:4]) +} + +// sanitizeForID removes characters that shouldn't be in IDs +func sanitizeForID(s string) string { + // Replace problematic characters + s = strings.ReplaceAll(s, ".", "-") + s = strings.ReplaceAll(s, "/", "-") + s = strings.ReplaceAll(s, " ", "-") + s = strings.ReplaceAll(s, "_", "-") + + // Remove multiple consecutive hyphens + for strings.Contains(s, "--") { + s = strings.ReplaceAll(s, "--", "-") + } + + // Remove leading/trailing hyphens + s = strings.Trim(s, "-") + + return strings.ToLower(s) +} diff --git a/internal/controller/dashboard/webextras.go b/internal/controller/dashboard/webextras.go deleted file mode 100644 index 50048ed2..00000000 --- a/internal/controller/dashboard/webextras.go +++ /dev/null @@ -1,266 +0,0 @@ -package dashboard - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" - cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" - - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -// Ensure the three additional dashboard/frontend resources exist: -// - TableUriMapping (dashboard.cozystack.io/v1alpha1) -// - Breadcrumb (dashboard.cozystack.io/v1alpha1) -// - CustomFormsOverride (dashboard.cozystack.io/v1alpha1) -// -// Call these from Manager.EnsureForCRD() after ensureCustomColumnsOverride. - -// --------------------------- TableUriMapping ----------------------------- - -func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { - // Links are fully managed by the CustomColumnsOverride. - return nil -} - -// ------------------------------- Breadcrumb ----------------------------- - -func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { - _, _, kind := pickGVK(crd) - - lowerKind := strings.ToLower(kind) - detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) - - obj := &dashv1alpha1.Breadcrumb{} - obj.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "dashboard.cozystack.io", - Version: "v1alpha1", - Kind: "Breadcrumb", - }) - obj.SetName(detailID) - - plural := pickPlural(kind, crd) - - // Prefer dashboard.Plural for UI label if provided - labelPlural := titleFromKindPlural(kind, plural) - if crd != nil && crd.Spec.Dashboard != nil && crd.Spec.Dashboard.Plural != "" { - labelPlural = crd.Spec.Dashboard.Plural - } - - key := plural // e.g., "virtualmachines" - label := labelPlural - link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/apps.cozystack.io/v1alpha1/%s", plural) - // If Name is set, change the first breadcrumb item to "Tenant Modules" - // TODO add parameter to this - if crd.Spec.Dashboard.Name != "" { - key = "tenantmodules" - label = "Tenant Modules" - link = "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules" - } - - items := []any{ - map[string]any{ - "key": key, - "label": label, - "link": link, - }, - map[string]any{ - "key": strings.ToLower(kind), // "etcd" - "label": "{6}", // literal, as in your example - }, - } - - spec := map[string]any{ - "id": detailID, - "breadcrumbItems": items, - } - - _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { - if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { - return err - } - b, err := json.Marshal(spec) - if err != nil { - return err - } - obj.Spec = dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} - return nil - }) - return err -} - -// --------------------------- CustomFormsOverride ------------------------ - -func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { - g, v, kind := pickGVK(crd) - plural := pickPlural(kind, crd) - - name := fmt.Sprintf("%s.%s.%s", g, v, plural) - customizationID := fmt.Sprintf("default-/%s/%s/%s", g, v, plural) - - obj := &dashv1alpha1.CustomFormsOverride{} - obj.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "dashboard.cozystack.io", - Version: "v1alpha1", - Kind: "CustomFormsOverride", - }) - obj.SetName(name) - - // Replicates your Helm includes (system metadata + api + status). - hidden := []any{} - hidden = append(hidden, hiddenMetadataSystem()...) - hidden = append(hidden, hiddenMetadataAPI()...) - hidden = append(hidden, hiddenStatus()...) - - // If Name is set, hide metadata - if crd.Spec.Dashboard != nil && strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { - hidden = append([]interface{}{ - []any{"metadata"}, - }, hidden...) - } - - sort := make([]any, len(crd.Spec.Dashboard.KeysOrder)) - for i, v := range crd.Spec.Dashboard.KeysOrder { - sort[i] = v - } - - spec := map[string]any{ - "customizationId": customizationID, - "hidden": hidden, - "sort": sort, - "schema": map[string]any{}, // {} - "strategy": "merge", - } - - _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { - if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { - return err - } - b, err := json.Marshal(spec) - if err != nil { - return err - } - obj.Spec = dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} - return nil - }) - return err -} - -// ----------------------- CustomFormsPrefill ----------------------- - -func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { - logger := log.FromContext(ctx) - - app := crd.Spec.Application - group := "apps.cozystack.io" - version := "v1alpha1" - - name := fmt.Sprintf("%s.%s.%s", group, version, app.Plural) - customizationID := fmt.Sprintf("default-/%s/%s/%s", group, version, app.Plural) - - values, err := buildPrefillValues(app.OpenAPISchema) - if err != nil { - return reconcile.Result{}, err - } - - // If Name is set, prefill metadata.name - if crd.Spec.Dashboard != nil && strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { - values = append([]interface{}{ - map[string]interface{}{ - "path": toIfaceSlice([]string{"metadata", "name"}), - "value": crd.Spec.Dashboard.Name, - }, - }, values...) - } - - cfp := &dashv1alpha1.CustomFormsPrefill{} - cfp.Name = name // cluster-scoped - - specMap := map[string]any{ - "customizationId": customizationID, - "values": values, - } - specBytes, err := json.Marshal(specMap) - if err != nil { - return reconcile.Result{}, err - } - - mutate := func() error { - if err := controllerutil.SetOwnerReference(crd, cfp, m.scheme); err != nil { - return err - } - cfp.Spec = dashv1alpha1.ArbitrarySpec{ - JSON: apiextv1.JSON{Raw: specBytes}, - } - return nil - } - - op, err := controllerutil.CreateOrUpdate(ctx, m.client, cfp, mutate) - if err != nil { - return reconcile.Result{}, err - } - switch op { - case controllerutil.OperationResultCreated: - logger.Info("Created CustomFormsPrefill", "name", cfp.Name) - case controllerutil.OperationResultUpdated: - logger.Info("Updated CustomFormsPrefill", "name", cfp.Name) - } - return reconcile.Result{}, nil -} - -// ------------------------------ Helpers --------------------------------- - -// titleFromKindPlural returns a presentable plural label, e.g.: -// kind="VirtualMachine", plural="virtualmachines" => "VirtualMachines" -func titleFromKindPlural(kind, plural string) string { - label := kind - if !strings.HasSuffix(strings.ToLower(plural), "s") || !strings.HasSuffix(strings.ToLower(plural), "S") { - label += "s" - } else { - label += "s" - } - return label -} - -// The hidden lists below mirror the Helm templates you shared. -// Each entry is a path as nested string array, e.g. ["metadata","creationTimestamp"]. - -func hiddenMetadataSystem() []any { - return []any{ - []any{"metadata", "annotations"}, - []any{"metadata", "labels"}, - []any{"metadata", "namespace"}, - []any{"metadata", "creationTimestamp"}, - []any{"metadata", "deletionGracePeriodSeconds"}, - []any{"metadata", "deletionTimestamp"}, - []any{"metadata", "finalizers"}, - []any{"metadata", "generateName"}, - []any{"metadata", "generation"}, - []any{"metadata", "managedFields"}, - []any{"metadata", "ownerReferences"}, - []any{"metadata", "resourceVersion"}, - []any{"metadata", "selfLink"}, - []any{"metadata", "uid"}, - } -} - -func hiddenMetadataAPI() []any { - return []any{ - []any{"kind"}, - []any{"apiVersion"}, - []any{"appVersion"}, - } -} - -func hiddenStatus() []any { - return []any{ - []any{"status"}, - } -} diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml index 05762936..2343e97e 100644 --- a/packages/core/platform/bundles/paas-full.yaml +++ b/packages/core/platform/bundles/paas-full.yaml @@ -297,24 +297,6 @@ releases: - keycloak-configure {{- end }} -- name: dashboard-config - releaseName: dashboard-config - chart: cozy-dashboard-config - namespace: cozy-dashboard - values: - {{- $dashboardKCconfig := lookup "v1" "ConfigMap" "cozy-dashboard" "kubeapps-auth-config" }} - {{- $dashboardKCValues := dig "data" "values.yaml" "" $dashboardKCconfig | fromYaml }} - {{- toYaml (deepCopy $dashboardKCValues | mergeOverwrite (fromYaml (include "cozystack.defaultDashboardValues" .))) | nindent 4 }} - dependsOn: - - cilium - - kubeovn - - dashboard - - cozystack-api - {{- if eq $oidcEnabled "true" }} - - keycloak-configure - {{- end }} - - - name: kamaji releaseName: kamaji chart: cozy-kamaji diff --git a/packages/core/platform/bundles/paas-hosted.yaml b/packages/core/platform/bundles/paas-hosted.yaml index 1980f67b..710dd439 100644 --- a/packages/core/platform/bundles/paas-hosted.yaml +++ b/packages/core/platform/bundles/paas-hosted.yaml @@ -182,22 +182,6 @@ releases: dependsOn: [] {{- end }} -- name: dashboard-config - releaseName: dashboard-config - chart: cozy-dashboard-config - namespace: cozy-dashboard - values: - {{- $dashboardKCconfig := lookup "v1" "ConfigMap" "cozy-dashboard" "kubeapps-auth-config" }} - {{- $dashboardKCValues := dig "data" "values.yaml" "" $dashboardKCconfig | fromYaml }} - {{- toYaml (deepCopy $dashboardKCValues | mergeOverwrite (fromYaml (include "cozystack.defaultDashboardValues" .))) | nindent 4 }} - dependsOn: - - cilium - - kubeovn - - dashboard - {{- if eq $oidcEnabled "true" }} - - keycloak-configure - {{- end }} - {{- if $oidcEnabled }} - name: keycloak releaseName: keycloak diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index c5e7c85f..338beef1 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,5 +1,5 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:latest@sha256:20483582c50ead02d76719d3acfe2bb5e0d5a62de7dce65ffebe4039f8f3c400 + image: ghcr.io/cozystack/cozystack/cozystack-controller:latest@sha256:1c439f383e0a0ca5eed3b3c820c8caf00b4d5347579d2f78ce6897f031041cf6 debug: false disableTelemetry: false cozystackVersion: "latest" diff --git a/packages/system/dashboard-config/Chart.yaml b/packages/system/dashboard-config/Chart.yaml deleted file mode 100644 index 4d848e27..00000000 --- a/packages/system/dashboard-config/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -version: 1.0.0 -name: cozy-dashboard-config diff --git a/packages/system/dashboard-config/Makefile b/packages/system/dashboard-config/Makefile deleted file mode 100644 index 041e535e..00000000 --- a/packages/system/dashboard-config/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -NAME := dashboard-config -NAMESPACE := cozy-dashboard - -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk diff --git a/packages/system/dashboard-config/templates/Breadcrumb/factory/configmap-details.yaml b/packages/system/dashboard-config/templates/Breadcrumb/factory/configmap-details.yaml deleted file mode 100644 index f365fb62..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/factory/configmap-details.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-factory-configmap-details -spec: - id: "stock-project-factory-configmap-details" - breadcrumbItems: - - key: configmaps - label: "v1/configmaps" - link: "/openapi-ui/{clusterName}/{namespace}/builtin-table/configmaps" - - key: configmap - label: "{6}" diff --git a/packages/system/dashboard-config/templates/Breadcrumb/factory/namespace-details.yaml b/packages/system/dashboard-config/templates/Breadcrumb/factory/namespace-details.yaml deleted file mode 100644 index 9e9ac4bf..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/factory/namespace-details.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-factory-namespace-details -spec: - id: "stock-cluster-factory-namespace-details" - breadcrumbItems: - - key: namespaces - label: "v1/namespaces" - link: "/openapi-ui/{clusterName}/builtin-table/namespaces" - - key: namespace - label: "{5}" diff --git a/packages/system/dashboard-config/templates/Breadcrumb/factory/node-details.yaml b/packages/system/dashboard-config/templates/Breadcrumb/factory/node-details.yaml deleted file mode 100644 index bcd1be70..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/factory/node-details.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-factory-node-details -spec: - id: "stock-cluster-factory-node-details" - breadcrumbItems: - - key: node - label: "v1/nodes" - link: "/openapi-ui/{clusterName}/builtin-table/nodes" - - key: node - label: "{5}" diff --git a/packages/system/dashboard-config/templates/Breadcrumb/factory/pod-details.yaml b/packages/system/dashboard-config/templates/Breadcrumb/factory/pod-details.yaml deleted file mode 100644 index f09bbf5a..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/factory/pod-details.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-factory-pod-details -spec: - id: "stock-project-factory-pod-details" - breadcrumbItems: - - key: pods - label: "v1/pods" - link: "/openapi-ui/{clusterName}/{namespace}/builtin-table/pods" - - key: pod - label: "{6}" diff --git a/packages/system/dashboard-config/templates/Breadcrumb/factory/secret-details.yaml b/packages/system/dashboard-config/templates/Breadcrumb/factory/secret-details.yaml deleted file mode 100644 index 5e5230bd..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/factory/secret-details.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-factory-secret-details -spec: - id: "stock-project-factory-secret-details" - breadcrumbItems: - - key: secrets - label: "v1/secrets" - link: "/openapi-ui/{clusterName}/{namespace}/builtin-table/secrets" - - key: secret - label: "{6}" diff --git a/packages/system/dashboard-config/templates/Breadcrumb/factory/service-details.yaml b/packages/system/dashboard-config/templates/Breadcrumb/factory/service-details.yaml deleted file mode 100644 index 83aca2e9..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/factory/service-details.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-factory-service-details -spec: - id: "stock-project-factory-service-details" - breadcrumbItems: - - key: services - label: "v1/services" - link: "/openapi-ui/{clusterName}/{namespace}/builtin-table/services" - - key: service - label: "{6}" diff --git a/packages/system/dashboard-config/templates/Breadcrumb/stock-cluster-api.yaml b/packages/system/dashboard-config/templates/Breadcrumb/stock-cluster-api.yaml deleted file mode 100644 index f402e37d..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/stock-cluster-api.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-api-table -spec: - id: "stock-cluster-api-table" - breadcrumbItems: - - key: api - label: "{apiGroup}/{apiVersion}/{typeName}" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-api-form -spec: - id: "stock-cluster-api-form" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "{apiGroup}/{apiVersion}/{typeName}" - link: "/openapi-ui/{clusterName}/api-table/{apiGroup}/{apiVersion}/{typeName}" - - key: create-api-res-namespaced-typename - label: Create - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-api-form-edit -spec: - id: "stock-cluster-api-form-edit" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "{apiGroup}/{apiVersion}/{typeName}" - link: "/openapi-ui/{clusterName}/api-table/{apiGroup}/{apiVersion}/{typeName}" - - key: create-api-res-namespaced-typename - label: Update diff --git a/packages/system/dashboard-config/templates/Breadcrumb/stock-cluster-builtin.yaml b/packages/system/dashboard-config/templates/Breadcrumb/stock-cluster-builtin.yaml deleted file mode 100644 index adf3847c..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/stock-cluster-builtin.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-builtin-table -spec: - id: "stock-cluster-builtin-table" - breadcrumbItems: - - key: api - label: "v1/{typeName}" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-builtin-form -spec: - id: "stock-cluster-builtin-form" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "v1/{typeName}" - link: "/openapi-ui/{clusterName}/builtin-table/{typeName}" - - key: create-api-res-namespaced-typename - label: Create - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-cluster-builtin-form-edit -spec: - id: "stock-cluster-builtin-form-edit" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "v1/{typeName}" - link: "/openapi-ui/{clusterName}/builtin-table/{typeName}" - - key: create-api-res-namespaced-typename - label: Update diff --git a/packages/system/dashboard-config/templates/Breadcrumb/stock-project-api.yaml b/packages/system/dashboard-config/templates/Breadcrumb/stock-project-api.yaml deleted file mode 100644 index 083c74f5..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/stock-project-api.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-api-table -spec: - id: "stock-project-api-table" - breadcrumbItems: - - key: api - label: "{apiGroup}/{apiVersion}/{typeName}" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-api-form -spec: - id: "stock-project-api-form" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "{apiGroup}/{apiVersion}/{typeName}" - link: "/openapi-ui/{clusterName}/{namespace}/api-table/{apiGroup}/{apiVersion}/{typeName}" - - key: create-api-res-namespaced-typename - label: Create - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-api-form-edit -spec: - id: "stock-project-api-form-edit" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "{apiGroup}/{apiVersion}/{typeName}" - link: "/openapi-ui/{clusterName}/{namespace}/api-table/{apiGroup}/{apiVersion}/{typeName}" - - key: create-api-res-namespaced-typename - label: Update diff --git a/packages/system/dashboard-config/templates/Breadcrumb/stock-project-builtin.yaml b/packages/system/dashboard-config/templates/Breadcrumb/stock-project-builtin.yaml deleted file mode 100644 index cb83795a..00000000 --- a/packages/system/dashboard-config/templates/Breadcrumb/stock-project-builtin.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-builtin-table -spec: - id: "stock-project-builtin-table" - breadcrumbItems: - - key: api - label: "v1/{typeName}" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-builtin-form -spec: - id: "stock-project-builtin-form" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "v1/{typeName}" - link: "/openapi-ui/{clusterName}/{namespace}/builtin-table/{typeName}" - - key: create-api-res-namespaced-typename - label: Create - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Breadcrumb -metadata: - name: stock-project-builtin-form-edit -spec: - id: "stock-project-builtin-form-edit" - breadcrumbItems: - - key: create-api-res-namespaced-table - label: "v1/{typeName}" - link: "/openapi-ui/{clusterName}/{namespace}/builtin-table/{typeName}" - - key: create-api-res-namespaced-typename - label: Update diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1.services.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1.services.yaml deleted file mode 100644 index bcab444b..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1.services.yaml +++ /dev/null @@ -1,126 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-details-v1.services -spec: - id: factory-details-v1.services - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "S" - "title" "service" - "backgroundColor" "#6ca100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "service-details" - ) | nindent 12 - }} - - jsonPath: .spec.clusterIP - name: ClusterIP - type: string - - jsonPath: .spec.loadBalancerIP - name: LoadbalancerIP - type: string - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: ClusterIP - value: "-" - - key: LoadbalancerIP - value: "-" - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: stock-namespace-v1.services -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "S" - "title" "service" - "backgroundColor" "#6ca100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "service-details" - ) | nindent 12 - }} - - jsonPath: .spec.clusterIP - name: ClusterIP - type: string - - jsonPath: .spec.loadBalancerIP - name: LoadbalancerIP - type: string - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: ClusterIP - value: "-" - - key: LoadbalancerIP - value: "-" - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: stock-namespace-/v1/services - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-service-details-port-mapping -spec: - additionalPrinterColumns: - - jsonPath: .name - name: Name - type: string - - jsonPath: .port - name: Port - type: string - - jsonPath: .protocol - name: Protocol - type: string - - jsonPath: .targetPort - name: Pod port or name - type: string - id: factory-service-details-port-mapping diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1alpha1.apps.cozystack.io.workloadmonitors.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1alpha1.apps.cozystack.io.workloadmonitors.yaml deleted file mode 100644 index d0ffe701..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1alpha1.apps.cozystack.io.workloadmonitors.yaml +++ /dev/null @@ -1,46 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-details-v1alpha1.cozystack.io.workloadmonitors -spec: - id: factory-details-v1alpha1.cozystack.io.workloadmonitors - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "W" - "title" "workloadmonitor" - "backgroundColor" "#c46100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "workloadmonitor-details" - ) | nindent 12 - }} - - jsonPath: .spec.type - name: TYPE - type: string - - jsonPath: .spec.version - name: VERSION - type: string - - jsonPath: .spec.replicas - name: REPLICAS - type: string - - jsonPath: .spec.minReplicas - name: MINREPLICAS - type: string - - jsonPath: .status.availableReplicas - name: AVAILABLE - type: string - - jsonPath: .status.observedReplicas - name: OBSERVED - type: string diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1alpha1.core.cozystack.io.tenantsecretstables.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1alpha1.core.cozystack.io.tenantsecretstables.yaml deleted file mode 100644 index 783a9659..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-details-v1alpha1.core.cozystack.io.tenantsecretstables.yaml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-details-v1alpha1.core.cozystack.io.tenantsecretstables -spec: - id: factory-details-v1alpha1.core.cozystack.io.tenantsecretstables - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "S" - "title" "secret" - "backgroundColor" "#c46100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "secret-details" - ) | nindent 12 - }} - - jsonPath: .data.key - name: Key - type: string - - name: Value - type: factory - customProps: - disableEventBubbling: true - items: - - type: SecretBase64Plain - data: - id: example-secterbase64 - plainTextValue: "hello" - base64Value: "{reqsJsonPath[0]['.data.value']['-']}" - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Namespace - value: '-' diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-ingress-details-rules.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-ingress-details-rules.yaml deleted file mode 100644 index b84a8426..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-ingress-details-rules.yaml +++ /dev/null @@ -1,47 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-ingress-details-rules -spec: - id: factory-ingress-details-rules - # Each item is a rule. We'll display the first HTTP path data where present - additionalPrinterColumns: - - jsonPath: .host - name: Host - type: string - - jsonPath: .http.paths[0].backend.service.name - name: Service - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "S" - "title" "service" - "backgroundColor" "#6ca100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".http.paths[0].backend.service.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "service-details" - ) | nindent 12 - }} - - jsonPath: .http.paths[0].backend.service.port.number - name: Port - type: string - - jsonPath: .http.paths[0].path - name: Path - type: string - additionalPrinterColumnsUndefinedValues: - - key: Service - value: "-" - - key: Port - value: "-" - - key: Path - value: "-" - - diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-node-images.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-node-images.yaml deleted file mode 100644 index d2c8d72e..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-node-images.yaml +++ /dev/null @@ -1,33 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-node-images -spec: - additionalPrinterColumns: - - jsonPath: .names[0] - name: ImageID - type: string - - jsonPath: .sizeBytes - name: Size - type: factory - customProps: - disableEventBubbling: true - items: - - type: ConverterBytes - data: - id: example-converter-bytes - bytesValue: "{reqsJsonPath[0]['.sizeBytes']['-']}" - format: true - precision: 1 - - additionalPrinterColumnsUndefinedValues: - - key: Message - value: "-" - - additionalPrinterColumnsTrimLengths: - - key: ImageID - value: 128 - - key: Size - value: 63 - id: "factory-node-images" diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-pod-details-list.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-pod-details-list.yaml deleted file mode 100644 index 47782a1f..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-pod-details-list.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-pod-details-volume-list -spec: - additionalPrinterColumns: - - jsonPath: .name - name: Name - type: string - id: "factory-pod-details-volume-list" diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-status-conditions.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-status-conditions.yaml deleted file mode 100644 index eac76733..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/factory-status-conditions.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-status-conditions -spec: - additionalPrinterColumns: - - jsonPath: .type - name: Type - type: string - - jsonPath: .status - name: Status - type: bool - - jsonPath: .lastTransitionTime - name: Updated - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".lastTransitionTime" - ) | nindent 8 - }} - - jsonPath: .reason - name: Reason - type: string - - jsonPath: .message - name: Message - type: string - - additionalPrinterColumnsUndefinedValues: - - key: Reason - value: "-" - - key: Message - value: "-" - - additionalPrinterColumnsTrimLengths: - - key: Message - value: 63 - id: "factory-status-conditions" diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/helpers/hidden.metadata.tpl b/packages/system/dashboard-config/templates/CustomColumnsOverride/helpers/hidden.metadata.tpl deleted file mode 100644 index e282731a..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/helpers/hidden.metadata.tpl +++ /dev/null @@ -1,162 +0,0 @@ -{{- define "incloud-web-resources.customformoverride.hidden.metadata.system" -}} -- - metadata - - creationTimestamp -- - metadata - - deletionGracePeriodSeconds -- - metadata - - deletionTimestamp -- - metadata - - finalizers -- - metadata - - generateName -- - metadata - - generation -- - metadata - - managedFields -- - metadata - - ownerReferences -- - metadata - - resourceVersion -- - metadata - - selfLink -- - metadata - - uid -{{- end -}} - -{{- define "incloud-web-resources.customformoverride.hidden.metadata.system-clusterscope" -}} -- - metadata - - creationTimestamp -- - metadata - - namespace -- - metadata - - deletionGracePeriodSeconds -- - metadata - - deletionTimestamp -- - metadata - - finalizers -- - metadata - - generateName -- - metadata - - generation -- - metadata - - managedFields -- - metadata - - ownerReferences -- - metadata - - resourceVersion -- - metadata - - selfLink -- - metadata - - uid -{{- end -}} - -{{- define "incloud-web-resources.customformoverride.hidden.metadata.system.job-template" -}} -- - spec - - jobTemplate - - metadata - - creationTimestamp -- - spec - - jobTemplate - - metadata - - namespace -- - spec - - jobTemplate - - metadata - - deletionGracePeriodSeconds -- - spec - - jobTemplate - - metadata - - deletionTimestamp -- - spec - - jobTemplate - - metadata - - finalizers -- - spec - - jobTemplate - - metadata - - generateName -- - spec - - jobTemplate - - metadata - - generation -- - spec - - jobTemplate - - metadata - - managedFields -- - spec - - jobTemplate - - metadata - - ownerReferences -- - spec - - jobTemplate - - metadata - - resourceVersion -- - spec - - jobTemplate - - metadata - - selfLink -- - spec - - jobTemplate - - metadata - - uid -{{- end -}} - -{{- define "incloud-web-resources.customformoverride.hidden.metadata.system.template" -}} -- - spec - - template - - metadata - - creationTimestamp -- - spec - - template - - metadata - - namespace -- - spec - - template - - metadata - - deletionGracePeriodSeconds -- - spec - - template - - metadata - - deletionTimestamp -- - spec - - template - - metadata - - finalizers -- - spec - - template - - metadata - - generateName -- - spec - - template - - metadata - - generation -- - spec - - template - - metadata - - managedFields -- - spec - - template - - metadata - - ownerReferences -- - spec - - template - - metadata - - resourceVersion -- - spec - - template - - metadata - - selfLink -- - spec - - template - - metadata - - uid -{{- end -}} - -{{- define "incloud-web-resources.customformoverride.hidden.metadata.api" -}} -- - kind -- - apiVersion -{{- end -}} - -{{- define "incloud-web-resources.customformoverride.hidden.status" -}} -- - status -{{- end -}} diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/helpers/icons.tpl b/packages/system/dashboard-config/templates/CustomColumnsOverride/helpers/icons.tpl deleted file mode 100644 index 12a8e7e9..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/helpers/icons.tpl +++ /dev/null @@ -1,89 +0,0 @@ -{{- define "incloud-web-resources.pod.icon" -}} -- type: antdFlex - data: - id: header-row - gap: 6 - align: center - # style: - # marginBottom: 24px - children: - - type: antdText - data: - id: header-badge - text: P - title: Pods - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#009596" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 -{{- end -}} - -{{- define "incloud-web-resources.namespace.icon" -}} -- type: antdFlex - data: - id: header-row - gap: 6 - align: center - # style: - # marginBottom: 24px - children: - - type: antdText - data: - id: header-badge - text: NS - title: Nanesoace - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#45a703ff" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 -{{- end -}} - - -{{- define "incloud-web-resources.icon" -}} -{{- $text := (default "" .text) -}} -{{- $title := (default "" .title) -}} -{{- $backgroundColor := (default "#a25792ff" .backgroundColor) -}} -- type: antdFlex - data: - id: header-row - gap: 6 - align: center - children: - # Badge with resource short name - - type: antdText - data: - id: header-badge - text: "{{ $text }}" - title: "{{ $title }}" - style: - fontSize: 15px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "{{ $backgroundColor }}" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 -{{- end -}} diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/k8s.container.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/k8s.container.yaml deleted file mode 100644 index 3b300e27..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/k8s.container.yaml +++ /dev/null @@ -1,154 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: container-status-init-containers-list -spec: - additionalPrinterColumns: - - jsonPath: .name - name: Name - type: string - - jsonPath: .imageID - name: Image - type: string - - jsonPath: .started - name: Started - type: bool - - jsonPath: .ready - name: Ready - type: bool - - jsonPath: .restartCount - name: RestartCount - type: string - - jsonPath: .state.waiting.reason - name: WaitingdReason - type: string - - jsonPath: .state.terminated.reason - name: TerminatedReason - type: string - - additionalPrinterColumnsUndefinedValues: - - key: TerminatedReason - value: "-" - - key: WaitingdReason - value: "-" - - additionalPrinterColumnsTrimLengths: - - key: Name - value: 63 - - key: Image - value: 63 - id: "container-status-init-containers-list" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: container-status-containers-list -spec: - additionalPrinterColumns: - - jsonPath: .name - name: Name - type: string - - jsonPath: .imageID - name: Image - type: string - - jsonPath: .started - name: Started - type: bool - - jsonPath: .ready - name: Ready - type: bool - - jsonPath: .restartCount - name: RestartCount - type: string - - jsonPath: .state.waiting.reason - name: WaitingdReason - type: string - - jsonPath: .state.terminated.reason - name: TerminatedReason - type: string - - additionalPrinterColumnsUndefinedValues: - - key: TerminatedReason - value: "-" - - key: WaitingdReason - value: "-" - - additionalPrinterColumnsTrimLengths: - - key: Name - value: 63 - - key: Image - value: 63 - id: "container-status-containers-list" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: container-spec-init-containers-list -spec: - additionalPrinterColumns: - - jsonPath: .name - name: Name - type: string - - jsonPath: .image - name: Image - type: string - - jsonPath: .resources.requests - name: Resources requests - type: array - - jsonPath: .resources.limits - name: Resources limits - type: array - additionalPrinterColumnsTrimLengths: - - key: Image - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Name - value: "-" - - key: Image - value: "-" - - key: Resources limits - value: "-" - - key: Resources requests - value: "-" - id: container-spec-init-containers-list - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: container-spec-containers-list -spec: - additionalPrinterColumns: - - jsonPath: .name - name: Name - type: string - - jsonPath: .image - name: Image - type: string - - jsonPath: .resources.requests - name: Resources requests - type: array - - jsonPath: .resources.limits - name: Resources limits - type: array - - jsonPath: .ports[*].containerPort - name: Ports - type: array - additionalPrinterColumnsTrimLengths: - - key: Image - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Name - value: "-" - - key: Image - value: "-" - - key: Resources limits - value: "-" - - key: Resources requests - value: "-" - - key: Ports - value: "-" - id: container-spec-containers-list \ No newline at end of file diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/networking.k8s.io.v1.ingresses.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/networking.k8s.io.v1.ingresses.yaml deleted file mode 100644 index 8caf34ba..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/networking.k8s.io.v1.ingresses.yaml +++ /dev/null @@ -1,117 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-details-networking.k8s.io.v1.ingresses -spec: - id: factory-details-networking.k8s.io.v1.ingresses - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "I" - "title" "ingress" - "backgroundColor" "#2e7dff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "ingress-details" - ) | nindent 12 - }} - - jsonPath: .spec.rules[*].host - name: Hosts - type: string - - jsonPath: .status.loadBalancer.ingress[0].ip - name: Address - type: string - - jsonPath: .spec.defaultBackend.service.port.number - name: Port - type: string - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: Hosts - value: "-" - - key: Address - value: "-" - - key: Port - value: "-" - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: stock-namespace-networking.k8s.io.v1.ingresses -spec: - id: stock-namespace-/networking.k8s.io/v1/ingresses - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "I" - "title" "ingress" - "backgroundColor" "#2e7dff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "ingress-details" - ) | nindent 12 - }} - - jsonPath: .spec.rules[*].host - name: Hosts - type: string - - jsonPath: .status.loadBalancer.ingress[0].ip - name: Address - type: string - - jsonPath: .spec.defaultBackend.service.port.number - name: Port - type: string - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: Hosts - value: "-" - - key: Address - value: "-" - - key: Port - value: "-" - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - - diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.configmaps.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.configmaps.yaml deleted file mode 100644 index 57e01a34..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.configmaps.yaml +++ /dev/null @@ -1,169 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "stock-cluster-v1.configmaps" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "CM" - "title" "configmap" - "backgroundColor" "#b48c78ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "configmap-details" - ) | nindent 12 - }} - - jsonPath: .metadata.namespace - name: Namespace - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 12 - }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Namespace - value: "-" - id: "stock-cluster-/v1/configmaps" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "stock-namespace-v1.configmaps" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "CM" - "title" "configmap" - "backgroundColor" "#b48c78ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "configmap-details" - ) | nindent 12 - }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: "stock-namespace-/v1/configmaps" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "cluster-v1.configmaps" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "CM" - "title" "configmap" - "backgroundColor" "#b48c78ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "configmap-details" - ) | nindent 12 - }} - - jsonPath: .metadata.namespace - name: Namespace - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 12 - }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Namespace - value: "-" - id: "cluster-/v1/configmaps" diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.nodes.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.nodes.yaml deleted file mode 100644 index e12b4a69..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.nodes.yaml +++ /dev/null @@ -1,46 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: stock-cluster-v1.nodes -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "N" - "title" "node" - "backgroundColor" "#8476d1" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "factory" "node-details" - ) | nindent 12 - }} - - name: Status - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.statuses.node" . | nindent 8 }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: stock-cluster-/v1/nodes diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.pods.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.pods.yaml deleted file mode 100644 index 35450665..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.pods.yaml +++ /dev/null @@ -1,364 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "factory-node-details-v1.pods" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "P" - "title" "pod" - "backgroundColor" "#009596" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "pod-details" - ) | nindent 12 - }} - - jsonPath: .metadata.namespace - name: Namespace - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 12 - }} - - jsonPath: .spec.restartPolicy - name: Restart Policy - type: string - - jsonPath: .status.podIP - name: Pod IP - type: string - # - jsonPath: .status.conditions[?(@.type=="ContainersReady")].status - # name: Status Containers - # type: string - # - jsonPath: .status.conditions[?(@.type=="PodScheduled")].status - # name: Status Scheduled - # type: string - - jsonPath: .status.qosClass - name: QOS - type: string - - name: Status - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.statuses.pod" . | nindent 8 }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: Node - value: Unschedulable - - key: Pod IP - value: NotAllocated - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: "factory-node-details-/v1/pods" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "factory-v1.pods" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "P" - "title" "pod" - "backgroundColor" "#009596" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "pod-details" - ) | nindent 12 - }} - - name: Node - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "N" - "title" "node" - "backgroundColor" "#8476d1" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".spec.nodeName" - "factory" "node-details" - ) | nindent 12 - }} - - jsonPath: .spec.restartPolicy - name: Restart Policy - type: string - - jsonPath: .status.podIP - name: Pod IP - type: string - # - jsonPath: .status.conditions[?(@.type=="ContainersReady" || @.type=="PodReady")].status - # name: Status Containers - # type: string - # - jsonPath: .status.conditions[?(@.type=="PodScheduled")].status - # name: Status Scheduled - # type: string - - jsonPath: .status.qosClass - name: QOS - type: string - - name: Status - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.statuses.pod" . | nindent 8 }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: Node - value: Unschedulable - - key: Pod IP - value: NotAllocated - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: "factory-/v1/pods" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "stock-cluster-v1.pods" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "P" - "title" "pod" - "backgroundColor" "#009596" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "pod-details" - ) | nindent 12 - }} - - jsonPath: .metadata.namespace - name: Namespace - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 12 - }} - - jsonPath: .spec.nodeName - name: Node - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "N" - "title" "node" - "backgroundColor" "#8476d1" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".spec.nodeName" - "factory" "node-details" - ) | nindent 12 - }} - - jsonPath: .spec.restartPolicy - name: Restart Policy - type: string - - jsonPath: .status.podIP - name: Pod IP - type: string - # - jsonPath: .status.conditions[?(@.type=="ContainersReady")].status - # name: Status Containers - # type: string - # - jsonPath: .status.conditions[?(@.type=="PodScheduled")].status - # name: Status Scheduled - # type: string - - jsonPath: .status.qosClass - name: QOS - type: string - - name: Status - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.statuses.pod" . | nindent 8 }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: Node - value: Unschedulable - - key: Pod IP - value: NotAllocated - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: "stock-cluster-/v1/pods" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "stock-namespace-v1.pods" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "P" - "title" "pod" - "backgroundColor" "#009596" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "pod-details" - ) | nindent 12 - }} - - name: Node - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "N" - "title" "node" - "backgroundColor" "#8476d1" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".spec.nodeName" - "factory" "node-details" - ) | nindent 12 - }} - - jsonPath: .spec.restartPolicy - name: Restart Policy - type: string - - jsonPath: .status.podIP - name: Pod IP - type: string - # - jsonPath: .status.conditions[?(@.type=="ContainersReady")].status - # name: Status Containers - # type: string - # - jsonPath: .status.conditions[?(@.type=="PodScheduled")].status - # name: Status Scheduled - # type: string - - jsonPath: .status.qosClass - name: QOS - type: string - - name: Status - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.statuses.pod" . | nindent 8 }} - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsUndefinedValues: - - key: Node - value: Unschedulable - - key: Pod IP - value: NotAllocated - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: "stock-namespace-/v1/pods" diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.secrets.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.secrets.yaml deleted file mode 100644 index dbcc6f3a..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1.secrets.yaml +++ /dev/null @@ -1,111 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "stock-cluster-v1.secrets" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "S" - "title" "secret" - "backgroundColor" "#c46100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "secret-details" - ) | nindent 12 - }} - - jsonPath: .metadata.namespace - name: Namespace - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 12 - }} - - jsonPath: .type - name: Type - type: string - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - id: "stock-cluster-/v1/secrets" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: "stock-namespace-v1.secrets" -spec: - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.icon" (dict - "text" "S" - "title" "secret" - "backgroundColor" "#c46100" - )| nindent 8 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".metadata.name" - "namespace" "{reqsJsonPath[0]['.metadata.namespace']['-']}" - "factory" "secret-details" - ) | nindent 12 - }} - - jsonPath: .type - name: Type - type: string - - jsonPath: .metadata.creationTimestamp - name: Created - type: factory - customProps: - disableEventBubbling: true - items: - {{ include "incloud-web-resources.factory.timeblock" (dict - "req" ".metadata.creationTimestamp" - ) | nindent 8 - }} - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Namespace - value: "-" - id: "stock-namespace-/v1/secrets" diff --git a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1alpha1.cozystack.io.workloads.yaml b/packages/system/dashboard-config/templates/CustomColumnsOverride/v1alpha1.cozystack.io.workloads.yaml deleted file mode 100644 index 2dc62cc3..00000000 --- a/packages/system/dashboard-config/templates/CustomColumnsOverride/v1alpha1.cozystack.io.workloads.yaml +++ /dev/null @@ -1,42 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomColumnsOverride -metadata: - name: factory-details-v1alpha1.cozystack.io.workloads -spec: - id: factory-details-v1alpha1.cozystack.io.workloads - additionalPrinterColumns: - - jsonPath: .metadata.name - name: Name - type: string - - jsonPath: .status.kind - name: Kind - type: string - - jsonPath: .status.type - name: Type - type: string - - jsonPath: .status.resources.cpu - name: CPU - type: string - - jsonPath: .status.resources.memory - name: Memory - type: string - - jsonPath: .status.operational - name: Operational - type: string - additionalPrinterColumnsTrimLengths: - - key: Name - value: 64 - additionalPrinterColumnsUndefinedValues: - - key: Name - value: "-" - - key: Kind - value: "-" - - key: Type - value: "-" - - key: CPU - value: "-" - - key: Memory - value: "-" - - key: Operational - value: "-" diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/networking.k8s.io.v1.ingresses.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/networking.k8s.io.v1.ingresses.yaml deleted file mode 100644 index e37effd6..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/networking.k8s.io.v1.ingresses.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-networking.k8s.io.v1.ingresses -spec: - customizationId: "default-/networking.k8s.io/v1/ingresses" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/storage.k8s.io.v1.storageclasses.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/storage.k8s.io.v1.storageclasses.yaml deleted file mode 100644 index 6d52f275..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/storage.k8s.io.v1.storageclasses.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-storage.k8s.io.v1.storageclasses -spec: - customizationId: "default-/storage.k8s.io/v1/storageclasses" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.configmaps.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.configmaps.yaml deleted file mode 100644 index 739cac5a..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.configmaps.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.configmaps -spec: - customizationId: "default-/v1/configmaps" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.namespaces.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.namespaces.yaml deleted file mode 100644 index 6106bf8f..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.namespaces.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.namespaces -spec: - customizationId: "default-/v1/namespaces" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system-clusterscope" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.nodes.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.nodes.yaml deleted file mode 100644 index e2b4bc3c..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.nodes.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.nodes -spec: - customizationId: "default-/v1/nodes" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system-clusterscope" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.persistentvolumeclaims.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.persistentvolumeclaims.yaml deleted file mode 100644 index e82b72ea..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.persistentvolumeclaims.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.persistentvolumeclaims -spec: - customizationId: "default-/v1/persistentvolumeclaims" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.persistentvolumes.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.persistentvolumes.yaml deleted file mode 100644 index 20472c98..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.persistentvolumes.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.persistentvolumes -spec: - customizationId: "default-/v1/persistentvolumes" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.pods.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.pods.yaml deleted file mode 100644 index 7acf2ddb..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.pods.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.pods -spec: - customizationId: "default-/v1/pods" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.secrets.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.secrets.yaml deleted file mode 100644 index a5127240..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.secrets.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.secrets -spec: - customizationId: "default-/v1/secrets" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/CustomFormOverride/v1.services.yaml b/packages/system/dashboard-config/templates/CustomFormOverride/v1.services.yaml deleted file mode 100644 index ed91341e..00000000 --- a/packages/system/dashboard-config/templates/CustomFormOverride/v1.services.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: CustomFormsOverride -metadata: - name: default-v1.services -spec: - customizationId: "default-/v1/services" - hidden: - {{ include "incloud-web-resources.customformoverride.hidden.metadata.system" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.metadata.api" . | nindent 4 }} - {{ include "incloud-web-resources.customformoverride.hidden.status" . | nindent 4 }} - schema: {} - strategy: merge diff --git a/packages/system/dashboard-config/templates/Factory/cozy-marketplace.yaml b/packages/system/dashboard-config/templates/Factory/cozy-marketplace.yaml deleted file mode 100644 index a2857001..00000000 --- a/packages/system/dashboard-config/templates/Factory/cozy-marketplace.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: marketplace -spec: - key: marketplace - sidebarTags: - - marketplace-sidebar - withScrollableMainContentCard: true - urlsToFetch: [] - data: - - type: ContentCard - data: - id: 31 - title: "Marketplace" - style: - flexGrow: 1 - children: - - type: MarketplaceCard - data: - id: 311 - clusterNamePartOfUrl: "{2}" - namespacePartOfUrl: "{3}" - baseApiGroup: dashboard.cozystack.io - baseApiVersion: v1alpha1 - mpResourceName: marketplacepanels - mpResourceKind: MarketplacePanel - baseprefix: openapi-ui diff --git a/packages/system/dashboard-config/templates/Factory/helpers/annotations.tpl b/packages/system/dashboard-config/templates/Factory/helpers/annotations.tpl deleted file mode 100644 index 8e49c717..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/annotations.tpl +++ /dev/null @@ -1,30 +0,0 @@ -{{- define "incloud-web-resources.factory.annotations.block" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $jsonPath := (default ".metadata.annotations" .jsonPath) -}} -{{- $endpoint := (default "" .endpoint) -}} -{{- $pathToValue := (default "/metadata/annotations" .pathToValue) -}} -- type: antdText - data: - id: annotations - strong: true - text: Annotations -- type: Annotations - data: - id: annotations - reqIndex: 0 - jsonPathToObj: "{{ $jsonPath }}" - text: "~counter~ Annotations" - errorText: "0 Annotations" - notificationSuccessMessage: "Updated successfully" - notificationSuccessMessageDescription: "Annotations have been updated" - modalTitle: "Edit annotations" - modalDescriptionText: "" - inputLabel: "" - endpoint: "{{ $endpoint }}" - pathToValue: "{{ $pathToValue }}" - editModalWidth: "800px" - cols: - - 11 - - 11 - - 2 -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/counters.tpl b/packages/system/dashboard-config/templates/Factory/helpers/counters.tpl deleted file mode 100644 index da6c1fdf..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/counters.tpl +++ /dev/null @@ -1,35 +0,0 @@ -{{- define "incloud-web-resources.factory.counters.fields" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $type := (default "counter" .type) -}} -{{- $title := (default "" .title) -}} -- type: antdText - data: - id: {{ printf "%s-label" $type }} - strong: true - text: "{{ $title }}" -- type: ItemCounter - data: - id: {{ printf "%s-counter" $type }} - text: "~counter~ {{ $type }}" - reqIndex: {{$i}} - jsonPathToArray: "{{ .jsonPath | default "" }}" - errorText: "Error" -{{- end -}} - -{{- define "incloud-web-resources.factory.counters.object.fields" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $type := (default "counter" .type) -}} -{{- $title := (default "" .title) -}} -- type: antdText - data: - id: {{ printf "%s-label" $type }} - strong: true - text: "{{ $title }}" -- type: KeyCounter - data: - id: {{ printf "%s-counter" $type }} - text: "~counter~ {{ $type }}" - reqIndex: {{$i}} - jsonPathToObj: "{{ .jsonPath | default "" }}" - errorText: "Error" -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/labels.tpl b/packages/system/dashboard-config/templates/Factory/helpers/labels.tpl deleted file mode 100644 index 9fe7afb3..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/labels.tpl +++ /dev/null @@ -1,64 +0,0 @@ -{{- define "incloud-web-resources.factory.labels" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $type := (default "labels" .type) -}} -{{- $title := (default "Labels" .title) -}} -{{- $jsonPath := (default ".metadata.labels" .jsonPath) -}} -{{- $endpoint := (default "" .endpoint) -}} -{{- $pathToValue := (default "/metadata/labels" .pathToValue) -}} -{{- $maxTagTextLength := (default 35 .maxTagTextLength) -}} -{{- $maxEditTagTextLength := (default 35 .maxEditTagTextLength) -}} -{{- $notificationSuccessMessage := (default "Updated successfully" .notificationSuccessMessage) -}} -{{- $notificationSuccessMessageDescription := (default "Labels have been updated" .notificationSuccessMessageDescription) -}} -{{- $modalTitle := (default "Edit labels" .modalTitle) -}} -{{- $modalDescriptionText := (default "" .modalDescriptionText) -}} -{{- $inputLabel := (default "" .inputLabel) -}} -{{- $containerMarginTop := (default "-30px" .containerMarginTop) -}} -- type: antdText - data: - id: {{ printf "%s-title" $type }} - text: "{{ $title }}" - strong: true - style: - fontSize: 14 -- type: Labels - data: - id: {{ printf "%s-editor" $type }} - reqIndex: {{ $i }} - jsonPathToLabels: "{{ $jsonPath }}" - selectProps: - maxTagTextLength: {{ $maxTagTextLength }} - notificationSuccessMessage: "{{ $notificationSuccessMessage }}" - notificationSuccessMessageDescription: "{{ $notificationSuccessMessageDescription }}" - modalTitle: "{{ $modalTitle }}" - modalDescriptionText: "{{ $modalDescriptionText }}" - inputLabel: "{{ $inputLabel }}" - containerStyle: - marginTop: "{{ $containerMarginTop }}" - maxEditTagTextLength: {{ $maxEditTagTextLength }} - endpoint: "{{ $endpoint }}" - pathToValue: "{{ $pathToValue }}" - editModalWidth: 650 - paddingContainerEnd: "24px" -{{- end -}} - -{{- define "incloud-web-resources.factory.labels.base.selector" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $type := (default "pod-selector" .type) -}} -{{- $title := (default "Pod selector" .title) -}} -{{- $jsonPath := (default ".spec.template.metadata.labels" .jsonPath) -}} -- type: antdText - data: - id: {{ printf "%s-selector" $type }} - text: "{{ $title }}" - strong: true - style: - fontSize: 14 -- type: LabelsToSearchParams - data: - id: {{ printf "%s-to-search-params" $type }} - reqIndex: {{$i}} - jsonPathToLabels: "{{ $jsonPath }}" - linkPrefix: "{{ .linkPrefix | default "/openapi-ui/{2}/search" }}" - errorText: "-" -{{- end -}} - diff --git a/packages/system/dashboard-config/templates/Factory/helpers/links.tpl b/packages/system/dashboard-config/templates/Factory/helpers/links.tpl deleted file mode 100644 index 7bfa6023..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/links.tpl +++ /dev/null @@ -1,43 +0,0 @@ -{{- define "incloud-web-resources.factory.links.details" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $type := (default "" .type) -}} -{{- $title := (default "" .title) -}} -{{- $jsonPath := (default "" .jsonPath) -}} -{{- $factory := (default "" .factory) -}} -{{- $ns := (default "" .namespace) -}} - -{{- $nsPart := "" -}} -{{- if ne $ns "" }} - {{- $nsPart = printf "%s/" $ns -}} -{{- end }} -- type: parsedText - data: - id: {{ printf "%s-title" $type }} - strong: true - text: "{{ $title }}" - style: - fontWeight: bold -- type: antdLink - data: - id: {{ printf "%s-link" $type }} - text: "{reqsJsonPath[{{$i}}]['{{ $jsonPath }}']['-']}" - href: "/openapi-ui/{2}/{{$nsPart}}factory/{{ $factory }}/{reqsJsonPath[{{$i}}]['{{ $jsonPath }}']['-']}" -{{- end -}} - -{{- define "incloud-web-resources.factory.linkblock" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $type := (default "" .type) -}} -{{- $jsonPath := (default "" .jsonPath) -}} -{{- $factory := (default "" .factory) -}} -{{- $ns := (default "" .namespace) -}} - -{{- $nsPart := "" -}} -{{- if ne $ns "" }} - {{- $nsPart = printf "%s/" $ns -}} -{{- end }} -- type: antdLink - data: - id: {{ printf "%s-link" $type }} - text: "{reqsJsonPath[{{$i}}]['{{ $jsonPath }}']['-']}" - href: "/openapi-ui/{2}/{{$nsPart}}factory/{{ $factory }}/{reqsJsonPath[{{$i}}]['{{ $jsonPath }}']['-']}" -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/statuses.tpl b/packages/system/dashboard-config/templates/Factory/helpers/statuses.tpl deleted file mode 100644 index 5725600c..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/statuses.tpl +++ /dev/null @@ -1,189 +0,0 @@ -{{- define "incloud-web-resources.factory.statuses.deployment" -}} -- type: StatusText - data: - id: header-status - # 1) Collect all possible Deployment conditions - values: - - "{reqsJsonPath[0]['.status.conditions[*].reason']['-']}" - - # 2) Criteria: positive / negative; neutral goes to fallback - criteriaSuccess: equals - valueToCompareSuccess: - # Positive reasons - - "MinimumReplicasAvailable" # Available: all replicas are healthy - - "NewReplicaSetAvailable" # Progressing: new RS serves traffic - - "ReplicaSetUpdated" # Progressing: RS is updated/synced - - "Complete" # Update completed successfully - - criteriaError: equals - valueToCompareError: - # Negative reasons - - "DeploymentReplicaFailure" # General replica failure - - "FailedCreate" # Failed to create Pod/RS - - "FailedDelete" # Failed to delete resource - - "FailedScaleUp" # Failed to scale up - - "FailedScaleDown" # Failed to scale down - - # 3) Texts to display - successText: "Available" - errorText: "Error" - fallbackText: "Progressing" - - # Notes on neutral/fallback cases: - # - ReplicaSetUpdated → neutral/positive (update in progress) - # - ScalingReplicaSet → neutral (normal scale up/down) - # - Paused / DeploymentPaused→ neutral (manually paused by admin) - # - NewReplicaSetCreated → neutral (new RS created, not yet serving) - # - FoundNewReplicaSet → neutral (RS found, syncing) - # - MinimumReplicasUnavailable → neutral (some replicas not ready yet) - # - ProgressDeadlineExceeded → error-like, stuck in progress -{{- end -}} - -{{- define "incloud-web-resources.factory.statuses.pod" -}} -- type: StatusText - data: - id: pod-status - - # --- Collected values from Pod status ----------------------------------- - values: - # Init containers - - "{reqsJsonPath[0]['.status.initContainerStatuses[*].state.waiting.reason']}" - - "{reqsJsonPath[0]['.status.initContainerStatuses[*].state.terminated.reason']}" - - "{reqsJsonPath[0]['.status.initContainerStatuses[*].lastState.terminated.reason']}" - - # Main containers - - "{reqsJsonPath[0]['.status.containerStatuses[*].state.waiting.reason']}" - - "{reqsJsonPath[0]['.status.containerStatuses[*].state.terminated.reason']}" - - "{reqsJsonPath[0]['.status.containerStatuses[*].lastState.terminated.reason']}" - - # Pod phase and general reason - - "{reqsJsonPath[0]['.status.phase']}" - - "{reqsJsonPath[0]['.status.reason']}" - - # Condition reasons (PodScheduled / Initialized / ContainersReady / Ready) - - "{reqsJsonPath[0]['.status.conditions[*].reason']}" - - # --- Success criteria --------------------------------------------------- - criteriaSuccess: notEquals - stategySuccess: every - valueToCompareSuccess: - # Graceful or expected state transitions - - "Preempted" - - "Shutdown" - - "NodeShutdown" - - "DisruptionTarget" - - # Transitional states (may require timeout) - - "Unschedulable" - - "SchedulingGated" - - "ContainersNotReady" - - "ContainersNotInitialized" - - # Temporary failures - - "BackOff" - - # Controlled shutdowns or benign errors - - "PreStopHookError" - - "KillError" - - "ContainerStatusUnknown" - - # --- Error criteria ----------------------------------------------------- - criteriaError: equals - strategyError: every - valueToCompareError: - # Pod-level fatal phases or errors - - "Failed" - - "Unknown" - - "Evicted" - - "NodeLost" - - "UnexpectedAdmissionError" - - # Scheduler-related failures - - "SchedulerError" - - "FailedScheduling" - - # Container-level fatal errors - - "CrashLoopBackOff" - - "ImagePullBackOff" - - "ErrImagePull" - - "ErrImageNeverPull" - - "InvalidImageName" - - "ImageInspectError" - - "CreateContainerConfigError" - - "CreateContainerError" - - "RunContainerError" - - "StartError" - - "PostStartHookError" - - "ContainerCannotRun" - - "OOMKilled" - - "Error" - - "DeadlineExceeded" - - "CreatePodSandboxError" - - # --- Output text rendering ---------------------------------------------- - successText: "{reqsJsonPath[0]['.status.phase']}" - errorText: "Error" - fallbackText: "Progressing" -{{- end -}} - -{{- define "incloud-web-resources.factory.statuses.node" -}} -- type: StatusText - data: - id: node-status - - # --- Collected values from Node status ---------------------------------- - values: - # Node phase and conditions - - "{reqsJsonPath[0]['.status.conditions[?(@.status=='True')].reason']['-']}" - - # --- Success criteria --------------------------------------------------- - criteriaSuccess: equals - stategySuccess: every - valueToCompareSuccess: - "KubeletReady" - - # --- Error criteria ----------------------------------------------------- - criteriaError: equals - strategyError: every - valueToCompareError: - # Node condition failures - - "KernelDeadlock" - - "ReadonlyFilesystem" - - "NetworkUnavailable" - - "MemoryPressure" - - "DiskPressure" - - "PIDPressure" - - # --- Output text rendering ---------------------------------------------- - successText: "Available" - errorText: "Unavailable" - fallbackText: "Progressing" -{{- end -}} - -{{- define "incloud-web-resources.factory.statuses.job" -}} -- type: StatusText - data: - id: header-status - - # --- Collected values from Job conditions ------------------------------- - values: - # Extracts the type of any condition where type == 'Complete' or 'Failed' - - "{reqsJsonPath[0]['.status.conditions[?(@.type=='Complete' || @.type=='Failed')].type']['-']}" - - # --- Success criteria --------------------------------------------------- - criteriaSuccess: equals - stategySuccess: every - valueToCompareSuccess: - - "Complete" # Job succeeded - - # --- Error criteria ----------------------------------------------------- - criteriaError: equals - strategyError: every # ← likely meant to be `strategyError` - valueToCompareError: - - "Failed" # Job failed - - # --- Output text rendering ---------------------------------------------- - successText: "Available" - errorText: "Unavailable" - fallbackText: "Progressing" -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/tables.tpl b/packages/system/dashboard-config/templates/Factory/helpers/tables.tpl deleted file mode 100644 index a8e211cf..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/tables.tpl +++ /dev/null @@ -1,40 +0,0 @@ -{{- define "incloud-web-resources.factory.containers.table" -}} - {{- $i := (default 0 .reqIndex) -}} - {{- $type := (default "" .type) -}} - {{- $title := (default "Init containers" .title) -}} - {{- $jsonPath := (default "" .jsonPath) -}} - {{- $pathToItems := (default "" .pathToItems) -}} - {{- $apiGroup := (default "" .apiGroup) -}} - {{- $kind := (default "" .kind) -}} - {{- $resourceName := (default "" .resourceName) -}} - {{- $namespace := (default "" .namespace) -}} - {{- $namespacePart := "" -}} - {{- if ne $namespace "" }} - {{- $namespacePart = printf "namespaces/%s/" $namespace -}} - {{- end }} -- type: VisibilityContainer - data: - id: {{ printf "%s-container" $type }} - value: "{reqsJsonPath[{{$i}}]['{{ $jsonPath }}']['-']}" - style: - margin: 0 - padding: 0 - children: - - type: antdText - data: - id: {{ printf "%s-title" $type }} - text: "{{ $title }}" - strong: true - style: - fontSize: 22 - marginBottom: 32px - - type: EnrichedTable - data: - id: {{ printf "%s-table" $type }} - fetchUrl: "/api/clusters/{2}/k8s/{{ $apiGroup }}/{{$namespacePart}}{{ $kind }}/{{$resourceName}}" - clusterNamePartOfUrl: "{2}" - customizationId: {{ .customizationId | default ("") }} - baseprefix: "/openapi-ui" - withoutControls: {{ default true .withoutControls }} - pathToItems: {{ $pathToItems }} -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/taints.tpl b/packages/system/dashboard-config/templates/Factory/helpers/taints.tpl deleted file mode 100644 index 1d10b69d..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/taints.tpl +++ /dev/null @@ -1,30 +0,0 @@ -{{- define "incloud-web-resources.factory.taints.block" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $jsonPathToArray := (default "" .jsonPathToArray) -}} -{{- $endpoint := (default "" .endpoint) -}} -{{- $pathToValue := (default "" .pathToValue) -}} -- type: antdText - data: - id: taints - strong: true - text: Taints -- type: Taints - data: - id: taints - reqIndex: {{ $i }} - jsonPathToArray: "{{ $jsonPathToArray }}" - text: "~counter~ Taints" - errorText: "0 Taints" - notificationSuccessMessage: "Updated successfully" - notificationSuccessMessageDescription: "Taints have been updated" - modalTitle: "Edit taints" - modalDescriptionText: "" - inputLabel: "" - endpoint: "{{ $endpoint }}" - pathToValue: "{{ $pathToValue }}" - editModalWidth: "800px" - cols: - - 8 - - 8 - - 6 -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/times.tpl b/packages/system/dashboard-config/templates/Factory/helpers/times.tpl deleted file mode 100644 index f939553a..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/times.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{{- define "incloud-web-resources.factory.time.create" -}} -{{- $i := (default 0 .reqIndex) -}} -- type: antdText - data: - id: {{ .labelId | default "time-label" }} - strong: true - text: "{{ .text | default "Created" }}" -- type: antdFlex - data: - id: {{ .id | default "time-block" }} - align: center - gap: 6 - children: - - type: antdText - data: - id: {{ .iconId | default "time-icon" }} - text: "🌐" - - type: parsedText - data: - id: {{ .valueId | default "time-value" }} - text: "{reqsJsonPath[{{$i}}]['{{ .req }}']['-']}" - formatter: timestamp -{{- end -}} - -{{- define "incloud-web-resources.factory.timeblock" -}} -{{- $i := (default 0 .reqIndex) -}} -- type: antdFlex - data: - id: {{ .id | default "time-block" }} - align: center - gap: 6 - children: - - type: antdText - data: - id: {{ .iconId | default "time-icon" }} - text: "🌐" - - type: parsedText - data: - id: {{ .valueId | default "time-value" }} - text: "{reqsJsonPath[{{$i}}]['{{ .req }}']['-']}" - formatter: timestamp -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/helpers/tolerations.tpl b/packages/system/dashboard-config/templates/Factory/helpers/tolerations.tpl deleted file mode 100644 index 0123a3b6..00000000 --- a/packages/system/dashboard-config/templates/Factory/helpers/tolerations.tpl +++ /dev/null @@ -1,32 +0,0 @@ -{{- define "incloud-web-resources.factory.tolerations.block" -}} -{{- $i := (default 0 .reqIndex) -}} -{{- $jsonPathToArray := (default "" .jsonPathToArray) -}} -{{- $endpoint := (default "" .endpoint) -}} -{{- $pathToValue := (default "" .pathToValue) -}} -- type: antdText - data: - id: tolerations - strong: true - text: Tolerations -- type: Tolerations - data: - id: tolerations - reqIndex: {{ $i }} - jsonPathToArray: "{{ $jsonPathToArray }}" - text: "~counter~ Tolerations" - errorText: "0 Tolerations" - notificationSuccessMessage: "Updated successfully" - notificationSuccessMessageDescription: "Tolerations have been updated" - modalTitle: "Edit tolerations" - modalDescriptionText: "" - inputLabel: "" - endpoint: "{{ $endpoint }}" - pathToValue: "{{ $pathToValue }}" - editModalWidth: "1000px" - cols: - - 8 - - 3 - - 8 - - 4 - - 1 -{{- end -}} diff --git a/packages/system/dashboard-config/templates/Factory/namespace-details.yaml b/packages/system/dashboard-config/templates/Factory/namespace-details.yaml deleted file mode 100644 index 759d18dc..00000000 --- a/packages/system/dashboard-config/templates/Factory/namespace-details.yaml +++ /dev/null @@ -1,232 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: namespace-details -spec: - # Unique key for this factory configuration - key: namespace-details - - # Sidebar category tags - sidebarTags: - - namespace-sidebar - - # Enable scrollable content card for main section - withScrollableMainContentCard: true - - # API endpoint for fetching Namespace details - urlsToFetch: - - "/api/clusters/{2}/k8s/api/v1/namespaces/{5}" - - data: - # === HEADER ROW === - - type: antdFlex - data: - id: header-row - gap: 6 - align: center - style: - marginBottom: 24px - children: - # Badge with resource short name - - type: antdText - data: - id: header-badge - text: "NS" - title: Namespace - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#a25792ff" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 - - # Namespace name - - type: parsedText - data: - id: header-name - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - style: - fontSize: 20px - lineHeight: 24px - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - - # === MAIN TABS === - - type: antdTabs - data: - id: tabs-root - defaultActiveKey: "details" - items: - # ------ DETAILS TAB ------ - - key: "details" - label: "Details" - children: - # Main card container for details section - - type: ContentCard - data: - id: details-card - style: - marginBottom: 24px - children: - # Section title - - type: antdText - data: - id: details-title - text: "Namespace details" - strong: true - style: - fontSize: 20 - marginBottom: 12px - - # Spacer for visual separation - - type: Spacer - data: - id: details-spacer - "$space": 16 - - # Grid layout: left and right columns - - type: antdRow - data: - id: details-grid - gutter: [48, 12] - children: - # LEFT COLUMN: metadata - - type: antdCol - data: - id: col-left - span: 12 - children: - - type: antdFlex - data: - id: left-stack - vertical: true - gap: 24 - children: - # Namespace name block - - type: antdFlex - data: - id: name-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: name-label - strong: true - text: "Name" - - type: parsedText - data: - id: name-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - - # Labels display block - - type: antdFlex - data: - id: labels-block - vertical: true - gap: 8 - children: - {{ include "incloud-web-resources.factory.labels" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{5}" - ) | nindent 34 - }} - - # Annotations counter block - - type: antdFlex - data: - id: ds-annotations - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.annotations.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{5}" - ) | nindent 34 - }} - - # Creation time block - - type: antdFlex - data: - id: created-time-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.time.create" (dict - "req" ".metadata.creationTimestamp" - "text" "Created" - ) | nindent 38 - }} - - # Owner information block - # - type: antdFlex - # data: - # id: owner-block - # vertical: true - # gap: 4 - # children: - # - type: antdText - # data: - # id: owner-label - # strong: true - # text: "Owner" - # - type: antdFlex - # data: - # id: owner-value-container - # gap: 6 - # align: center - # children: - # - type: antdText - # data: - # id: owner-value - # text: "No owner" - # style: - # color: "#FF0000" - - # RIGHT COLUMN: status - - type: antdCol - data: - id: col-right - span: 12 - children: - - type: antdFlex - data: - id: right-stack - vertical: true - gap: 24 - children: - # Namespace status phase - - type: antdFlex - data: - id: status-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: status-label - strong: true - text: "Status" - - type: parsedText - data: - id: status-value - text: "{reqsJsonPath[0]['.status.phase']['-']}" - - # ------ YAML TAB ------ - - key: "yaml" - label: "YAML" - children: - # YAML editor for Namespace manifest - - type: YamlEditorSingleton - data: - id: yaml-editor - cluster: "{2}" - isNameSpaced: false - type: "builtin" - typeName: namespaces - prefillValuesRequestIndex: 0 - substractHeight: 400 diff --git a/packages/system/dashboard-config/templates/Factory/node-details.yaml b/packages/system/dashboard-config/templates/Factory/node-details.yaml deleted file mode 100644 index b0012d61..00000000 --- a/packages/system/dashboard-config/templates/Factory/node-details.yaml +++ /dev/null @@ -1,511 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: node-details -spec: - # Unique key for this factory configuration - key: node-details - - # Sidebar category tags - sidebarTags: - - node-sidebar - - # Enable scrollable content card for main section - withScrollableMainContentCard: true - - # API endpoint for fetching Node details - urlsToFetch: - - "/api/clusters/{2}/k8s/api/v1/nodes/{5}" - - data: - # === HEADER ROW === - - type: antdFlex - data: - id: header-row - gap: 6 - align: center - style: - marginBottom: 24px - children: - # Badge with resource short name - - type: antdText - data: - id: header-badge - text: "N" - title: nodes - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#8476d1" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 - - # Node name - - type: parsedText - data: - id: header-name - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - style: - fontSize: 20px - lineHeight: 24px - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - - - type: antdFlex - data: - id: status-header-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.statuses.node" . | nindent 12 }} - - # === MAIN TABS === - - type: antdTabs - data: - id: tabs-root - defaultActiveKey: "details" - items: - # ------ DETAILS TAB ------ - - key: "details" - label: "Details" - children: - # Main card container for details section - - type: ContentCard - data: - id: details-card - style: - marginBottom: 24px - children: - # Section title - - type: antdText - data: - id: details-title - text: "Node details" - strong: true - style: - fontSize: 20 - marginBottom: 12px - - # Spacer for visual separation - - type: Spacer - data: - id: details-spacer - "$space": 16 - - # Grid layout: left and right columns - - type: antdRow - data: - id: details-grid - gutter: [48, 12] - children: - # LEFT COLUMN: general metadata - - type: antdCol - data: - id: col-left - span: 12 - children: - - type: antdFlex - data: - id: left-stack - vertical: true - gap: 24 - children: - # Node name block - - type: antdFlex - data: - id: name-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: name-label - strong: true - text: "Name" - - type: parsedText - data: - id: name-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - - # External ID block - - type: antdFlex - data: - id: external-id-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: external-id-label - strong: true - text: "External ID" - - type: parsedText - data: - id: external-id-value - text: "{reqsJsonPath[0]['.spec.externalID']['-']}" - - # Uptime block (placeholder for future calculated value) - # TODO To be done - # - type: antdFlex - # data: - # id: uptime-block - # vertical: true - # gap: 4 - # children: - # - type: antdText - # data: - # id: uptime-label - # strong: true - # text: "Uptime" - # - type: parsedText - # data: - # id: uptime-value - # text: "Function needed to calculate uptime" - - # Node address block - - type: antdFlex - data: - id: node-address-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: node-address-label - strong: true - text: "Node address" - - type: antdFlex - data: - id: node-address-stack - vertical: true - gap: 2 - children: - - type: ArrayOfObjectsToKeyValues - data: - id: node-address - reqIndex: 0 - jsonPathToArray: ".status.addresses" - keyFieldName: type - valueFieldName: address - keyFieldStyle: - color: "#aaabac" - - # Labels block - - type: antdFlex - data: - id: labels-block - vertical: true - gap: 8 - children: - {{ include "incloud-web-resources.factory.labels" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/nodes/{5}" - ) | nindent 34 - }} - - # Taints counter block - - type: antdFlex - data: - id: taints-counter-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.taints.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/nodes/{5}" - "jsonPathToArray" ".spec.taints" - "pathToValue" "/spec/taints" - ) | nindent 34 - }} - - # Annotations counter block - - type: antdFlex - data: - id: ds-annotations - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.annotations.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/nodes/{5}" - ) | nindent 34 - }} - - # Provider ID block - - type: antdFlex - data: - id: provider-id-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: provider-id-label - strong: true - text: "Provider ID" - - type: parsedText - data: - id: provider-id-value - text: "{reqsJsonPath[0]['.spec.providerID']['-']}" - - # Creation time block - - type: antdFlex - data: - id: created-time-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.time.create" (dict - "req" ".metadata.creationTimestamp" - "text" "Created" - ) | nindent 38 - }} - - # RIGHT COLUMN: node info and status - - type: antdCol - data: - id: col-right - span: 12 - children: - - type: antdFlex - data: - id: right-stack - vertical: true - gap: 24 - children: - # Status block - - type: antdFlex - data: - id: status-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: status-label - strong: true - text: "Status" - - type: antdFlex - data: - id: status-header-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.statuses.node" . | nindent 38 }} - - # Operating system block - - type: antdFlex - data: - id: operating-system-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: operating-system-label - strong: true - text: "Operating system" - - type: parsedText - data: - id: operating-system-value - text: "{reqsJsonPath[0]['.status.nodeInfo.operatingSystem']['-']}" - - # OS image block - - type: antdFlex - data: - id: os-image-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: os-image-label - strong: true - text: "OS image" - - type: parsedText - data: - id: os-image-value - text: "{reqsJsonPath[0]['.status.nodeInfo.osImage']['-']}" - - # Architecture block - - type: antdFlex - data: - id: architecture-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: architecture-label - strong: true - text: "Architecture" - - type: parsedText - data: - id: architecture-value - text: "{reqsJsonPath[0]['.status.nodeInfo.architecture']['-']}" - - # Kernel version block - - type: antdFlex - data: - id: kernel-version-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: kernel-version-label - strong: true - text: "Kernel versions" - - type: parsedText - data: - id: kernel-version-value - text: "{reqsJsonPath[0]['.status.nodeInfo.kernelVersion']['-']}" - - # Boot ID block - - type: antdFlex - data: - id: boot-id-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: boot-id-label - strong: true - text: "Boot ID" - - type: parsedText - data: - id: boot-id-value - text: "{reqsJsonPath[0]['.status.nodeInfo.bootID']['-']}" - - # Container runtime block - - type: antdFlex - data: - id: container-runtime-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: container-runtime-label - strong: true - text: "Container runtime" - - type: parsedText - data: - id: container-runtime-value - text: "{reqsJsonPath[0]['.status.nodeInfo.containerRuntimeVersion']['-']}" - - # Kubelet version block - - type: antdFlex - data: - id: kubelet-version-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: kubelet-version-label - strong: true - text: "Kubelet version" - - type: parsedText - data: - id: kubelet-version-value - text: "{reqsJsonPath[0]['.status.nodeInfo.kubeletVersion']['-']}" - - # CONDITIONS SECTION - - type: antdCol - data: - id: conditions-col - style: - marginTop: 32px - padding: 16px - children: - - type: antdText - data: - id: conditions-title - text: "Conditions" - strong: true - style: - fontSize: 22 - - type: EnrichedTable - data: - id: conditions-table - fetchUrl: "/api/clusters/{2}/k8s/api/v1/nodes/{5}" - clusterNamePartOfUrl: "{2}" - customizationId: "factory-status-conditions" - baseprefix: "/openapi-ui" - withoutControls: true - pathToItems: ".status.conditions" - - # IMAGES SECTION - - type: antdCol - data: - id: images-col - style: - marginTop: 32px - padding: 16px - children: - - type: antdText - data: - id: images-title - text: "Images" - strong: true - style: - fontSize: 22 - - type: EnrichedTable - data: - id: images-table - fetchUrl: "/api/clusters/{2}/k8s/api/v1/nodes/{5}" - clusterNamePartOfUrl: "{2}" - customizationId: "factory-node-images" - baseprefix: "/openapi-ui" - withoutControls: true - pathToItems: ".status.images" - - # ------ YAML TAB ------ - - key: "yaml" - label: "YAML" - children: - # YAML editor for Node manifest - - type: YamlEditorSingleton - data: - id: yaml-editor - cluster: "{2}" - isNameSpaced: false - type: "builtin" - typeName: nodes - prefillValuesRequestIndex: 0 - substractHeight: 400 - - # ------ PODS TAB ------ - - key: "pods" - label: "Pods" - children: - # Table of Pods scheduled on this Node - - type: EnrichedTable - data: - id: pods-table - fetchUrl: "/api/clusters/{2}/k8s/api/v1/pods" - clusterNamePartOfUrl: "{2}" - customizationId: "factory-node-details-/v1/pods" - baseprefix: "/openapi-ui" - withoutControls: true - fieldSelector: - fieldName: "spec.nodeName" - parsedText: "{reqsJsonPath[0]['.metadata.name']['-']}" - pathToItems: ".items" - - # ------ TERMINAL TAB ------ - - key: "terminal" - label: "Terminal" - children: - # Interactive node terminal - - type: NodeTerminal - data: - id: terminal - cluster: "{2}" - nodeName: "{reqsJsonPath[0]['.metadata.name']['-']}" - substractHeight: 400 diff --git a/packages/system/dashboard-config/templates/Factory/pod-details.yaml b/packages/system/dashboard-config/templates/Factory/pod-details.yaml deleted file mode 100644 index 376ccf51..00000000 --- a/packages/system/dashboard-config/templates/Factory/pod-details.yaml +++ /dev/null @@ -1,533 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: pod-details -spec: - key: pod-details - sidebarTags: - - pods-sidebar - withScrollableMainContentCard: true - urlsToFetch: - - "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}" - - # Header row with badge, pod name, and status - data: - - type: antdFlex - data: - id: header-row - gap: 6 - align: center - style: - marginBottom: 24px - children: - # Pod badge - - type: antdText - data: - id: header-badge - text: P - title: Pods - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#009596" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 - # Pod name next to badge - - type: parsedText - data: - id: header-pod-name - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - style: - fontSize: 20px - lineHeight: 24px - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - - - type: antdFlex - data: - id: status-header-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.statuses.pod" . | nindent 38 }} - - # Tabs with Details, YAML, Logs, and Terminal views - - type: antdTabs - data: - id: pod-tabs - defaultActiveKey: "details" - items: - # Details tab with metadata and spec - - key: "details" - label: "Details" - children: - - type: ContentCard - data: - id: details-card - style: - marginBottom: 24px - children: - # Title of the details section - - type: antdText - data: - id: details-title - text: "Pod details" - strong: true - style: - fontSize: 20 - marginBottom: 12px - # Spacer before grid - - type: Spacer - data: - id: details-spacer - "$space": 16 - # Two-column layout for metadata (left) and status/spec (right) - - - type: antdRow - data: - id: details-grid - gutter: [48, 12] - children: - # Left column: metadata, links, labels - - type: antdCol - data: - id: col-left - span: 12 - children: - - type: antdFlex - data: - id: col-left-stack - vertical: true - gap: 24 - children: - # Display name label/value - - type: antdFlex - data: - id: meta-name-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: meta-name-label - strong: true - text: "Name" - - type: parsedText - data: - id: meta-name-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - - # Namespace link (kept as include) - - type: antdFlex - data: - id: meta-namespace-block - vertical: true - gap: 8 - children: - - type: antdText - data: - id: meta-name-label - text: Namespace - strong: true - - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 34 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 38 - }} - # Labels list (kept as include) - - type: antdFlex - data: - id: meta-labels-block - vertical: true - gap: 8 - children: - {{ include "incloud-web-resources.factory.labels" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}" - ) | nindent 34 - }} - - # Node selector chips (kept as include) - - type: antdFlex - data: - id: meta-node-selector-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.labels.base.selector" (dict - "type" "node" - "title" "Node selector" - "jsonPath" ".spec.nodeSelector" - ) | nindent 34 - }} - - # Tolerations counter (kept as include) - - type: antdFlex - data: - id: meta-tolerations-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.tolerations.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}" - "jsonPathToArray" ".spec.tolerations" - "pathToValue" "/spec/tolerations" - ) | nindent 34 - }} - - # Annotations counter block - - type: antdFlex - data: - id: ds-annotations - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.annotations.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}" - ) | nindent 34 - }} - - # Created timestamp (kept as include) - - type: antdFlex - data: - id: meta-created-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.time.create" (dict - "req" ".metadata.creationTimestamp" - "text" "Created" - ) | nindent 38 - }} - - # Owner information (fallback when absent) - # - type: antdFlex - # data: - # id: meta-owner-block - # vertical: true - # gap: 4 - # children: - # - type: antdText - # data: - # id: meta-owner-label - # strong: true - # text: "Owner" - # - type: parsedText - # data: - # id: meta-owner-fallback - # strong: true - # text: "No owner" - # style: - # color: red - - # Right column: status and runtime info - - type: antdCol - data: - id: col-right - span: 12 - children: - - type: antdFlex - data: - id: col-right-stack - vertical: true - gap: 24 - children: - # Status block with readiness reason mapping - - type: antdFlex - data: - id: status-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: status-label - strong: true - text: "Status" - # Pod readiness/status indicator - - type: antdFlex - data: - id: status-label-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.statuses.pod" . | nindent 38 }} - - # Restart policy - - type: antdFlex - data: - id: restart-policy-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: restart-policy-label - strong: true - text: "Restart policy" - - type: parsedText - data: - id: restart-policy-value - text: "{reqsJsonPath[0]['.spec.restartPolicy']['-']} restart" - - # Active deadline seconds - - type: antdFlex - data: - id: active-deadline-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: active-deadline-label - strong: true - text: "Active deadline seconds" - - type: parsedText - data: - id: active-deadline-value - text: "{reqsJsonPath[0]['.spec.activeDeadlineSeconds']['-']}" - - # Pod IP - - type: antdFlex - data: - id: pod-ip-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: pod-ip-label - strong: true - text: "Pod IP" - - type: parsedText - data: - id: pod-ip-value - text: "{reqsJsonPath[0]['.status.podIP']['-']}" - - # Host IP - - type: antdFlex - data: - id: host-ip-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: host-ip-label - strong: true - text: "Host IP" - - type: parsedText - data: - id: host-ip-value - text: "{reqsJsonPath[0]['.status.hostIP']['-']}" - - # Node details link (kept as include) - - type: antdFlex - data: - id: node-link-block - vertical: true - gap: 8 - children: - - type: antdText - data: - id: meta-node - text: Node - strong: true - - {{ include "incloud-web-resources.icon" (dict - "text" "N" - "title" "node" - "backgroundColor" "#8476d1" - )| nindent 34 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "name" - "jsonPath" ".spec.nodeName" - "factory" "node-details" - ) | nindent 38 - }} - - # QoS class - - type: antdFlex - data: - id: qos-class-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: qos-class-label - strong: true - text: "QOS class" - - type: parsedText - data: - id: qos-class-value - text: "{reqsJsonPath[0]['.status.qosClass']['-']}" - - # Volumes section (table is hidden if no volumes) - # TODO to be done - # - type: antdCol - # data: - # id: volumes-col - # style: - # marginTop: 10 - # padding: 10 - # children: - # - type: VisibilityContainer - # data: - # id: volumes-visibility - # value: "{reqsJsonPath[0]['.spec.volumes']['-']}" - # style: - # margin: 0 - # padding: 0 - # children: - # # Volumes title - # - type: antdText - # data: - # id: volumes-title - # text: "Volumes" - # strong: true - # style: - # fontSize: 22 - # marginBottom: 32px - # # Volumes table - # - type: EnrichedTable - # data: - # id: volumes-table - # fetchUrl: "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}" - # clusterNamePartOfUrl: "{2}" - # customizationId: factory-pod-details-volume-list - # baseprefix: "/openapi-ui" - # withoutControls: true - # pathToItems: ".spec.volumes" - - - # Init containers section (hidden if none) - - type: antdCol - data: - id: init-containers-col - style: - marginTop: 10 - padding: 10 - children: - {{ include "incloud-web-resources.factory.containers.table" (dict - "title" "Init containers" - "customizationId" "container-status-init-containers-list" - "type" "init-containers" - "apiGroup" "api/v1" - "kind" "pods" - "resourceName" "{6}" - "namespace" "{3}" - "jsonPath" ".status.initContainerStatuses" - "pathToItems" "['status','initContainerStatuses']" - ) | nindent 22 - }} - - # Containers section (hidden if none) - - type: antdCol - data: - id: containers-col - style: - marginTop: 10 - padding: 10 - children: - {{ include "incloud-web-resources.factory.containers.table" (dict - "title" "Containers" - "customizationId" "container-status-containers-list" - "type" "containers" - "apiGroup" "api/v1" - "kind" "pods" - "resourceName" "{6}" - "namespace" "{3}" - "jsonPath" ".status.containerStatuses" - "pathToItems" "['status','containerStatuses']" - ) | nindent 22 - }} - - # Conditions section (hidden if none) - - type: antdCol - data: - id: conditions-col - style: - marginTop: 10 - padding: 10 - children: - - type: VisibilityContainer - data: - id: conditions-visibility - value: "{reqsJsonPath[0]['.status.conditions']['-']}" - style: - margin: 0 - padding: 0 - children: - # Conditions title - - type: antdText - data: - id: conditions-title - text: "Conditions" - strong: true - style: - fontSize: 22 - # Conditions table - - type: EnrichedTable - data: - id: conditions-table - fetchUrl: "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods/{6}" - clusterNamePartOfUrl: "{2}" - customizationId: factory-status-conditions - baseprefix: "/openapi-ui" - withoutControls: true - pathToItems: ".status.conditions" - - # YAML tab with inline editor - - key: "yaml" - label: "YAML" - children: - - type: YamlEditorSingleton - data: - id: yaml-editor - cluster: "{2}" - isNameSpaced: true - type: "builtin" - typeName: pods - prefillValuesRequestIndex: 0 - substractHeight: 400 - - # Logs tab with live pod logs - - key: "logs" - label: "Logs" - children: - - type: PodLogs - data: - id: pod-logs - cluster: "{2}" - namespace: "{reqsJsonPath[0]['.metadata.namespace']['-']}" - podName: "{reqsJsonPath[0]['.metadata.name']['-']}" - substractHeight: 400 - - # Terminal tab with exec into pod - - key: "terminal" - label: "Terminal" - children: - - type: PodTerminal - data: - id: pod-terminal - cluster: "{2}" - namespace: "{reqsJsonPath[0]['.metadata.namespace']['-']}" - podName: "{reqsJsonPath[0]['.metadata.name']['-']}" - substractHeight: 400 diff --git a/packages/system/dashboard-config/templates/Factory/secret-details.yaml b/packages/system/dashboard-config/templates/Factory/secret-details.yaml deleted file mode 100644 index 4525db80..00000000 --- a/packages/system/dashboard-config/templates/Factory/secret-details.yaml +++ /dev/null @@ -1,268 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: secret-details -spec: - key: secret-details - sidebarTags: - - secret-sidebar - withScrollableMainContentCard: true - urlsToFetch: - - "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}" - - # Header row with badge and Secret name - data: - - type: antdFlex - data: - id: header-row - gap: 6 - align: center - style: - marginBottom: 24px - children: - # Secret badge - - type: antdText - data: - id: badge-secret - text: S - title: secret - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#c46100" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 - # Secret name - - type: parsedText - data: - id: header-secret-name - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - style: - fontSize: 20px - lineHeight: 24px - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - - # Tabs with Details and YAML - - type: antdTabs - data: - id: secret-tabs - defaultActiveKey: "details" - items: - # Details tab with metadata and spec info - - key: "details" - label: "Details" - children: - - type: ContentCard - data: - id: details-card - style: - marginBottom: 24px - children: - # Title - - type: antdText - data: - id: details-title - text: "Secret details" - strong: true - style: - fontSize: 20 - marginBottom: 12px - - # Spacer - - type: Spacer - data: - id: details-spacer - "$space": 16 - - # Two-column layout - - type: antdRow - data: - id: details-grid - gutter: [48, 12] - children: - # Left column: metadata - - type: antdCol - data: - id: col-left - span: 12 - children: - - type: antdFlex - data: - id: col-left-stack - vertical: true - gap: 24 - children: - # Name block - - type: antdFlex - data: - id: meta-name-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: meta-name-label - strong: true - text: "Name" - - type: parsedText - data: - id: meta-name-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - - # Namespace link - - type: antdFlex - data: - id: meta-namespace-block - vertical: true - gap: 8 - children: - - type: antdText - data: - id: meta-name-label - text: Namespace - strong: true - - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 34 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 38 - }} - - # Labels - - type: antdFlex - data: - id: meta-labels-block - vertical: true - gap: 8 - children: - {{ include "incloud-web-resources.factory.labels" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}" - ) | nindent 34 - }} - - # Annotations counter block - - type: antdFlex - data: - id: ds-annotations - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.annotations.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}" - ) | nindent 34 - }} - - # Created timestamp - - type: antdFlex - data: - id: meta-created-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.time.create" (dict - "req" ".metadata.creationTimestamp" - "text" "Created" - ) | nindent 38 - }} - - # Owner block - # - type: antdFlex - # data: - # id: meta-owner-block - # vertical: true - # gap: 4 - # children: - # - type: antdText - # data: - # id: meta-owner-label - # strong: true - # text: "Owner" - # - type: antdFlex - # data: - # id: meta-owner-flex - # gap: 6 - # align: center - # children: - # - type: parsedText - # data: - # id: owner-value - # strong: true - # text: "No owner" - # style: - # color: red - - # Right column: type info - - type: antdCol - data: - id: col-right - span: 12 - children: - - type: antdFlex - data: - id: col-right-stack - vertical: true - gap: 24 - children: - # Secret type - - type: antdFlex - data: - id: secret-type-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: secret-type-label - strong: true - text: "Type" - - type: parsedText - data: - id: secret-type-value - text: "{reqsJsonPath[0]['.type']['-']}" - - # Secret SA - - type: antdFlex - data: - id: secret-sa-block - vertical: true - gap: 4 - children: - # SA Link - {{ include "incloud-web-resources.factory.links.details" (dict - "reqIndex" 0 - "type" "serviceaccount" - "title" "ServiceAccount" - "jsonPath" ".metadata.annotations['kubernetes.io/service-account.name']" - "namespace" "{3}" - "factory" "serviceaccount-details" - ) | nindent 34 - }} - - # YAML tab - - key: yaml - label: YAML - children: - - type: YamlEditorSingleton - data: - id: yaml-editor - cluster: "{2}" - isNameSpaced: true - type: builtin - typeName: secrets - prefillValuesRequestIndex: 0 - substractHeight: 400 diff --git a/packages/system/dashboard-config/templates/Factory/service-details.yaml b/packages/system/dashboard-config/templates/Factory/service-details.yaml deleted file mode 100644 index 2e3d4db3..00000000 --- a/packages/system/dashboard-config/templates/Factory/service-details.yaml +++ /dev/null @@ -1,403 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: service-details -spec: - key: service-details - withScrollableMainContentCard: true - sidebarTags: - - service-sidebar - urlsToFetch: - - "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}" - - # Header row with badge and Service name - data: - - type: antdFlex - data: - id: header-row - gap: 6 - align: center - style: - marginBottom: 24px - children: - # Service badge - - type: antdText - data: - id: badge-service - text: S - title: services - style: - fontSize: 20px - lineHeight: 24px - padding: "0 9px" - borderRadius: "20px" - minWidth: 24 - display: inline-block - textAlign: center - whiteSpace: nowrap - color: "#fff" - backgroundColor: "#6ca100" - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontWeight: 400 - # Service name - - type: parsedText - data: - id: service-name - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - style: - fontSize: 20px - lineHeight: 24px - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - - # Tabs with Details, YAML, and Pods - - type: antdTabs - data: - id: service-tabs - defaultActiveKey: "details" - items: - # Details tab - - key: "details" - label: "Details" - children: - - type: ContentCard - data: - id: details-card - style: - marginBottom: 24px - children: - - type: antdRow - data: - id: details-grid - gutter: [48, 12] - children: - # Left column: metadata and config - - type: antdCol - data: - id: col-left - span: 12 - children: - - type: antdText - data: - id: details-title - text: "Service details" - strong: true - style: - fontSize: 20 - marginBottom: 12px - - type: Spacer - data: - id: details-spacer - "$space": 16 - - type: antdFlex - data: - id: col-left-stack - vertical: true - gap: 24 - children: - # Name block - - type: antdFlex - data: - id: meta-name-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: meta-name-label - strong: true - text: "Name" - - type: parsedText - data: - id: meta-name-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - - # Namespace link - - type: antdFlex - data: - id: meta-namespace-block - vertical: true - gap: 8 - children: - - type: antdText - data: - id: meta-name-label - text: Namespace - strong: true - - {{ include "incloud-web-resources.icon" (dict - "text" "NS" - "title" "namespace" - "backgroundColor" "#a25792ff" - )| nindent 34 - }} - {{ include "incloud-web-resources.factory.linkblock" (dict - "reqIndex" 0 - "type" "namespace" - "jsonPath" ".metadata.namespace" - "factory" "namespace-details" - ) | nindent 38 - }} - - # Labels - - type: antdFlex - data: - id: meta-labels-block - vertical: true - gap: 8 - children: - {{ include "incloud-web-resources.factory.labels" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}" - ) | nindent 34 - }} - - # Pod selector - - type: antdFlex - data: - id: meta-pod-selector-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.labels.base.selector" (dict - "type" "pod" - "title" "Pod selector" - "jsonPath" ".spec.template.metadata.labels" - ) | nindent 34 - }} - - # Annotations counter block - - type: antdFlex - data: - id: ds-annotations - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.annotations.block" (dict - "endpoint" "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}" - ) | nindent 34 - }} - - # Session affinity - - type: antdFlex - data: - id: meta-session-affinity-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: meta-session-affinity-label - strong: true - text: "Session affinity" - - type: parsedText - data: - id: meta-session-affinity-value - text: "{reqsJsonPath[0]['.spec.sessionAffinity']['Not configured']}" - - # Created timestamp - - type: antdFlex - data: - id: meta-created-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.time.create" (dict - "req" ".metadata.creationTimestamp" - "text" "Created" - ) | nindent 34 - }} - - # Owner - # - type: antdFlex - # data: - # id: meta-owner-block - # vertical: true - # gap: 4 - # children: - # - type: antdText - # data: - # id: meta-owner-label - # strong: true - # text: "Owner" - # - type: antdFlex - # data: - # id: meta-owner-flex - # gap: 6 - # align: center - # children: - # - type: antdText - # data: - # id: meta-owner-fallback - # text: "No owner" - # style: - # color: "#FF0000" - - # Right column: routing and ports - - type: antdCol - data: - id: col-right - span: 12 - children: - - type: antdText - data: - id: routing-title - text: "Service routing" - strong: true - style: - fontSize: 20 - marginBottom: 12px - - type: Spacer - data: - id: routing-spacer - "$space": 16 - - type: antdFlex - data: - id: col-right-stack - vertical: true - gap: 24 - children: - # Hostname - - type: antdFlex - data: - id: service-hostname-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: service-hostname-label - strong: true - text: "Hostname" - - type: parsedText - data: - id: service-hostname-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}.{reqsJsonPath[0]['.metadata.namespace']['-']}.svc.cluster.local" - - # IP addresses block - - type: antdFlex - data: - id: service-ip-block - vertical: true - gap: 12 - children: - # ClusterIP - - type: antdFlex - data: - id: clusterip-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: clusterip-label - strong: true - text: "ClusterIP address" - - type: parsedText - data: - id: clusterip-value - text: "{reqsJsonPath[0]['.spec.clusterIP']['-']}" - - # LoadBalancerIP - - type: antdFlex - data: - id: loadbalancerip-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: loadbalancerip-label - strong: true - text: "LoadBalancerIP address" - - type: parsedText - data: - id: loadbalancerip-value - text: "{reqsJsonPath[0]['.status.loadBalancer.ingress[0].ip']['Not Configured']}" - - # Service port mapping - - type: antdFlex - data: - id: service-port-mapping-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: service-port-mapping-label - strong: true - text: "Service port mapping" - - type: EnrichedTable - data: - id: service-port-mapping-table - fetchUrl: "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}" - clusterNamePartOfUrl: "{2}" - customizationId: "factory-service-details-port-mapping" - baseprefix: "/openapi-ui" - withoutControls: true - pathToItems: ".spec.ports" - - # Pod serving - - type: VisibilityContainer - data: - id: service-pod-serving-vis - value: "{reqsJsonPath[0]['.spec.selector']['-']}" - style: { margin: 0, padding: 0 } - children: - - type: antdFlex - data: - id: service-pod-serving-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: service-pod-serving-label - strong: true - text: "Pod serving" - - type: EnrichedTable - data: - id: service-pod-serving-table - fetchUrl: "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices" - clusterNamePartOfUrl: "{2}" - customizationId: "factory-service-details-endpointslice" - baseprefix: "/openapi-ui" - withoutControls: true - labelsSelector: - kubernetes.io/service-name: "{reqsJsonPath[0]['.metadata.name']['-']}" - pathToItems: ".items[*].endpoints" - - # YAML tab - - key: "yaml" - label: "YAML" - children: - - type: YamlEditorSingleton - data: - id: yaml-editor - cluster: "{2}" - isNameSpaced: true - type: "builtin" - typeName: services - prefillValuesRequestIndex: 0 - substractHeight: 400 - - # Pods tab - - key: "pods" - label: "Pods" - children: - - type: VisibilityContainer - data: - id: service-pod-serving-vis - value: "{reqsJsonPath[0]['.spec.selector']['-']}" - style: { margin: 0, padding: 0 } - children: - - type: EnrichedTable - data: - id: pods-table - fetchUrl: "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods" - clusterNamePartOfUrl: "{2}" - customizationId: "factory-node-details-/v1/pods" - baseprefix: "/openapi-ui" - withoutControls: false - labelsSelectorFull: - reqIndex: 0 - pathToLabels: ".spec.selector" - pathToItems: ".items" diff --git a/packages/system/dashboard-config/templates/Factory/workloadmonitor-details.yaml b/packages/system/dashboard-config/templates/Factory/workloadmonitor-details.yaml deleted file mode 100644 index bb9f45f1..00000000 --- a/packages/system/dashboard-config/templates/Factory/workloadmonitor-details.yaml +++ /dev/null @@ -1,328 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Factory -metadata: - name: workloadmonitor-details -spec: - key: workloadmonitor-details - sidebarTags: - - workloadmonitor-sidebar - withScrollableMainContentCard: true - urlsToFetch: - - "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors/{6}" - data: - - type: antdFlex - data: - id: header-row - gap: 6 - align: center - style: - marginBottom: 24 - children: - - type: antdText - data: - id: badge-workloadmonitor - text: "W" - title: workloadmonitors - style: - backgroundColor: "#c46100" - borderRadius: "20px" - color: "#fff" - display: inline-block - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontSize: 20 - fontWeight: 400 - lineHeight: "24px" - minWidth: 24 - padding: "0 9px" - textAlign: center - whiteSpace: nowrap - - type: parsedText - data: - id: workloadmonitor-name - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - style: - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontSize: 20 - lineHeight: "24px" - - - type: antdTabs - data: - id: workloadmonitor-tabs - defaultActiveKey: details - items: - - key: details - label: Details - children: - - type: ContentCard - data: - id: details-card - style: - marginBottom: 24 - children: - - type: antdRow - data: - id: details-grid - gutter: [48, 12] - children: - - type: antdCol - data: - id: col-left - span: 12 - children: - - type: antdFlex - data: - id: col-left-stack - vertical: true - gap: 24 - children: - - type: antdFlex - data: - id: meta-name-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: meta-name-label - text: Name - strong: true - - type: parsedText - data: - id: meta-name-value - text: "{reqsJsonPath[0]['.metadata.name']['-']}" - - - type: antdFlex - data: - id: meta-namespace-block - vertical: true - gap: 8 - children: - - type: antdText - data: - id: meta-namespace-label - text: Namespace - strong: true - - type: antdFlex - data: - id: namespace-row - align: center - gap: 6 - children: - - type: antdText - data: - id: ns-badge - text: NS - style: - backgroundColor: "#a25792ff" - borderRadius: "20px" - color: "#fff" - display: inline-block - fontFamily: RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif - fontSize: 15 - fontWeight: 400 - lineHeight: "24px" - minWidth: 24 - padding: "0 9px" - textAlign: center - whiteSpace: nowrap - - type: antdLink - data: - id: namespace-link - text: "{reqsJsonPath[0]['.metadata.namespace']['-']}" - href: "/openapi-ui/{2}/factory/namespace-details/{reqsJsonPath[0]['.metadata.namespace']['-']}" - - - type: antdFlex - data: - id: meta-created-block - vertical: true - gap: 4 - children: - {{ include "incloud-web-resources.factory.time.create" (dict - "req" ".metadata.creationTimestamp" - "text" "Created" - ) | nindent 34 - }} - - - type: antdFlex - data: - id: meta-kind-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: kind-label - text: Kind - strong: true - - type: parsedText - data: - id: kind-value - text: "{reqsJsonPath[0]['.spec.kind']['-']}" - - - type: antdFlex - data: - id: meta-type-block - vertical: true - gap: 4 - children: - - type: antdText - data: - id: type-label - text: Type - strong: true - - type: parsedText - data: - id: type-value - text: "{reqsJsonPath[0]['.spec.type']['-']}" - - - type: antdCol - data: - id: col-right - span: 12 - children: - - type: antdFlex - data: - id: col-right-stack - vertical: true - gap: 24 - children: - - type: antdText - data: - id: params-title - text: Parameters - strong: true - style: - fontSize: 20 - marginBottom: 12 - - - type: antdFlex - data: - id: params-list - vertical: true - gap: 24 - children: - - type: antdFlex - data: - id: param-version - vertical: true - gap: 4 - children: - - type: antdText - data: - id: param-version-label - text: Version - strong: true - - type: parsedText - data: - id: param-version-value - text: "{reqsJsonPath[0]['.spec.version']['-']}" - - - type: antdFlex - data: - id: param-replicas - vertical: true - gap: 4 - children: - - type: antdText - data: - id: param-replicas-label - text: Replicas - strong: true - - type: parsedText - data: - id: param-replicas-value - text: "{reqsJsonPath[0]['.spec.replicas']['-']}" - - - type: antdFlex - data: - id: param-minreplicas - vertical: true - gap: 4 - children: - - type: antdText - data: - id: param-minreplicas-label - text: MinReplicas - strong: true - - type: parsedText - data: - id: param-minreplicas-value - text: "{reqsJsonPath[0]['.spec.minReplicas']['-']}" - - - type: antdFlex - data: - id: param-available - vertical: true - gap: 4 - children: - - type: antdText - data: - id: param-available-label - text: AvailableReplicas - strong: true - - type: parsedText - data: - id: param-available-value - text: "{reqsJsonPath[0]['.status.availableReplicas']['-']}" - - - type: antdFlex - data: - id: param-observed - vertical: true - gap: 4 - children: - - type: antdText - data: - id: param-observed-label - text: ObservedReplicas - strong: true - - type: parsedText - data: - id: param-observed-value - text: "{reqsJsonPath[0]['.status.observedReplicas']['-']}" - - - type: antdFlex - data: - id: param-operational - vertical: true - gap: 4 - children: - - type: antdText - data: - id: param-operational-label - text: Operational - strong: true - - type: parsedText - data: - id: param-operational-value - text: "{reqsJsonPath[0]['.status.operational']['-']}" - - - key: workloads - label: Workloads - children: - - type: EnrichedTable - data: - id: workloads-table - fetchUrl: "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads" - clusterNamePartOfUrl: "{2}" - baseprefix: "/openapi-ui" - customizationId: "factory-details-v1alpha1.cozystack.io.workloads" - pathToItems: ["items"] - labelsSelector: - workloads.cozystack.io/monitor: "{reqs[0]['metadata','name']}" - - - key: yaml - label: YAML - children: - - type: YamlEditorSingleton - data: - id: yaml-editor - cluster: "{2}" - isNameSpaced: true - type: builtin - typeName: workloadmonitors - prefillValuesRequestIndex: 0 - substractHeight: 400 - - diff --git a/packages/system/dashboard-config/templates/Navigation/navigaton.yaml b/packages/system/dashboard-config/templates/Navigation/navigaton.yaml deleted file mode 100644 index 9f5bbbc8..00000000 --- a/packages/system/dashboard-config/templates/Navigation/navigaton.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: Navigation -metadata: - name: navigation -spec: - namespaces: - change: /openapi-ui/{selectedCluster}/{value}/factory/marketplace - clear: /openapi-ui/{selectedCluster}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces diff --git a/packages/system/dashboard-config/templates/TableUriMapping/apps.cozystack.io.v1alpha1.virtualmachines.yaml b/packages/system/dashboard-config/templates/TableUriMapping/apps.cozystack.io.v1alpha1.virtualmachines.yaml deleted file mode 100644 index 4fa2eebd..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/apps.cozystack.io.v1alpha1.virtualmachines.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: TableUriMapping -metadata: - name: virtualmachine-details - name: stock-namespace-default-apps.cozystack.io.v1alpha1.virtualmachines.yaml -spec: - id: "stock-namespace-/apps.cozystack.io/v1alpha1/virtualmachines" - pathToNavigate: "/openapi-ui/{2}/{3}/factory/virtualmachine-details/~recordValue~" - keysToParse: - - metadata - - name ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: TableUriMapping -metadata: - name: stock-cluster-service-details -spec: - id: "vm-factory-services" - pathToNavigate: "/openapi-ui/default/~recordValueSecond~/factory/service-details/~recordValue~" - keysToParse: - - metadata - - name - keysToParseSecond: - - metadata - - namespace diff --git a/packages/system/dashboard-config/templates/TableUriMapping/core.cozystack.io.v1alpha1.tenantnamespaces.yaml b/packages/system/dashboard-config/templates/TableUriMapping/core.cozystack.io.v1alpha1.tenantnamespaces.yaml deleted file mode 100644 index 4af0a482..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/core.cozystack.io.v1alpha1.tenantnamespaces.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: TableUriMapping -metadata: - name: namespaces -spec: - id: "stock-cluster-/core.cozystack.io/v1alpha1/tenantnamespaces" - pathToNavigate: "/openapi-ui/{clusterName}/~recordValue~/factory/marketplace" - keysToParse: - - metadata - - name diff --git a/packages/system/dashboard-config/templates/TableUriMapping/networking.k8s.io.v1.ingresses.yaml b/packages/system/dashboard-config/templates/TableUriMapping/networking.k8s.io.v1.ingresses.yaml deleted file mode 100644 index ccdae10e..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/networking.k8s.io.v1.ingresses.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: TableUriMapping -metadata: - name: stock-cluster-networking.k8s.io.v1.ingress-details -spec: - keysToParse: ".metadata.name" - keysToParseSecond: ".metadata.namespace" - id: "stock-cluster-/networking.k8s.io/v1/ingresses" - pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/ingress-details/~recordValue~" - ---- -apiVersion: dashboard.cozystack.io/v1alpha1 -kind: TableUriMapping -metadata: - name: stock-namespace-networking.k8s.io.v1.ingress-details -spec: - keysToParse: ".metadata.name" - keysToParseSecond: ".metadata.namespace" - id: "stock-namespace-/networking.k8s.io/v1/ingresses" - pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/ingress-details/~recordValue~" - - diff --git a/packages/system/dashboard-config/templates/TableUriMapping/v1.configmaps.yaml b/packages/system/dashboard-config/templates/TableUriMapping/v1.configmaps.yaml deleted file mode 100644 index 78240dfd..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/v1.configmaps.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-cluster-configmap-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-cluster-/v1/configmaps" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/configmap-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-namespace-configmap-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-namespace-/v1/configmaps" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/configmap-details/~recordValue~" diff --git a/packages/system/dashboard-config/templates/TableUriMapping/v1.namespaces.yaml b/packages/system/dashboard-config/templates/TableUriMapping/v1.namespaces.yaml deleted file mode 100644 index b8bd64d5..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/v1.namespaces.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-cluster-namespace-details -# spec: -# keysToParse: ".metadata.name" -# id: "stock-cluster-/v1/namespaces" -# pathToNavigate: "/openapi-ui/{clusterName}/factory/namespace-details/~recordValue~" diff --git a/packages/system/dashboard-config/templates/TableUriMapping/v1.nodes.yaml b/packages/system/dashboard-config/templates/TableUriMapping/v1.nodes.yaml deleted file mode 100644 index ea084ab7..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/v1.nodes.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-cluster-node-details -# spec: -# keysToParse: ".metadata.name" -# id: "stock-cluster-/v1/nodes" -# pathToNavigate: "/openapi-ui/{clusterName}/factory/node-details/~recordValue~" diff --git a/packages/system/dashboard-config/templates/TableUriMapping/v1.pods.yaml b/packages/system/dashboard-config/templates/TableUriMapping/v1.pods.yaml deleted file mode 100644 index 334a562b..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/v1.pods.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-cluster-pod-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-cluster-/v1/pods" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/pod-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-namespace-pod-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-namespace-/v1/pods" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/pod-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: factory-node-details-table-pods -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "factory-node-details-/v1/pods" -# pathToNavigate: "/openapi-ui/{2}/~recordValueSecond~/factory/pod-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: factory-service-details-endpointslice -# spec: -# keysToParse: ".targetRef.name" -# keysToParseSecond: ".targetRef.namespace" -# id: "factory-service-details-endpointslice" -# pathToNavigate: "/openapi-ui/{2}/~recordValueSecond~/factory/pod-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: "factory-v1.pods" -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "factory-/v1/pods" -# pathToNavigate: "/openapi-ui/{2}/~recordValueSecond~/factory/pod-details/~recordValue~" diff --git a/packages/system/dashboard-config/templates/TableUriMapping/v1.secrets.yaml b/packages/system/dashboard-config/templates/TableUriMapping/v1.secrets.yaml deleted file mode 100644 index 736749b6..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/v1.secrets.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-cluster-secret-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-cluster-/v1/secrets" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/secret-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-namespace-secret-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-namespace-/v1/secrets" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/secret-details/~recordValue~" diff --git a/packages/system/dashboard-config/templates/TableUriMapping/v1.services.yaml b/packages/system/dashboard-config/templates/TableUriMapping/v1.services.yaml deleted file mode 100644 index c177b9dd..00000000 --- a/packages/system/dashboard-config/templates/TableUriMapping/v1.services.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-cluster-service-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-cluster-/v1/services" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/service-details/~recordValue~" - -# --- -# apiVersion: dashboard.cozystack.io/v1alpha1 -# kind: TableUriMapping -# metadata: -# name: stock-namespace-service-details -# spec: -# keysToParse: ".metadata.name" -# keysToParseSecond: ".metadata.namespace" -# id: "stock-namespace-/v1/services" -# pathToNavigate: "/openapi-ui/{clusterName}/~recordValueSecond~/factory/service-details/~recordValue~" diff --git a/packages/system/dashboard-config/templates/_helpers.tpl b/packages/system/dashboard-config/templates/_helpers.tpl deleted file mode 100644 index 4dd9a4b7..00000000 --- a/packages/system/dashboard-config/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "incloud-web-resources.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "incloud-web-resources.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "incloud-web-resources.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "incloud-web-resources.labels" -}} -helm.sh/chart: {{ include "incloud-web-resources.chart" . }} -{{ include "incloud-web-resources.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "incloud-web-resources.selectorLabels" -}} -app.kubernetes.io/name: {{ include "incloud-web-resources.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "incloud-web-resources.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "incloud-web-resources.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/packages/system/dashboard/Makefile b/packages/system/dashboard/Makefile index c8930ef6..7b5f2506 100644 --- a/packages/system/dashboard/Makefile +++ b/packages/system/dashboard/Makefile @@ -5,7 +5,7 @@ include ../../../scripts/common-envs.mk include ../../../scripts/package.mk update: update-crd update-dockerfiles -image: image-openapi-ui image-openapi-ui-k8s-bff image-token-proxy +image: image-openapi-ui image-openapi-ui-k8s-bff image-token-proxy update-tenant-text update-dockerfiles: @@ -65,3 +65,6 @@ image-token-proxy: IMAGE="$(REGISTRY)/token-proxy:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/token-proxy.json -r)" \ yq -i '.tokenProxy.image = strenv(IMAGE)' values.yaml rm -f images/token-proxy.json + +update-tenant-text: + yq -i '.data.TENANT_TEXT = "$(TAG)"' ./templates/configmap.yaml diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml new file mode 100644 index 00000000..9f58c653 --- /dev/null +++ b/packages/system/dashboard/templates/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: incloud-web-dashboard-config + labels: + app.kubernetes.io/instance: incloud-web + app.kubernetes.io/name: web +data: + FOOTER_TEXT: "Cozystack" + TITLE_TEXT: "Cozystack Dashboard" + TENANT_TEXT: "latest" + LOGO_TEXT: "false" + LOGO_SVG: "PHN2ZyB3aWR0aD0iMTUwIiBoZWlnaHQ9IjMwIiB2aWV3Qm94PSIwIDAgMTUwIDMwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwYXRoIGQ9Ik0xMzMuMzMxMDkgMjIuMzczOTg3VjQuNzk1Mjc2OWgyLjA0NDUyVjEyLjc3Mzg5aC4wNDk5bDguNTI3NzEtNy45NzkxNjcyaDIuNjE4MjdsLTkuNzc0NjQgOS4xMDExNTgyLjAyNDktMS4wOTcwNTkgMTAuMjk4MjQgOS41NzQ4ODloLTIuNjkyNzlsLTkuMDAxNjktOC4yNzgzNjNoLS4wNDk5djguMjc4MzYzem0tOS4xNzIzNi4yMjQzOTljLTEuNzI4NjkuMC0zLjIwODA1LS4zNjU2ODYtNC40MzgxLTEuMDk3MDU5LTEuMjMwNTktLjczMTM3My0yLjE3ODA0LTEuNzcwMjU0LTIuODQyOTMtMy4xMTY2NDUtLjY0ODI3LTEuMzQ2NjY3LS45NzIzOS0yLjk1MDk3OC0uOTcyMzktNC44MTI2NTQuMC0xLjg2MTY3Ny4zMjQxMi0zLjQ1NzM5OS45NzIzOS00Ljc4NzQ0NS42NjQ4OS0xLjM0NjM5MDYgMS42MTIzNC0yLjM4NTI3MjUgMi44NDI2Ni0zLjExNjkyMjQgMS4yMzAwMy0uNzMxMzcyOSAyLjcwOTQxLTEuMDk3MDU5MiA0LjQzODM3LTEuMDk3MDU5MiAxLjIxMzQyLjAgMi4zMzU0MS4xOTExNTQzIDMuMzY2MjYuNTczNDYyOCAxLjAzMDU3LjM4MjMwODUgMS44OTQ5My45MzkxNDkxIDIuNTkzMDUgMS42NzA1MjE5bC0uNzk3ODYgMS42NzA1MjE5Yy0uNzY0NjItLjcxNDc1LTEuNTYyNDgtMS4yMzAwMzYtMi4zOTM1OS0xLjU0NTg1NjEtLjgxNDQ3LS4zMzI0NDIxLTEuNzIwMzgtLjQ5ODY2MzItMi43MTc3MS0uNDk4NjYzMi0xLjk3ODU5LjAtMy40OTEyLjYyMzMyOS00LjUzODM4IDEuODY5OTg4My0xLjA0NzIxIDEuMjQ2NjU3LTEuNTcwOCAzLjAwMDU2Ni0xLjU3MDggNS4yNjE0NTEuMCAyLjI2MDYwNi41MjM1OSA0LjAyMjU0OSAxLjU3MDggNS4yODYxMDggMS4wNDcxOCAxLjI0NjY1NyAyLjU1OTc5IDEuODY5OTg2IDQuNTM4MDkgMS44Njk5ODYuOTk3MzQuMCAxLjkwMzI0LS4xNTc5MDkgMi43MTc3My0uNDczNzMuODMxMzgtLjMzMjQ0MiAxLjYyOTI0LS44NTYwMzggMi4zOTM4Ni0xLjU3MDc4OWwuNzk3ODYgMS42NzA1MjJjLS42OTgxMi43MTQ3NTEtMS41NjI0OCAxLjI3MTg2OS0yLjU5MzA1IDEuNjcwNzk5LTEuMDMwNTguMzgyMzA5LTIuMTUyNTcuNTczNDYyLTMuMzY1OTcuNTczNDYyek05Ni45ODQ2MzEgMjIuMzczOTg3IDEwNC43Mzk0IDQuNzk0OTk5OWgxLjc0NTMzbDcuNzU0NzYgMTcuNTc4OTg3MWgtMi4xMTkzMmwtMi4xNjkxOS01LjAxMTg0MS45OTczMy41MjM1OTVoLTEwLjcyMjA5bDEuMDIyMjctLjUyMzU5NS0yLjE0NDI1NCA1LjAxMTg0MXptOC42MDI0OTktMTUuMTg1NDAzNC00LjAxNDI0IDkuNDUwNTAwNC0uNTk4NC0uNDczNzNoOS4yMjU1NWwtLjU0ODUzLjQ3MzczLTQuMDE0MjUtOS40NTA1MDA0ek04OS44OTM5MjEgMjIuMzczOTg3VjYuNTY1NTMxNkg4My41MTAyMDJWNC43OTUyNzY5aDE0LjgzNjMzVjYuNTY1NTMxNkg5MS45NjMzNjZWMjIuMzc0MjY1eiIgZmlsbD17dG9rZW4uY29sb3JUZXh0fT48L3BhdGg+CiAgPHBhdGggZD0ibTY3Ljg1NDM4NSA0Ljc3MzExNDJoMTQuMDgwODd2MS43NjAwMDQyaC0xNC4wODA4N3ptMCAxNS44NDA4Njk4aDE0LjA4MDg3djEuNzYwMDAzaC0xNC4wODA4N3ptMTQuMDgwODctNy45MjA0MzVoLTE0LjA4MDg3djEuNzYwMDA1aDE0LjA4MDg3eiIgZmlsbD17dG9rZW4uY29sb3JUZXh0fT48L3BhdGg+CiAgPHBhdGggZD0ibTU3LjY2NDQ3MyAyMi4zNzM5ODd2LTkuMTAxMTU4bC40NDg4MDQgMS40MjExOTEtNy4xODEzMDktOS44OTkwMjAxaDIuMzkzODYxbDUuNjYwMTA5IDcuODI5NTY3MWgtLjUyMzYwNmw1LjY2MDM5MS03LjgyOTU2NzFoMi4zMTg3ODdsLTcuMTU2MzczIDkuODk4NzQyMS40MjQxNC0xLjQyMTE4OXY5LjEwMTE1OHptLTIwLjE4ODEwMi4wVjIwLjg1MzA2NUw0OC4xNDg1OTYgNS43NjczOTMydi43OTc4NjEzSDM3LjQ3NjM3MVY0Ljc5NDk5OTlINTAuMDY4NDVWNi4zMTU5MjI4TDM5LjM5NjUwMSAyMS4zNzY2NjF2LS43NzI5MjdoMTEuMDIxMjl2MS43NzAyNTN6bS0xMC4zOTYyOTguMjI0Mzk5Yy0xLjIxMzQxNC4wLTIuMzE5MDYyLS4yMDc3NzYtMy4zMTYzODktLjYyMzMyOC0uOTk3MzI3LS40MzIxNzUtMS44NDUwNTUtMS4wMzg4ODItMi41NDMxODQtMS44MjAxMjEtLjY5ODQwNi0uNzgxMjM5LTEuMjM4NjI0LTEuNzI4Ny0xLjYyMDkzMy0yLjg0MjY1OC0uMzY1Njg2LTEuMTEzNjgyLS41NDg1MjktMi4zNjAzMzktLjU0ODUyOS0zLjc0MDI1MS4wLTEuMzk2MjU3LjE4Mjg0My0yLjY0MjkxNy41NDg1MjktMy43NDAyNTIuMzgyMzA5LTEuMTEzNjgyLjkyMjUyNy0yLjA1MjgzMDkgMS42MjA2NTYtMi44MTc0NDc1LjY5ODEyOS0uNzgxMjM5MiAxLjUzNzU0NS0xLjM3OTkxMjEgMi41MTgyNS0xLjc5NTQ2NDguOTk3NjA0LS40MzIxNzQ5IDIuMTExMjg3LS42NDgyNjIzIDMuMzQxNi0uNjQ4MjYyMyAxLjI0NjY1Ny4wIDIuMzYwMzM5LjIwNzc3NjMgMy4zNDEwNDQuNjIzMzI5MS45OTczMjcuNDE1NTUyNyAxLjg0NTA1NSAxLjAxMzk0ODYgMi41NDM0NTkgMS43OTUxODc3LjcxNDc1MS43ODEyMzk4IDEuMjU0OTY5IDEuNzI4Njk5OCAxLjYyMDY1NSAyLjg0MjY1NzguMzgyMzEgMS4wOTcwNTkuNTczNDY0IDIuMzM1NDA2LjU3MzQ2NCAzLjcxNTA0Mi4wIDEuMzk2NTMzLS4xOTExNTQgMi42NTE3NzktLjU3MzQ2NCAzLjc2NTQ2MS0uMzgyMzA4IDEuMTEzNjgyLS45MjI1MjYgMi4wNjExNDItMS42MjA2NTUgMi44NDIzODFzLTEuNTQ1ODU2IDEuMzg3OTQ2LTIuNTQzMTgzIDEuODIwMzk4Yy0uOTgwNzA0LjQxNTU1Mi0yLjA5NDY2My42MjMzMjgtMy4zNDEzMi42MjMzMjh6bTAtMS44MjAxMmMxLjI2MzI3OS4wIDIuMzI3MDk0LS4yODI1NzYgMy4xOTE0NDMtLjg0NzcyOC44ODA5NzMtLjU2NTE1MiAxLjU1NDE2OS0xLjM4Nzk0NiAyLjAxOTU4OC0yLjQ2ODY2LjQ2NTY5Ni0xLjA4MDQzNy42OTg0MDYtMi4zNzY5NjEuNjk4NDA2LTMuODg5ODUuMC0xLjUyOTIzNC0uMjMyNzEtMi44MjU3NTgtLjY5ODEyOS0zLjg4OTg1MS0uNDY1NDE5LTEuMDYzODE1LTEuMTM4NjE0LTEuODc4Mjk3OC0yLjAxOTU4Ny0yLjQ0MzQ1LS44NjQzNS0uNTY1MTUxOC0xLjkyODQ0Mi0uODQ3NzI3Ni0zLjE5MTcyMS0uODQ3NzI3Ni0xLjIzMDAzOC4wLTIuMjg1ODE4LjI4MjU3NTgtMy4xNjY3OTEuODQ3NzI3Ni0uODY0MzUuNTY1MTUyMi0xLjUyOTIzNCAxLjM4Nzk0Ni0xLjk5NDY1MyAyLjQ2ODM4My0uNDY1NDE5IDEuMDYzODE1LS42OTgxMjkgMi4zNTIwMjktLjY5ODEyOSAzLjg2NDY0MS4wIDEuNTEyODg5LjIzMjcxIDIuODA5NjkuNjk4MTI5IDMuODkwMTI3LjQ2NTQxOSAxLjA2MzgxNSAxLjEzMDMwMyAxLjg4NjYwOSAxLjk5NDY1MyAyLjQ2ODM4My44ODA5NzMuNTY1MTUxIDEuOTM2NDc4Ljg0NzcyNyAzLjE2NjUxMy44NDc3Mjd6bS0xNi4wNDE3MjQgMS44MjAxMmMtMS43Mjg2OTgxLjAtMy4yMDgzNDE3LS4zNjU2ODYtNC40Mzg2NTYxLTEuMDk3MDU5QzUuMzY5NjU3MSAyMC43Njk5NTQgNC40MjIxOTY2IDE5LjczMTA3MyAzLjc1NzMxMzEgMTguMzg0NjgyYy0uNjQ4MjYzLTEuMzQ2NjY3LS45NzIzOTM2LTIuOTUwOTc4LS45NzIzOTM2LTQuODEyNjU0LjAtMS44NjE2NzcuMzI0MTMwNi0zLjQ1NzM5OS45NzIzOTM2LTQuNzg3NDQ1LjY2NDg4MzUtMS4zNDYzOTA2IDEuNjEyMzQ0LTIuMzg1MjcyNSAyLjg0MjM3OTgtMy4xMTY5MjI0QzcuODI5NzI5OCA0LjkzNjI4NzcgOS4zMDkzNzM0IDQuNTcwNjAxNCAxMS4wMzgzNDkgNC41NzA2MDE0YzEuMjEzNDE0LjAgMi4zMzU0MDguMTkxMTU0MyAzLjM2NTk3OC41NzM0NjI4IDEuMDMwNTcyLjM4MjMwODUgMS44OTQ5MjIuOTM5MTQ5MSAyLjU5MzMyNiAxLjY3MDUyMTlMMTYuMTk5NzkxIDguNDg1MTA4QzE1LjQzNTE3NSA3Ljc3MDM1OCAxNC42MzczMTMgNy4yNTUwNzIgMTMuODA2MjA5IDYuOTM5MjUxOSAxMi45OTE0NDcgNi42MDY4MDk4IDEyLjA4NTU0MyA2LjQ0MDU4ODcgMTEuMDg4MjE2IDYuNDQwNTg4N2MtMS45NzgwMzA0LjAtMy40OTA5MTg4LjYyMzMyOS00LjUzODM4OTIgMS44Njk5ODgzLTEuMDQ3MTkyNyAxLjI0NjY1Ny0xLjU3MDc4ODYgMy4wMDA1NjYtMS41NzA3ODg2IDUuMjYxNDUxLjAgMi4yNjA2MDYuNTIzNTk1OSA0LjAyMjU0OSAxLjU3MDc4ODYgNS4yODYxMDggMS4wNDcxOTI5IDEuMjQ2NjU3IDIuNTYwMDgyMyAxLjg2OTk4NiA0LjUzODM4OTIgMS44Njk5ODYuOTk3MzI3LjAgMS45MDMyMzEtLjE1NzkwOSAyLjcxNzcxNS0uNDczNzMuODMxMTA2LS4zMzI0NDIgMS42Mjg5NjgtLjg1NjAzOCAyLjM5MzU4NC0xLjU3MDc4OWwuNzk4MTM4IDEuNjcwNTIyYy0uNjk4MTI4LjcxNDc1MS0xLjU2MjQ3OCAxLjI3MTg2OS0yLjU5MzA0OCAxLjY3MDc5OS0xLjAzMDU3Mi4zODIzMDktMi4xNTI4NDIuNTczNDYyLTMuMzY2MjU2LjU3MzQ2MnoiIGZpbGw9e3Rva2VuLmNvbG9yVGV4dH0+PC9wYXRoPgo8L3N2Zz4K" + ICON_SVG: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgd2lkdGg9IjkxIgogICBoZWlnaHQ9IjkxIgogICB2aWV3Qm94PSIwIDAgNjguMjUwMDI0IDY4LjI1MDAyNCIKICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnODI2IgogICBzb2RpcG9kaTpkb2NuYW1lPSJmYXZpY29uLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4xLjEgKGMzMDg0ZWYsIDIwMjEtMDktMjIpIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4MzAiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJuYW1lZHZpZXc4MjgiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZWNoZWNrZXJib2FyZD0iMCIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMC43NzAzNTc0MSIKICAgICBpbmtzY2FwZTpjeD0iNDM2LjgxMDIzIgogICAgIGlua3NjYXBlOmN5PSI1NDEuOTU2MjMiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxNzIwIgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEzODciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjE3MjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjI1IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnODI2IiAvPgogIDxyZWN0CiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC42MTc5MDUiCiAgICAgaWQ9InJlY3Q5MzgiCiAgICAgd2lkdGg9IjY4LjI1MDAyMyIKICAgICBoZWlnaHQ9IjY4LjI1MDAyMyIKICAgICB4PSIwIgogICAgIHk9Ii0xLjc3NjM1NjhlLTE1IiAvPgogIDxwYXRoCiAgICAgZmlsbC1ydWxlPSJldmVub2RkIgogICAgIGNsaXAtcnVsZT0iZXZlbm9kZCIKICAgICBkPSJtIDE2LjYwMTU5OCwxMy45MjY5MSBjIDAsLTEuMjgwMTE1IDEuMDE1MDUxLC0yLjMxNzg2MSAyLjI2NzQyNCwtMi4zMTc4NjEgaCAzMS43NDM5MzcgYyAxLjI1MjM3OCwwIDIuMjY3NDIsMS4wMzc3NDYgMi4yNjc0MiwyLjMxNzg2MSB2IDAgYyAwLDEuMjgwMTMzIC0xLjAxNTA0MiwyLjMxNzg3IC0yLjI2NzQyLDIuMzE3ODcgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsLTEuMDM3NzM3IC0yLjI2NzQyNCwtMi4zMTc4NyB6IG0gMCw0MS43MjE1NzIgYyAwLC0xLjI4MDA4MSAxLjAxNTA1MSwtMi4zMTc4NjUgMi4yNjc0MjQsLTIuMzE3ODY1IGggMzEuNzQzOTM3IGMgMS4yNTIzNzgsMCAyLjI2NzQyLDEuMDM3Nzg0IDIuMjY3NDIsMi4zMTc4NjUgdiAwIGMgMCwxLjI4MDE1OCAtMS4wMTUwNDIsMi4zMTc4NjYgLTIuMjY3NDIsMi4zMTc4NjYgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsLTEuMDM3NzA4IC0yLjI2NzQyNCwtMi4zMTc4NjYgeiBtIDM2LjI3ODc4MSwtMjAuODYwOCBjIDAsLTEuMjgwMDggLTEuMDE1MDQyLC0yLjMxNzg2NSAtMi4yNjc0MiwtMi4zMTc4NjUgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsMS4wMzc3ODUgLTIuMjY3NDI0LDIuMzE3ODY1IHYgMCBjIDAsMS4yODAxNTggMS4wMTUwNTEsMi4zMTc4NjYgMi4yNjc0MjQsMi4zMTc4NjYgaCAzMS43NDM5MzcgYyAxLjI1MjM3OCwwIDIuMjY3NDIsLTEuMDM3NzA4IDIuMjY3NDIsLTIuMzE3ODY2IHoiCiAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICBpZD0icGF0aDg0MCIKICAgICBzdHlsZT0ic3Ryb2tlLXdpZHRoOjAuNzY0MTYzIiAvPgo8L3N2Zz4K" diff --git a/packages/system/dashboard/templates/web.yaml b/packages/system/dashboard/templates/web.yaml index 7a87b813..8a12d3f5 100644 --- a/packages/system/dashboard/templates/web.yaml +++ b/packages/system/dashboard/templates/web.yaml @@ -131,17 +131,35 @@ spec: - name: LOGIN_USERNAME_FIELD value: "preferredUsername" - name: FOOTER_TEXT - value: Cozystack + valueFrom: + configMapKeyRef: + name: incloud-web-dashboard-config + key: FOOTER_TEXT - name: TITLE_TEXT - value: Cozystack Dashboard + valueFrom: + configMapKeyRef: + name: incloud-web-dashboard-config + key: TITLE_TEXT - name: TENANT_TEXT - value: v0.37.0-alpha.0 + valueFrom: + configMapKeyRef: + name: incloud-web-dashboard-config + key: TENANT_TEXT - name: LOGO_TEXT - value: "false" + valueFrom: + configMapKeyRef: + name: incloud-web-dashboard-config + key: LOGO_TEXT - name: LOGO_SVG - value: PHN2ZyB3aWR0aD0iMTUwIiBoZWlnaHQ9IjMwIiB2aWV3Qm94PSIwIDAgMTUwIDMwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwYXRoIGQ9Ik0xMzMuMzMxMDkgMjIuMzczOTg3VjQuNzk1Mjc2OWgyLjA0NDUyVjEyLjc3Mzg5aC4wNDk5bDguNTI3NzEtNy45NzkxNjcyaDIuNjE4MjdsLTkuNzc0NjQgOS4xMDExNTgyLjAyNDktMS4wOTcwNTkgMTAuMjk4MjQgOS41NzQ4ODloLTIuNjkyNzlsLTkuMDAxNjktOC4yNzgzNjNoLS4wNDk5djguMjc4MzYzem0tOS4xNzIzNi4yMjQzOTljLTEuNzI4NjkuMC0zLjIwODA1LS4zNjU2ODYtNC40MzgxLTEuMDk3MDU5LTEuMjMwNTktLjczMTM3My0yLjE3ODA0LTEuNzcwMjU0LTIuODQyOTMtMy4xMTY2NDUtLjY0ODI3LTEuMzQ2NjY3LS45NzIzOS0yLjk1MDk3OC0uOTcyMzktNC44MTI2NTQuMC0xLjg2MTY3Ny4zMjQxMi0zLjQ1NzM5OS45NzIzOS00Ljc4NzQ0NS42NjQ4OS0xLjM0NjM5MDYgMS42MTIzNC0yLjM4NTI3MjUgMi44NDI2Ni0zLjExNjkyMjQgMS4yMzAwMy0uNzMxMzcyOSAyLjcwOTQxLTEuMDk3MDU5MiA0LjQzODM3LTEuMDk3MDU5MiAxLjIxMzQyLjAgMi4zMzU0MS4xOTExNTQzIDMuMzY2MjYuNTczNDYyOCAxLjAzMDU3LjM4MjMwODUgMS44OTQ5My45MzkxNDkxIDIuNTkzMDUgMS42NzA1MjE5bC0uNzk3ODYgMS42NzA1MjE5Yy0uNzY0NjItLjcxNDc1LTEuNTYyNDgtMS4yMzAwMzYtMi4zOTM1OS0xLjU0NTg1NjEtLjgxNDQ3LS4zMzI0NDIxLTEuNzIwMzgtLjQ5ODY2MzItMi43MTc3MS0uNDk4NjYzMi0xLjk3ODU5LjAtMy40OTEyLjYyMzMyOS00LjUzODM4IDEuODY5OTg4My0xLjA0NzIxIDEuMjQ2NjU3LTEuNTcwOCAzLjAwMDU2Ni0xLjU3MDggNS4yNjE0NTEuMCAyLjI2MDYwNi41MjM1OSA0LjAyMjU0OSAxLjU3MDggNS4yODYxMDggMS4wNDcxOCAxLjI0NjY1NyAyLjU1OTc5IDEuODY5OTg2IDQuNTM4MDkgMS44Njk5ODYuOTk3MzQuMCAxLjkwMzI0LS4xNTc5MDkgMi43MTc3My0uNDczNzMuODMxMzgtLjMzMjQ0MiAxLjYyOTI0LS44NTYwMzggMi4zOTM4Ni0xLjU3MDc4OWwuNzk3ODYgMS42NzA1MjJjLS42OTgxMi43MTQ3NTEtMS41NjI0OCAxLjI3MTg2OS0yLjU5MzA1IDEuNjcwNzk5LTEuMDMwNTguMzgyMzA5LTIuMTUyNTcuNTczNDYyLTMuMzY1OTcuNTczNDYyek05Ni45ODQ2MzEgMjIuMzczOTg3IDEwNC43Mzk0IDQuNzk0OTk5OWgxLjc0NTMzbDcuNzU0NzYgMTcuNTc4OTg3MWgtMi4xMTkzMmwtMi4xNjkxOS01LjAxMTg0MS45OTczMy41MjM1OTVoLTEwLjcyMjA5bDEuMDIyMjctLjUyMzU5NS0yLjE0NDI1NCA1LjAxMTg0MXptOC42MDI0OTktMTUuMTg1NDAzNC00LjAxNDI0IDkuNDUwNTAwNC0uNTk4NC0uNDczNzNoOS4yMjU1NWwtLjU0ODUzLjQ3MzczLTQuMDE0MjUtOS40NTA1MDA0ek04OS44OTM5MjEgMjIuMzczOTg3VjYuNTY1NTMxNkg4My41MTAyMDJWNC43OTUyNzY5aDE0LjgzNjMzVjYuNTY1NTMxNkg5MS45NjMzNjZWMjIuMzc0MjY1eiIgZmlsbD17dG9rZW4uY29sb3JUZXh0fT48L3BhdGg+CiAgPHBhdGggZD0ibTY3Ljg1NDM4NSA0Ljc3MzExNDJoMTQuMDgwODd2MS43NjAwMDQyaC0xNC4wODA4N3ptMCAxNS44NDA4Njk4aDE0LjA4MDg3djEuNzYwMDAzaC0xNC4wODA4N3ptMTQuMDgwODctNy45MjA0MzVoLTE0LjA4MDg3djEuNzYwMDA1aDE0LjA4MDg3eiIgZmlsbD17dG9rZW4uY29sb3JUZXh0fT48L3BhdGg+CiAgPHBhdGggZD0ibTU3LjY2NDQ3MyAyMi4zNzM5ODd2LTkuMTAxMTU4bC40NDg4MDQgMS40MjExOTEtNy4xODEzMDktOS44OTkwMjAxaDIuMzkzODYxbDUuNjYwMTA5IDcuODI5NTY3MWgtLjUyMzYwNmw1LjY2MDM5MS03LjgyOTU2NzFoMi4zMTg3ODdsLTcuMTU2MzczIDkuODk4NzQyMS40MjQxNC0xLjQyMTE4OXY5LjEwMTE1OHptLTIwLjE4ODEwMi4wVjIwLjg1MzA2NUw0OC4xNDg1OTYgNS43NjczOTMydi43OTc4NjEzSDM3LjQ3NjM3MVY0Ljc5NDk5OTlINTAuMDY4NDVWNi4zMTU5MjI4TDM5LjM5NjUwMSAyMS4zNzY2NjF2LS43NzI5MjdoMTEuMDIxMjl2MS43NzAyNTN6bS0xMC4zOTYyOTguMjI0Mzk5Yy0xLjIxMzQxNC4wLTIuMzE5MDYyLS4yMDc3NzYtMy4zMTYzODktLjYyMzMyOC0uOTk3MzI3LS40MzIxNzUtMS44NDUwNTUtMS4wMzg4ODItMi41NDMxODQtMS44MjAxMjEtLjY5ODQwNi0uNzgxMjM5LTEuMjM4NjI0LTEuNzI4Ny0xLjYyMDkzMy0yLjg0MjY1OC0uMzY1Njg2LTEuMTEzNjgyLS41NDg1MjktMi4zNjAzMzktLjU0ODUyOS0zLjc0MDI1MS4wLTEuMzk2MjU3LjE4Mjg0My0yLjY0MjkxNy41NDg1MjktMy43NDAyNTIuMzgyMzA5LTEuMTEzNjgyLjkyMjUyNy0yLjA1MjgzMDkgMS42MjA2NTYtMi44MTc0NDc1LjY5ODEyOS0uNzgxMjM5MiAxLjUzNzU0NS0xLjM3OTkxMjEgMi41MTgyNS0xLjc5NTQ2NDguOTk3NjA0LS40MzIxNzQ5IDIuMTExMjg3LS42NDgyNjIzIDMuMzQxNi0uNjQ4MjYyMyAxLjI0NjY1Ny4wIDIuMzYwMzM5LjIwNzc3NjMgMy4zNDEwNDQuNjIzMzI5MS45OTczMjcuNDE1NTUyNyAxLjg0NTA1NSAxLjAxMzk0ODYgMi41NDM0NTkgMS43OTUxODc3LjcxNDc1MS43ODEyMzk4IDEuMjU0OTY5IDEuNzI4Njk5OCAxLjYyMDY1NSAyLjg0MjY1NzguMzgyMzEgMS4wOTcwNTkuNTczNDY0IDIuMzM1NDA2LjU3MzQ2NCAzLjcxNTA0Mi4wIDEuMzk2NTMzLS4xOTExNTQgMi42NTE3NzktLjU3MzQ2NCAzLjc2NTQ2MS0uMzgyMzA4IDEuMTEzNjgyLS45MjI1MjYgMi4wNjExNDItMS42MjA2NTUgMi44NDIzODFzLTEuNTQ1ODU2IDEuMzg3OTQ2LTIuNTQzMTgzIDEuODIwMzk4Yy0uOTgwNzA0LjQxNTU1Mi0yLjA5NDY2My42MjMzMjgtMy4zNDEzMi42MjMzMjh6bTAtMS44MjAxMmMxLjI2MzI3OS4wIDIuMzI3MDk0LS4yODI1NzYgMy4xOTE0NDMtLjg0NzcyOC44ODA5NzMtLjU2NTE1MiAxLjU1NDE2OS0xLjM4Nzk0NiAyLjAxOTU4OC0yLjQ2ODY2LjQ2NTY5Ni0xLjA4MDQzNy42OTg0MDYtMi4zNzY5NjEuNjk4NDA2LTMuODg5ODUuMC0xLjUyOTIzNC0uMjMyNzEtMi44MjU3NTgtLjY5ODEyOS0zLjg4OTg1MS0uNDY1NDE5LTEuMDYzODE1LTEuMTM4NjE0LTEuODc4Mjk3OC0yLjAxOTU4Ny0yLjQ0MzQ1LS44NjQzNS0uNTY1MTUxOC0xLjkyODQ0Mi0uODQ3NzI3Ni0zLjE5MTcyMS0uODQ3NzI3Ni0xLjIzMDAzOC4wLTIuMjg1ODE4LjI4MjU3NTgtMy4xNjY3OTEuODQ3NzI3Ni0uODY0MzUuNTY1MTUyMi0xLjUyOTIzNCAxLjM4Nzk0Ni0xLjk5NDY1MyAyLjQ2ODM4My0uNDY1NDE5IDEuMDYzODE1LS42OTgxMjkgMi4zNTIwMjktLjY5ODEyOSAzLjg2NDY0MS4wIDEuNTEyODg5LjIzMjcxIDIuODA5NjkuNjk4MTI5IDMuODkwMTI3LjQ2NTQxOSAxLjA2MzgxNSAxLjEzMDMwMyAxLjg4NjYwOSAxLjk5NDY1MyAyLjQ2ODM4My44ODA5NzMuNTY1MTUxIDEuOTM2NDc4Ljg0NzcyNyAzLjE2NjUxMy44NDc3Mjd6bS0xNi4wNDE3MjQgMS44MjAxMmMtMS43Mjg2OTgxLjAtMy4yMDgzNDE3LS4zNjU2ODYtNC40Mzg2NTYxLTEuMDk3MDU5QzUuMzY5NjU3MSAyMC43Njk5NTQgNC40MjIxOTY2IDE5LjczMTA3MyAzLjc1NzMxMzEgMTguMzg0NjgyYy0uNjQ4MjYzLTEuMzQ2NjY3LS45NzIzOTM2LTIuOTUwOTc4LS45NzIzOTM2LTQuODEyNjU0LjAtMS44NjE2NzcuMzI0MTMwNi0zLjQ1NzM5OS45NzIzOTM2LTQuNzg3NDQ1LjY2NDg4MzUtMS4zNDYzOTA2IDEuNjEyMzQ0LTIuMzg1MjcyNSAyLjg0MjM3OTgtMy4xMTY5MjI0QzcuODI5NzI5OCA0LjkzNjI4NzcgOS4zMDkzNzM0IDQuNTcwNjAxNCAxMS4wMzgzNDkgNC41NzA2MDE0YzEuMjEzNDE0LjAgMi4zMzU0MDguMTkxMTU0MyAzLjM2NTk3OC41NzM0NjI4IDEuMDMwNTcyLjM4MjMwODUgMS44OTQ5MjIuOTM5MTQ5MSAyLjU5MzMyNiAxLjY3MDUyMTlMMTYuMTk5NzkxIDguNDg1MTA4QzE1LjQzNTE3NSA3Ljc3MDM1OCAxNC42MzczMTMgNy4yNTUwNzIgMTMuODA2MjA5IDYuOTM5MjUxOSAxMi45OTE0NDcgNi42MDY4MDk4IDEyLjA4NTU0MyA2LjQ0MDU4ODcgMTEuMDg4MjE2IDYuNDQwNTg4N2MtMS45NzgwMzA0LjAtMy40OTA5MTg4LjYyMzMyOS00LjUzODM4OTIgMS44Njk5ODgzLTEuMDQ3MTkyNyAxLjI0NjY1Ny0xLjU3MDc4ODYgMy4wMDA1NjYtMS41NzA3ODg2IDUuMjYxNDUxLjAgMi4yNjA2MDYuNTIzNTk1OSA0LjAyMjU0OSAxLjU3MDc4ODYgNS4yODYxMDggMS4wNDcxOTI5IDEuMjQ2NjU3IDIuNTYwMDgyMyAxLjg2OTk4NiA0LjUzODM4OTIgMS44Njk5ODYuOTk3MzI3LjAgMS45MDMyMzEtLjE1NzkwOSAyLjcxNzcxNS0uNDczNzMuODMxMTA2LS4zMzI0NDIgMS42Mjg5NjgtLjg1NjAzOCAyLjM5MzU4NC0xLjU3MDc4OWwuNzk4MTM4IDEuNjcwNTIyYy0uNjk4MTI4LjcxNDc1MS0xLjU2MjQ3OCAxLjI3MTg2OS0yLjU5MzA0OCAxLjY3MDc5OS0xLjAzMDU3Mi4zODIzMDktMi4xNTI4NDIuNTczNDYyLTMuMzY2MjU2LjU3MzQ2MnoiIGZpbGw9e3Rva2VuLmNvbG9yVGV4dH0+PC9wYXRoPgo8L3N2Zz4K + valueFrom: + configMapKeyRef: + name: incloud-web-dashboard-config + key: LOGO_SVG - name: ICON_SVG - value: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgd2lkdGg9IjkxIgogICBoZWlnaHQ9IjkxIgogICB2aWV3Qm94PSIwIDAgNjguMjUwMDI0IDY4LjI1MDAyNCIKICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnODI2IgogICBzb2RpcG9kaTpkb2NuYW1lPSJmYXZpY29uLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4xLjEgKGMzMDg0ZWYsIDIwMjEtMDktMjIpIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4MzAiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJuYW1lZHZpZXc4MjgiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZWNoZWNrZXJib2FyZD0iMCIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMC43NzAzNTc0MSIKICAgICBpbmtzY2FwZTpjeD0iNDM2LjgxMDIzIgogICAgIGlua3NjYXBlOmN5PSI1NDEuOTU2MjMiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxNzIwIgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEzODciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjE3MjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjI1IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnODI2IiAvPgogIDxyZWN0CiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC42MTc5MDUiCiAgICAgaWQ9InJlY3Q5MzgiCiAgICAgd2lkdGg9IjY4LjI1MDAyMyIKICAgICBoZWlnaHQ9IjY4LjI1MDAyMyIKICAgICB4PSIwIgogICAgIHk9Ii0xLjc3NjM1NjhlLTE1IiAvPgogIDxwYXRoCiAgICAgZmlsbC1ydWxlPSJldmVub2RkIgogICAgIGNsaXAtcnVsZT0iZXZlbm9kZCIKICAgICBkPSJtIDE2LjYwMTU5OCwxMy45MjY5MSBjIDAsLTEuMjgwMTE1IDEuMDE1MDUxLC0yLjMxNzg2MSAyLjI2NzQyNCwtMi4zMTc4NjEgaCAzMS43NDM5MzcgYyAxLjI1MjM3OCwwIDIuMjY3NDIsMS4wMzc3NDYgMi4yNjc0MiwyLjMxNzg2MSB2IDAgYyAwLDEuMjgwMTMzIC0xLjAxNTA0MiwyLjMxNzg3IC0yLjI2NzQyLDIuMzE3ODcgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsLTEuMDM3NzM3IC0yLjI2NzQyNCwtMi4zMTc4NyB6IG0gMCw0MS43MjE1NzIgYyAwLC0xLjI4MDA4MSAxLjAxNTA1MSwtMi4zMTc4NjUgMi4yNjc0MjQsLTIuMzE3ODY1IGggMzEuNzQzOTM3IGMgMS4yNTIzNzgsMCAyLjI2NzQyLDEuMDM3Nzg0IDIuMjY3NDIsMi4zMTc4NjUgdiAwIGMgMCwxLjI4MDE1OCAtMS4wMTUwNDIsMi4zMTc4NjYgLTIuMjY3NDIsMi4zMTc4NjYgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsLTEuMDM3NzA4IC0yLjI2NzQyNCwtMi4zMTc4NjYgeiBtIDM2LjI3ODc4MSwtMjAuODYwOCBjIDAsLTEuMjgwMDggLTEuMDE1MDQyLC0yLjMxNzg2NSAtMi4yNjc0MiwtMi4zMTc4NjUgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsMS4wMzc3ODUgLTIuMjY3NDI0LDIuMzE3ODY1IHYgMCBjIDAsMS4yODAxNTggMS4wMTUwNTEsMi4zMTc4NjYgMi4yNjc0MjQsMi4zMTc4NjYgaCAzMS43NDM5MzcgYyAxLjI1MjM3OCwwIDIuMjY3NDIsLTEuMDM3NzA4IDIuMjY3NDIsLTIuMzE3ODY2IHoiCiAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICBpZD0icGF0aDg0MCIKICAgICBzdHlsZT0ic3Ryb2tlLXdpZHRoOjAuNzY0MTYzIiAvPgo8L3N2Zz4K + valueFrom: + configMapKeyRef: + name: incloud-web-dashboard-config + key: ICON_SVG image: {{ .Values.openapiUI.image | quote }} imagePullPolicy: IfNotPresent livenessProbe: diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 19e406c0..86f825ff 100644 --- a/packages/system/dashboard/values.yaml +++ b/packages/system/dashboard/values.yaml @@ -1,6 +1,6 @@ openapiUI: - image: ghcr.io/cozystack/cozystack/openapi-ui:latest@sha256:a8cd0ce7ba55a59df39c23d930bc3bf5b6bb390a24dce157d36c3828e9052365 + image: ghcr.io/cozystack/cozystack/openapi-ui:latest@sha256:db5cc174e221fe74df7379cbed2b7ab8f6cb17760168790307649ff63ef37b12 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:latest@sha256:9647ec88955e4749079e4f10c3a941dc57bd74bda67094d2ea0d45943d04a8e7 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:latest@sha256:4ed5debc85d64c7fc09f719e1efb1a2e1515219a355728c0ae574d4989485850 tokenProxy: image: ghcr.io/cozystack/cozystack/token-proxy:latest@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b