Skip to content

feat(dashboard): add restorejob related views - #2387

Closed
Andrey Kolkov (androndo) wants to merge 1 commit into
mainfrom
feat/dashboard-restores
Closed

Andrey Kolkov (androndo) wants to merge 1 commit into
mainfrom
feat/dashboard-restores

Conversation

@androndo

@androndo Andrey Kolkov (androndo) commented Apr 13, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Related #1973

Release note

added views related ResoreJobs (list, get, create)
image 2

Summary by CodeRabbit

  • New Features
    • Added RestoreJob management to the dashboard sidebar under the Backups section.
    • Introduced RestoreJob creation and editing forms with fields for backup reference, target application, and configuration options.
    • Added RestoreJob details view displaying metadata, status (phase, timestamps, messages), and configuration information.
    • Enabled RestoreJob table display with columns for name, phase, backup reference, and creation time.

@dosubot dosubot Bot added size/L This PR changes 100-499 lines, ignoring generated files kind/feature Categorizes issue or PR as related to a new feature labels Apr 13, 2026
@coderabbitai

coderabbitai Bot commented Apr 13, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change adds comprehensive dashboard UI support for the RestoreJob resource in the backups.cozystack.io API group, including sidebar navigation, custom columns display, form configuration for resource creation/editing, and a detailed view factory with metadata and status rendering.

Changes

Cohort / File(s) Summary
RestoreJob Sidebar & Navigation
internal/controller/dashboard/sidebar.go
Added hardcoded sidebar entry for RestoreJobs under Backups menu section with tag mapping and navigation target for details view.
RestoreJob UI Configuration & Factory
internal/controller/dashboard/static_refactored.go
Added CustomColumnsOverride defining table columns (name with link, phase, backup ref, created time); CustomFormsOverride with form fields and list-input schema for backup selection; "restorejob-details" factory rendering metadata, status, and spec information; helper function listInputSchemaItemBackup() for backup resource selection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A RestoreJob now graces the sidebar so fine,
With columns and forms in perfect design,
The factory stitches details with care,
Backup selections floating through air,
Dashboard magic, complete and divine!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(dashboard): add restorejob related views' accurately summarizes the main change - adding UI views (list, create, details) for RestoreJob resources to the dashboard.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-restores

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for RestoreJob resources in the dashboard, including sidebar integration, custom list columns, form overrides for creation, and a detailed resource view. Feedback includes addressing hardcoded driver-specific labels in the form, resolving duplicate component IDs in the details factory, and improving the display of optional target application references to avoid broken string concatenations.

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"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The label for spec.options is very specific to VMInstance. While this might be the primary use case, RestoreJob is a generic resource. Hardcoding driver-specific keys in the label makes the dashboard less generic. Consider a more neutral label or a way to provide driver-specific labels dynamically.

Comment on lines +2024 to +2112
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']['-']}",
},
},
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The IDs time-block, time-icon, and time-value are duplicated within the restorejob-details factory (used for 'Created', 'Started At', and 'Completed At'). Duplicate IDs within the same factory can lead to issues in the dashboard frontend when targeting specific elements. Please use unique IDs for each block, such as created-time-block, started-time-block, and completed-time-block.

}),
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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The targetApplicationRef field is optional. If it is omitted, the current JSONPath concatenation will render as .-/-. Since this is a common case (restoring to the original application), it would be better to handle this gracefully in the UI, perhaps by showing a descriptive text like 'Original application' when the reference is missing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/dashboard/sidebar.go`:
- Line 269: The sidebar with ID 'stock-project-factory-restorejob-details' is
being created without the labels/owner refs applied by upsertMultipleSidebars(),
so make it follow the static-label branch or explicitly add the same
labels/owner refs used for other 'stock-project-factory-*-details' entries;
update the logic in upsertMultipleSidebars() (or the place where the list of
sidebar IDs is defined in internal/controller/dashboard/sidebar.go) to include
'stock-project-factory-restorejob-details' in the static branch or add the same
label set and owner reference assignment so the RestoreJob details sidebar is
managed consistently.

In `@internal/controller/dashboard/static_refactored.go`:
- Around line 554-556: Update the user-visible labels for the
TargetApplicationRef fields so they don't restrict the feature to renames: in
the createFormItem calls for "spec.targetApplicationRef.apiGroup",
"spec.targetApplicationRef.kind", and "spec.targetApplicationRef.name" replace
the label text that currently reads "Target Application ... (optional, only for
rename)" with a neutral description such as "Target Application API Group
(optional, used to restore into a different application)", "Target Application
Kind (optional, used to restore into a different application)", and "Target
Application Name (optional, used to restore into a different application)"
respectively so the UI accurately conveys that restoring into a different
application (not just rename) is supported.
- Around line 2054-2056: The UI currently renders noisy "-.-/-" when
.spec.targetApplicationRef is missing; update the rendering around
antdFlexVertical("spec-target-application-ref-block", ...) so it either
conditionally hides the whole block when
reqsJsonPath[0]['.spec.targetApplicationRef'] is undefined/null, or replace
parsedText("target-application-ref-value", ...) with logic that detects missing
parts and returns a single fallback string (e.g., "Same as backup" or "Not
set"). Locate the block using antdFlexVertical and parsedText and implement the
conditional check upstream of parsedText or within its value resolution so
absent targetApplicationRef yields the chosen fallback instead of "-.-/-".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58db88a6-c588-454e-815d-b536a877c316

📥 Commits

Reviewing files that changed from the base of the PR and between 655eb39 and dcafd03.

📒 Files selected for processing (2)
  • internal/controller/dashboard/sidebar.go
  • internal/controller/dashboard/static_refactored.go

"stock-project-factory-plan-details",
"stock-project-factory-backupjob-details",
"stock-project-factory-backup-details",
"stock-project-factory-restorejob-details",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mark this sidebar as static, or label it explicitly.

Line 269 adds stock-project-factory-restorejob-details, but upsertMultipleSidebars() only assigns labels/owner refs for stock-project-factory-*-details IDs when they match the current ApplicationDefinition (Lines 321-345). RestoreJob never hits that match, so this Sidebar gets created unmanaged. Please route it through the static-label branch or set the labels explicitly for this backup-specific details sidebar.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/dashboard/sidebar.go` at line 269, The sidebar with ID
'stock-project-factory-restorejob-details' is being created without the
labels/owner refs applied by upsertMultipleSidebars(), so make it follow the
static-label branch or explicitly add the same labels/owner refs used for other
'stock-project-factory-*-details' entries; update the logic in
upsertMultipleSidebars() (or the place where the list of sidebar IDs is defined
in internal/controller/dashboard/sidebar.go) to include
'stock-project-factory-restorejob-details' in the static branch or add the same
label set and owner reference assignment so the RestoreJob details sidebar is
managed consistently.

Comment on lines +554 to +556
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"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don’t describe targetApplicationRef as rename-only.

These labels are user-visible, and they narrow the feature too much. RestoreJobSpec.TargetApplicationRef is the target application in general, so users can also restore into a different application, not just rename. The current wording is likely to hide that case from the UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/dashboard/static_refactored.go` around lines 554 - 556,
Update the user-visible labels for the TargetApplicationRef fields so they don't
restrict the feature to renames: in the createFormItem calls for
"spec.targetApplicationRef.apiGroup", "spec.targetApplicationRef.kind", and
"spec.targetApplicationRef.name" replace the label text that currently reads
"Target Application ... (optional, only for rename)" with a neutral description
such as "Target Application API Group (optional, used to restore into a
different application)", "Target Application Kind (optional, used to restore
into a different application)", and "Target Application Name (optional, used to
restore into a different application)" respectively so the UI accurately conveys
that restoring into a different application (not just rename) is supported.

Comment on lines +2054 to +2056
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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle an omitted targetApplicationRef as one fallback.

targetApplicationRef is optional, so the common case here is "not set". With the current composition, Lines 2054-2056 render -.-/-, which is noisy and misleading on the default path. Please either hide this block when absent or show a single fallback like Same as backup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/dashboard/static_refactored.go` around lines 2054 - 2056,
The UI currently renders noisy "-.-/-" when .spec.targetApplicationRef is
missing; update the rendering around
antdFlexVertical("spec-target-application-ref-block", ...) so it either
conditionally hides the whole block when
reqsJsonPath[0]['.spec.targetApplicationRef'] is undefined/null, or replace
parsedText("target-application-ref-value", ...) with logic that detects missing
parts and returns a single fallback string (e.g., "Same as backup" or "Not
set"). Locate the block using antdFlexVertical and parsedText and implement the
conditional check upstream of parsedText or within its value resolution so
absent targetApplicationRef yields the chosen fallback instead of "-.-/-".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: please address the open bot review threads before merge — the findings from coderabbit and gemini are substantive, not noise:

  • spec.targetApplicationRef labels shouldn't describe the field as rename-only (coderabbit, gemini on lines 554-556) — it also covers cross-application restore.
  • Omitted spec.targetApplicationRef renders as -.-/- on the details page (coderabbit, gemini on line 2056) — this is the common default case for same-application restores and needs a clean fallback.
  • spec.options form label hardcodes VMInstance keys (gemini on line 559).
  • Duplicate component IDs (time-block, time-icon, time-value) inside the restorejob-details factory (gemini on line 2112).
  • stock-project-factory-restorejob-details sidebar is created unlabelled by upsertMultipleSidebars (coderabbit on sidebar.go:269) — pre-existing pattern for backup/backupjob/plan, but worth resolving consistently here.

One additional observation below.

}),
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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation (non-blocking, related to gemini's comment on line 559): the details view hardcodes four VMInstance-driver-specific fields (targetNamespace, failIfTargetExists, keepOriginalPVC, keepOriginalIpAndMac) at spec.options.*. RestoreJob is a generic resource — for restores created by a non-VMInstance driver all four rows will render "-" unconditionally.

Worth considering rendering spec.options generically (e.g. key-value over the RawExtension payload) once there's a second driver, or gating this block on spec.targetApplicationRef.kind == VMInstance.

myasnikovdaniil added a commit that referenced this pull request Apr 21, 2026
## What this PR does

- Introduce list, details, create, and sidebar views for `RestoreJob` in
the dashboard controller.
- Auto-generate stable component IDs for sidebar entries and detail
sub-blocks so upserts remain consistent across reconciles.
- Render a single "Same as backup" fallback for an omitted
`spec.targetApplicationRef` on the details view instead of concatenating
three missing-path fallbacks.
- Mark non-CRD-backed `stock-project-factory-*-details` sidebars
(`kube-*`, `plan`, `backupjob`, `backup`, `restorejob`) as static so
they pick up consistent managed-by labels.

### Release note

```release-note
[dashboard] Add RestoreJob views (list, details, create, sidebar link) to the dashboard controller.
```

## Test plan

- [x] Deploy controller to dev stand and open the restore pages in the
dashboard
- [x] Create a RestoreJob through the UI against an existing Backup;
reconcile succeeds and the details view renders
- [ ] CI green

## Previous PR
#2387


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Introduced comprehensive RestoreJobs dashboard interface enabling
users to view and manage backup restoration operations.
* New RestoreJobs section added to the Backups menu with dedicated
details page and navigation.
* Dashboard displays restore job status, phase, backup references,
target applications, and operation timestamps for easy monitoring.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@myasnikovdaniil

Copy link
Copy Markdown
Contributor

merged in #2437

auto-merge was automatically disabled May 6, 2026 04:48

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants