Skip to content

Commit 88da2d1

Browse files
committed
[backups,dashboard] User-facing UI
## What this PR does Add representation for backup Plans in the dashboard and a readme describing, how to add further resources to the dashboard. ### Release note ```release-note [backups,dashboard] Add backup Plans to the dashboard. ``` Signed-off-by: Timofei Larkin <[email protected]>
1 parent 1f0b5ff commit 88da2d1

3 files changed

Lines changed: 167 additions & 5 deletions

File tree

‎internal/controller/dashboard/sidebar.go‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
// - Tenant Info (/openapi-ui/{clusterName}/{namespace}/factory/info-details/info)
2727
// - All other sections are built from CRDs where spec.dashboard != nil.
2828
// - Categories are ordered strictly as:
29-
// Marketplace, IaaS, PaaS, NaaS, <others A→Z>, Resources, Administration
29+
// Marketplace, IaaS, PaaS, NaaS, <others A→Z>, Resources, Backups, Administration
3030
// - Items within each category: sort by Weight (desc), then Label (A→Z).
3131
func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
3232
// Build the full menu once.
@@ -112,6 +112,9 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
112112
keysAndTags["secrets"] = []any{"secret-sidebar"}
113113
keysAndTags["ingresses"] = []any{"ingress-sidebar"}
114114

115+
// Add sidebar for backups.cozystack.io Plan resource
116+
keysAndTags["plans"] = []any{"plan-sidebar"}
117+
115118
// 3) Sort items within each category by Weight (desc), then Label (A→Z)
116119
for cat := range categories {
117120
sort.Slice(categories[cat], func(i, j int) bool {
@@ -123,10 +126,10 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
123126
}
124127

125128
// 4) Order categories strictly:
126-
// Marketplace (hardcoded), IaaS, PaaS, NaaS, <others A→Z>, Resources, Administration
129+
// Marketplace (hardcoded), IaaS, PaaS, NaaS, <others A→Z>, Resources, Backups (hardcoded), Administration (hardcoded)
127130
orderedCats := orderCategoryLabels(categories)
128131

129-
// 5) Build menuItems (hardcode "Marketplace"; then dynamic categories; then hardcode "Administration")
132+
// 5) Build menuItems (hardcode "Marketplace"; then dynamic categories; then hardcode "Backups" and "Administration")
130133
menuItems := []any{
131134
map[string]any{
132135
"key": "marketplace",
@@ -142,8 +145,8 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
142145
}
143146

144147
for _, cat := range orderedCats {
145-
// Skip "Marketplace" and "Administration" here since they're hardcoded
146-
if strings.EqualFold(cat, "Marketplace") || strings.EqualFold(cat, "Administration") {
148+
// Skip "Marketplace", "Backups", and "Administration" here since they're hardcoded
149+
if strings.EqualFold(cat, "Marketplace") || strings.EqualFold(cat, "Backups") || strings.EqualFold(cat, "Administration") {
147150
continue
148151
}
149152
children := []any{}
@@ -163,6 +166,19 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
163166
}
164167
}
165168

169+
// Add hardcoded Backups section
170+
menuItems = append(menuItems, map[string]any{
171+
"key": "backups",
172+
"label": "Backups",
173+
"children": []any{
174+
map[string]any{
175+
"key": "plans",
176+
"label": "Plans",
177+
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/plans",
178+
},
179+
},
180+
})
181+
166182
// Add hardcoded Administration section
167183
menuItems = append(menuItems, map[string]any{
168184
"key": "administration",
@@ -201,6 +217,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
201217
"stock-project-factory-kube-service-details",
202218
"stock-project-factory-kube-secret-details",
203219
"stock-project-factory-kube-ingress-details",
220+
"stock-project-factory-plan-details",
204221
"stock-project-api-form",
205222
"stock-project-api-table",
206223
"stock-project-builtin-form",

‎internal/controller/dashboard/static_helpers.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,27 @@ func createReadyColumn() map[string]any {
10231023
}
10241024
}
10251025

1026+
// createApplicationRefColumn creates a column that displays
1027+
// applicationRef in the format "Kind.apiGroup/name"
1028+
func createApplicationRefColumn(name string) map[string]any {
1029+
return map[string]any{
1030+
"name": name,
1031+
"type": "factory",
1032+
"customProps": map[string]any{
1033+
"disableEventBubbling": true,
1034+
"items": []any{
1035+
map[string]any{
1036+
"type": "parsedText",
1037+
"data": map[string]any{
1038+
"id": "application-ref-text",
1039+
"text": "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}",
1040+
},
1041+
},
1042+
},
1043+
},
1044+
}
1045+
}
1046+
10261047
// createConverterBytesColumn creates a column with ConverterBytes component
10271048
func createConverterBytesColumn(name, jsonPath string) map[string]any {
10281049
return map[string]any{

‎internal/controller/dashboard/static_refactored.go‎

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,13 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
371371
createCustomColumnWithJsonPath("Name", ".metadata.name", "TenantNamespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
372372
createTimestampColumn("Created", ".metadata.creationTimestamp"),
373373
}),
374+
375+
// Stock namespace backups cozystack io v1alpha1 plans
376+
createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/plans", []any{
377+
createCustomColumnWithJsonPath("Name", ".metadata.name", "Plan", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/plan-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
378+
createApplicationRefColumn("Application"),
379+
createTimestampColumn("Created", ".metadata.creationTimestamp"),
380+
}),
374381
}
375382
}
376383

@@ -1433,6 +1440,122 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
14331440
}
14341441
workloadmonitorSpec := createFactorySpec("workloadmonitor-details", []any{"workloadmonitor-sidebar"}, []any{"/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors/{6}"}, workloadmonitorHeader, workloadmonitorTabs)
14351442

1443+
// Plan details factory using unified approach
1444+
planConfig := UnifiedResourceConfig{
1445+
Name: "plan-details",
1446+
ResourceType: "factory",
1447+
Kind: "Plan",
1448+
Plural: "plans",
1449+
Title: "plan",
1450+
}
1451+
planTabs := []any{
1452+
map[string]any{
1453+
"key": "details",
1454+
"label": "Details",
1455+
"children": []any{
1456+
contentCard("details-card", map[string]any{
1457+
"marginBottom": "24px",
1458+
}, []any{
1459+
antdText("details-title", true, "Plan details", map[string]any{
1460+
"fontSize": 20,
1461+
"marginBottom": "12px",
1462+
}),
1463+
spacer("details-spacer", 16),
1464+
antdRow("details-grid", []any{48, 12}, []any{
1465+
antdCol("col-left", 12, []any{
1466+
antdFlexVertical("col-left-stack", 24, []any{
1467+
antdFlexVertical("meta-name-block", 4, []any{
1468+
antdText("meta-name-label", true, "Name", nil),
1469+
parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil),
1470+
}),
1471+
antdFlexVertical("meta-namespace-block", 8, []any{
1472+
antdText("meta-namespace-label", true, "Namespace", nil),
1473+
antdFlex("header-row", 6, []any{
1474+
map[string]any{
1475+
"type": "antdText",
1476+
"data": map[string]any{
1477+
"id": "header-badge",
1478+
"text": "NS",
1479+
"title": "namespace",
1480+
"style": map[string]any{
1481+
"backgroundColor": "#a25792ff",
1482+
"borderRadius": "20px",
1483+
"color": "#fff",
1484+
"display": "inline-block",
1485+
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
1486+
"fontSize": "15px",
1487+
"fontWeight": 400,
1488+
"lineHeight": "24px",
1489+
"minWidth": 24,
1490+
"padding": "0 9px",
1491+
"textAlign": "center",
1492+
"whiteSpace": "nowrap",
1493+
},
1494+
},
1495+
},
1496+
map[string]any{
1497+
"type": "antdLink",
1498+
"data": map[string]any{
1499+
"id": "namespace-link",
1500+
"text": "{reqsJsonPath[0]['.metadata.namespace']['-']}",
1501+
"href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
1502+
},
1503+
},
1504+
}),
1505+
}),
1506+
antdFlexVertical("meta-created-block", 4, []any{
1507+
antdText("time-label", true, "Created", nil),
1508+
antdFlex("time-block", 6, []any{
1509+
map[string]any{
1510+
"type": "antdText",
1511+
"data": map[string]any{
1512+
"id": "time-icon",
1513+
"text": "🌐",
1514+
},
1515+
},
1516+
map[string]any{
1517+
"type": "parsedText",
1518+
"data": map[string]any{
1519+
"formatter": "timestamp",
1520+
"id": "time-value",
1521+
"text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}",
1522+
},
1523+
},
1524+
}),
1525+
}),
1526+
}),
1527+
}),
1528+
antdCol("col-right", 12, []any{
1529+
antdFlexVertical("col-right-stack", 24, []any{
1530+
antdFlexVertical("spec-application-ref-block", 4, []any{
1531+
antdText("application-ref-label", true, "Application", nil),
1532+
parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil),
1533+
}),
1534+
antdFlexVertical("spec-storage-ref-block", 4, []any{
1535+
antdText("storage-ref-label", true, "Storage", nil),
1536+
parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil),
1537+
}),
1538+
antdFlexVertical("spec-strategy-ref-block", 4, []any{
1539+
antdText("strategy-ref-label", true, "Strategy", nil),
1540+
parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.kind']['-']}.{reqsJsonPath[0]['.spec.strategyRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil),
1541+
}),
1542+
antdFlexVertical("spec-schedule-type-block", 4, []any{
1543+
antdText("schedule-type-label", true, "Schedule Type", nil),
1544+
parsedText("schedule-type-value", "{reqsJsonPath[0]['.spec.schedule.type']['-']}", nil),
1545+
}),
1546+
antdFlexVertical("spec-schedule-cron-block", 4, []any{
1547+
antdText("schedule-cron-label", true, "Schedule Cron", nil),
1548+
parsedText("schedule-cron-value", "{reqsJsonPath[0]['.spec.schedule.cron']['-']}", nil),
1549+
}),
1550+
}),
1551+
}),
1552+
}),
1553+
}),
1554+
},
1555+
},
1556+
}
1557+
planSpec := createUnifiedFactory(planConfig, planTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/plans/{6}"})
1558+
14361559
return []*dashboardv1alpha1.Factory{
14371560
createFactory("marketplace", marketplaceSpec),
14381561
createFactory("namespace-details", namespaceSpec),
@@ -1442,6 +1565,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
14421565
createFactory("kube-service-details", serviceSpec),
14431566
createFactory("kube-ingress-details", ingressSpec),
14441567
createFactory("workloadmonitor-details", workloadmonitorSpec),
1568+
createFactory("plan-details", planSpec),
14451569
}
14461570
}
14471571

0 commit comments

Comments
 (0)