${this._escapeHTML(name)}
${this._escapeHTML(kind)}
+ ${readOnly ? '
Read-only mirror
' : ''}
${groups.map((group, gi) => `
${gi > 0 ? '
' : ''}
@@ -190,6 +207,14 @@ export class ContextMenu {
const name = this.resource.metadata?.name;
const namespace = this.resource.metadata?.namespace;
+ if (cluster?.isReadOnly?.() && MUTATING_EVENTS.has(event)) {
+ engine.emit('cluster:write-blocked', {
+ action: event,
+ reason: cluster.readOnlyReason || 'Mirrored cluster is read-only',
+ });
+ return;
+ }
+
switch (event) {
case 'resource:delete':
if (gameEngine) {
diff --git a/js/ui/InspectorPanel.js b/js/ui/InspectorPanel.js
index 4a07cc6..979d073 100644
--- a/js/ui/InspectorPanel.js
+++ b/js/ui/InspectorPanel.js
@@ -124,7 +124,8 @@ export class InspectorPanel {
const editBtn = document.getElementById('inspector-edit');
const editableKinds = ['Deployment', 'Service', 'ConfigMap', 'StatefulSet', 'DaemonSet', 'ReplicaSet', 'Secret', 'Pod', 'Ingress', 'NetworkPolicy', 'HorizontalPodAutoscaler'];
- if (editableKinds.includes(kind)) {
+ const readOnly = window.game?.cluster?.isReadOnly?.() || false;
+ if (editableKinds.includes(kind) && !readOnly) {
editBtn.classList.remove('hidden');
} else {
editBtn.classList.add('hidden');
@@ -473,6 +474,13 @@ export class InspectorPanel {
_onEdit() {
if (!this.resource) return;
+ if (window.game?.cluster?.isReadOnly?.()) {
+ window.game?.engine?.emit('cluster:write-blocked', {
+ action: 'edit',
+ reason: window.game.cluster.readOnlyReason || 'Mirrored cluster is read-only',
+ });
+ return;
+ }
const kind = this.resource.kind;
const name = this.resource.metadata?.name;
window.game?.engine.emit('ui:edit-resource', { uid: this.resource.metadata?.uid, kind, name });
diff --git a/style.css b/style.css
index 5cc4d6e..23c9468 100644
--- a/style.css
+++ b/style.css
@@ -362,6 +362,20 @@ html, body {
box-shadow: 0 0 16px rgba(50, 108, 229, 0.2), inset 0 0 8px rgba(50, 108, 229, 0.1);
}
+.palette-item.disabled,
+.mirror-readonly .palette-item {
+ cursor: not-allowed;
+ opacity: 0.38;
+}
+
+.palette-item.disabled:hover,
+.mirror-readonly .palette-item:hover {
+ transform: none;
+ box-shadow: none;
+ background: transparent;
+ border-color: transparent;
+}
+
.palette-icon {
width: 24px;
height: 24px;