diff --git a/src/renderer/components/dock/create-resource.tsx b/src/renderer/components/dock/create-resource.tsx index 15fcdcbe8c..c8902c4d2b 100644 --- a/src/renderer/components/dock/create-resource.tsx +++ b/src/renderer/components/dock/create-resource.tsx @@ -30,6 +30,7 @@ import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource- import { Notifications } from "../notifications"; import { TabKind } from "./dock.store"; import { dockViewsManager } from "./dock.views-manager"; +import logger from "../../../common/logger"; interface Props extends InfoPanelProps { } @@ -58,13 +59,13 @@ export class CreateResourceInfoPanel extends React.Component { } create = async (): Promise => { - if (this.error || !this.data.trim()) { + if (!this.draft) { // do not save when field is empty or there is an error return null; } // skip empty documents - const resources = yaml.loadAll(this.data).filter(Boolean); + const resources = yaml.loadAll(this.draft).filter(Boolean); const createdResources: string[] = []; if (resources.length === 0) { diff --git a/src/renderer/components/dock/edit-resource.store.ts b/src/renderer/components/dock/edit-resource.store.ts index 157293bc2c..d5120cabc0 100644 --- a/src/renderer/components/dock/edit-resource.store.ts +++ b/src/renderer/components/dock/edit-resource.store.ts @@ -26,7 +26,7 @@ import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./doc import type { KubeObject } from "../../../common/k8s-api/kube-object"; import { apiManager } from "../../../common/k8s-api/api-manager"; import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; export interface EditingResource { resource: string; // resource path, e.g. /api/v1/namespaces/default @@ -61,7 +61,7 @@ export class EditResourceStore extends DockTabStore { let resource = this.getResource(tabId); resource ??= await store.loadFromPath(data.resource); - data.draft ||= jsYaml.safeDump(resource.toPlainObject()); // dump resource if empty + data.draft ||= yaml.dump(resource.toPlainObject()); // dump resource if empty } catch (error) { console.error(`[DOCK]: dumping '${data.resource}' has failed: ${error}`, { store, data }); } diff --git a/src/renderer/components/dock/edit-resource.tsx b/src/renderer/components/dock/edit-resource.tsx index a12553c954..667a9a4667 100644 --- a/src/renderer/components/dock/edit-resource.tsx +++ b/src/renderer/components/dock/edit-resource.tsx @@ -21,13 +21,14 @@ import React from "react"; import { observer } from "mobx-react"; -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import { editResourceStore } from "./edit-resource.store"; import { InfoPanel, InfoPanelProps } from "./info-panel"; import { Badge } from "../badge"; import type { KubeObject } from "../../../common/k8s-api/kube-object"; import { TabKind } from "./dock.store"; import { dockViewsManager } from "./dock.views-manager"; +import { createPatch } from "rfc6902"; interface Props extends InfoPanelProps { } @@ -42,50 +43,43 @@ export class EditResourceInfoPanel extends React.Component { return editResourceStore.getResource(this.tabId); } - @computed get draft(): string { - if (!this.isReadyForEditing) { - return ""; // wait until tab's data and kube-object resource are loaded - } - - const { draft } = editResourceStore.getData(this.tabId); - - if (typeof draft === "string") { - return draft; - } - - return yaml.dump(this.resource.toPlainObject()); // dump resource first time + get draft(): string { + return editResourceStore.getData(this.tabId)?.draft; } - @action - saveDraft(draft: string | object) { + private saveDraft(draft: string | KubeObject) { if (typeof draft === "object") { - draft = draft ? yaml.dump(draft) : undefined; + draft = draft ? yaml.dump(draft.toPlainObject()) : undefined; } - editResourceStore.setData(this.tabId, { - firstDraft: draft, // this must be before the next line - ...editResourceStore.getData(this.tabId), - draft, - }); + editResourceStore.getData(this.tabId).draft = draft; } - onChange = (draft: string, error?: string) => { - this.error = error; - this.saveDraft(draft); - }; - save = async () => { - if (this.error) { - return null; + const store = editResourceStore.getStore(this.tabId); + const { resource: resourcePath, draft } = editResourceStore.getData(this.tabId); + const resource = store.getByPath(resourcePath) ?? await store.loadFromPath(resourcePath); + const currentVersion = resource.toPlainObject(); + const editedVersion = yaml.load(draft) as KubeObject; + const patches = createPatch(currentVersion, editedVersion); + const editingVersion = editedVersion.metadata.resourceVersion; + + if (editingVersion != resource.getResourceVersion()) { + throw ( +
+

+ {resource.kind} version {resource.getName()} is updated on the server while editing. +

+

+ Please backup your changes and this.saveDraft(resource)}>refresh resource to the editor. +

+
+ ); } - const store = editResourceStore.getStore(this.tabId); - const currentVersion = yaml.load(this.draft); - const firstVersion = yaml.load(editResourceStore.getData(this.tabId).firstDraft ?? this.draft); - const patches = createPatch(firstVersion, currentVersion); const updatedResource = await store.patch(this.resource, patches); - editResourceStore.clearInitialDraft(this.tabId); + this.saveDraft(updatedResource); // dump latest resource version for editing return (

diff --git a/src/renderer/components/dock/info-panel.tsx b/src/renderer/components/dock/info-panel.tsx index 2fa761f1fc..5cb16d0e62 100644 --- a/src/renderer/components/dock/info-panel.tsx +++ b/src/renderer/components/dock/info-panel.tsx @@ -23,7 +23,7 @@ import "./info-panel.scss"; import React, { Component, ReactNode } from "react"; import { makeObservable, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import { cssNames } from "../../utils"; +import { cssNames, isReactNode } from "../../utils"; import { Button } from "../button"; import { Icon } from "../icon"; import { Spinner } from "../spinner"; @@ -85,7 +85,9 @@ export class InfoPanel extends Component { if (showNotifications) Notifications.ok(result); } catch (error) { - if (showNotifications) Notifications.error(error.toString()); + const errorMessage = isReactNode(error) ? error : String(error); + + if (showNotifications) Notifications.error(errorMessage); } finally { this.waiting = false; } @@ -126,12 +128,12 @@ export class InfoPanel extends Component { {showStatusPanel && (

- {waiting ? <> {submittingMessage} : this.renderErrorIcon()} + {waiting ? <> {submittingMessage} : this.renderErrorIcon()}
)} {showButtons && ( <> -