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