Add read-only Kubernetes cluster mirror mode with filtering and 3D layered layout#35
Add read-only Kubernetes cluster mirror mode with filtering and 3D layered layout#35Denis112345 wants to merge 1 commit into
Conversation
|
@Denis112345 is attempting to deploy a commit to the rohitg00's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis PR introduces two major features: a Mirror Cluster read-only mode for importing Kubernetes snapshots and a Rendered Objects filter panel with live search and visibility control. Read-only enforcement spans state management, game engine commands, UI components, and input handlers. The renderer gains visibility control and smart camera framing. Auto-layout logic is refactored to respect namespaces. ChangesMirror Cluster Mode and Rendered Objects Filtering
Sequence Diagram(s)sequenceDiagram
participant User
participant UILayer as Index.html<br/>(UI Events)
participant ClusterState
participant ClusterRenderer
participant FilterSystem
User->>UILayer: Click "Mirror Cluster"
UILayer->>UILayer: Show mirror dialog
User->>UILayer: Paste/upload K8s JSON
UILayer->>ClusterState: importSnapshot(objects, {mirrorInfo})
ClusterState->>ClusterState: Normalize objects
ClusterState->>ClusterState: Clear state (skip defaults)
ClusterState->>ClusterState: addResource() for each K8s object
ClusterState->>ClusterState: setReadOnly(true)
ClusterState->>UILayer: Emit cluster:imported
UILayer->>FilterSystem: refreshRenderFilterOptions()
UILayer->>UILayer: autoAlignResources()
UILayer->>ClusterRenderer: frameAllResources()
ClusterRenderer->>ClusterRenderer: Compute visible bounds
ClusterRenderer->>ClusterRenderer: Animate camera to frame
User->>UILayer: Interact with resources (read-only)
UILayer->>ClusterState: isReadOnly()
ClusterState-->>UILayer: true
UILayer->>UILayer: Block action, emit write-blocked
UILayer->>User: Show notification (action denied)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
js/rendering/ClusterRenderer.js (1)
781-794: ⚡ Quick winPrevent overlapping camera frame animations
frameAllResources()starts a new RAF animation each call but does not cancel an in-flight one. Rapid Fit/filter actions can cause competing camera tweens and jitter. Track and cancel the previous frame animation before starting a new one.Proposed fix
+ if (this._frameCameraRafId) { + cancelAnimationFrame(this._frameCameraRafId); + this._frameCameraRafId = null; + } const startTime = performance.now(); const startCamera = this.camera.position.clone(); const startTarget = this.controls.target.clone(); const animate = () => { const elapsed = performance.now() - startTime; @@ this.camera.position.lerpVectors(startCamera, targetPosition, ease); this.controls.target.lerpVectors(startTarget, center, ease); this.controls.update(); - if (t < 1) requestAnimationFrame(animate); + if (t < 1) { + this._frameCameraRafId = requestAnimationFrame(animate); + } else { + this._frameCameraRafId = null; + } }; - requestAnimationFrame(animate); + this._frameCameraRafId = requestAnimationFrame(animate);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/rendering/ClusterRenderer.js` around lines 781 - 794, frameAllResources starts a new requestAnimationFrame loop each call and doesn't cancel any previous one, causing overlapping camera tweens and jitter; modify frameAllResources to store the RAF id (e.g., this._frameAnimationId) before calling requestAnimationFrame, call cancelAnimationFrame(this._frameAnimationId) if set to cancel any in-flight animation, assign the returned id when starting the new animate, and clear this._frameAnimationId (set to null) when the animation completes (when t >= 1) so subsequent calls won't try to cancel a finished handle.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@js/engine/ClusterState.js`:
- Around line 895-899: Imported Job completion uses the string "Succeeded" but
elsewhere in ClusterState.js completed Jobs are marked "Complete"; update the
Job handling branch (the obj.kind === 'Job' block) to return 'Complete' when
obj.status?.succeeded is truthy (leaving the failed, active/Running, and Pending
branches unchanged) so imported snapshots use the same "Complete" status as the
rest of the engine.
In `@js/rendering/ClusterRenderer.js`:
- Line 293: The drag persistence currently records the group's animated Y
(bobbing) by calling onResourceMoved(rid, { x: group.position.x, y:
group.position.y, z: group.position.z }); change it to use the baseline Y stored
in group.userData.baseY (falling back to group.position.y when baseY is missing)
so the call becomes onResourceMoved(rid, { x: group.position.x, y:
(group.userData && group.userData.baseY) ?? group.position.y, z:
group.position.z }); to prevent gradual vertical drift after repeated drags.
In `@js/ui/InspectorPanel.js`:
- Around line 127-128: Header readOnly state is only sampled in _renderHeader(),
so toggling mirror/read-only mode doesn't re-render the header; subscribe to the
cluster change event and trigger a header re-render: in the InspectorPanel class
(e.g., in activateListeners or constructor) add a listener on
window.game?.cluster (use the cluster's event, e.g., 'change' or the specific
mirror/readOnly event if available) that calls this._renderHeader() or
this.render(), and remove that listener on teardown (e.g., in close/destroy) to
avoid leaks; keep the existing editableKinds/kind logic but ensure the header is
re-rendered whenever window.game.cluster.isReadOnly() can change.
---
Nitpick comments:
In `@js/rendering/ClusterRenderer.js`:
- Around line 781-794: frameAllResources starts a new requestAnimationFrame loop
each call and doesn't cancel any previous one, causing overlapping camera tweens
and jitter; modify frameAllResources to store the RAF id (e.g.,
this._frameAnimationId) before calling requestAnimationFrame, call
cancelAnimationFrame(this._frameAnimationId) if set to cancel any in-flight
animation, assign the returned id when starting the new animate, and clear
this._frameAnimationId (set to null) when the animation completes (when t >= 1)
so subsequent calls won't try to cancel a finished handle.
🪄 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: 616e101b-26a7-407c-95d3-201481168958
📒 Files selected for processing (9)
index.htmljs/engine/ClusterState.jsjs/engine/GameEngine.jsjs/engine/SimulationTick.jsjs/rendering/ClusterRenderer.jsjs/ui/CommandBar.jsjs/ui/ContextMenu.jsjs/ui/InspectorPanel.jsstyle.css
| if (obj.kind === 'Job') { | ||
| if (obj.status?.failed) return 'Failed'; | ||
| if (obj.status?.succeeded) return 'Succeeded'; | ||
| return obj.status?.active ? 'Running' : 'Pending'; | ||
| } |
There was a problem hiding this comment.
Keep imported Job completion status aligned with the rest of the engine.
Completed Jobs are marked as Complete elsewhere in this class, but mirror imports derive Succeeded instead. That makes imported snapshots diverge from in-app Job status handling and can misclassify finished Jobs in status-based UI.
Suggested fix
- if (obj.status?.succeeded) return 'Succeeded';
+ if (obj.status?.succeeded) return 'Complete';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@js/engine/ClusterState.js` around lines 895 - 899, Imported Job completion
uses the string "Succeeded" but elsewhere in ClusterState.js completed Jobs are
marked "Complete"; update the Job handling branch (the obj.kind === 'Job' block)
to return 'Complete' when obj.status?.succeeded is truthy (leaving the failed,
active/Running, and Pending branches unchanged) so imported snapshots use the
same "Complete" status as the rest of the engine.
| const group = this.resourceMeshes.get(rid); | ||
| if (group && this.onResourceMoved) { | ||
| this.onResourceMoved(rid, { x: group.position.x, y: 0, z: group.position.z }); | ||
| this.onResourceMoved(rid, { x: group.position.x, y: group.position.y, z: group.position.z }); |
There was a problem hiding this comment.
Use baseline Y when persisting drag result
Line 293 should not persist the animated Y offset for bobbing resources. Emit group.userData.baseY (fallback to group.position.y) to avoid gradual vertical drift after repeated drags.
Proposed fix
- this.onResourceMoved(rid, { x: group.position.x, y: group.position.y, z: group.position.z });
+ this.onResourceMoved(rid, {
+ x: group.position.x,
+ y: group.userData.baseY ?? group.position.y,
+ z: group.position.z
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.onResourceMoved(rid, { x: group.position.x, y: group.position.y, z: group.position.z }); | |
| this.onResourceMoved(rid, { | |
| x: group.position.x, | |
| y: group.userData.baseY ?? group.position.y, | |
| z: group.position.z | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@js/rendering/ClusterRenderer.js` at line 293, The drag persistence currently
records the group's animated Y (bobbing) by calling onResourceMoved(rid, { x:
group.position.x, y: group.position.y, z: group.position.z }); change it to use
the baseline Y stored in group.userData.baseY (falling back to group.position.y
when baseY is missing) so the call becomes onResourceMoved(rid, { x:
group.position.x, y: (group.userData && group.userData.baseY) ??
group.position.y, z: group.position.z }); to prevent gradual vertical drift
after repeated drags.
| const readOnly = window.game?.cluster?.isReadOnly?.() || false; | ||
| if (editableKinds.includes(kind) && !readOnly) { |
There was a problem hiding this comment.
Refresh the header when read-only mode changes.
readOnly is only sampled during _renderHeader(). If a resource is already selected when mirror mode is entered or exited, the Edit button state stays stale until the selection changes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@js/ui/InspectorPanel.js` around lines 127 - 128, Header readOnly state is
only sampled in _renderHeader(), so toggling mirror/read-only mode doesn't
re-render the header; subscribe to the cluster change event and trigger a header
re-render: in the InspectorPanel class (e.g., in activateListeners or
constructor) add a listener on window.game?.cluster (use the cluster's event,
e.g., 'change' or the specific mirror/readOnly event if available) that calls
this._renderHeader() or this.render(), and remove that listener on teardown
(e.g., in close/destroy) to avoid leaks; keep the existing editableKinds/kind
logic but ensure the header is re-rendered whenever
window.game.cluster.isReadOnly() can change.
This PR adds a new Mirror Cluster mode that lets users import a real Kubernetes cluster snapshot into K8s Games and explore it visually without mutating the cluster model.
The import currently supports kubectl ... -o json snapshots, including large JSON dumps and UTF-16/BOM encoded files. Once imported, the mirrored cluster is locked as read-only: create, edit, delete, scale, drain, cordon, rollout, palette actions, context-menu mutations, and simulated kubectl mutation commands are blocked.
What changed
Added a new Mirror Cluster entry in the main menu.
Added a Mirror toolbar action for importing/replacing a snapshot.
Added read-only enforcement at the cluster state, game engine, command bar, context menu, inspector, palette, and simulation layers.
Added support for importing real Kubernetes objects while preserving metadata, spec, status, UID, namespace, labels, annotations, owner references, and finalizers.
Added object filtering by namespace, kind, status, and name.
Added visibility-aware connection rendering.
Improved auto-layout for large clusters with compact 3D layered placement instead of one long flat row.
Expanded camera range and added fit-to-visible-resources behavior.
Added UTF-16/BOM snapshot file handling.
Why
Large real Kubernetes clusters are hard to inspect in the current flat layout: objects spread too far horizontally, and camera limits make navigation difficult. This change makes real cluster snapshots usable for visual inspection while keeping them safe from accidental edits.
Testing
Ran JS syntax checks for modified modules.
Verified the index.html module script parses successfully.
Tested import with a real Kubernetes dump containing 706 objects.
Verified imported mirror mode is read-only and write attempts are blocke
Summary by CodeRabbit
Release Notes
New Features
Improvements