mirror of
https://github.com/outbackdingo/cozystack.git
synced 2026-01-28 10:18:42 +00:00
336 lines
9.6 KiB
Go
336 lines
9.6 KiB
Go
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 {
|
|
Kind string // Resource kind in PascalCase (e.g., "VirtualMachine") - used for value and auto-generation
|
|
Text string // Optional abbreviation override (if empty, ResourceBadge auto-generates from Kind)
|
|
Color string // Optional custom backgroundColor override
|
|
}
|
|
|
|
// createUnifiedBadge creates a badge using the unified BadgeConfig with ResourceBadge component
|
|
func createUnifiedBadge(id string, config BadgeConfig) map[string]any {
|
|
data := map[string]any{
|
|
"id": id,
|
|
"value": config.Kind,
|
|
}
|
|
|
|
// Add abbreviation override if specified (otherwise ResourceBadge auto-generates from Kind)
|
|
if config.Text != "" {
|
|
data["abbreviation"] = config.Text
|
|
}
|
|
|
|
// Add custom color if specified
|
|
if config.Color != "" {
|
|
data["style"] = map[string]any{
|
|
"backgroundColor": config.Color,
|
|
}
|
|
}
|
|
|
|
return map[string]any{
|
|
"type": "ResourceBadge",
|
|
"data": data,
|
|
}
|
|
}
|
|
|
|
// createUnifiedBadgeFromKind creates a badge from kind with ResourceBadge component
|
|
// Abbreviation is auto-generated by ResourceBadge from kind, but can be customized if needed
|
|
func createUnifiedBadgeFromKind(id, kind string) map[string]any {
|
|
return map[string]any{
|
|
"type": "ResourceBadge",
|
|
"data": map[string]any{
|
|
"id": id,
|
|
"value": kind,
|
|
// abbreviation is optional - ResourceBadge auto-generates from value
|
|
},
|
|
}
|
|
}
|
|
|
|
// ---------------- 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 := BadgeConfig{
|
|
Kind: kind,
|
|
}
|
|
|
|
return ResourceConfig{
|
|
SpecID: specID,
|
|
MetadataName: metadataName,
|
|
Kind: kind,
|
|
Title: title,
|
|
BadgeConfig: badgeConfig,
|
|
}
|
|
}
|
|
|
|
// ---------------- Enhanced color generation ----------------
|
|
|
|
// ---------------- 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
|
|
}
|
|
|
|
// 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{
|
|
Kind: config.Kind,
|
|
Text: config.BadgeText,
|
|
Color: config.Color,
|
|
}
|
|
|
|
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 := BadgeConfig{
|
|
Kind: kind,
|
|
}
|
|
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)
|
|
}
|