Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Andrey Kolkov
dcafd036fa feat(dashboard): add restorejob list and add page and sidebar link
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-04-13 16:24:50 +04:00
2 changed files with 228 additions and 3 deletions

View file

@ -144,6 +144,9 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
// Add sidebar for backups.cozystack.io Backup resource
keysAndTags["backups"] = []any{"backup-sidebar"}
// Add sidebar for backups.cozystack.io RestoreJob resource
keysAndTags["restorejobs"] = []any{"restorejob-sidebar"}
// 3) Sort items within each category by Weight (desc), then Label (A→Z)
for cat := range categories {
sort.Slice(categories[cat], func(i, j int) bool {
@ -215,6 +218,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
"label": "Backups",
"link": "/openapi-ui/{cluster}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backups",
},
map[string]any{
"key": "restorejobs",
"label": "RestoreJobs",
"link": "/openapi-ui/{cluster}/{namespace}/api-table/backups.cozystack.io/v1alpha1/restorejobs",
},
},
})
@ -258,6 +266,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
"stock-project-factory-plan-details",
"stock-project-factory-backupjob-details",
"stock-project-factory-backup-details",
"stock-project-factory-restorejob-details",
"stock-project-factory-external-ips",
"stock-project-api-form",
"stock-project-api-table",

View file

@ -411,6 +411,14 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
createTimestampColumn("Taken At", ".spec.takenAt"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock namespace backups cozystack io v1alpha1 restorejobs
createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/restorejobs", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "RestoreJob", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/restorejob-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Phase", ".status.phase"),
createStringColumn("Backup", ".spec.backupRef.name"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
}
}
@ -533,6 +541,31 @@ func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride {
"backupClassName": listInputScemaItemBackupClass(),
}),
}),
// RestoreJobs form override - backups.cozystack.io/v1alpha1
createCustomFormsOverride("default-/backups.cozystack.io/v1alpha1/restorejobs", map[string]any{
"formItems": []any{
createFormItem("metadata.name", "Name", "text"),
createFormItem("metadata.namespace", "Namespace", "text"),
createFormItem("spec.backupRef.name", "Backup", "text"),
// Target application: leave empty to restore into the same application
// as referenced by the selected Backup. Fill all three only to rename
// or restore into a different application.
createFormItem("spec.targetApplicationRef.apiGroup", "Target Application API Group (optional, only for rename)", "text"),
createFormItem("spec.targetApplicationRef.kind", "Target Application Kind (optional, only for rename)", "text"),
createFormItem("spec.targetApplicationRef.name", "Target Application Name (optional, only for rename)", "text"),
// Driver-specific options (key-value editor). Known keys for VMInstance:
// targetNamespace, failIfTargetExists, keepOriginalPVC, keepOriginalIpAndMac.
createFormItem("spec.options", "Options (VMInstance keys: targetNamespace, failIfTargetExists, keepOriginalPVC, keepOriginalIpAndMac)", "object"),
},
"schema": createSchema(map[string]any{
"backupRef": map[string]any{
"properties": map[string]any{
"name": listInputSchemaItemBackup(),
},
},
}),
}),
}
}
@ -1923,6 +1956,174 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
}
backupSpec := createUnifiedFactory(backupConfig, backupTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/backups/{6}"})
// RestoreJob details factory using unified approach
restoreJobConfig := UnifiedResourceConfig{
Name: "restorejob-details",
ResourceType: "factory",
Kind: "RestoreJob",
Plural: "restorejobs",
Title: "restorejob",
}
restoreJobTabs := []any{
map[string]any{
"key": "details",
"label": "Details",
"children": []any{
contentCard("details-card", map[string]any{
"marginBottom": "24px",
}, []any{
antdText("details-title", true, "RestoreJob details", map[string]any{
"fontSize": 20,
"marginBottom": "12px",
}),
spacer("details-spacer", 16),
antdRow("details-grid", []any{48, 12}, []any{
antdCol("col-left", 12, []any{
antdFlexVertical("col-left-stack", 24, []any{
antdFlexVertical("meta-name-block", 4, []any{
antdText("meta-name-label", true, "Name", nil),
parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil),
}),
antdFlexVertical("meta-namespace-block", 8, []any{
antdText("meta-namespace-label", true, "Namespace", nil),
antdFlex("header-row", 6, []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "header-badge",
"text": "NS",
"title": "namespace",
"style": map[string]any{
"backgroundColor": "#a25792ff",
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
},
map[string]any{
"type": "antdLink",
"data": map[string]any{
"id": "namespace-link",
"text": "{reqsJsonPath[0]['.metadata.namespace']['-']}",
"href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
},
},
}),
}),
antdFlexVertical("meta-created-block", 4, []any{
antdText("time-label", true, "Created", nil),
antdFlex("time-block", 6, []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "time-icon",
"text": "🌐",
},
},
map[string]any{
"type": "parsedText",
"data": map[string]any{
"formatter": "timestamp",
"id": "time-value",
"text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}",
},
},
}),
}),
}),
}),
antdCol("col-right", 12, []any{
antdFlexVertical("col-right-stack", 24, []any{
antdFlexVertical("status-phase-block", 4, []any{
antdText("phase-label", true, "Phase", nil),
parsedText("phase-value", "{reqsJsonPath[0]['.status.phase']['-']}", nil),
}),
antdFlexVertical("spec-backup-ref-block", 4, []any{
antdText("backup-ref-label", true, "Backup Ref", nil),
parsedText("backup-ref-value", "{reqsJsonPath[0]['.spec.backupRef.name']['-']}", nil),
}),
antdFlexVertical("spec-target-application-ref-block", 4, []any{
antdText("target-application-ref-label", true, "Target Application", nil),
parsedText("target-application-ref-value", "{reqsJsonPath[0]['.spec.targetApplicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.targetApplicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.targetApplicationRef.name']['-']}", nil),
}),
antdFlexVertical("spec-options-target-namespace-block", 4, []any{
antdText("options-target-namespace-label", true, "Target Namespace", nil),
parsedText("options-target-namespace-value", "{reqsJsonPath[0]['.spec.options.targetNamespace']['-']}", nil),
}),
antdFlexVertical("spec-options-fail-if-target-exists-block", 4, []any{
antdText("options-fail-if-target-exists-label", true, "Fail If Target Exists", nil),
parsedText("options-fail-if-target-exists-value", "{reqsJsonPath[0]['.spec.options.failIfTargetExists']['-']}", nil),
}),
antdFlexVertical("spec-options-keep-original-pvc-block", 4, []any{
antdText("options-keep-original-pvc-label", true, "Keep Original PVC", nil),
parsedText("options-keep-original-pvc-value", "{reqsJsonPath[0]['.spec.options.keepOriginalPVC']['-']}", nil),
}),
antdFlexVertical("spec-options-keep-original-ip-mac-block", 4, []any{
antdText("options-keep-original-ip-mac-label", true, "Keep Original IP/MAC", nil),
parsedText("options-keep-original-ip-mac-value", "{reqsJsonPath[0]['.spec.options.keepOriginalIpAndMac']['-']}", nil),
}),
antdFlexVertical("status-started-at-block", 4, []any{
antdText("started-at-label", true, "Started At", nil),
antdFlex("time-block", 6, []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "time-icon",
"text": "🌐",
},
},
map[string]any{
"type": "parsedText",
"data": map[string]any{
"formatter": "timestamp",
"id": "time-value",
"text": "{reqsJsonPath[0]['.status.startedAt']['-']}",
},
},
}),
}),
antdFlexVertical("status-completed-at-block", 4, []any{
antdText("completed-at-label", true, "Completed At", nil),
antdFlex("time-block", 6, []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "time-icon",
"text": "🌐",
},
},
map[string]any{
"type": "parsedText",
"data": map[string]any{
"formatter": "timestamp",
"id": "time-value",
"text": "{reqsJsonPath[0]['.status.completedAt']['-']}",
},
},
}),
}),
antdFlexVertical("status-message-block", 4, []any{
antdText("message-label", true, "Message", nil),
parsedText("message-value", "{reqsJsonPath[0]['.status.message']['-']}", nil),
}),
}),
}),
}),
}),
},
},
}
restoreJobSpec := createUnifiedFactory(restoreJobConfig, restoreJobTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/restorejobs/{6}"})
// External IPs factory (filtered services)
externalIPsTabs := []any{
map[string]any{
@ -1975,6 +2176,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
createFactory("plan-details", planSpec),
createFactory("backupjob-details", backupJobSpec),
createFactory("backup-details", backupSpec),
createFactory("restorejob-details", restoreJobSpec),
createFactory("external-ips", externalIPsSpec),
}
}
@ -1994,9 +2196,10 @@ func CreateAllNavigations() []*dashboardv1alpha1.Navigation {
"base-factory-namespaced-api-networking.k8s.io-v1-ingresses": "kube-ingress-details",
"base-factory-namespaced-api-cozystack.io-v1alpha1-workloadmonitors": "workloadmonitor-details",
// Backup resources (not ApplicationDefinitions, so ensureNavigation doesn't cover them)
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-plans": "plan-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backupjobs": "backupjob-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backups": "backup-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-plans": "plan-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backupjobs": "backupjob-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backups": "backup-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-restorejobs": "restorejob-details",
}
return []*dashboardv1alpha1.Navigation{
@ -2121,6 +2324,19 @@ func listInputScemaItemBackupClass() map[string]any {
}
}
// listInputSchemaItemBackup returns a listInput schema overlay for selecting a Backup
// from the current namespace (used by RestoreJob form for spec.backupRef.name).
func listInputSchemaItemBackup() map[string]any {
return map[string]any{
"type": "listInput",
"customProps": map[string]any{
"valueUri": "/api/clusters/{cluster}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{namespace}/backups",
"keysToValue": []any{"metadata", "name"},
"keysToLabel": []any{"metadata", "name"},
},
}
}
// backupClassSchema returns the schema for spec.backupClassName as listInput (BackupJob/Plan).
func createSchema(customProps map[string]any) map[string]any {
return map[string]any{