This document explains how the Widget Layout Backend loads and processes its configuration for base templates and widget mappings.
The Widget Layout Backend loads its configuration from Kubernetes ConfigMaps through environment variables. The configuration is processed at application startup and stored in runtime registries for fast access.
The application expects two main configuration environment variables:
BASE_LAYOUTS- JSON string containing base widget dashboard templatesWIDGET_MAPPING- JSON string containing widget module federation metadata
The configuration is mounted from ConfigMaps as defined in the clowdapp.yaml:
env:
# FEO generated base layout config
- name: BASE_LAYOUTS
valueFrom:
configMapKeyRef:
name: ${FEO_BASE_LAYOUTS_CONFIGMAP}
key: base-widget-dashboard-templates.json
# FEO generated widget mapping config
- name: WIDGET_MAPPING
valueFrom:
configMapKeyRef:
name: ${FEO_WIDGET_MAPPING_CONFIGMAP}
key: widget-registry.json- Application Startup: The Go
init()functions in service packages execute - Config Loading: Environment variables are read and parsed as JSON
- Registry Population: Parsed data is stored in in-memory registries
- Error Handling: Invalid configurations cause fatal errors and service shutdown
File: pkg/service/BaseLayoutTemplate.go
func init() {
cfg := config.GetConfig()
if err := LoadBaseTemplatesFromConfig(cfg.BaseWidgetDashboardTemplates); err != nil {
logrus.Fatalln("Failed to parse base widget dashboard templates, shutting down the service", err)
}
}The LoadBaseTemplatesFromConfig function:
- Parses JSON array of base templates
- Validates template structure
- Stores templates in
BaseTemplateRegistry - Logs successful loading
File: pkg/service/WidgetMapping.go
func init() {
cfg := config.GetConfig()
if err := LoadWidgetMappingsFromConfig(cfg.WidgetMappingConfig); err != nil {
logrus.Fatalln("Failed to parse widget mappings, shutting down the service", err)
}
}The LoadWidgetMappingsFromConfig function:
- Parses JSON array of widget mappings
- Generates unique keys for each widget
- Stores mappings in
WidgetMappingRegistry - Logs successful loading
Widget mappings are stored in the registry using a unique key generated from the widget metadata:
Key Format: {scope}-{module}[-{importName}]
Algorithm:
func (wc *WidgetModuleFederationMetadata) GetWidgetKey() string {
key := fmt.Sprintf("%s-%s", wc.Scope, wc.Module)
if wc.ImportName != nil && *wc.ImportName != "" {
key = fmt.Sprintf("%s-%s", key, *wc.ImportName)
}
return key
}Examples:
insights-vulnerabilities-widget(no importName)insights-compliance-widget-ComplianceWidget(with importName)monitoring-alerts-widget-AlertsWidget(with importName)
This ensures each widget has a unique identifier even when multiple widgets come from the same scope/module combination.
JSON Structure:
[
{
"name": "insights-dashboard",
"displayName": "Insights Dashboard",
"templateConfig": {
"sm": [
{
"w": 1,
"h": 4,
"maxH": 10,
"minH": 1,
"cx": 0,
"cy": 0,
"i": "insights-compliance",
"static": false
}
],
"md": [...],
"lg": [...],
"xl": [...]
}
}
]Field Descriptions:
name: Unique identifier for the templatedisplayName: Human-readable nametemplateConfig: Responsive layout configurationsm,md,lg,xl: Breakpoint-specific widget layoutsw,h: Widget width and heightmaxH,minH: Maximum and minimum height constraintscx,cy: Widget coordinates (see coordinate system section)i: Widget identifier/typestatic: Whether widget is locked in position
JSON Structure:
[
{
"scope": "insights",
"module": "dashboard-widget",
"importName": "DashboardWidget",
"featureFlag": "enable-insights-dashboard",
"config": {
"title": "Insights Dashboard",
"icon": "insights-icon",
"permissions": ["read:insights"],
"headerLink": {
"name": "View Details",
"href": "https://console.redhat.com/insights"
}
},
"defaults": {
"w": 2,
"h": 3,
"maxH": 6,
"minH": 1
}
}
]Field Descriptions:
scope: Module federation scopemodule: Module federation module nameimportName: (Optional) Specific import namefeatureFlag: (Optional) Feature flag for conditional loadingconfig: Widget configurationtitle: Widget display titleicon: Widget icon identifierpermissions: (Optional) Required permissions arrayheaderLink: (Optional) Header link configuration
defaults: Default widget dimensions
Configuration files use cx and cy (config x/y) instead of x and y due to a YAML parser limitation:
Technical Issue: The YAML parser treats the character "y" as a reserved word that translates to the boolean value true, causing unmarshaling errors when processing widget coordinates.
Solution:
- Configuration files: Use
cxandcyexclusively - REST API: Use
xandyexclusively - Runtime conversion:
cx/cyis automatically converted tox/yduring configuration loading
Configuration Files (ConfigMaps, JSON config):
{
"w": 2,
"h": 3,
"cx": 0, // Configuration uses cx
"cy": 1, // Configuration uses cy
"i": "widget-id"
}REST API (Requests/Responses):
{
"w": 2,
"h": 3,
"x": 0, // API uses x
"y": 1, // API uses y
"i": "widget-id"
}The WidgetItem.UnmarshalJSON() method handles the conversion. The actual implementation includes comprehensive type checking and reflection-based field mapping:
func (wi *WidgetItem) UnmarshalJSON(data []byte) error {
var temp map[string]interface{}
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
// use original values
if temp["x"] != nil && temp["y"] != nil {
x, ok := temp["x"].(float64)
if ok {
vi := int(x)
wi.X = &vi
}
// ... error handling
y, ok := temp["y"].(float64)
if ok {
vi := int(y)
wi.Y = &vi
}
// ... error handling
} else if temp["cx"] != nil && temp["cy"] != nil {
cx, ok := temp["cx"].(float64)
if ok {
vi := int(cx)
wi.X = &vi
}
// ... error handling
cy, ok := temp["cy"].(float64)
if ok {
vi := int(cy)
wi.Y = &vi
}
// ... error handling
} else if temp["x"] == nil && temp["y"] == nil && temp["cx"] == nil && temp["cy"] == nil {
return errors.New("WidgetItem must have either x/y or cx/cy attributes")
}
// Additional reflection-based field mapping for other properties...
// (Full implementation includes comprehensive field mapping logic)
}Note: The above shows the core coordinate conversion logic. The actual implementation in api/common.go includes additional reflection-based field mapping for all widget properties.
- Use
cxandcyONLY in JSON/YAML configuration files - Use
xandyONLY in REST API requests/responses and runtime objects - No mixing formats: Configuration files must use
cx/cy, REST API must usex/y - Automatic conversion:
cx/cyis converted tox/yduring configuration loading - Validation ensures at least one coordinate system is present in configuration
// Access base templates
template, exists := service.BaseTemplateRegistry.GetBase("template-name")
// Get all base templates
templates := service.BaseTemplateRegistry.GetAllBases()// Access widget mappings
mapping, exists := service.WidgetMappingRegistry.GetWidgetMapping("widget-key")
// Get all widget mappings
mappings := service.WidgetMappingRegistry.GetAllWidgetMappings()- Invalid JSON: Service fails to start with fatal error
- Missing Required Fields: Validation errors logged, service continues
- Empty Configuration: Handled gracefully, empty registries created
- Missing Templates: HTTP 404 responses
- Invalid Coordinates: Unmarshaling errors with descriptive messages
- Registry Access: Safe fallbacks to empty collections
- Validate Configuration: Test JSON structure before deployment
- Use cx/cy: Always use
cxandcyin configuration files - Handle Optionals: Mark optional fields appropriately
- Monitor Logs: Check startup logs for configuration loading status
- Feature Flags: Use feature flags for conditional widget loading
[
{
"name": "minimal-dashboard",
"displayName": "Minimal Dashboard",
"templateConfig": {
"sm": [
{
"w": 1,
"h": 2,
"maxH": 4,
"minH": 1,
"cx": 0,
"cy": 0,
"i": "simple-widget"
}
],
"md": [...],
"lg": [...],
"xl": [...]
}
}
][
{
"scope": "basic",
"module": "simple-widget",
"config": {
"title": "Simple Widget",
"icon": "widget-icon"
},
"defaults": {
"w": 1,
"h": 2,
"maxH": 4,
"minH": 1
}
}
]For local development, environment variables can be set through several methods:
Create a .env file in the project root:
# .env file for local development
BASE_LAYOUTS='[{"name":"local-dashboard","displayName":"Local Dashboard","templateConfig":{"sm":[],"md":[],"lg":[],"xl":[]}}]'
WIDGET_MAPPING='[{"scope":"local","module":"test-widget","config":{"title":"Test Widget","icon":"test-icon"},"defaults":{"w":2,"h":2,"maxH":4,"minH":1}}]'Set variables directly in your shell:
# Set widget mapping
export WIDGET_MAPPING='[
{
"scope": "insights",
"module": "test-widget",
"config": {
"title": "Test Widget",
"icon": "test-icon"
},
"defaults": {
"w": 2,
"h": 2,
"maxH": 4,
"minH": 1
}
}
]'
# Set base layout
export BASE_LAYOUTS='[
{
"name": "test-template",
"displayName": "Test Template",
"templateConfig": {
"sm": [],
"md": [],
"lg": [],
"xl": []
}
}
]'| Environment | Configuration Source | Format |
|---|---|---|
| Local Development | Environment variables or .env file |
JSON strings |
| Production/Kubernetes | ConfigMaps mounted as environment variables | JSON strings |
Important Notes:
- Use
cx/cyin configuration JSON (notx/y) - JSON must be valid - invalid JSON causes service startup failure
- Empty configurations are handled gracefully (empty registries)
This configuration system provides a flexible, scalable way to manage widget layouts and mappings while handling the technical constraints of YAML parsing and Kubernetes ConfigMap integration.