From 907dcb5e8bb923c197fe041b63bf5122b45ca2e0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 17:14:35 +0200 Subject: [PATCH 1/7] [dashboard] Fix API group for the applications Signed-off-by: Andrei Kvapil --- internal/controller/dashboard/helpers.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/internal/controller/dashboard/helpers.go b/internal/controller/dashboard/helpers.go index 7baa8a33..e5361c53 100644 --- a/internal/controller/dashboard/helpers.go +++ b/internal/controller/dashboard/helpers.go @@ -32,22 +32,11 @@ func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kin 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] - } - } + // For applications, always use apps.cozystack.io group, not the CRD's own group + group = "apps.cozystack.io" + version = "v1alpha1" // Reasonable fallbacks if any are empty: - if group == "" { - group = "apps.cozystack.io" - } - if version == "" { - version = "v1alpha1" - } if kind == "" { kind = "Resource" } From f130895b308bcc6ae71718c4dfb068ed5ea278b0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 18:02:39 +0200 Subject: [PATCH 2/7] fix sidebars Signed-off-by: Andrei Kvapil --- internal/controller/dashboard/manager.go | 33 +++---- internal/controller/dashboard/sidebar.go | 108 ++++++++++++++++------- 2 files changed, 94 insertions(+), 47 deletions(-) diff --git a/internal/controller/dashboard/manager.go b/internal/controller/dashboard/manager.go index 49e42848..4065ec9e 100644 --- a/internal/controller/dashboard/manager.go +++ b/internal/controller/dashboard/manager.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -245,29 +246,29 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResource continue } - // Skip resources with non-empty spec.dashboard.name (tenant modules) - if strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { - continue - } + // Note: We include ALL resources with dashboard config, regardless of dashboard.name + // because ensureFactory and ensureBreadcrumb create resources for all CRDs with dashboard config g, v, kind := pickGVK(&crd) plural := pickPlural(kind, &crd) - // CustomColumnsOverride + // CustomColumnsOverride - created for ALL CRDs with dashboard config name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural) expected["CustomColumnsOverride"][name] = true - // CustomFormsOverride + // CustomFormsOverride - created for ALL CRDs with dashboard config name = fmt.Sprintf("%s.%s.%s", g, v, plural) expected["CustomFormsOverride"][name] = true - // CustomFormsPrefill + // CustomFormsPrefill - created for ALL CRDs with dashboard config expected["CustomFormsPrefill"][name] = true - // MarketplacePanel (name matches CRD name) - expected["MarketplacePanel"][crd.Name] = true + // MarketplacePanel - only created for CRDs WITHOUT dashboard.name + if strings.TrimSpace(crd.Spec.Dashboard.Name) == "" { + expected["MarketplacePanel"][crd.Name] = true + } - // Sidebar resources (multiple per CRD) + // Sidebar resources - created for ALL CRDs with dashboard config lowerKind := strings.ToLower(kind) detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) expected["Sidebar"][detailsID] = true @@ -291,15 +292,15 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResource expected["Sidebar"][sidebarID] = true } - // TableUriMapping + // TableUriMapping - created for ALL CRDs with dashboard config name = fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural) expected["TableUriMapping"][name] = true - // Breadcrumb + // Breadcrumb - created for ALL CRDs with dashboard config detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) expected["Breadcrumb"][detailID] = true - // Factory + // Factory - created for ALL CRDs with dashboard config factoryName := fmt.Sprintf("%s-details", lowerKind) expected["Factory"][factoryName] = true } @@ -423,22 +424,24 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O case *dashv1alpha1.BreadcrumbList: for _, item := range l.Items { if !expected[item.Name] { + logger := log.FromContext(ctx) + logger.Info("Deleting orphaned Breadcrumb resource", "name", item.Name) if err := m.client.Delete(ctx, &item); err != nil { if !apierrors.IsNotFound(err) { return err } - // Resource already deleted, continue } } } case *dashv1alpha1.FactoryList: for _, item := range l.Items { if !expected[item.Name] { + logger := log.FromContext(ctx) + logger.Info("Deleting orphaned Factory resource", "name", item.Name) if err := m.client.Delete(ctx, &item); err != nil { if !apierrors.IsNotFound(err) { return err } - // Resource already deleted, continue } } } diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index 59901924..cbce7c44 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -57,6 +57,9 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack categories := map[string][]item{} // category label -> children keysAndTags := map[string]any{} // plural -> []string{ "-sidebar" } + // Collect sidebar names for resources with dashboard.name + var moduleSidebars []any + for i := range all { def := &all[i] @@ -65,35 +68,46 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack continue } - // Skip resources with non-empty spec.dashboard.name - if strings.TrimSpace(def.Spec.Dashboard.Name) != "" { - continue - } - g, v, kind := pickGVK(def) plural := pickPlural(kind, def) - cat := safeCategory(def) // falls back to "Resources" if empty + lowerKind := strings.ToLower(kind) - // Label: prefer dashboard.Plural if provided - label := titleFromKindPlural(kind, plural) - if def.Spec.Dashboard.Plural != "" { - label = def.Spec.Dashboard.Plural + // Check if this resource has dashboard.name set + if strings.TrimSpace(def.Spec.Dashboard.Name) != "" { + // Add to modules sidebar list + moduleSidebars = append(moduleSidebars, fmt.Sprintf("%s-sidebar", lowerKind)) + } else { + // Add to keysAndTags for resources without dashboard.name + keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)} } - // Weight (default 0) - weight := def.Spec.Dashboard.Weight + // Only add to menu categories if dashboard.name is empty + if strings.TrimSpace(def.Spec.Dashboard.Name) == "" { + cat := safeCategory(def) // falls back to "Resources" if empty - link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", g, v, plural) + // Label: prefer dashboard.Plural if provided + label := titleFromKindPlural(kind, plural) + if def.Spec.Dashboard.Plural != "" { + label = def.Spec.Dashboard.Plural + } - categories[cat] = append(categories[cat], item{ - Key: plural, - Label: label, - Link: link, - Weight: weight, - }) + // Weight (default 0) + weight := def.Spec.Dashboard.Weight - // keysAndTags: plural -> [ "-sidebar" ] - keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", strings.ToLower(kind))} + link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", g, v, plural) + + categories[cat] = append(categories[cat], item{ + Key: plural, + Label: label, + Link: link, + Weight: weight, + }) + } + } + + // Add modules to keysAndTags if we have any module sidebars + if len(moduleSidebars) > 0 { + keysAndTags["modules"] = moduleSidebars } // 3) Sort items within each category by Weight (desc), then Label (A→Z) @@ -171,14 +185,8 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack }) // 6) Prepare the list of Sidebar IDs to upsert with the SAME content - _, _, thisKind := pickGVK(crd) - lowerThisKind := strings.ToLower(thisKind) - detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerThisKind) - + // Create sidebars for ALL CRDs with dashboard config targetIDs := []string{ - // original details sidebar - detailsID, - // stock-instance sidebars "stock-instance-api-form", "stock-instance-api-table", @@ -196,6 +204,18 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack "stock-project-crd-table", } + // Add details sidebars for all CRDs with dashboard config + for i := range all { + def := &all[i] + if def.Spec.Dashboard == nil { + continue + } + _, _, kind := pickGVK(def) + lowerKind := strings.ToLower(kind) + detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) + targetIDs = append(targetIDs, detailsID) + } + // 7) Upsert all target sidebars with identical menuItems and keysAndTags return m.upsertMultipleSidebars(ctx, crd, targetIDs, keysAndTags, menuItems) } @@ -219,11 +239,35 @@ func (m *Manager) upsertMultipleSidebars( obj.SetName(id) if _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error { - if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { - return err + // Only set owner reference for dynamic sidebars (stock-project-factory-{kind}-details) + // Static sidebars (stock-instance-*, stock-project-*) should not have owner references + if strings.HasPrefix(id, "stock-project-factory-") && strings.HasSuffix(id, "-details") { + // This is a dynamic sidebar, set owner reference only if it matches the current CRD + _, _, kind := pickGVK(crd) + lowerKind := strings.ToLower(kind) + expectedID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind) + if id == expectedID { + if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil { + return err + } + // Add dashboard labels to dynamic resources + m.addDashboardLabels(obj, crd, ResourceTypeDynamic) + } else { + // This is a different CRD's sidebar, don't modify owner references or labels + // Just update the spec + } + } else { + // This is a static sidebar, don't set owner references + // Add static labels + labels := obj.GetLabels() + if labels == nil { + labels = make(map[string]string) + } + labels[LabelManagedBy] = ManagedByValue + labels[LabelResourceType] = ResourceTypeStatic + obj.SetLabels(labels) } - // Add dashboard labels to dynamic resources - m.addDashboardLabels(obj, crd, ResourceTypeDynamic) + b, err := json.Marshal(spec) if err != nil { return err From 37f9065d5513fc01200777bfa1f62e409607dc79 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 18:05:26 +0200 Subject: [PATCH 3/7] Introduce module parameter Signed-off-by: Andrei Kvapil --- api/v1alpha1/cozystackresourcedefinitions_types.go | 3 +++ internal/controller/dashboard/breadcrumb.go | 5 ++--- internal/controller/dashboard/manager.go | 6 +++--- internal/controller/dashboard/marketplacepanel.go | 7 +++---- internal/controller/dashboard/sidebar.go | 12 ++++++------ .../cozystack-resource-definitions/etcd.yaml | 1 + .../cozystack-resource-definitions/info.yaml | 1 + .../cozystack-resource-definitions/ingress.yaml | 1 + .../cozystack-resource-definitions/monitoring.yaml | 1 + .../cozystack-resource-definitions/seaweedfs.yaml | 1 + .../cozystack.io_cozystackresourcedefinitions.yaml | 3 +++ 11 files changed, 25 insertions(+), 16 deletions(-) diff --git a/api/v1alpha1/cozystackresourcedefinitions_types.go b/api/v1alpha1/cozystackresourcedefinitions_types.go index 94759edf..760a5922 100644 --- a/api/v1alpha1/cozystackresourcedefinitions_types.go +++ b/api/v1alpha1/cozystackresourcedefinitions_types.go @@ -153,4 +153,7 @@ type CozystackResourceDefinitionDashboard struct { // Order of keys in the YAML view // +optional KeysOrder [][]string `json:"keysOrder,omitempty"` + // Whether this resource is a module (tenant module) + // +optional + Module bool `json:"module,omitempty"` } diff --git a/internal/controller/dashboard/breadcrumb.go b/internal/controller/dashboard/breadcrumb.go index 788a32b7..0ad53ddb 100644 --- a/internal/controller/dashboard/breadcrumb.go +++ b/internal/controller/dashboard/breadcrumb.go @@ -34,9 +34,8 @@ func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.Cozyst 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) != "" { + // If this is a module, change the first breadcrumb item to "Tenant Modules" + if crd.Spec.Dashboard != nil && crd.Spec.Dashboard.Module { key = "tenantmodules" label = "Tenant Modules" link = "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules" diff --git a/internal/controller/dashboard/manager.go b/internal/controller/dashboard/manager.go index 4065ec9e..4b5c2fb3 100644 --- a/internal/controller/dashboard/manager.go +++ b/internal/controller/dashboard/manager.go @@ -246,7 +246,7 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResource continue } - // Note: We include ALL resources with dashboard config, regardless of dashboard.name + // Note: We include ALL resources with dashboard config, regardless of module flag // because ensureFactory and ensureBreadcrumb create resources for all CRDs with dashboard config g, v, kind := pickGVK(&crd) @@ -263,8 +263,8 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResource // CustomFormsPrefill - created for ALL CRDs with dashboard config expected["CustomFormsPrefill"][name] = true - // MarketplacePanel - only created for CRDs WITHOUT dashboard.name - if strings.TrimSpace(crd.Spec.Dashboard.Name) == "" { + // MarketplacePanel - only created for non-module CRDs + if !crd.Spec.Dashboard.Module { expected["MarketplacePanel"][crd.Name] = true } diff --git a/internal/controller/dashboard/marketplacepanel.go b/internal/controller/dashboard/marketplacepanel.go index 571d84e4..a6900737 100644 --- a/internal/controller/dashboard/marketplacepanel.go +++ b/internal/controller/dashboard/marketplacepanel.go @@ -3,7 +3,6 @@ package dashboard import ( "context" "encoding/json" - "strings" dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" @@ -39,8 +38,8 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1. return reconcile.Result{}, nil } - // Skip resources with non-empty spec.dashboard.name - if strings.TrimSpace(crd.Spec.Dashboard.Name) != "" { + // Skip module resources (they don't need MarketplacePanel) + if crd.Spec.Dashboard.Module { err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp) if apierrors.IsNotFound(err) { return reconcile.Result{}, nil @@ -51,7 +50,7 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1. 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) + logger.Info("Deleted MarketplacePanel because resource is a module", "name", mp.Name) return reconcile.Result{}, nil } diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index cbce7c44..8ec4ef25 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -57,7 +57,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack categories := map[string][]item{} // category label -> children keysAndTags := map[string]any{} // plural -> []string{ "-sidebar" } - // Collect sidebar names for resources with dashboard.name + // Collect sidebar names for module resources var moduleSidebars []any for i := range all { @@ -72,17 +72,17 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack plural := pickPlural(kind, def) lowerKind := strings.ToLower(kind) - // Check if this resource has dashboard.name set - if strings.TrimSpace(def.Spec.Dashboard.Name) != "" { + // Check if this resource is a module + if def.Spec.Dashboard.Module { // Add to modules sidebar list moduleSidebars = append(moduleSidebars, fmt.Sprintf("%s-sidebar", lowerKind)) } else { - // Add to keysAndTags for resources without dashboard.name + // Add to keysAndTags for non-module resources keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)} } - // Only add to menu categories if dashboard.name is empty - if strings.TrimSpace(def.Spec.Dashboard.Name) == "" { + // Only add to menu categories if not a module + if !def.Spec.Dashboard.Module { cat := safeCategory(def) // falls back to "Resources" if empty // Label: prefer dashboard.Plural if provided diff --git a/packages/system/cozystack-api/templates/cozystack-resource-definitions/etcd.yaml b/packages/system/cozystack-api/templates/cozystack-resource-definitions/etcd.yaml index c70e0290..862a2cf6 100644 --- a/packages/system/cozystack-api/templates/cozystack-resource-definitions/etcd.yaml +++ b/packages/system/cozystack-api/templates/cozystack-resource-definitions/etcd.yaml @@ -26,6 +26,7 @@ spec: plural: Etcd name: etcd description: Storage for Kubernetes clusters + module: true icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9Ii0wLjAwMTk1MzEyIiB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgcng9IjI0IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjgzXzI5NjMpIi8+CjxwYXRoIGQ9Ik0xMjIuNDQyIDczLjQ3MjlDMTIxLjk1OSA3My41MTM0IDEyMS40NzQgNzMuNTMyMiAxMjAuOTU4IDczLjUzMjJDMTE3Ljk2NSA3My41MzIyIDExNS4wNjEgNzIuODMwNCAxMTIuNDQyIDcxLjU0NTFDMTEzLjMxNCA2Ni41NDIxIDExMy42ODUgNjEuNTAxOSAxMTMuNTg4IDU2LjQ4MDJDMTEwLjc0OCA1Mi4zNzIzIDEwNy41MDIgNDguNDk2NSAxMDMuODM4IDQ0LjkyNTdDMTA1LjQyOCA0MS45NDU0IDEwNy43NzggMzkuMzgxMSAxMTAuNzExIDM3LjU2MjhMMTExLjk3MSAzNi43ODQyTDExMC45ODkgMzUuNjc3NEMxMDUuOTMyIDI5Ljk4MzIgOTkuODk3MSAyNS41ODA5IDkzLjA1NDcgMjIuNTkzN0w5MS42OTAyIDIyTDkxLjM0MzcgMjMuNDQyM0M5MC41Mjc3IDI2LjgwMzYgODguODIyMiAyOS44MzYgODYuNDgwNyAzMi4yNjk1QzgxLjk4MDMgMjkuODc3NCA3Ny4yNzg4IDI3Ljk0NCA3Mi40MzA1IDI2LjQ3OTdDNjcuNTkzNyAyNy45NDA4IDYyLjkwMDUgMjkuODY4NiA1OC40MDIgMzIuMjU3MUM1Ni4wNzAxIDI5LjgyNjggNTQuMzY4OCAyNi44MDE4IDUzLjU1NiAyMy40NTAxTDUzLjIwNzIgMjIuMDA4M0w1MS44NDc3IDIyLjU5OTJDNDUuMDkxNCAyNS41NDMxIDM4Ljg5MDEgMzAuMDY0NyAzMy45MTYyIDM1LjY3NDJMMzIuOTMxOCAzNi43ODMzTDM0LjE5IDM3LjU2MTlDMzcuMTE0MiAzOS4zNzMzIDM5LjQ1NzYgNDEuOTIyNCA0MS4wNDQ0IDQ0Ljg4NjZDMzcuMzkxNyA0OC40NDM1IDM0LjE0OTUgNTIuMzA3IDMxLjMxMTkgNTYuMzk1OUMzMS4yMDE0IDYxLjQxNTQgMzEuNTUzNSA2Ni40OTI0IDMyLjQyOTcgNzEuNTY0NEMyOS44MjMxIDcyLjgzNzggMjYuOTM1OCA3My41MzE4IDIzLjk2MjggNzMuNTMxOEMyMy40NDA5IDczLjUzMTggMjIuOTUyNyA3My41MTI5IDIyLjQ3ODIgNzMuNDczM0wyMSA3My4zNjA2TDIxLjEzODUgNzQuODM2NUMyMS44NjI5IDgyLjMwMzMgMjQuMTgxNCA4OS40MDUzIDI4LjAzMzQgOTUuOTQ3MUwyOC43ODUzIDk3LjIyMzdMMjkuOTE0MiA5Ni4yNjU2QzMyLjUzMDUgOTQuMDQ2NSAzNS42OTE3IDkyLjU3NyAzOS4wNTMgOTEuOTg0N0M0MS4yNjg5IDk2LjUxNTUgNDMuODk1MyAxMDAuNzcyIDQ2Ljg3NDcgMTA0LjcyNUM1MS42Mjg3IDEwNi4zODcgNTYuNTgxOSAxMDcuNjI5IDYxLjY5NzEgMTA4LjM2N0M2Mi4xODc3IDExMS43NSA2MS43OTcgMTE1LjI0OSA2MC40NjI0IDExOC40ODRMNTkuODk5NSAxMTkuODU1TDYxLjM0NjkgMTIwLjE3NEM2NS4wNTI5IDEyMC45ODkgNjguNzkxNyAxMjEuNDA0IDcyLjQ1MjYgMTIxLjQwNEw4My41NTUxIDEyMC4xNzRMODUuMDAzOSAxMTkuODU1TDg0LjQzOTcgMTE4LjQ4MkM4My4xMDg3IDExNS4yNDYgODIuNzE4IDExMS43NDMgODMuMjA4NiAxMDguMzZDODguMzAzNiAxMDcuNjIxIDkzLjIzODQgMTA2LjM4MiA5Ny45NzQ4IDEwNC43MjVDMTAwLjk1NyAxMDAuNzY5IDEwMy41ODYgOTYuNTA5NSAxMDUuODA1IDkxLjk3MjhDMTA5LjE3NyA5Mi41NjE0IDExMi4zNTYgOTQuMDMxNyAxMTQuOTg5IDk2LjI1NzNMMTE2LjExOCA5Ny4yMTQxTDExNi44NjYgOTUuOTQwN0MxMjAuNzI1IDg5LjM5MDUgMTIzLjA0MyA4Mi4yODkxIDEyMy43NTYgNzQuODM0MkwxMjMuODk1IDczLjM2MUwxMjIuNDQyIDczLjQ3MjlaTTg4LjMxOTcgOTEuNTE4MUM4My4wNjczIDkyLjk0NjYgNzcuNzMzIDkzLjY2NzcgNzIuNDMwNSA5My42Njc3QzY3LjExMzcgOTMuNjY3NyA2MS43ODU5IDkyLjk0NyA1Ni41MjkgOTEuNTE4MUM1My42NDQ4IDg3LjAzNjYgNTEuMzY0NSA4Mi4yMzU3IDQ5LjcyMzQgNzcuMTgxMkM0OC4wODkyIDcyLjE1MDIgNDcuMTMyOSA2Ni44Nzk1IDQ2Ljg1NTQgNjEuNDUyMkM1MC4yNTA0IDU3LjI1NDcgNTQuMTExIDUzLjU3NzYgNTguMzc2NyA1MC40ODIzQzYyLjcxMTQgNDcuMzI5NCA2Ny40MjcxIDQ0Ljc2NzkgNzIuNDMwNSA0Mi44NDFDNzcuNDI1NiA0NC43NjgzIDgyLjEzMjYgNDcuMzI2MiA4Ni40NTcyIDUwLjQ2NTdDOTAuNzM5NCA1My41Nzc2IDk0LjYxNzEgNTcuMjgzMiA5OC4wMjg3IDYxLjUwN0M5Ny43Mzc4IDY2LjkwMzQgOTYuNzcgNzIuMTQzOCA5NS4xMzMgNzcuMTY2NUM5My40OTYxIDgyLjIyIDkxLjIwODQgODcuMDM2MSA4OC4zMTk3IDkxLjUxODFaTTc2Ljc2ODQgNjYuMTk3NEM3Ni43Njg0IDY5LjkwODEgNzkuNzc1NCA3Mi45MDk2IDgzLjQ4MSA3Mi45MDk2Qzg3LjE4NTcgNzIuOTA5NiA5MC4xODk1IDY5LjkwODYgOTAuMTg5NSA2Ni4xOTc0QzkwLjE4OTUgNjIuNTAxIDg3LjE4NTcgNTkuNDg4MSA4My40ODEgNTkuNDg4MUM3OS43NzU0IDU5LjQ4ODEgNzYuNzY4NCA2Mi41MDEgNzYuNzY4NCA2Ni4xOTc0Wk02OC4wOTU0IDY2LjE5NzRDNjguMDk1NCA2OS45MDgxIDY1LjA4ODggNzIuOTA5NiA2MS4zODMyIDcyLjkwOTZDNTcuNjc0OSA3Mi45MDk2IDU0LjY3NjYgNjkuOTA4NiA1NC42NzY2IDY2LjE5NzRDNTQuNjc2NiA2Mi41MDI0IDU3LjY3NTMgNTkuNDg5NCA2MS4zODMyIDU5LjQ4OTRDNjUuMDg4OCA1OS40ODk0IDY4LjA5NTQgNjIuNTAyNCA2OC4wOTU0IDY2LjE5NzRaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4M18yOTYzIiB4MT0iNS41IiB5MT0iMTEiIHgyPSIxNDEiIHkyPSIxMjQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjNTNCMkYwIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzQxOUVEQSIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo= keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "size"], ["spec", "storageClass"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resources", "cpu"], ["spec", "resources", "memory"]] secrets: diff --git a/packages/system/cozystack-api/templates/cozystack-resource-definitions/info.yaml b/packages/system/cozystack-api/templates/cozystack-resource-definitions/info.yaml index 1abbb833..57945cd8 100644 --- a/packages/system/cozystack-api/templates/cozystack-resource-definitions/info.yaml +++ b/packages/system/cozystack-api/templates/cozystack-resource-definitions/info.yaml @@ -26,6 +26,7 @@ spec: singular: Info plural: Info description: Info + module: true icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX3JhZGlhbF8xNDRfMykiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE0NF8zKSI+CjxwYXRoIGQ9Ik03Ny42NDA3IDk3LjA4NDRMODIuODMzIDk3LjM2MDRWMTA0LjYzN0g2MS4xNzI4Vjk3LjcxOTdMNjQuMTc3MSA5Ny40NDk1QzY1LjgxMDEgOTcuMjY4NCA2Ni44MTA2IDk2LjcxOTMgNjYuODEwNiA5NC41MzQzVjY5LjIzMTRDNjYuODEwNiA2Ny4yMjE3IDY2LjI3MDEgNjYuNTg2NCA2NC41MzY1IDY2LjU4NjRMNjEuMzU2OCA2Ni40MDgxVjU4Ljg1ODRINzcuNjQ2NUw3Ny42NDA3IDk3LjA4NDRaTTcxLjI3MjYgMzkuMzYzQzc1LjI4MDQgMzkuMzYzIDc4LjE4NyA0Mi4zNzMxIDc4LjE4NyA0Ni4xODgzQzc4LjE4NyA1MC4wMTQ5IDc1LjI3MTggNTIuODM4MSA3MS4xNzc4IDUyLjgzODFDNjYuOTk3NSA1Mi44MzgxIDY0LjI2NjMgNTAuMDE0OSA2NC4yNjYzIDQ2LjE4ODNDNjQuMjY2MyA0Mi4zNzMxIDY2Ljk5NzUgMzkuMzYzIDcxLjI3MjYgMzkuMzYzWk03MiAxMThDNDYuNjM2OCAxMTggMjYgOTcuMzYzMiAyNiA3MkMyNiA0Ni42MzY4IDQ2LjYzNjggMjYgNzIgMjZDOTcuMzU3NSAyNiAxMTggNDYuNjM2OCAxMTggNzJDMTE4IDk3LjM2MzIgOTcuMzU3NSAxMTggNzIgMTE4Wk03MiAzNC42MjVDNTEuMzkyIDM0LjYyNSAzNC42MjUgNTEuMzkyIDM0LjYyNSA3MkMzNC42MjUgOTIuNjA4IDUxLjM5MiAxMDkuMzc1IDcyIDEwOS4zNzVDOTIuNjA4IDEwOS4zNzUgMTA5LjM3NSA5Mi42MDggMTA5LjM3NSA3MkMxMDkuMzc1IDUxLjM5MiA5Mi42MDggMzQuNjI1IDcyIDM0LjYyNVoiIGZpbGw9IndoaXRlIi8+CjwvZz4KPGRlZnM+CjxyYWRpYWxHcmFkaWVudCBpZD0icGFpbnQwX3JhZGlhbF8xNDRfMyIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgxLjMyMjk4ZS0wNSAtNy41MDAwMSkgcm90YXRlKDQ0LjcxNzgpIHNjYWxlKDIxNS4zMTcgMzEyLjQ1NSkiPgo8c3RvcCBzdG9wLWNvbG9yPSIjMDBCNUU3Ii8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwMzk4NCIvPgo8L3JhZGlhbEdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzE0NF8zIj4KPHJlY3Qgd2lkdGg9IjkyIiBoZWlnaHQ9IjkyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjYgMjYpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg== keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"]] secrets: diff --git a/packages/system/cozystack-api/templates/cozystack-resource-definitions/ingress.yaml b/packages/system/cozystack-api/templates/cozystack-resource-definitions/ingress.yaml index 48142dcf..a319d3a2 100644 --- a/packages/system/cozystack-api/templates/cozystack-resource-definitions/ingress.yaml +++ b/packages/system/cozystack-api/templates/cozystack-resource-definitions/ingress.yaml @@ -26,6 +26,7 @@ spec: plural: Ingress name: ingress description: NGINX Ingress Controller + module: true icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODRfMzIyOSkiLz4KPHBhdGggZD0iTTg2LjkyNzQgMzcuMTA3NEgxN1YxMDcuMDM1SDg2LjkyNzRWMzcuMTA3NFoiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEyNy42NDMgMjlIMTA3LjQ1NVY0OS4xODgzSDEyNy42NDNWMjlaIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xMjcuNjQzIDYxLjcyNjZIMTA3LjQ1NVY4MS45MTQ5SDEyNy42NDNWNjEuNzI2NloiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEyNy42NDMgOTQuNDUyMUgxMDcuNDU1VjExNC42NEgxMjcuNjQzVjk0LjQ1MjFaIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik04OC41MTM3IDcyLjA3MTNIMTA2LjI3IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik04Ny41Njc0IDgwLjQyNDhMMTA3LjczIDk1Ljc4MDUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTg3LjU2NzQgNjMuNzE4MUwxMDcuNzMgNDguMzYyMyIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4NF8zMjI5IiB4MT0iMTAiIHkxPSIxNS41IiB4Mj0iMTQ0IiB5Mj0iMTMxLjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzAwREE1MyIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMwMDk2MzkiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "whitelist"], ["spec", "cloudflareProxy"], ["spec", "resources"], ["spec", "resourcesPreset"]] secrets: diff --git a/packages/system/cozystack-api/templates/cozystack-resource-definitions/monitoring.yaml b/packages/system/cozystack-api/templates/cozystack-resource-definitions/monitoring.yaml index a891adba..a6908d5e 100644 --- a/packages/system/cozystack-api/templates/cozystack-resource-definitions/monitoring.yaml +++ b/packages/system/cozystack-api/templates/cozystack-resource-definitions/monitoring.yaml @@ -26,6 +26,7 @@ spec: plural: Monitoring name: monitoring description: Monitoring and observability stack + module: true icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzI2OCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zMjY4KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMzg0NTRGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNNTQuMTcyMiAzOC43NzU3SDMzLjEwMzJDMzIuMTMwNyAzOC43NzU3IDMxLjQ4MjQgMzguMTI3NCAzMS40ODI0IDM3LjE1NDlDMzEuNDgyNCAzNi4xODI0IDMyLjEzMDcgMzUuNTM0MiAzMy4xMDMyIDM1LjUzNDJINTQuMTcyMkM1NS4xNDQ3IDM1LjUzNDIgNTUuNzkzIDM2LjE4MjQgNTUuNzkzIDM3LjE1NDlDNTUuNzkyOCAzOC4xMjc0IDU1LjE0NDUgMzguNzc1NyA1NC4xNzIyIDM4Ljc3NTdaIiBmaWxsPSIjREQzNDJFIi8+CjxwYXRoIGQ9Ik02My44OTYzIDQ1LjI1OTFINDEuMjA2N0M0MC4yMzQyIDQ1LjI1OTEgMzkuNTg1OSA0NC42MTA4IDM5LjU4NTkgNDMuNjM4M0MzOS41ODU5IDQyLjY2NTggNDAuMjM0MiA0Mi4wMTc2IDQxLjIwNjcgNDIuMDE3Nkg2My44OTYzQzY0Ljg2ODggNDIuMDE3NiA2NS41MTcxIDQyLjY2NTggNjUuNTE3MSA0My42MzgzQzY1LjUxNzEgNDQuNjEwOCA2NC44Njg4IDQ1LjI1OTEgNjMuODk2MyA0NS4yNTkxWiIgZmlsbD0iIzczODNCRiIvPgo8cGF0aCBkPSJNMzQuNzI0IDQ1LjI1OTFIMzMuMTAzMkMzMi4xMzA3IDQ1LjI1OTEgMzEuNDgyNCA0NC42MTA4IDMxLjQ4MjQgNDMuNjM4M0MzMS40ODI0IDQyLjY2NTggMzIuMTMwNyA0Mi4wMTc2IDMzLjEwMzIgNDIuMDE3NkgzNC43MjRDMzUuNjk2NCA0Mi4wMTc2IDM2LjM0NDcgNDIuNjY1OCAzNi4zNDQ3IDQzLjYzODNDMzYuMzQ0NyA0NC42MTA4IDM1LjY5NjMgNDUuMjU5MSAzNC43MjQgNDUuMjU5MVoiIGZpbGw9IiM0MkIwNUMiLz4KPHBhdGggZD0iTTYzLjg5NjMgMzguNzc1N0g2MC42NTQ5QzU5LjY4MjQgMzguNzc1NyA1OS4wMzQyIDM4LjEyNzQgNTkuMDM0MiAzNy4xNTQ5QzU5LjAzNDIgMzYuMTgyNCA1OS42ODI0IDM1LjUzNDIgNjAuNjU0OSAzNS41MzQySDYzLjg5NjNDNjQuODY4OCAzNS41MzQyIDY1LjUxNzEgMzYuMTgyNCA2NS41MTcxIDM3LjE1NDlDNjUuNTE3MSAzOC4xMjc0IDY0Ljg2ODggMzguNzc1NyA2My44OTYzIDM4Ljc3NTdaIiBmaWxsPSIjRUNCQTE2Ii8+CjxwYXRoIGQ9Ik00Ny42ODkzIDUxLjc0MTNIMzMuMTAzMkMzMi4xMzA3IDUxLjc0MTMgMzEuNDgyNCA1MS4wOTMxIDMxLjQ4MjQgNTAuMTIwNkMzMS40ODI0IDQ5LjE0ODEgMzIuMTMwNyA0OC41IDMzLjEwMzIgNDguNUg0Ny42ODkzQzQ4LjY2MTggNDguNSA0OS4zMTAxIDQ5LjE0ODMgNDkuMzEwMSA1MC4xMjA4QzQ5LjMxMDEgNTEuMDkzMyA0OC42NjE4IDUxLjc0MTMgNDcuNjg5MyA1MS43NDEzWiIgZmlsbD0iI0REMzQyRSIvPgo8cGF0aCBkPSJNNjMuODk2OCA1MS43NDEzSDU0LjE3MjVDNTMuMiA1MS43NDEzIDUyLjU1MTggNTEuMDkzMSA1Mi41NTE4IDUwLjEyMDZDNTIuNTUxOCA0OS4xNDgxIDUzLjIwMDIgNDguNSA1NC4xNzI3IDQ4LjVINjMuODk2OUM2NC44Njk0IDQ4LjUgNjUuNTE3NyA0OS4xNDgzIDY1LjUxNzcgNTAuMTIwOEM2NS41MTc3IDUxLjA5MzMgNjQuODY5MiA1MS43NDEzIDYzLjg5NjggNTEuNzQxM1oiIGZpbGw9IiNFQ0JBMTYiLz4KPHBhdGggZD0iTTU0LjE3MjIgNTguMjI0SDMzLjEwMzJDMzIuMTMwNyA1OC4yMjQgMzEuNDgyNCA1Ny41NzU3IDMxLjQ4MjQgNTYuNjAzMkMzMS40ODI0IDU1LjYzMDcgMzIuMTMwNyA1NC45ODI0IDMzLjEwMzIgNTQuOTgyNEg1NC4xNzIyQzU1LjE0NDcgNTQuOTgyNCA1NS43OTMgNTUuNjMwNyA1NS43OTMgNTYuNjAzMkM1NS43OTMgNTcuNTc1NyA1NS4xNDQ1IDU4LjIyNCA1NC4xNzIyIDU4LjIyNFoiIGZpbGw9IiM0MkIwNUMiLz4KPHBhdGggZD0iTTYzLjg5NjMgNjQuNzA3NEg0MS4yMDY3QzQwLjIzNDIgNjQuNzA3NCAzOS41ODU5IDY0LjA1OTEgMzkuNTg1OSA2My4wODY2QzM5LjU4NTkgNjIuMTE0MSA0MC4yMzQyIDYxLjQ2NTggNDEuMjA2NyA2MS40NjU4SDYzLjg5NjNDNjQuODY4OCA2MS40NjU4IDY1LjUxNzEgNjIuMTE0MSA2NS41MTcxIDYzLjA4NjZDNjUuNTE3MSA2NC4wNTkxIDY0Ljg2ODggNjQuNzA3NCA2My44OTYzIDY0LjcwNzRaIiBmaWxsPSIjRUNCQTE2Ii8+CjxwYXRoIGQ9Ik0zNC43MjQgNjQuNzA3NEgzMy4xMDMyQzMyLjEzMDcgNjQuNzA3NCAzMS40ODI0IDY0LjA1OTEgMzEuNDgyNCA2My4wODY2QzMxLjQ4MjQgNjIuMTE0MSAzMi4xMzA3IDYxLjQ2NTggMzMuMTAzMiA2MS40NjU4SDM0LjcyNEMzNS42OTY0IDYxLjQ2NTggMzYuMzQ0NyA2Mi4xMTQxIDM2LjM0NDcgNjMuMDg2NkMzNi4zNDQ3IDY0LjA1OTEgMzUuNjk2MyA2NC43MDc0IDM0LjcyNCA2NC43MDc0WiIgZmlsbD0iI0REMzQyRSIvPgo8cGF0aCBkPSJNNDcuNjg5MyA3MS4xODk4SDMzLjEwMzJDMzIuMTMwNyA3MS4xODk4IDMxLjQ4MjQgNzAuNTQxNSAzMS40ODI0IDY5LjU2OUMzMS40ODI0IDY4LjU5NjUgMzIuMTMwNyA2Ny45NDgyIDMzLjEwMzIgNjcuOTQ4Mkg0Ny42ODkzQzQ4LjY2MTggNjcuOTQ4MiA0OS4zMTAxIDY4LjU5NjUgNDkuMzEwMSA2OS41NjlDNDkuMzEwMSA3MC41NDE1IDQ4LjY2MTggNzEuMTg5OCA0Ny42ODkzIDcxLjE4OThaIiBmaWxsPSIjNDJCMDVDIi8+CjxwYXRoIGQ9Ik02My44OTY4IDcxLjE4OThINTQuMTcyNUM1My4yIDcxLjE4OTggNTIuNTUxOCA3MC41NDE1IDUyLjU1MTggNjkuNTY5QzUyLjU1MTggNjguNTk2NSA1My4yIDY3Ljk0ODIgNTQuMTcyNSA2Ny45NDgySDYzLjg5NjhDNjQuODY5MiA2Ny45NDgyIDY1LjUxNzUgNjguNTk2NSA2NS41MTc1IDY5LjU2OUM2NS41MTc1IDcwLjU0MTUgNjQuODY5MiA3MS4xODk4IDYzLjg5NjggNzEuMTg5OFoiIGZpbGw9IiM3MzgzQkYiLz4KPHBhdGggZD0iTTU0LjE3MjIgNzcuNjcyMkgzMy4xMDMyQzMyLjEzMDcgNzcuNjcyMiAzMS40ODI0IDc3LjAyMzkgMzEuNDgyNCA3Ni4wNTE0QzMxLjQ4MjQgNzUuMDc4OSAzMi4xMzA3IDc0LjQzMDcgMzMuMTAzMiA3NC40MzA3SDU0LjE3MjJDNTUuMTQ0NyA3NC40MzA3IDU1Ljc5MyA3NS4wNzg5IDU1Ljc5MyA3Ni4wNTE0QzU1Ljc5MjggNzcuMDIzOSA1NS4xNDQ1IDc3LjY3MjIgNTQuMTcyMiA3Ny42NzIyWiIgZmlsbD0iI0VDQkExNiIvPgo8cGF0aCBkPSJNNjMuODk2MyA3Ny42NzIySDYwLjY1NDlDNTkuNjgyNCA3Ny42NzIyIDU5LjAzNDIgNzcuMDIzOSA1OS4wMzQyIDc2LjA1MTRDNTkuMDM0MiA3NS4wNzg5IDU5LjY4MjQgNzQuNDMwNyA2MC42NTQ5IDc0LjQzMDdINjMuODk2M0M2NC44Njg4IDc0LjQzMDcgNjUuNTE3MSA3NS4wNzg5IDY1LjUxNzEgNzYuMDUxNEM2NS41MTcxIDc3LjAyMzkgNjQuODY4OCA3Ny42NzIyIDYzLjg5NjMgNzcuNjcyMloiIGZpbGw9IiM0MkIwNUMiLz4KPHBhdGggZD0iTTEwMS4xNzIgNzcuNjcyMkg4MC4xMDMyQzc5LjEzMDcgNzcuNjcyMiA3OC40ODI0IDc3LjAyMzkgNzguNDgyNCA3Ni4wNTE0Qzc4LjQ4MjQgNzUuMDc4OSA3OS4xMzA3IDc0LjQzMDcgODAuMTAzMiA3NC40MzA3SDEwMS4xNzJDMTAyLjE0NSA3NC40MzA3IDEwMi43OTMgNzUuMDc4OSAxMDIuNzkzIDc2LjA1MTRDMTAyLjc5MyA3Ny4wMjM5IDEwMi4xNDUgNzcuNjcyMiAxMDEuMTcyIDc3LjY3MjJaIiBmaWxsPSIjREQzNDJFIi8+CjxwYXRoIGQ9Ik0xMTAuODk2IDc3LjY3MjJIMTA3LjY1NUMxMDYuNjgyIDc3LjY3MjIgMTA2LjAzNCA3Ny4wMjM5IDEwNi4wMzQgNzYuMDUxNEMxMDYuMDM0IDc1LjA3ODkgMTA2LjY4MiA3NC40MzA3IDEwNy42NTUgNzQuNDMwN0gxMTAuODk2QzExMS44NjkgNzQuNDMwNyAxMTIuNTE3IDc1LjA3ODkgMTEyLjUxNyA3Ni4wNTE0QzExMi41MTcgNzcuMDIzOSAxMTEuODY5IDc3LjY3MjIgMTEwLjg5NiA3Ny42NzIyWiIgZmlsbD0iIzQyQjA1QyIvPgo8cGF0aCBkPSJNNjMuODk2MyA1OC4yMjRINjAuNjU0OUM1OS42ODI0IDU4LjIyNCA1OS4wMzQyIDU3LjU3NTcgNTkuMDM0MiA1Ni42MDMyQzU5LjAzNDIgNTUuNjMwNyA1OS42ODI0IDU0Ljk4MjQgNjAuNjU0OSA1NC45ODI0SDYzLjg5NjNDNjQuODY4OCA1NC45ODI0IDY1LjUxNzEgNTUuNjMwNyA2NS41MTcxIDU2LjYwMzJDNjUuNTE3MSA1Ny41NzU3IDY0Ljg2ODggNTguMjI0IDYzLjg5NjMgNTguMjI0WiIgZmlsbD0iIzczODNCRiIvPgo8cGF0aCBkPSJNMTEyLjUxNyA1MS43NDExQzExMi41MTcgNjAuNjU0OSAxMDUuMjI0IDY3Ljk0OCA5Ni4zMTA0IDY3Ljk0OEM4Ny4zOTY2IDY3Ljk0OCA4MC4xMDM1IDYwLjY1NDkgODAuMTAzNSA1MS43NDExQzgwLjEwMzUgNDIuODI3MyA4Ny4zOTY2IDM1LjUzNDIgOTYuMzEwNCAzNS41MzQyQzEwNS4yMjQgMzUuNTM0MiAxMTIuNTE3IDQyLjgyNzMgMTEyLjUxNyA1MS43NDExWiIgZmlsbD0iI0VDQkExNiIvPgo8cGF0aCBkPSJNODAuMTAzNSA1MS43NDExQzgwLjEwMzUgNTIuMjI3MyA4MC4xMDM1IDUyLjg3NTUgODAuMTAzNSA1My4zNjE5SDk2LjMxMDRWMzUuNTM0MkM4Ny4zOTY2IDM1LjUzNDIgODAuMTAzNSA0Mi44MjczIDgwLjEwMzUgNTEuNzQxMVoiIGZpbGw9IiM0MkIwNUMiLz4KPC9nPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zMjY4IiB4MT0iMS4yMzIzOWUtMDYiIHkxPSItOS41MDAwMSIgeDI9IjE2OCIgeTI9IjE2MiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjOEZEREZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwNzVGRiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzY4N18zMjY4Ij4KPHJlY3Qgd2lkdGg9Ijk0IiBoZWlnaHQ9Ijk0IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjUgMjUpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg== keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "metricsStorages"], ["spec", "logsStorages"], ["spec", "alerta"], ["spec", "alerta", "storage"], ["spec", "alerta", "storageClassName"], ["spec", "alerta", "resources"], ["spec", "alerta", "resources", "limits"], ["spec", "alerta", "resources", "limits", "cpu"], ["spec", "alerta", "resources", "limits", "memory"], ["spec", "alerta", "resources", "requests"], ["spec", "alerta", "resources", "requests", "cpu"], ["spec", "alerta", "resources", "requests", "memory"], ["spec", "alerta", "alerts"], ["spec", "alerta", "alerts", "telegram"], ["spec", "alerta", "alerts", "telegram", "token"], ["spec", "alerta", "alerts", "telegram", "chatID"], ["spec", "alerta", "alerts", "telegram", "disabledSeverity"], ["spec", "grafana"], ["spec", "grafana", "db"], ["spec", "grafana", "db", "size"], ["spec", "grafana", "resources"], ["spec", "grafana", "resources", "limits"], ["spec", "grafana", "resources", "limits", "cpu"], ["spec", "grafana", "resources", "limits", "memory"], ["spec", "grafana", "resources", "requests"], ["spec", "grafana", "resources", "requests", "cpu"], ["spec", "grafana", "resources", "requests", "memory"]] secrets: diff --git a/packages/system/cozystack-api/templates/cozystack-resource-definitions/seaweedfs.yaml b/packages/system/cozystack-api/templates/cozystack-resource-definitions/seaweedfs.yaml index 97156a3c..f4519c85 100644 --- a/packages/system/cozystack-api/templates/cozystack-resource-definitions/seaweedfs.yaml +++ b/packages/system/cozystack-api/templates/cozystack-resource-definitions/seaweedfs.yaml @@ -26,6 +26,7 @@ spec: plural: SeaweedFS name: seaweedfs description: Seaweedfs + module: true icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82NzlfMTkxMCkiLz4KPHBhdGggZD0iTTEzOC42ODUgMTIxLjA1N0MxMzguNjg1IDEyNi42NTIgMTM2LjQ2MiAxMzIuMDE3IDEzMi41MDQgMTM1Ljk3M0MxMjguNTQ3IDEzOS45MjkgMTIzLjE3OSAxNDIuMTUxIDExNy41ODIgMTQyLjE1MUMxMTEuOTg1IDE0Mi4xNTEgMTA2LjYxOCAxMzkuOTI5IDEwMi42NiAxMzUuOTczQzk4LjcwMjggMTMyLjAxNyA5Ni40Nzk1IDEyNi42NTIgOTYuNDc5NSAxMjEuMDU3Qzk2LjQ3OTUgMTE4LjI4NyA5Ny4wMjUzIDExNS41NDQgOTguMDg1OCAxMTIuOTg1Qzk5LjE0NjMgMTEwLjQyNSAxMDAuNzAxIDEwOC4xIDEwMi42NiAxMDYuMTQxQzEwNC42MiAxMDQuMTgyIDEwNi45NDYgMTAyLjYyOSAxMDkuNTA3IDEwMS41NjlDMTEyLjA2NyAxMDAuNTA5IDExNC44MTEgOTkuOTYyOSAxMTcuNTgyIDk5Ljk2MjlDMTIwLjM1MyA5OS45NjI5IDEyMy4wOTggMTAwLjUwOSAxMjUuNjU4IDEwMS41NjlDMTI4LjIxOCAxMDIuNjI5IDEzMC41NDQgMTA0LjE4MiAxMzIuNTA0IDEwNi4xNDFDMTM0LjQ2NCAxMDguMSAxMzYuMDE4IDExMC40MjUgMTM3LjA3OSAxMTIuOTg1QzEzOC4xMzkgMTE1LjU0NCAxMzguNjg1IDExOC4yODcgMTM4LjY4NSAxMjEuMDU3WiIgZmlsbD0idXJsKCNwYWludDFfcmFkaWFsXzY3OV8xOTEwKSIvPgo8cGF0aCBkPSJNMTEwLjMzNiAxMjYuMTQ3SDEyMy42OFYxMzYuODIzSDExMC4zMzZWMTI2LjE0N1oiIGZpbGw9IiMwRjVFOUMiLz4KPHBhdGggZD0iTTExOS4yNyAxMTIuMjk0QzExOS4yNyAxMTIuNzE0IDExOS4xNzggMTEzLjEzMSAxMTkgMTEzLjUxOUMxMTguODIxIDExMy45MDggMTE4LjU2IDExNC4yNjEgMTE4LjIzIDExNC41NThDMTE3LjkwMSAxMTQuODU1IDExNy41MDkgMTE1LjA5MSAxMTcuMDc5IDExNS4yNTJDMTE2LjY0OCAxMTUuNDEzIDExNi4xODYgMTE1LjQ5NiAxMTUuNzIgMTE1LjQ5NkMxMTQuNzc4IDExNS40OTYgMTEzLjg3NSAxMTUuMTU4IDExMy4yMSAxMTQuNTU4QzExMi41NDQgMTEzLjk1NyAxMTIuMTcgMTEzLjE0MyAxMTIuMTcgMTEyLjI5NEMxMTIuMTcgMTExLjQ0NSAxMTIuNTQ0IDExMC42MyAxMTMuMjEgMTEwLjAzQzExMy44NzUgMTA5LjQyOSAxMTQuNzc4IDEwOS4wOTIgMTE1LjcyIDEwOS4wOTJDMTE2LjE4NiAxMDkuMDkyIDExNi42NDggMTA5LjE3NSAxMTcuMDc5IDEwOS4zMzZDMTE3LjUwOSAxMDkuNDk2IDExNy45MDEgMTA5LjczMiAxMTguMjMgMTEwLjAzQzExOC41NiAxMTAuMzI3IDExOC44MjEgMTEwLjY4IDExOSAxMTEuMDY4QzExOS4xNzggMTExLjQ1NyAxMTkuMjcgMTExLjg3MyAxMTkuMjcgMTEyLjI5NFoiIGZpbGw9IiM1OTY4NkYiLz4KPHBhdGggZD0iTTEyOC4xMSAxMTQuMTAzQzEyOC4xMSAxMTQuOTUzIDEyNy43MzYgMTE1Ljc2NyAxMjcuMDcgMTE2LjM2OEMxMjYuNDA0IDExNi45NjggMTI1LjUwMSAxMTcuMzA1IDEyNC41NiAxMTcuMzA1QzEyNC4wOTQgMTE3LjMwNSAxMjMuNjMyIDExNy4yMjMgMTIzLjIwMSAxMTcuMDYyQzEyMi43NzEgMTE2LjkwMSAxMjIuMzc5IDExNi42NjUgMTIyLjA1IDExNi4zNjhDMTIxLjcyIDExNi4wNyAxMjEuNDU4IDExNS43MTcgMTIxLjI4IDExNS4zMjlDMTIxLjEwMiAxMTQuOTQgMTIxLjAxIDExNC41MjQgMTIxLjAxIDExNC4xMDNDMTIxLjAxIDExMy42ODMgMTIxLjEwMiAxMTMuMjY2IDEyMS4yOCAxMTIuODc4QzEyMS40NTggMTEyLjQ5IDEyMS43MiAxMTIuMTM3IDEyMi4wNSAxMTEuODM5QzEyMi4zNzkgMTExLjU0MiAxMjIuNzcxIDExMS4zMDYgMTIzLjIwMSAxMTEuMTQ1QzEyMy42MzIgMTEwLjk4NCAxMjQuMDk0IDExMC45MDEgMTI0LjU2IDExMC45MDFDMTI1LjUwMSAxMTAuOTAxIDEyNi40MDQgMTExLjIzOSAxMjcuMDcgMTExLjgzOUMxMjcuNzM2IDExMi40NCAxMjguMTEgMTEzLjI1NCAxMjguMTEgMTE0LjEwM1oiIGZpbGw9IiM1OTY4NkYiLz4KPHBhdGggZD0iTTEyMi4zMzMgMTE4Ljk3NkMxMjIuMzMzIDExOS44MjYgMTIxLjk1OCAxMjAuNjQgMTIxLjI5MyAxMjEuMjQxQzEyMC42MjcgMTIxLjg0MSAxMTkuNzI0IDEyMi4xNzggMTE4Ljc4MiAxMjIuMTc4QzExOC4zMTYgMTIyLjE3OCAxMTcuODU1IDEyMi4wOTYgMTE3LjQyNCAxMjEuOTM1QzExNi45OTMgMTIxLjc3NCAxMTYuNjAyIDEyMS41MzggMTE2LjI3MiAxMjEuMjQxQzExNS45NDMgMTIwLjk0MyAxMTUuNjgxIDEyMC41OSAxMTUuNTAzIDEyMC4yMDJDMTE1LjMyNCAxMTkuODEzIDExNS4yMzIgMTE5LjM5NyAxMTUuMjMyIDExOC45NzZDMTE1LjIzMiAxMTguNTU2IDExNS4zMjQgMTE4LjE0IDExNS41MDMgMTE3Ljc1MUMxMTUuNjgxIDExNy4zNjMgMTE1Ljk0MyAxMTcuMDEgMTE2LjI3MiAxMTYuNzEyQzExNi42MDIgMTE2LjQxNSAxMTYuOTkzIDExNi4xNzkgMTE3LjQyNCAxMTYuMDE4QzExNy44NTUgMTE1Ljg1NyAxMTguMzE2IDExNS43NzQgMTE4Ljc4MiAxMTUuNzc0QzExOS43MjQgMTE1Ljc3NCAxMjAuNjI3IDExNi4xMTIgMTIxLjI5MyAxMTYuNzEyQzEyMS45NTggMTE3LjMxMyAxMjIuMzMzIDExOC4xMjcgMTIyLjMzMyAxMTguOTc2WiIgZmlsbD0iIzU5Njg2RiIvPgo8cGF0aCBkPSJNMTE1LjMwOCAxMjEuOTA1QzExMy43MzUgMTIxLjQyNiAxMTUuNzA3IDEyMC42OCAxMTUuNDI5IDEyMC41NzNDMTE0LjY1MyAxMjAuMjc2IDExNS43MyAxMTkuMzMzIDExNi43NCAxMTguNTM5QzExNy42MjggMTE3Ljg0MSAxMTcuNjU5IDExNy44MzkgMTE3Ljg5MiAxMTguNDY4QzExOC4wMjQgMTE4LjgyMyAxMTguMzcxIDExOS4yMDYgMTE4LjY2NSAxMTkuMzE5QzExOS4yODkgMTE5LjU1OCAxMjAuOTUxIDExOS4yNjkgMTIwLjk1MSAxMTguODM1QzEyMC45NTEgMTE4LjIyMyAxMjEuNDE1IDExOC42OTkgMTIxLjc5NCAxMTkuNTMxQzEyMi40NTcgMTIwLjk4NyAxMjIuNDM3IDEyMi40NzIgMTIxLjQ1IDEyMi40NzJDMTIwLjg1OSAxMjIuNDcyIDEyMC4zMSAxMjIuNjkxIDEyMC4xOSAxMjMuMTQ3QzExNS4wNDYgMTI0LjYgMTE2LjQ3MSAxMjMuMjg0IDExNS4zMDggMTIxLjkwNVpNMTIzLjA5NCAxMTguMDU0QzEyMS42MDkgMTE3LjQ2NiAxMjAuNTQ3IDExNC44MzggMTIwLjU0NyAxMTMuMzA5QzEyMC41NDcgMTExLjMyMyAxMjIuNTQxIDEwOS4wOTUgMTI0LjMxNyAxMDkuMDk1QzEyOC4zMTUgMTA5LjA5NSAxMzAuNjg0IDExMi4yNjEgMTI4LjgzOCAxMTguNDA5QzEyOC4zODUgMTE5LjkxOSAxMjMuOTMzIDExOC4zODcgMTIzLjA5NCAxMTguMDU0Wk0xMjUuODY2IDExNS42NDlDMTI3LjU0NCAxMTQuNDc0IDEyNS44NTcgMTExLjc1NSAxMjMuOTgxIDExMi42MUMxMjIuNDc5IDExMy4yOTQgMTIzLjExOSAxMTYuMTE1IDEyNC43NyAxMTYuMTY0QzEyNC45NjEgMTE2LjE2OSAxMjUuNDU0IDExNS45MzggMTI1Ljg2NiAxMTUuNjQ5Wk0xMjQuMzM5IDExNC41NzlDMTIzLjc3MSAxMTQuMzgxIDEyMy44MDMgMTEzLjI1NiAxMjQuMzgzIDExMy4wMzRDMTI0LjkwMiAxMTIuODM1IDEyNS42MDQgMTEzLjM5OCAxMjUuNjA0IDExNC4wMTRDMTI1LjYwNCAxMTQuNDg5IDEyNC45MzYgMTE0Ljc4NyAxMjQuMzM5IDExNC41NzlaTTExMi4zMTkgMTE2Ljk4QzEwOS45NiAxMTUuNjcgMTEwLjI4NiAxMTEuMDA2IDExMi40MTcgMTA4Ljk2NUMxMTQuMzk2IDEwNy4wNjkgMTE4Ljc3OCAxMDcuMTAzIDExOS43NjQgMTA5LjQ2MkMxMjAuNDE1IDExMS4wMjEgMTIwLjIyOCAxMTIuNiAxMTkuMzk4IDExNC4wNzdDMTE4LjE5NCAxMTYuMjE5IDExNC4yNDIgMTE4LjA0OSAxMTIuMzE5IDExNi45OFpNMTE2LjU2IDExMy41OTNDMTE3LjMyNSAxMTIuOTAxIDExNy4yOTcgMTEyLjA2IDExNi41NzIgMTExLjI1OUMxMTUuNzc3IDExMC4zNzkgMTE0LjY5MiAxMTAuMzQ1IDExNC4wMzUgMTExLjM0N0MxMTMuNTI0IDExMi4xMjcgMTEzLjQzMSAxMTIuNTI4IDExMy45NDMgMTEzLjMwOUMxMTQuNTggMTE0LjI4IDExNS42NyAxMTQuMzk5IDExNi41NiAxMTMuNTkzWk0xMTQuMzc0IDExMi4zNEMxMTQuMTI4IDExMS42OTggMTE0Ljk4NSAxMTAuOTgxIDExNS42OCAxMTEuMjQ3QzExNS45NzQgMTExLjM2IDExNi4xNjQgMTExLjcxOCAxMTYuMTAyIDExMi4wNDRDMTE1Ljk2MSAxMTIuNzg1IDExNC42MzIgMTEzLjAxMiAxMTQuMzc0IDExMi4zNFoiIGZpbGw9IiNEM0Q2REEiLz4KPHBhdGggZD0iTTExOC40NjMgMTIxLjAwOEwxMTcuOTQ1IDEyMC43OEMxMTcuNTEgMTIwLjY4NSAxMTcuMjMxIDEyMS4xMDcgMTE3LjEzNiAxMjEuNTQzTDExNi44ODEgMTIyLjcwOUMxMTYuNzg2IDEyMy4xNDUgMTE3LjA2MSAxMjMuNTcxIDExNy40OTYgMTIzLjY2NkwxMTguNDQ3IDEyMy44NzRDMTE4Ljg4MiAxMjMuOTY5IDExOS4zMTEgMTIzLjY5NCAxMTkuNDA2IDEyMy4yNTlMMTE5LjY4NSAxMjEuNTU2QzExOS43OCAxMjEuMTIgMTE5LjQ3OSAxMjEuMjI4IDExOS4wNDMgMTIxLjEzM0wxMTguNjI4IDEyMS4wNDNDMTE4LjU3MyAxMjEuMzIxIDExOC41MTYgMTIxLjYxMyAxMTguNDcxIDEyMS44MzNDMTE4LjQzOCAxMjEuOTk4IDExOC40MDcgMTIyLjE0OCAxMTguMzc5IDEyMi4yODJDMTE4LjM1MSAxMjIuNDE1IDExOC4zMjcgMTIyLjUzMyAxMTguMzA1IDEyMi42MzVDMTE4LjI4MiAxMjIuNzM4IDExOC4yNjIgMTIyLjgyNSAxMTguMjQ1IDEyMi44OTdDMTE4LjIyOCAxMjIuOTY5IDExOC4yMTQgMTIzLjAyNSAxMTguMjAyIDEyMy4wNjhDMTE4LjE5NiAxMjMuMDg5IDExOC4xOTMgMTIzLjEwNyAxMTguMTg4IDEyMy4xMjFDMTE4LjE4MyAxMjMuMTM1IDExOC4xNzggMTIzLjE0NSAxMTguMTc1IDEyMy4xNTJDMTE4LjE3MyAxMjMuMTU1IDExOC4xNzIgMTIzLjE1NiAxMTguMTcxIDEyMy4xNThDMTE4LjE3IDEyMy4xNTkgMTE4LjE2OCAxMjMuMTYgMTE4LjE2NyAxMjMuMTZMMTE4LjE2NSAxMjMuMTU4QzExOC4xNjQgMTIzLjE1NiAxMTguMTYzIDEyMy4xNTIgMTE4LjE2MyAxMjMuMTQ4QzExOC4xNjIgMTIzLjE0IDExOC4xNjIgMTIzLjEyOSAxMTguMTYzIDEyMy4xMTVDMTE4LjE2MyAxMjMuMSAxMTguMTYzIDEyMy4wODMgMTE4LjE2NSAxMjMuMDYxQzExOC4xNjggMTIzLjAxOCAxMTguMTc1IDEyMi45NjEgMTE4LjE4MyAxMjIuODkxQzExOC4xOTIgMTIyLjgyIDExOC4yMDMgMTIyLjczNiAxMTguMjE2IDEyMi42NEMxMTguMjI5IDEyMi41NDMgMTE4LjI0NiAxMjIuNDMxIDExOC4yNjQgMTIyLjMwOEMxMTguMjgxIDEyMi4xODUgMTE4LjMwMSAxMjIuMDUxIDExOC4zMjMgMTIxLjkwM0MxMTguMzQ2IDEyMS43NTUgMTE4LjM3IDEyMS41OTIgMTE4LjM5NyAxMjEuNDE5QzExOC40MTYgMTIxLjI5OSAxMTguNDQyIDEyMS4xNCAxMTguNDYzIDEyMS4wMDhaIiBmaWxsPSIjOThDNkQ4Ii8+CjxwYXRoIGQ9Ik0xMDMuNTgxIDExNi4zNjdDMTAzLjQ3OSAxMTYuMTAzIDEwMy42MjIgMTE1LjY5OSAxMDMuODk4IDExNS40NjlDMTA0LjMwMyAxMTUuMTMzIDEwNC41MDQgMTE1LjE1NSAxMDQuOTMgMTE1LjU4MUMxMDUuMjIgMTE1Ljg3MSAxMDUuMzU1IDExNi4yNzYgMTA1LjIzIDExNi40NzlDMTA0LjkwMiAxMTcuMDA5IDEwMy43OTggMTE2LjkzNCAxMDMuNTgxIDExNi4zNjdaIiBmaWxsPSIjOThDNkQ4Ii8+CjxwYXRoIGQ9Ik0xMDYuMDM4IDExMi4yODFDMTA1LjY4IDExMS44NDkgMTA1LjcwMiAxMTEuNjYgMTA2LjE2NSAxMTEuMTk3QzEwNi42ODkgMTEwLjY3NCAxMDYuNzY0IDExMC42NzQgMTA3LjI4NyAxMTEuMTk3QzEwNy43NTEgMTExLjY2IDEwNy43NzMgMTExLjg0OSAxMDcuNDE1IDExMi4yODFDMTA3LjE3NiAxMTIuNTY4IDEwNi44NjYgMTEyLjgwMyAxMDYuNzI2IDExMi44MDNDMTA2LjU4NiAxMTIuODAzIDEwNi4yNzcgMTEyLjU2OCAxMDYuMDM4IDExMi4yODFaIiBmaWxsPSIjOThDNkQ4Ii8+CjxwYXRoIGQ9Ik0xMDYuMDM4IDEwNy4yMjRDMTA1LjY4MiAxMDYuNzk1IDEwNS42OTQgMTA2LjYxMiAxMDYuMTA2IDEwNi4yMDFDMTA2LjY1IDEwNS42NTcgMTA3LjczOCAxMDUuODggMTA3LjczOCAxMDYuNTM2QzEwNy43MzggMTA2Ljk3IDEwNy4wNzIgMTA3Ljc0NyAxMDYuNyAxMDcuNzQ3QzEwNi41NzUgMTA3Ljc0NyAxMDYuMjc3IDEwNy41MTIgMTA2LjAzOCAxMDcuMjI0WiIgZmlsbD0iIzk4QzZEOCIvPgo8cGF0aCBkPSJNMTE3Ljk1NSAxMzYuNTQxQzExNC41NzggMTM2LjExMSAxMTAuMDg3IDEzNC44ODEgMTA5LjQxIDEzNC4yNzRDMTA4LjEyMyAxMzMuMTE5IDEwOC45NDIgMTI3Ljg3OSAxMTAuNDY2IDEyNi41NEMxMTEuMzg3IDEyNS43MzEgMTE1LjAzOSAxMjQuNjYzIDExNy4xODIgMTI1LjE2OUMxMTkuNjQyIDEyNS43NDkgMTIzLjkzMiAxMjguNjQ4IDEyNC4yNSAxMjkuNTA4QzEyNC42MjUgMTMwLjUyMiAxMjQuMDQ4IDEzNC41NDEgMTIzLjMyMyAxMzUuNTM2QzEyMi42OTQgMTM2LjM5OSAxMjAuMzI1IDEzNi44NDMgMTE3Ljk1NSAxMzYuNTQxWk0xMjAuNTQgMTM0LjA4NEMxMjEuMjk3IDEzMy4zOTkgMTIxLjM0MiAxMzEuODQyIDEyMC42MjcgMTMxLjEyNkMxMTkuNDkgMTI5Ljk4OSAxMTcuMTEyIDEzMC44NjkgMTE3LjExMiAxMzIuNDI4QzExNy4xMTIgMTM0LjMwNCAxMTkuMTg5IDEzNS4zMDcgMTIwLjU0IDEzNC4wODRaTTExOC41ODIgMTMzLjEyNUMxMTguMzExIDEzMi40MTkgMTE4LjY4IDEzMS42MDggMTE5LjI3MiAxMzEuNjA4QzEyMCAxMzEuNjA4IDEyMC4yMjggMTMyLjE3MyAxMTkuODEyIDEzMi45NDlDMTE5LjM4MSAxMzMuNzU1IDExOC44NTMgMTMzLjgzIDExOC41ODIgMTMzLjEyNVpNMTE2LjQyMiAxMzMuMTQzQzExNy4wOTQgMTMyLjMzMyAxMTYuNzExIDEzMS4xNzYgMTE1LjY4MSAxMzAuOTAyQzExNC41ODEgMTMwLjYxIDExNC4wNTIgMTMxLjE4MyAxMTQuODY5IDEzMS43OEMxMTUuNjgzIDEzMi4zNzUgMTE1LjU1MSAxMzIuNjc5IDExNC41MzMgMTMyLjU1N0MxMTMuMzI3IDEzMi40MTMgMTEyLjgzMyAxMzEuNTY2IDExMy4yNTQgMTMwLjM2MkMxMTMuNjgxIDEyOS4xNDIgMTE1LjE5NyAxMjguODYzIDExNS43NTMgMTI5LjkwMkMxMTYuMTkzIDEzMC43MjUgMTE2LjYyNyAxMzAuNzkzIDExNi45IDEzMC4wODNDMTE3LjIyOSAxMjkuMjI0IDExNS45MTQgMTI4LjIzNyAxMTQuNDQgMTI4LjIzN0MxMTEuOTkzIDEyOC4yMzcgMTEwLjgxNiAxMzEuMDc0IDExMi41NDYgMTMyLjgwM0MxMTMuNSAxMzMuNzU4IDExNS43NDkgMTMzLjk1NSAxMTYuNDIyIDEzMy4xNDNaIiBmaWxsPSIjN0JBOUI5Ii8+CjxwYXRoIGQ9Ik0xMjAuMTE4IDEzOC41OTJDMTE4Ljc1MSAxMzcuMjk3IDEyMS45MTYgMTM2LjA5NiAxMjMuNjA2IDEzOC4yODNDMTI0LjQ2OSAxMzkuMzIyIDEyMi44NTMgMTM5Ljk3NiAxMjAuMTE4IDEzOC41OTJaTTEwNi4xMiAxMzUuNjU4QzEwNC43NTEgMTM0LjI4OSAxMDYuOTYzIDEzMy45MDQgMTA4LjU4MSAxMzUuMjNMMTA5Ljk0MSAxMzUuOTJMMTA4LjA1OSAxMzYuMDYxQzEwNy4yMTUgMTM2LjA2MiAxMDYuMzQzIDEzNS44ODEgMTA2LjEyIDEzNS42NThWMTM1LjY1OFpNMTI1LjM2MyAxMjcuODY2QzEyNS4wODIgMTI3LjQxIDEyOC4zMzMgMTI2LjI2NyAxMjguNjg4IDEyNi40ODZDMTI4Ljg0NiAxMjYuNTg0IDEyOC45NzUgMTI3LjMzNyAxMjguOTc1IDEyOC4xNjFDMTI4Ljk3NSAxMjkuMjM5IDEyOC44NCAxMjkuNjU4IDEyOC40OSAxMjkuNjU4QzEyOC4yMjIgMTI5LjY1OCAxMjUuNDc2IDEyOC4wNDcgMTI1LjM2MyAxMjcuODY2Wk0xMTguNjQxIDEyMS4wOTVDMTE0Ljg3MSAxMjAuNzUzIDExNS4zNzggMTE5LjMyNyAxMTYuMzYxIDExOC40OTlDMTE2LjkyOCAxMTguMDIxIDExNy4zNCAxMTcuNzc5IDExNy45MTMgMTE3LjY2N0MxMTcuOTEzIDExNy42NjcgMTE3Ljg3MiAxMTkuMDQ2IDExOC42NjYgMTE5LjMxOUMxMTkuMjk3IDExOS41MzYgMTIwLjI3OCAxMTkuMDY0IDEyMC40NzEgMTE4LjY3NUMxMjIuMTg4IDExNS4yMDEgMTIxLjQxNSAxMTguNjk5IDEyMS43OTQgMTE5LjUzMkMxMjMuMjM4IDEyNC4wMDcgMTIwLjE4OCAxMjEuNzMgMTE4LjY0MSAxMjEuMDk1Wk0xMDQuOTEyIDEyMS4yM0MxMDQuMjQyIDEyMC40ODcgMTAzLjY5MyAxMTkuNzI5IDEwMy42OTMgMTE5LjU0NEMxMDMuNjkzIDExOS4xMTYgMTA0Ljc0NiAxMTkuMTEyIDEwNS44NjMgMTE5LjUzN0MxMDYuNTkzIDExOS44MTQgMTEwLjM0NyAxMjAuMDA2IDExMC4zNDcgMTIxLjE1M0MxMTAuMzQ3IDEyMS44OTkgMTA4Ljg5IDEyMy43NjIgMTA4LjcyNiAxMjMuNzYyQzEwOC41NjMgMTIzLjc2MiAxMDUuNTgzIDEyMS45NzIgMTA0LjkxMiAxMjEuMjNaIiBmaWxsPSIjQTZCM0MyIi8+CjxwYXRoIGQ9Ik0xMTguNDYzIDEyMS4wMDhDMTE4LjQ0MiAxMjEuMTQgMTE4LjQxNiAxMjEuMjk5IDExOC4zOTcgMTIxLjQxOUMxMTguMzcgMTIxLjU5MiAxMTguMzQ1IDEyMS43NTUgMTE4LjMyMyAxMjEuOTAzQzExOC4zIDEyMi4wNTEgMTE4LjI4MSAxMjIuMTg1IDExOC4yNjMgMTIyLjMwOEMxMTguMjQ1IDEyMi40MzEgMTE4LjIyOSAxMjIuNTQyIDExOC4yMTYgMTIyLjYzOUMxMTguMjAzIDEyMi43MzYgMTE4LjE5MSAxMjIuODIgMTE4LjE4MyAxMjIuODlDMTE4LjE3NSAxMjIuOTYxIDExOC4xNjggMTIzLjAxOCAxMTguMTY1IDEyMy4wNjFDMTE4LjE2MyAxMjMuMDgzIDExOC4xNjQgMTIzLjEgMTE4LjE2MyAxMjMuMTE1QzExOC4xNjIgMTIzLjEyOSAxMTguMTYyIDEyMy4xNCAxMTguMTYzIDEyMy4xNDhDMTE4LjE2MyAxMjMuMTUyIDExOC4xNjQgMTIzLjE1NiAxMTguMTY1IDEyMy4xNThMMTE4LjE2NyAxMjMuMTZDMTE4LjE2OCAxMjMuMTYgMTE4LjE3IDEyMy4xNTkgMTE4LjE3MSAxMjMuMTU4QzExOC4xNzIgMTIzLjE1NyAxMTguMTczIDEyMy4xNTUgMTE4LjE3NSAxMjMuMTUyQzExOC4xNzggMTIzLjE0NSAxMTguMTgyIDEyMy4xMzUgMTE4LjE4NyAxMjMuMTIxQzExOC4xOTIgMTIzLjEwNyAxMTguMTk2IDEyMy4wODkgMTE4LjIwMiAxMjMuMDY3QzExOC4yMTMgMTIzLjAyNSAxMTguMjI4IDEyMi45NjkgMTE4LjI0NSAxMjIuODk3QzExOC4yNjIgMTIyLjgyNSAxMTguMjgyIDEyMi43MzggMTE4LjMwNSAxMjIuNjM1QzExOC4zMjcgMTIyLjUzMyAxMTguMzUxIDEyMi40MTUgMTE4LjM3OSAxMjIuMjgxQzExOC40MDYgMTIyLjE0OCAxMTguNDM4IDEyMS45OTggMTE4LjQ3MSAxMjEuODMzQzExOC41MTYgMTIxLjYxMyAxMTguNTczIDEyMS4zMjEgMTE4LjYyOCAxMjEuMDQzTDExOC40NjMgMTIxLjAwOFoiIGZpbGw9IiM0QzlDQkIiLz4KPHBhdGggZD0iTTMwLjk5ODYgMTQyLjY3M0MyNi40NDg5IDE0MC45MTYgMjEuNDY2NCAxMzYuODA1IDE4Ljc5MjEgMTMyLjYwM0MxNS40MDUzIDEyNy4yODEgMTQuNDA1MiAxMjMuNDE5IDE1LjA2MzYgMTE4LjIwM0MxNS42ODA2IDExMy4zMTUgMTcuMzU1OCAxMTAuNTU3IDIyLjIyMzIgMTA2LjQxM0MyOS4wNDE3IDEwMC42MDggMzQuNDg0NiA5Ny45Nzk5IDQwLjU5NDcgOTcuNTQxNUM0OS44MjEyIDk2Ljg3OTQgNTUuMDk3OCA5OS44MTA5IDU3LjY0NjUgMTA3LjAxNUM1OC41MDQzIDEwOS40MzkgNTguNjg1NiAxMTUuNTcgNTguMDU0NCAxMjAuODA5QzU3LjgzMTEgMTIyLjY2MyA1Ny40Nzg5IDEyNi4wNTUgNTcuMjcxOCAxMjguMzQ4QzU2LjU0NjcgMTM2LjM3NSA1NC40MTQ3IDE0MC40MTcgNDkuNjI1MyAxNDIuODQzQzQ4LjI5NzkgMTQzLjUxNiA0Ny43MTk5IDE0My41NjEgNDAuNjkyNCAxNDMuNTM5QzMzLjUxNTEgMTQzLjUxNiAzMy4wODEgMTQzLjQ3OCAzMC45OTg2IDE0Mi42NzNaTTAuMDc5NTc2NSAxMDQuOTY1QzAuMDgxNTcyMyAxMDMuODUzIDAuMTQ0NDAzIDEwMy40MzggMC4yMTk0MjcgMTA0LjA0NEMwLjI5NDQ1MSAxMDQuNjUgMC4yOTI4OTkgMTA1LjU2IDAuMjE1OTk2IDEwNi4wNjdDMC4xMzkwNzkgMTA2LjU3MyAwLjA3NzY4MzggMTA2LjA3OCAwLjA3OTU3NjUgMTA0Ljk2NVpNMC4wMDE3MzM1MSAxMDIuMjRDMC4wMTc4OTkyIDEwMS44NDggMC4wOTc3ODk3IDEwMS43NjggMC4yMDUzOTggMTAyLjAzN0MwLjMwMjc3MiAxMDIuMjggMC4yOTA3OTcgMTAyLjU3MSAwLjE3ODc4NyAxMDIuNjgzQzAuMDY2Nzc0OCAxMDIuNzk1IC0wLjAxMjkwMjEgMTAyLjU5NiAwLjAwMTczMzUxIDEwMi4yNFpNMC4wMjgzNDQ0IDc1LjEzMjZDMC4wMjgzNDQ0IDc0LjY2OTEgMC4xMDQ4NTcgNzQuNDc5NSAwLjE5ODM2OSA3NC43MTEyQzAuMjkxODg0IDc0Ljk0MyAwLjI5MTg4NCA3NS4zMjIyIDAuMTk4MzY5IDc1LjU1MzlDMC4xMDQ4NTcgNzUuNzg1NyAwLjAyODM0NDQgNzUuNTk2MSAwLjAyODM0NDQgNzUuMTMyNloiIGZpbGw9InVybCgjcGFpbnQyX3JhZGlhbF82NzlfMTkxMCkiLz4KPHBhdGggZD0iTTM1LjMxMzMgMTEyLjU0NEwzNy45IDExMS4wOTFMMzkuMzUxNyAxMjEuNDZMMzkuMjQ4IDEyOC45MjZMMzUuMzcwMyAxMzAuODUxTDMyLjkyMjkgMTI2LjQzN0wzNS4zMTMzIDExMi41NDRaIiBmaWxsPSIjMzA2MEFEIi8+CjxwYXRoIGQ9Ik0zOC43Mjk1IDExNi42OUwzOS4zNTE2IDEyNC41NzFMNDMuMjkxOSAxMjUuOTE5TDQzLjM5NTYgMTA5LjIyNUw0MC4xODEyIDExMC4wNTRMMzguNzI5NSAxMTYuNjlaIiBmaWxsPSIjNjA2MzY4Ii8+CjxwYXRoIGQ9Ik0zNS4zNjk5IDEzMC44NTFDMzMuMDMyNCAxMjkuNzMgMjkuNzMwMSAxMjYuNDA4IDI4LjY2MjEgMTI0LjEwM0MyOC4wMzMyIDEyMi43NDUgMjcuNjM5NyAxMjIuMzEgMjYuODkyMyAxMjIuMTQ2QzI1Ljc4NzkgMTIxLjkwMyAyNS44MDg0IDEyMS45NzEgMjYuMzIxOCAxMjAuMjU4QzI2LjU5MzYgMTE5LjM1IDI2LjkwMzIgMTE4Ljk1NCAyNy4zNDA2IDExOC45NTRDMjcuNjg2MiAxMTguOTU0IDI5LjE1OTcgMTE3Ljg0MSAzMC42MTQ5IDExNi40OEMzMi4wNzAyIDExNS4xMTkgMzMuNzIyNiAxMTMuNjc3IDM0LjI4NjkgMTEzLjI3NUwzNS4zMTI5IDExMi41NDRMMzUuNjc2NyAxMTQuMjQxQzM3LjA0NDMgMTIwLjYxOCAzNy4wMjcgMTI0LjI2NiAzNS42MTk4IDEyNi4yNDNDMzQuNTUyMSAxMjcuNzQ0IDM0Ljg0MTcgMTI4LjM5MyAzNi41NzkxIDEyOC4zOTNDMzcuMzk3IDEyOC4zOTMgMzcuODIyMiAxMjguMTg1IDM4LjEzMDUgMTI3LjYzNEMzOC42NzQ4IDEyNi42NjMgMzguNjMxNSAxMjAuOTY4IDM4LjA0NTQgMTE2LjQyNkMzNy4zNDQ2IDExMC45OTUgMzcuMzEyNyAxMTEuMTY3IDM5LjE1NzggMTEwLjM5OEM0MC4wMzYgMTEwLjAzMSA0MC44MTggMTA5Ljc5NyA0MC44OTU1IDEwOS44NzdDNDAuOTczMSAxMDkuOTU2IDQwLjgxOTEgMTExLjIzNSA0MC41NTM0IDExMi43MThDMzkuNjA2NyAxMTguMDAzIDM5LjkzMTUgMTIzLjIzOCA0MS4yNzUyIDEyNC4zNTNDNDEuNjg1MSAxMjQuNjkzIDQxLjcyMzMgMTIzLjk2OSA0MS41NDUyIDExOS4yMzJDNDEuMzE1NiAxMTMuMTIzIDQxLjUxNzQgMTEwLjkxOSA0Mi40MDc1IDEwOS44MkM0Mi45MTk4IDEwOS4xODggNDMuMjEzIDEwOS4xMTYgNDQuNDk1OSAxMDkuMzFDNDYuOTU2NiAxMDkuNjgyIDQ2Ljk1MDYgMTA5LjY2OCA0Ni4yMDk5IDExMy4yNTRDNDUuODQ5NiAxMTQuOTk5IDQ1LjQ0ODMgMTE3LjIyNCA0NS4zMTggMTE4LjJMNDUuMDgxMiAxMTkuOTczTDQ1Ljk0NDYgMTE5LjY0NUM0Ni40MTk1IDExOS40NjUgNDcuNDQxMyAxMTguOTk0IDQ4LjIxNTMgMTE4LjU5OUw0OS42MjI2IDExNy44ODFMNDkuMzg5MyAxMTguNjdDNDkuMjYxIDExOS4xMDUgNDkuMDY3MiAxMjAuNzAxIDQ4Ljk1ODcgMTIyLjIxOEw0OC43NjE0IDEyNC45NzZMNDcuMTA5NSAxMjQuMzI1QzQ2LjIwMSAxMjMuOTY3IDQ1LjMxMTYgMTIzLjY3NCA0NS4xMzMxIDEyMy42NzRDNDQuOTQxIDEyMy42NzQgNDQuODA4NSAxMjUuNTMyIDQ0LjgwODUgMTI4LjIyNEM0NC44MDg1IDEzMS44OTEgNDQuNzE3MSAxMzIuNzc1IDQ0LjMzODYgMTMyLjc3NUM0MS4wMTk4IDEzMi4yOTUgMzguNzg0MiAxMzIuNDU4IDM1LjM2OTkgMTMwLjg1MVoiIGZpbGw9IiNFRUMyM0IiLz4KPHBhdGggZD0iTTQxLjE2MzkgMTMyLjQxOEM0MS4xNjM5IDEzMi4yODMgNDEuMzQwOCAxMzAuNTE1IDQxLjY2OTIgMTI4LjE0NUM0MS45OTc3IDEyNS43NzUgNDIuMzU3NyAxMjIuOTYxIDQyLjQ2OTIgMTIxLjg5M0M0Mi41ODA4IDEyMC44MjQgNDIuNzk2OSAxMjAuMDI3IDQyLjk0OTUgMTIwLjEyMUM0My42NDQzIDEyMC41NTEgNDQuMjkzOCAxMjkuMzc1IDQ0LjA0NzQgMTMxLjQyQzQzLjg5MDkgMTMyLjcxOSA0My42NTk1IDEzMi43MzcgNDMuMzAxOSAxMzIuNjc3QzQyLjY2MDIgMTMyLjU3IDQxLjkxMDEgMTMyLjUxMyA0MS4xNjM5IDEzMi40MThaTTMyLjU5NjkgMTIyLjczNUMzMS42ODEzIDEyMi41ODMgMzAuODI0MiAxMjEuNDQyIDMwLjgyNDIgMTIwLjM3NEMzMC44MjQyIDExOS40MDcgMzIuMjM4MSAxMTcuOTM3IDMzLjE2ODUgMTE3LjkzN0MzNC42NjY2IDExNy45MzcgMzUuODM5MiAxMjAuNTEzIDM0Ljk0ODQgMTIxLjg0N0MzNC41NTE3IDEyMi40NDEgMzMuNDE0NyAxMjIuODcgMzIuNTk2OSAxMjIuNzM1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExNC4wMjEgMTM3LjQ5OEMxMDcuMzE4IDEzNS43NCAxMDYuMjczIDEzNC4xMDQgMTA2LjYwOSAxMjUuODkxQzEwNi43MTYgMTIzLjI4MSAxMDYuODg1IDEyMC41MzkgMTA2Ljk4NiAxMTkuNzk3QzEwNy41NDUgMTE1LjY2NCAxMDguMzQyIDExMi4xNzMgMTA5LjA4NiAxMTAuNTk5QzEwOS43NDQgMTA5LjIwNiAxMDkuODQyIDEwOC43MTIgMTA5LjUzNSAxMDguMzQyQzEwOC40NSAxMDcuMDM1IDExMC4zNzEgMTA0Ljk5NSAxMTEuOTEgMTA1LjgxOUMxMTIuNDU3IDEwNi4xMTEgMTEyLjkxOCAxMDYuMDc3IDExMy45NDUgMTA1LjY2NUMxMTcuMDYyIDEwNC40MTkgMTIzLjExNyAxMDUuNTcxIDEyNi44MTQgMTA4LjExNUMxMjguMTQ5IDEwOS4wMzMgMTI4LjQ4NSAxMDkuMTM2IDEyOS4wMzggMTA4Ljc5MUMxMzAuNTIzIDEwNy44NjMgMTMyLjQ1MSAxMTAuNTczIDEzMS4yNSAxMTEuOUMxMzAuNzg3IDExMi40MTIgMTMwLjc1NSAxMTIuNzA1IDEzMS4wNjUgMTEzLjU5NkMxMzEuODUgMTE1Ljg0NiAxMzEuNzk0IDExNi4xMDQgMTI4LjAwNiAxMjcuNzE5QzEyNS41OCAxMzUuMTYgMTI0LjU0MyAxMzcuMzY1IDEyMy4yNjQgMTM3LjgxMUMxMjEuNjUzIDEzOC4zNzMgMTE2LjcyMyAxMzguMjA2IDExNC4wMjEgMTM3LjQ5OFpNMTIyLjU0MSAxMzUuMjI5QzEyMy4wMDMgMTM0LjU0MSAxMjMuMzU5IDEzMy4zMTMgMTIzLjUxMyAxMzEuODc4QzEyMy43OTQgMTI5LjI1IDEyMy44MTkgMTI5LjI4MyAxMjAuMzE3IDEyNy42MDFDMTE3LjY4NSAxMjYuMzM4IDExMy44MzMgMTI1Ljk3MiAxMTEuODU1IDEyNi43OThDMTEwLjgyOSAxMjcuMjI3IDExMC41OTUgMTI3LjU1NiAxMTAuMDU5IDEyOS4zMjdDMTA5LjExNiAxMzIuNDQzIDEwOS43MDMgMTM0LjMwOCAxMTEuODMyIDEzNC45NThDMTEzLjEzOSAxMzUuMzU3IDEyMC4wOSAxMzYuNDMgMTIwLjk4IDEzNi4zN0MxMjEuNTA0IDEzNi4zMzUgMTIyLjA4MiAxMzUuOTEyIDEyMi41NDEgMTM1LjIyOVpNMTE5LjQ1MSAxMjIuNDJDMTE5LjYzIDEyMi4wODEgMTE5LjY4NSAxMjEuNTU2IDExOS42ODUgMTIxLjU1NkMxMjAuMTU2IDEyMS42NTQgMTIwLjQ4IDEyMS42ODMgMTIwLjcyNiAxMjEuNzE5QzEyMS4xMDUgMTIxLjc3NCAxMjEuNTY0IDEyMS41ODcgMTIxLjc0NSAxMjEuMzAzQzEyMi4xNDkgMTIwLjY3MSAxMjAuNDc0IDExNy43MDEgMTE5LjU0OCAxMTcuNDA4QzExOC42NjggMTE3LjEyOCAxMTUuOTM1IDExOC44MjcgMTE1LjkzNSAxMTkuNjUzQzExNS45MzUgMTIwLjAyMiAxMTYuNTUzIDEyMC40MTEgMTE2LjgzMSAxMjAuNTE4QzExNi45ODIgMTIwLjU3NiAxMTcuMjUyIDEyMC43MzggMTE3LjU3NiAxMjAuOTM4QzExNy4zOCAxMjEuMzUxIDExNy4zNDYgMTIxLjQ3OCAxMTcuMzEyIDEyMS43MjRDMTE3LjIwNCAxMjIuNDk4IDExNy40MzUgMTIyLjY4NiAxMTguNDE2IDEyMi45MTJDMTE5LjE1MSAxMjIuOTkxIDExOS4yNSAxMjIuODQ1IDExOS40NTEgMTIyLjQyWk0xMjcuMjY1IDExNi44MzZDMTMwLjkzMyAxMTMuNDc1IDEyNy4wNzQgMTA3LjQxIDEyMi43MDUgMTA5LjY3QzEyMS42MjYgMTEwLjIyOCAxMjAuNjU0IDExMi4wMTkgMTIwLjY1NCAxMTMuNDQ5QzEyMC42NTQgMTE1LjY2NCAxMjIuODMxIDExNy45MzcgMTI0Ljk1NyAxMTcuOTQxQzEyNS43MzMgMTE3Ljk0MyAxMjYuNDE0IDExNy42MTcgMTI3LjI2NSAxMTYuODM2Wk0xMTcuMTQxIDExNS42MjJDMTE5LjAzNSAxMTQuNDY4IDExOS44MDQgMTExLjc5NSAxMTguODAyIDEwOS44NTdDMTE4LjA4MiAxMDguNDY0IDExNy4wMDggMTA3LjgzIDExNS4zNjYgMTA3LjgzQzExMS41MDggMTA3LjgzIDEwOS40ODUgMTEzLjIzNCAxMTIuNDY1IDExNS41NzhDMTEzLjU3IDExNi40NDggMTE1Ljc1NCAxMTYuNDY4IDExNy4xNDEgMTE1LjYyMloiIGZpbGw9IiMwOTk2RDEiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNMzMuMTc1NyAyNkwzMi4zODEgMjcuMjE5QzMxLjk0NDkgMjcuODg5IDMxLjI4MzYgMjkuMzI0OCAzMC45MDkxIDMwLjQwODhDMzAuNTM0NyAzMS40OTI4IDI5Ljk5MjIgMzIuOTcwMSAyOS43MDQzIDMzLjY5MjhDMjkuNDE2MyAzNC40MTU0IDI5LjE3MzMgMzYuMTE1IDI5LjE2MzggMzcuNDdDMjkuMTQzNiA0MC4zNTMgMjguNzQ5NCA0MS44ODg4IDI3LjYzNjQgNDMuNDIyNUMyNi45MjEyIDQ0LjQwOCAyNi42NzUxIDQ0LjUyMjMgMjUuNDUzMiA0NC40NDFDMjMuODk5NSA0NC4zMzc3IDIyLjgxNTMgNDUuMDgxNSAyMS4yMTcgNDcuMzQ4MUMyMC43MTY1IDQ4LjA1OCAyMC4xMzY3IDQ4LjYzOTMgMTkuOTI4OSA0OC42MzkzQzE5LjcyMTEgNDguNjM5MyAxOS4xODA1IDQ4LjIzMTkgMTguNzI2MiA0Ny43MzUxQzE4LjI3MTkgNDcuMjM4MiAxNy45OTQ0IDQ3LjAxMTUgMTguMTExIDQ3LjIyOThDMTguNDkgNDcuOTM5NiAxNy41OTA0IDUzLjI2MTUgMTYuODM3OCA1NC43NjQyQzE1Ljk2NyA1Ni41MDI4IDE0LjM4NzIgNTcuNzIzMyAxMi41MzU0IDU4LjA4NDNDMTEuMDA1OSA1OC4zODI2IDExLjIyODMgNTguMDkgOC43MTc5NSA2My4wOTQ2QzYuNjYzMzQgNjcuMTkwNyA1Ljk2OTM1IDY3LjgyNjQgMi42NTUzMyA2OC42NTQyQzEuNDk3MTcgNjguOTQzNCAwLjQ1ODc1OSA2OS43MTQyIDAgNzAuNTI4N1Y3Mi44MjYzQzAuMzU2MTkzIDcyLjc0NzMgMC44Mjg3MjggNzIuNjIwMyAxLjYxNDk5IDcyLjM4NzNDMi45OTQ4NiA3MS45NzgyIDQuODIzNDggNzEuNDgwNiA1LjY3ODEgNzEuMjgwNkM3LjcyOTM0IDcwLjgwMDYgOS4zOTM1MSA2OS4yMTk1IDkuOTIyNzkgNjcuMjQ2N0MxMC4yNDI0IDY2LjA1NTQgMTAuNzAzMyA2NS40MjQxIDExLjk5OTIgNjQuNDA1OEMxMy4yODQgNjMuMzk2MiAxMy43NTY1IDYyLjc1NDYgMTQuMDY3MSA2MS41OTY5QzE0LjM3MzkgNjAuNDUzMiAxNC43MDExIDYwLjAwNCAxNS40NDA3IDU5LjcwNjNDMTcuMDE1MyA1OS4wNzI0IDIwLjY0NDQgNTUuODY1NSAyMS4yMzYzIDU0LjU4NThDMjEuNTM3MyA1My45MzQ5IDIyLjExOTggNTIuNTM5OSAyMi41Mjg3IDUxLjQ4NjJDMjMuMjYzNCA0OS41OTMyIDI1LjAxOTUgNDcuMzI2MSAyNS43NTAxIDQ3LjMyNjFDMjUuOTU3NCA0Ny4zMjYxIDI3LjEwOSA0Ni43MjQ4IDI4LjMwOTMgNDUuOTkwOEMzMS4yMzc2IDQ0LjIwMDMgMzEuNTk5MSA0My40MTM2IDMxLjU5OTEgMzguODYxNEMzMS41OTkxIDM1LjY4MTQgMzEuNjg3IDM1LjE1NTcgMzIuMzQ2OCAzNC4zNjg0QzMzLjAwMzcgMzMuNTg0NyAzMy4wOTk3IDMzLjAyNDkgMzMuMTM1MSAyOS43MzkxTDMzLjE3NTcgMjZaTTI0LjMzMzggNjMuNTk1OEMyNC4yMDM3IDYzLjYxMzUgMjQuMDczOCA2My42NjE4IDIzLjkyNTggNjMuNzM2MkMyMi45Mzk3IDY0LjIzMTUgMjIuNjEzNiA2NC45MTAyIDIxLjU5NzMgNjguNTg2QzIxLjAxNzkgNzAuNjgxNSAyMC40MjEyIDcyLjA4MTkgMTkuODc5OCA3Mi42MTc4QzE5LjQyMzUgNzMuMDY5NSAxOC45MzgzIDczLjg4MjMgMTguODAxIDc0LjQyNDJDMTguMTgxIDc2Ljg3MTUgMTYuNzQ1NCA3OC42MDczIDE0LjQ0OTUgNzkuNjg3MUMxMi40NTU1IDgwLjYyNDkgMTAuNjQwNyA4Mi4xMTc5IDkuMTExMDIgODQuMDc5OEM4LjMyMTA5IDg1LjA5MjkgNy40ODc1MiA4Ni4wMjkgNy4yNTY3OCA4Ni4xNjA5QzYuNzYxNjQgODYuNDQ0IDUuNzA4OTkgODguODk0MSA0LjYwNzg1IDkyLjMyOEMzLjcxMDkyIDk1LjEyNSAxLjcxMDY1IDk3LjQ2ODIgMC40Nzg1MTQgOTcuMTY1OEMwLjI1NDkzMSA5Ny4xMTA5IDAuMTA2OTExIDk3LjExMzcgMCA5Ny4yOTAxVjEwNC40OTJDMC41NTY0MTEgMTAzLjY2NCAxLjIzNDIgMTAyLjYyOSAyLjU2MzQ3IDEwMC41NEM0LjM0NTExIDk3LjczOTcgNi40NjM4NSA5NC40ODY3IDcuMjcxNzMgOTMuMzEyNEM4LjA3OTYgOTIuMTM4MSA5LjM1Njg3IDkwLjE0ODggMTAuMTA4NiA4OC44OTE2QzExLjIzNDIgODcuMDA5MiAxMi4xMDE2IDg2LjEyNDMgMTUuMDI4NCA4My44ODMzTDE4LjU4MzEgODEuMTYyN0wyMC40MDUzIDc1Ljk4ODFDMjMuMTg5NSA2OC4wODE3IDIzLjk1ODUgNjcuMTk5NyAyNy43NjI0IDY3LjU1MTVDMjkuNDYwNSA2Ny43MDg1IDMwLjYzMzQgNjcuNjE5NiAzMi4wNzM0IDY3LjIyNjdDMzQuMTgyNiA2Ni42NTEyIDM0LjQ3OTkgNjYuMTg3MiAzMy4zNTUxIDY1LjIzMThDMzIuNjU1NCA2NC42Mzc2IDMxLjMxNTMgNjQuNjQgMjguMjM4OCA2NS4yNDE4QzI3LjMzOTcgNjUuNDE3NyAyNi45MzQzIDY1LjI3NTcgMjUuODkzMiA2NC40MTc4QzI1LjEwNTEgNjMuNzY4NCAyNC43MjQxIDYzLjU0MjYgMjQuMzMzOCA2My41OTU4Wk0zMS43MTY2IDgzLjI1OThDMzEuNjYwNSA4My4yNzQ5IDMxLjU5MjEgODMuMzEyNiAzMS41MTE1IDgzLjM3MjFDMzEuMjcwOSA4My41NSAzMC42NTM2IDgzLjc5IDMwLjEzNzkgODMuOTA1NEMyOS4zODc2IDg0LjA3MzQgMjkuMDQ5OCA4NC40OTMyIDI4LjQ0ODIgODYuMDA0NUMyOC4wMzQ3IDg3LjA0MzIgMjcuNDkxNSA4OC4xMTUzIDI3LjI0MTIgODguMzg2M0MyNi45OTA5IDg4LjY1NzMgMjYuNDc1IDg5LjU1MSAyNi4wOTQxIDkwLjM3MzJDMjQuOTQyMiA5Mi44NTkyIDIzLjUyODIgOTQuMDYxOCAxOS45MDExIDk1LjY0MjFDMTYuNjEwNyA5Ny4wNzU2IDE2LjU2NyA5Ny4xMDg2IDE1LjY2NzEgOTguOTgwMkMxNS4xMDI3IDEwMC4xNTQgMTQuNjc5MiAxMDEuNzU3IDE0LjU0OTkgMTAzLjIwNUMxNC4yNzMxIDEwNi4zMDIgMTMuNTk2MiAxMDcuMDMxIDkuNTIxMTggMTA4LjYxNkM1LjkxNDk3IDExMC4wMTkgMy41NzcwMiAxMTEuNzQ1IDMuNDE1ODMgMTEzLjEyM0MzLjI0MDQxIDExNC42MjMgMi41OTgxMyAxMTUuMDg0IDAuOTI5MjYgMTE0LjkwN0MwLjUzOTUyOSAxMTQuODY2IDAuMjQ0MzMyIDExNC44NTEgMCAxMTQuODgxVjEyMUMxLjAyNjU4IDExOS42NTggMi43MTkyMiAxMTguMzYxIDYuMjI0OTcgMTE2LjIwNkMxMC4zNjA0IDExMy42NjUgMTIuNzA2NSAxMTEuNTc1IDEzLjU3NzkgMTA5LjY1NEMxMy45MjYgMTA4Ljg4NyAxNC42NTQ3IDEwNy4zOTMgMTUuMTk3MSAxMDYuMzM0QzE1LjczOTUgMTA1LjI3NSAxNi4yODQ1IDEwMy45MDEgMTYuNDA4NCAxMDMuMjgxQzE2LjczMjQgMTAxLjY2IDE4LjMwMzIgMTAwLjM0OSAyMC42NjU5IDk5LjcyNkMyNS4xNzcgOTguNTM3MiAyNS43NDYxIDk4LjAxNjMgMjYuMzUyNSA5NC41MzU0QzI2Ljg1NjEgOTEuNjQ1MiAyOC42MTIgODguMDY2MSAzMC41MjY3IDg2LjAyNDZDMzEuMzA5NSA4NS4xOSAzMS45NDk1IDg0LjE4IDMxLjk0OTUgODMuNzc5MUMzMS45NDk1IDgzLjM3NjYgMzEuODg0OSA4My4yMTQ2IDMxLjcxNjYgODMuMjU5OFoiIGZpbGw9IiMzNTkxMzYiLz4KPHBhdGggZD0iTTguMDgxNzcgNzQuNDA4M0M2LjAzNDE5IDczLjg3MDUgNS44OTQ0MyA3My43MzAyIDYuMDQyNzEgNzIuMzYxNUM2LjEzMDI1IDcxLjU1MzMgNi4yNDAxNyA3MC44NTc1IDYuMjg2OTYgNzAuODE1NEM2LjMzMzc2IDcwLjc3MzMgNy43MDQ0MyA3MS4wNjg3IDkuMzMyOTIgNzEuNDcxOUMxMS45NzY4IDcyLjEyNjQgMTIuNDg2NiA3Mi4xMjM4IDE0LjA5NDcgNzEuNDQ3NEMxNi40MDQxIDcwLjQ3NiAxNy4yMzgzIDY4Ljc3NTMgMTYuNDAyMiA2Ni43NDMxQzE1Ljk0MjQgNjUuNjI1NyAxNC45MjA0IDY0LjgyODEgMTIuMjM4NCA2My40OTRDNy42NDE3IDYxLjIwNzUgNi41MzA2MiA1OS45OTkgNi4yNzQ2NSA1Ny4wMDc2QzUuODkzMzcgNTIuNTUxOSA4Ljg0Njg5IDQ5Ljk4MzYgMTQuMzUyMiA0OS45ODM2QzE1Ljk3MzMgNDkuOTgzNiAxNy44NDg3IDUwLjE5MzcgMTguNTE5NiA1MC40NTA1QzE5LjYzNjggNTAuODc4MSAxOS42OTM3IDUxLjAzOTkgMTkuMTk0NSA1Mi4zNzEyQzE4LjcxODIgNTMuNjQxNiAxOC41MDI3IDUzLjc2ODggMTcuNDg2MSA1My4zNzk2QzE1LjUxOTkgNTIuNjI3IDEyLjE5NDggNTIuODU1NCAxMS4wMDU4IDUzLjgyNDdDOS43OTA1OCA1NC44MTUzIDkuNTU3MzYgNTYuOTI1MiAxMC41MzI0IDU4LjEwNzlDMTAuODcyNyA1OC41MjA3IDEyLjkwODcgNTkuNzc5IDE1LjA1NjkgNjAuOTA0MUMxOC40NjI4IDYyLjY4ODEgMTkuMDgyIDYzLjIyMTQgMTkuODk1MSA2NS4wNzE5QzIxLjExNTIgNjcuODQ4NyAyMC42Nzg4IDcwLjM0MzcgMTguNjA3OCA3Mi40MzIzQzE2LjE2MDcgNzQuOTAwMiAxMi41NDEzIDc1LjU3OTYgOC4wODE3NyA3NC40MDgzWk0yOC45MzI4IDc0LjM4MjNDMjUuMjM2NSA3Mi45OTU4IDIzLjcxMzggNzAuNTQwNSAyMy43MTM4IDY1Ljk2NjdDMjMuNzEzOCA2Mi41MDM0IDI0LjU1ODggNjAuNDY5NyAyNi44MzMzIDU4LjQ1OTNDMjguMzk2NCA1Ny4wNzc2IDI4Ljk4NjcgNTYuODY4NiAzMS4zMjY4IDU2Ljg2ODZDMzIuODE2OCA1Ni44Njg2IDM0LjY4MjEgNTcuMjEyOSAzNS41MDA1IDU3LjYzODlDMzcuMzE2OSA1OC41ODQ2IDM4LjgzMTkgNjEuNzQyNiAzOC44NDY2IDY0LjYxNDNMMzguODU3MyA2Ni43MDQ0SDMyLjk0MjNIMjcuMDI3M0wyNy4zNDIgNjguMDU2OUMyNy41MTUgNjguODAwNyAyOC4zMzU0IDcwLjAzOTEgMjkuMTY1IDcwLjgwOUMzMC41NzczIDcyLjExOTYgMzAuODk0OSA3Mi4xOTc5IDM0LjE1NDcgNzIuMDM4NUMzNy4zOTM4IDcxLjg4MDEgMzcuNjQ2MyA3MS45NDA5IDM3Ljc4NCA3Mi45MTIyQzM3Ljk2MjYgNzQuMTcyNSAzNy4xODgyIDc0LjUyMzYgMzMuNTI0MiA3NC44NDM0QzMxLjgwODUgNzQuOTkzMiAzMC4xMDU2IDc0LjgyMjIgMjguOTMyOCA3NC4zODIzWk0zNS4wODgxIDYyLjUyNDJDMzQuOTU1OCA2MC42NzMgMzMuMzY0NCA1OS4zMjc2IDMxLjMwNzIgNTkuMzI3NkMyOS43MDg2IDU5LjMyNzYgMjguNzI4MSA2MC4wOTA5IDI3LjY2MiA2Mi4xNjU1QzI2Ljc0NTcgNjMuOTQ4NiAyNy40MTIgNjQuMjk2NSAzMS40NDg4IDY0LjE0MjVMMzUuMTkzNiA2My45OTk2TDM1LjA4ODEgNjIuNTI0MlpNNDQuMzkxNCA3NC4zNzA4QzQyLjU3MTEgNzMuNTQ2OCA0MS43MDU5IDcyLjEyODYgNDEuNjQyMSA2OS44NjQ3QzQxLjU0MTYgNjYuMjk0NCA0NC4xMTEgNjQuMTQ2OCA0OS4yNTc2IDYzLjQ5OTRDNTEuODQxNCA2My4xNzQ0IDUyLjA0OTUgNjMuMDU3NyA1MS43NTkzIDYyLjA5NjlDNTAuOTY1IDU5LjQ2NjYgNDguMjQyMyA1OC41NjQ0IDQ0LjkyNzYgNTkuODMzMUM0My40MzAzIDYwLjQwNjMgNDMuNDg2OCA2MC40MzEzIDQzLjA1MTQgNTkuMDAzMkM0Mi43ODAxIDU4LjExMzUgNDIuOTk4MiA1Ny44NTExIDQ0LjM5NSA1Ny4zODdDNDYuOTM2NCA1Ni41NDI3IDUxLjM3NDUgNTYuNjM0MiA1Mi43Nzc5IDU3LjU2QzU0LjkzOTIgNTguOTg1NyA1NS4zNzUxIDYwLjYwMTkgNTUuNTQ4NyA2Ny44MzI4TDU1LjcxMDYgNzQuNTc0NUw1NC4xMjMgNzQuNTc0QzUzLjAzNzcgNzQuNTczMyA1Mi41MzUzIDc0LjMzOTYgNTIuNTM1MyA3My44MzU0QzUyLjUzNTMgNzIuOTA2NiA1Mi4wNTI4IDcyLjkwMjQgNTAuNzUyIDczLjgxOTZDNDkuMjAxMyA3NC45MTMgNDYuMTY5NyA3NS4xNzU3IDQ0LjM5MTQgNzQuMzcwOFpNNTAuOTQ3NyA3MC44MTc1QzUxLjcwOTkgNjkuOTczNSA1Mi4wNDY4IDY4Ljk4NiA1Mi4wNDY4IDY3LjU5NjZWNjUuNTkyOUw0OS44MTgyIDY1Ljg0NThDNDUuNzE5IDY2LjMxMSA0My44NzQ5IDY5LjUxMTggNDYuNTA1OCA3MS41OTUzQzQ4LjAzNzEgNzIuODA3OSA0OS4zNTk0IDcyLjU3NjMgNTAuOTQ3NyA3MC44MTc1Wk05Mi4wMDIgNzQuNTc4OEM4OC40MDI0IDczLjQ4NjQgODYuMjQxOSA3MC4yNDY2IDg2LjI0MTkgNjUuOTQxQzg2LjI0MTkgNjAuNDA0MiA4OS41MjExIDU2LjgzMjUgOTQuNjQxIDU2Ljc5MjVDOTguNjk4OSA1Ni43NjA5IDEwMS4yNjMgNTkuNDY3OSAxMDEuNTAzIDY0LjAzNjZMMTAxLjYzIDY2LjQ1ODVMOTYuMjU2MiA2Ni41ODg1QzkzLjMwMDcgNjYuNjYgOTAuNjE1OCA2Ni44MTYgOTAuMjg5NyA2Ni45MzUxQzg5LjM4MTggNjcuMjY2OCA5MC40Nzg4IDY5Ljk0NyA5Mi4wMzA2IDcxLjE4ODZDOTMuMTU5OSA3Mi4wOTIxIDkzLjc3MSA3Mi4xOTEyIDk2LjgxNzggNzEuOTY1QzEwMC4xOTMgNzEuNzE0NCAxMDAuMzE4IDcxLjc0NDkgMTAwLjUzMSA3Mi44Njg0QzEwMC43MyA3My45MTMzIDEwMC41MDkgNzQuMDgzNSA5OC4zNTc5IDc0LjU0OEM5NS42MTAzIDc1LjE0MTIgOTMuODgyOSA3NS4xNDk2IDkyLjAwMiA3NC41Nzg4Wk05Ny45NjU5IDYzLjA4MjNDOTcuOTY1OSA2MS4xMjMxIDk2LjE4NTggNTkuMzI3NiA5NC4yNDM0IDU5LjMyNzZDOTIuMjQ5NCA1OS4zMjc2IDkwLjE0OTkgNjEuMjU2MiA5MC4xNDk5IDYzLjA4NzhDOTAuMTQ5OSA2NC4yMDcgOTAuMjc5NyA2NC4yNDU1IDk0LjA1NzkgNjQuMjQ1NUM5Ny44NDE0IDY0LjI0NTUgOTcuOTY1OSA2NC4yMDg0IDk3Ljk2NTkgNjMuMDgyM1pNMTEwLjA0MiA3NC40N0MxMDYuMTQ5IDczLjQzIDEwMy45MyA2OS40MDI5IDEwNC41NCA2NC40ODYyQzEwNS4xNjYgNTkuNDQ0MiAxMDcuOTUxIDU2Ljg3MzYgMTEyLjc5MiA1Ni44NzA1QzExNS4zNzQgNTYuODY5IDExNS44MDUgNTcuMDI0OSAxMTcuMjYgNTguNDlDMTE4Ljg3OCA2MC4xMTg1IDExOS45NDEgNjIuODM0MyAxMTkuOTQ2IDY1LjM1MkwxMTkuOTQ5IDY2LjcwNDRIMTE0LjA4N0MxMDguNDg4IDY2LjcwNDQgMTA4LjIyNSA2Ni43NTAxIDEwOC4yMjUgNjcuNzIwM0MxMDguMjI1IDcwLjcxMjIgMTEwLjk1MiA3Mi4zNDQyIDExNS4zNTcgNzEuOTg4MkMxMTguMTM2IDcxLjc2MzcgMTE4LjQyMyA3MS44MzE1IDExOC42NjcgNzIuNzY3NEMxMTguOTk4IDc0LjA0NDMgMTE4Ljk2OCA3NC4wNzIgMTE2LjY5IDc0LjYwNDNDMTE0LjM2NyA3NS4xNDcgMTEyLjQzIDc1LjEwNzggMTEwLjA0MiA3NC40N1pNMTE2LjExOSA2Mi43NzAxQzExNS44NjQgNjAuODc4NyAxMTQuMTU5IDU5LjMyNzYgMTEyLjMzNSA1OS4zMjc2QzExMC41NjYgNTkuMzI3NiAxMDguMjI0IDYxLjYwMTQgMTA4LjIyNCA2My4zMTk1QzEwOC4yMjQgNjQuMjEyOCAxMDguNTI3IDY0LjI3NDYgMTEyLjI1NSA2NC4xNDI0QzExNi4yNDEgNjQuMDAxMSAxMTYuMjgzIDYzLjk4NjIgMTE2LjExOSA2Mi43NzAxWk0xMjYuODk0IDc0LjI0N0MxMjQuODI2IDczLjIyMDggMTIzLjUzMiA3MS4zNTg5IDEyMi44OTQgNjguNDg4OUMxMjEuOTc1IDY0LjM2MDUgMTIzLjA3NCA2MC40OTE0IDEyNS44MyA1OC4xNTY3QzEyNy44MjcgNTYuNDY1NyAxMzEuODM4IDU2LjMzMDUgMTMzLjgwNSA1Ny44ODc5TDEzNS4wOTIgNTguOTA3MlY1My45NTM2VjQ5SDEzNy4wNDZIMTM5VjYxLjc4NjVWNzQuNTczMUgxMzcuMjlDMTM1Ljc2MiA3NC41NzMxIDEzNS41OCA3NC40MzMxIDEzNS41OCA3My4yNTc5VjcxLjk0MjdMMTM0LjQ4MSA3Mi45NzkyQzEzMi4yMzMgNzUuMDk5OCAxMjkuNTI1IDc1LjU1MjMgMTI2Ljg5NCA3NC4yNDdaTTEzMy42NjQgNzAuNjc2NkMxMzQuOTc2IDY5LjM1NTcgMTM1LjA5MiA2OC45NTM3IDEzNS4wOTIgNjUuNzIwOUMxMzUuMDkyIDYyLjQ1NzIgMTM0Ljk4NSA2Mi4wOTQ1IDEzMy42MDcgNjAuNzA3NkMxMzIuMzY1IDU5LjQ1NzUgMTMxLjg2NSA1OS4yNjIyIDEzMC41NTQgNTkuNTE1NEMxMjYuMTQ0IDYwLjM2NzIgMTI0LjgzNSA2OC4xNDIzIDEyOC41OTggNzEuMTIyOUMxMzAuMzQ4IDcyLjUwODEgMTMxLjk4OCA3Mi4zNjM1IDEzMy42NjQgNzAuNjc2NlpNNjEuNDEwNyA2Ni4wNTg0QzYwLjAyMiA2MS4zNzU0IDU4Ljg4NTkgNTcuMzkxOSA1OC44ODU5IDU3LjIwNjJDNTguODg1OSA1Ny4wMjA2IDU5LjcyNzQgNTYuODY4NiA2MC43NTU5IDU2Ljg2ODZINjIuNjI1OUw2NC4yNTUxIDYzLjg3NjZDNjUuMTUxMiA2Ny43MzEgNjUuOTU2IDcwLjY2MzMgNjYuMDQzNyA3MC4zOTI5QzY2LjEzMTMgNzAuMTIyNCA2Ny4wNTU3IDY3LjAyNDEgNjguMDk3OCA2My41MDc4TDY5Ljk5MjYgNTcuMTE0NUg3MS41OTY5SDczLjIwMTJMNzQuODg2NSA2My4yNjE5Qzc1LjgxMzQgNjYuNjQzIDc2LjY4MzIgNjkuNzQxMiA3Ni44MTk0IDcwLjE0N0M3Ni45NTU3IDcwLjU1MjcgNzcuODQxOSA2Ny43MzEgNzguNzg5IDYzLjg3NjZMODAuNTEwOSA1Ni44Njg2SDgyLjQ1MTdDODMuOTUxNyA1Ni44Njg2IDg0LjMzMDMgNTcuMDMxNiA4NC4xMTkgNTcuNTg2QzgzLjk2ODYgNTcuOTgwNiA4Mi42OTU2IDYxLjk2NDEgODEuMjkwMiA2Ni40MzgyTDc4LjczNDkgNzQuNTczMUg3Ni45NjM1SDc1LjE5MjFMNzMuNTgxMiA2OC43OTQ1QzcyLjY5NTIgNjUuNjE2MyA3MS44NTg1IDYyLjY4NDEgNzEuNzIxNyA2Mi4yNzgzQzcxLjQ0NjUgNjEuNDYxNiA3MS41OTExIDYxLjA3MzMgNjkuMTExNCA2OS4yODYzTDY3LjUxNTIgNzQuNTczMUg2NS43MjU0SDYzLjkzNTZMNjEuNDEwNyA2Ni4wNTg0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE5Ljk3NDYgMTE5LjI3NEMxOS4wMTU4IDExOC4xMTkgMjAuMzM4MSAxMTYuMTY2IDIxLjU2MDQgMTE2LjkzMkMyMi43NjMyIDExNy42ODYgMjIuMzMwNSAxMTkuNzYxIDIwLjk3MDQgMTE5Ljc2MUMyMC42NDUgMTE5Ljc2MSAyMC4xOTY5IDExOS41NDIgMTkuOTc0NiAxMTkuMjc0WiIgZmlsbD0idXJsKCNwYWludDNfcmFkaWFsXzY3OV8xOTEwKSIvPgo8cGF0aCBkPSJNMjMuODM2IDExMi42M0MyMi4xMTUyIDExMS42ODkgMjEuODAyOSAxMDkuNzk3IDIzLjE0NjUgMTA4LjQ1M0MyNC4wODU3IDEwNy41MTQgMjUuMjEgMTA3LjM5NCAyNi4yNTA3IDEwOC4xMjNDMjcuODU3MSAxMDkuMjQ5IDI3LjQ2NTUgMTEyLjAxMyAyNS42MDc5IDExMi42NkMyNS42MDc5IDExMi42NiAyNC40NzQ1IDExMi45NzMgMjMuODM2IDExMi42M1oiIGZpbGw9InVybCgjcGFpbnQ0X3JhZGlhbF82NzlfMTkxMCkiLz4KPHBhdGggZD0iTTMzLjc4NTUgMTIwLjAzNkMzMy43ODU1IDEyMC4xODggMzMuNzU1NyAxMjAuMzM4IDMzLjY5NzYgMTIwLjQ3OUMzMy42Mzk1IDEyMC42MTkgMzMuNTU0NCAxMjAuNzQ2IDMzLjQ0NzEgMTIwLjg1M0MzMy4zMzk4IDEyMC45NjEgMzMuMjEyNCAxMjEuMDQ2IDMzLjA3MjMgMTIxLjEwNEMzMi45MzIxIDEyMS4xNjIgMzIuNzgxOCAxMjEuMTkyIDMyLjYzMDEgMTIxLjE5MkMzMi40NzgzIDEyMS4xOTIgMzIuMzI4MSAxMjEuMTYyIDMyLjE4NzkgMTIxLjEwNEMzMi4wNDc3IDEyMS4wNDYgMzEuOTIwMyAxMjAuOTYxIDMxLjgxMyAxMjAuODUzQzMxLjcwNTcgMTIwLjc0NiAzMS42MjA2IDEyMC42MTkgMzEuNTYyNiAxMjAuNDc5QzMxLjUwNDUgMTIwLjMzOCAzMS40NzQ2IDEyMC4xODggMzEuNDc0NiAxMjAuMDM2QzMxLjQ3NDYgMTE5LjczIDMxLjU5NjMgMTE5LjQzNiAzMS44MTMgMTE5LjIxOUMzMi4wMjk3IDExOS4wMDMgMzIuMzIzNiAxMTguODgxIDMyLjYzMDEgMTE4Ljg4MUMzMi45MzY1IDExOC44ODEgMzMuMjMwNCAxMTkuMDAzIDMzLjQ0NzEgMTE5LjIxOUMzMy42NjM4IDExOS40MzYgMzMuNzg1NSAxMTkuNzMgMzMuNzg1NSAxMjAuMDM2WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTEyMC43MjEgMTE4LjI3NkMxMjAuNzIxIDExOC42MjMgMTIwLjU2NSAxMTguOTU2IDEyMC4yODcgMTE5LjIwMUMxMjAuMDA5IDExOS40NDYgMTE5LjYzMiAxMTkuNTg0IDExOS4yMzkgMTE5LjU4NEMxMTguODQ2IDExOS41ODQgMTE4LjQ2OSAxMTkuNDQ2IDExOC4xOTEgMTE5LjIwMUMxMTcuOTEzIDExOC45NTYgMTE3Ljc1NyAxMTguNjIzIDExNy43NTcgMTE4LjI3NkMxMTcuNzU3IDExOC4xMDUgMTE3Ljc5NSAxMTcuOTM1IDExNy44NyAxMTcuNzc2QzExNy45NDQgMTE3LjYxNyAxMTguMDUzIDExNy40NzMgMTE4LjE5MSAxMTcuMzUyQzExOC4zMjkgMTE3LjIzIDExOC40OTIgMTE3LjEzNCAxMTguNjcyIDExNy4wNjhDMTE4Ljg1MSAxMTcuMDAzIDExOS4wNDQgMTE2Ljk2OSAxMTkuMjM5IDExNi45NjlDMTE5LjQzMyAxMTYuOTY5IDExOS42MjYgMTE3LjAwMyAxMTkuODA2IDExNy4wNjhDMTE5Ljk4NiAxMTcuMTM0IDEyMC4xNDkgMTE3LjIzIDEyMC4yODcgMTE3LjM1MkMxMjAuNDI0IDExNy40NzMgMTIwLjUzMyAxMTcuNjE3IDEyMC42MDggMTE3Ljc3NkMxMjAuNjgyIDExNy45MzUgMTIwLjcyMSAxMTguMTA1IDEyMC43MjEgMTE4LjI3NloiIGZpbGw9IiM1OTY4NkYiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl82NzlfMTkxMCIgeDE9IjExIiB5MT0iLTQuNzc3NjhlLTA3IiB4Mj0iMTY0LjUiIHkyPSIxNTAuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjNjRCN0ZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwNURBRCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8cmFkaWFsR3JhZGllbnQgaWQ9InBhaW50MV9yYWRpYWxfNjc5XzE5MTAiIGN4PSIwIiBjeT0iMCIgcj0iMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI0LjkwNCAxMjguNjM2KSByb3RhdGUoLTAuMDQ4NTUyKSBzY2FsZSgzNi4yODAyIDMxLjQwMzIpIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzExNzdDRSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM3RUM3RkYiIHN0b3Atb3BhY2l0eT0iMCIvPgo8L3JhZGlhbEdyYWRpZW50Pgo8cmFkaWFsR3JhZGllbnQgaWQ9InBhaW50Ml9yYWRpYWxfNjc5XzE5MTAiIGN4PSIwIiBjeT0iMCIgcj0iMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzkuMzUxMiAxMjEuNDYpIHJvdGF0ZSgtMC4wNTczKSBzY2FsZSg1MC4yMjc2IDUxLjMwOTEpIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzExNzdDRSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM3RUM3RkYiIHN0b3Atb3BhY2l0eT0iMCIvPgo8L3JhZGlhbEdyYWRpZW50Pgo8cmFkaWFsR3JhZGllbnQgaWQ9InBhaW50M19yYWRpYWxfNjc5XzE5MTAiIGN4PSIwIiBjeT0iMCIgcj0iMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjAuOTU0OCAxMTguMjYpIHNjYWxlKDEuMzA5MjkgMS41MDEyNykiPgo8c3RvcCBzdG9wLWNvbG9yPSIjODhBM0QwIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzVEODNCRiIvPgo8L3JhZGlhbEdyYWRpZW50Pgo8cmFkaWFsR3JhZGllbnQgaWQ9InBhaW50NF9yYWRpYWxfNjc5XzE5MTAiIGN4PSIwIiBjeT0iMCIgcj0iMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjQuNzg0MyAxMTAuMjIxKSBzY2FsZSgyLjQ3Mjc3IDIuNTY5NTQpIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzg4QTNEMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM1RDgzQkYiLz4KPC9yYWRpYWxHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "topology"], ["spec", "replicationFactor"], ["spec", "db"], ["spec", "db", "replicas"], ["spec", "db", "size"], ["spec", "db", "storageClass"], ["spec", "db", "resources"], ["spec", "db", "resourcesPreset"], ["spec", "master"], ["spec", "master", "replicas"], ["spec", "master", "resources"], ["spec", "master", "resourcesPreset"], ["spec", "filer"], ["spec", "filer", "replicas"], ["spec", "filer", "resources"], ["spec", "filer", "resourcesPreset"], ["spec", "filer", "grpcHost"], ["spec", "filer", "grpcPort"], ["spec", "filer", "whitelist"], ["spec", "volume"], ["spec", "volume", "replicas"], ["spec", "volume", "size"], ["spec", "volume", "storageClass"], ["spec", "volume", "resources"], ["spec", "volume", "resourcesPreset"], ["spec", "volume", "zones"], ["spec", "s3"], ["spec", "s3", "replicas"], ["spec", "s3", "resources"], ["spec", "s3", "resourcesPreset"]] secrets: diff --git a/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml b/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml index 47c882be..a02a8b2a 100644 --- a/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml +++ b/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml @@ -84,6 +84,9 @@ spec: type: string type: array type: array + module: + description: Whether this resource is a module (tenant module) + type: boolean name: description: Hard-coded name used in the UI (e.g., "bucket") type: string From b4c9ca36a93591471e23b57612acede06eafa818 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 18:07:01 +0200 Subject: [PATCH 4/7] fix keysAndTags for info Signed-off-by: Andrei Kvapil --- internal/controller/dashboard/sidebar.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index 8ec4ef25..a8877c12 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -74,8 +74,13 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack // Check if this resource is a module if def.Spec.Dashboard.Module { - // Add to modules sidebar list - moduleSidebars = append(moduleSidebars, fmt.Sprintf("%s-sidebar", lowerKind)) + // Special case: info should have its own keysAndTags, not be in modules + if lowerKind == "info" { + keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)} + } else { + // Add to modules sidebar list + moduleSidebars = append(moduleSidebars, fmt.Sprintf("%s-sidebar", lowerKind)) + } } else { // Add to keysAndTags for non-module resources keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)} From ef7dcabe649c52abcdb30aec60a04cd8cd9bc822 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 18:09:46 +0200 Subject: [PATCH 5/7] always prefill name in dashboard Signed-off-by: Andrei Kvapil --- .../controller/dashboard/customformsprefill.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/controller/dashboard/customformsprefill.go b/internal/controller/dashboard/customformsprefill.go index e16725a0..b4dd2491 100644 --- a/internal/controller/dashboard/customformsprefill.go +++ b/internal/controller/dashboard/customformsprefill.go @@ -31,15 +31,17 @@ func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha 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...) + // Always prefill metadata.name (empty string if not specified in CRD) + var nameValue string + if crd.Spec.Dashboard != nil { + nameValue = strings.TrimSpace(crd.Spec.Dashboard.Name) } + values = append([]interface{}{ + map[string]interface{}{ + "path": toIfaceSlice([]string{"metadata", "name"}), + "value": nameValue, + }, + }, values...) cfp := &dashv1alpha1.CustomFormsPrefill{} cfp.Name = name // cluster-scoped From dd76166e444e1eeffa9d20cc06d04a7d821ddb39 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 18:46:47 +0200 Subject: [PATCH 6/7] Add factory for ingress resources Signed-off-by: Andrei Kvapil --- internal/controller/dashboard/sidebar.go | 8 + .../controller/dashboard/static_helpers.go | 6 +- .../controller/dashboard/static_refactored.go | 196 ++++++++++++++++-- 3 files changed, 185 insertions(+), 25 deletions(-) diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index a8877c12..3edd6aca 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -115,6 +115,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack keysAndTags["modules"] = moduleSidebars } + // Add sidebars for built-in Kubernetes resources + keysAndTags["services"] = []any{"service-sidebar"} + keysAndTags["secrets"] = []any{"secret-sidebar"} + keysAndTags["ingresses"] = []any{"ingress-sidebar"} + // 3) Sort items within each category by Weight (desc), then Label (A→Z) for cat := range categories { sort.Slice(categories[cat], func(i, j int) bool { @@ -201,6 +206,9 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack // stock-project sidebars "stock-project-factory-marketplace", "stock-project-factory-workloadmonitor-details", + "stock-project-factory-kube-service-details", + "stock-project-factory-kube-secret-details", + "stock-project-factory-kube-ingress-details", "stock-project-api-form", "stock-project-api-table", "stock-project-builtin-form", diff --git a/internal/controller/dashboard/static_helpers.go b/internal/controller/dashboard/static_helpers.go index d7816164..a18e6296 100644 --- a/internal/controller/dashboard/static_helpers.go +++ b/internal/controller/dashboard/static_helpers.go @@ -172,7 +172,7 @@ func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *das } } - if name == "factory-ingress-details-rules" { + if name == "factory-kube-ingress-details-rules" { data["additionalPrinterColumnsUndefinedValues"] = []any{ map[string]any{ "key": "Service", @@ -687,10 +687,10 @@ func getTabsId(key string) string { if key == "workloadmonitor-details" { return "workloadmonitor-tabs" } - if key == "secret-details" { + if key == "kube-secret-details" { return "secret-tabs" } - if key == "service-details" { + if key == "kube-service-details" { return "service-tabs" } return strings.ToLower(key) + "-tabs" diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 71df5d4b..8e74c5d7 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -40,17 +40,23 @@ func CreateAllBreadcrumbs() []*dashboardv1alpha1.Breadcrumb { }), // Stock project factory secret details - createBreadcrumb("stock-project-factory-secret-details", []map[string]any{ + createBreadcrumb("stock-project-factory-kube-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{ + createBreadcrumb("stock-project-factory-kube-service-details", []map[string]any{ createBreadcrumbItem("services", "v1/services", "/openapi-ui/{clusterName}/{namespace}/builtin-table/services"), createBreadcrumbItem("service", "{6}"), }), + // Stock project factory ingress details + createBreadcrumb("stock-project-factory-kube-ingress-details", []map[string]any{ + createBreadcrumbItem("ingresses", "networking.k8s.io/v1/ingresses", "/openapi-ui/{clusterName}/{namespace}/builtin-table/ingresses"), + createBreadcrumbItem("ingress", "{6}"), + }), + // Stock cluster api table createBreadcrumb("stock-cluster-api-table", []map[string]any{ createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{typeName}"), @@ -126,7 +132,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid 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']['-']}"), + createCustomColumnWithSpecificColor("Name", "Service", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"), createStringColumn("ClusterIP", ".spec.clusterIP"), createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"), createTimestampColumn("Created", ".metadata.creationTimestamp"), @@ -134,7 +140,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid // 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']['-']}"), + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"), createStringColumn("ClusterIP", ".spec.clusterIP"), createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"), createTimestampColumn("Created", ".metadata.creationTimestamp"), @@ -149,7 +155,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid }), // Factory service details port mapping - createCustomColumnsOverride("factory-service-details-port-mapping", []any{ + createCustomColumnsOverride("factory-kube-service-details-port-mapping", []any{ createStringColumn("Name", ".name"), createStringColumn("Port", ".port"), createStringColumn("Protocol", ".protocol"), @@ -169,16 +175,16 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid // 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']['-']}"), + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", getColorForType("secret"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-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{ + createCustomColumnsOverride("factory-kube-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']['-']}"), + createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-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"), }), @@ -244,7 +250,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid // 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']['-']}"), + createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-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"), @@ -253,7 +259,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid // 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']['-']}"), + createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-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"), @@ -309,7 +315,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid // 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("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{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"), @@ -329,15 +335,15 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid // 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']['-']}"), + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{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']['-']}"), + createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"), createStringColumn("Type", ".type"), createTimestampColumn("Created", ".metadata.creationTimestamp"), }), @@ -789,7 +795,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { }, }, } - secretSpec := createFactorySpec("secret-details", []any{"secret-sidebar"}, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{3}/secrets/{6}"}, secretHeader, secretTabs) + secretSpec := createFactorySpec("kube-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{ @@ -1015,7 +1021,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "id": "service-port-mapping-table", "baseprefix": "/openapi-ui", "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-service-details-port-mapping", + "customizationId": "factory-kube-service-details-port-mapping", "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}", "pathToItems": ".spec.ports", "withoutControls": true, @@ -1041,7 +1047,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "id": "service-pod-serving-table", "baseprefix": "/openapi-ui", "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-service-details-endpointslice", + "customizationId": "factory-kube-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']['-']}", @@ -1113,7 +1119,152 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { }, }, } - serviceSpec := createFactorySpec("service-details", []any{"service-sidebar"}, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}"}, serviceHeader, serviceTabs) + serviceSpec := createFactorySpec("kube-service-details", []any{"service-sidebar"}, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}"}, serviceHeader, serviceTabs) + + // Ingress details factory + ingressHeader := map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "header-row", + "align": "center", + "gap": 6, + "style": map[string]any{ + "marginBottom": float64(24), + }, + }, + "children": []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "badge-ingress", + "text": "I", + "title": "ingresses", + "style": map[string]any{ + "backgroundColor": "#2e7dff", + "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": "ingress-name", + "text": "{reqsJsonPath[0]['.metadata.name']['-']}", + "style": map[string]any{ + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": float64(20), + "lineHeight": "24px", + }, + }, + }, + }, + } + + ingressTabs := []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), + map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": "namespace-row", + "align": "center", + "gap": 6, + }, + "children": []any{ + createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium), + antdLink("namespace-link", + "{reqsJsonPath[0]['.metadata.namespace']['-']}", + "/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{ + antdText("time-icon", false, "🌐", nil), + parsedTextWithFormatter("time-value", "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", "timestamp"), + }), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdFlexVertical("status-ingress-ip", 4, []any{ + antdText("status-ingress-ip-label", true, "LoadBalancer IP", nil), + parsedText("status-ingress-ip-value", "{reqsJsonPath[0]['.status.loadBalancer.ingress[0].ip']['-']}", nil), + }), + antdFlexVertical("status-ingress-hostname", 4, []any{ + antdText("status-ingress-hostname-label", true, "LoadBalancer Hostname", nil), + parsedText("status-ingress-hostname-value", "{reqsJsonPath[0]['.status.loadBalancer.ingress[0].hostname']['-']}", nil), + }), + }), + }), + }), + spacer("rules-title-spacer", float64(16)), + antdText("rules-title", true, "Rules", map[string]any{ + "fontSize": float64(20), + }), + spacer("rules-spacer", float64(8)), + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "rules-table", + "fetchUrl": "/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses/{6}", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-kube-ingress-details-rules", + "baseprefix": "/openapi-ui", + "withoutControls": true, + "pathToItems": []any{"spec", "rules"}, + }, + }, + }), + }, + }, + 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, + "type": "builtin", + "typeName": "ingresses", + "prefillValuesRequestIndex": float64(0), + "substractHeight": float64(400), + }, + }, + }, + }, + } + ingressSpec := createFactorySpec("kube-ingress-details", []any{"ingress-sidebar"}, []any{"/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses/{6}"}, ingressHeader, ingressTabs) // Workloadmonitor details factory workloadmonitorHeader := createWorkloadmonitorHeader() @@ -1273,8 +1424,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { createFactory("namespace-details", namespaceSpec), createFactory("node-details", nodeSpec), createFactory("pod-details", podSpec), - createFactory("secret-details", secretSpec), - createFactory("service-details", serviceSpec), + createFactory("kube-secret-details", secretSpec), + createFactory("kube-service-details", serviceSpec), + createFactory("kube-ingress-details", ingressSpec), createFactory("workloadmonitor-details", workloadmonitorSpec), } } @@ -1312,7 +1464,7 @@ func CreateAllTableUriMappings() []*dashboardv1alpha1.TableUriMapping { "keysToParse": ".metadata.name", "keysToParseSecond": ".metadata.namespace", "id": "stock-cluster-/networking.k8s.io/v1/ingresses", - "pathToNavigate": "/openapi-ui/{clusterName}/~recordValueSecond~/factory/ingress-details/~recordValue~", + "pathToNavigate": "/openapi-ui/{clusterName}/~recordValueSecond~/factory/kube-ingress-details/~recordValue~", }), // Stock namespace networking k8s io v1 ingress details @@ -1320,7 +1472,7 @@ func CreateAllTableUriMappings() []*dashboardv1alpha1.TableUriMapping { "keysToParse": ".metadata.name", "keysToParseSecond": ".metadata.namespace", "id": "stock-namespace-/networking.k8s.io/v1/ingresses", - "pathToNavigate": "/openapi-ui/{clusterName}/~recordValueSecond~/factory/ingress-details/~recordValue~", + "pathToNavigate": "/openapi-ui/{clusterName}/~recordValueSecond~/factory/kube-ingress-details/~recordValue~", }), } } From 364cba3100f780b447a69a322a0e0abc9511751e Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 25 Sep 2025 18:57:54 +0200 Subject: [PATCH 7/7] Add formated tables for tenantnamespaces Signed-off-by: Andrei Kvapil --- .../controller/dashboard/static_refactored.go | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 8e74c5d7..67ea72ed 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -357,6 +357,12 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid createStringColumn("Memory", ".status.resources.memory"), createStringColumn("Operational", ".status.operational"), }), + + // Stock cluster core cozystack io v1alpha1 tenantnamespaces + createCustomColumnsOverride("stock-cluster-/core.cozystack.io/v1alpha1/tenantnamespaces", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "TN", "tenantnamespace", getColorForType("tenantnamespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), } } @@ -1445,36 +1451,8 @@ func CreateAllNavigations() []*dashboardv1alpha1.Navigation { // 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/kube-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/kube-ingress-details/~recordValue~", - }), - } + // links are now handled through CustomFormsPrefills + return []*dashboardv1alpha1.TableUriMapping{} } // ---------------- Additional helper functions for missing resource types ----------------