mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
master-merge fixes, fine-tuning
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
d6b7001f72
commit
59357d0a69
@ -30,6 +30,7 @@ import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-
|
|||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { TabKind } from "./dock.store";
|
import { TabKind } from "./dock.store";
|
||||||
import { dockViewsManager } from "./dock.views-manager";
|
import { dockViewsManager } from "./dock.views-manager";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends InfoPanelProps {
|
interface Props extends InfoPanelProps {
|
||||||
}
|
}
|
||||||
@ -58,13 +59,13 @@ export class CreateResourceInfoPanel extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create = async (): Promise<undefined> => {
|
create = async (): Promise<undefined> => {
|
||||||
if (this.error || !this.data.trim()) {
|
if (!this.draft) {
|
||||||
// do not save when field is empty or there is an error
|
// do not save when field is empty or there is an error
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip empty documents
|
// skip empty documents
|
||||||
const resources = yaml.loadAll(this.data).filter(Boolean);
|
const resources = yaml.loadAll(this.draft).filter(Boolean);
|
||||||
const createdResources: string[] = [];
|
const createdResources: string[] = [];
|
||||||
|
|
||||||
if (resources.length === 0) {
|
if (resources.length === 0) {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./doc
|
|||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||||
import jsYaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
|
|
||||||
export interface EditingResource {
|
export interface EditingResource {
|
||||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||||
@ -61,7 +61,7 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
|||||||
let resource = this.getResource(tabId);
|
let resource = this.getResource(tabId);
|
||||||
|
|
||||||
resource ??= await store.loadFromPath(data.resource);
|
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) {
|
} catch (error) {
|
||||||
console.error(`[DOCK]: dumping '${data.resource}' has failed: ${error}`, { store, data });
|
console.error(`[DOCK]: dumping '${data.resource}' has failed: ${error}`, { store, data });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,13 +21,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import jsYaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { editResourceStore } from "./edit-resource.store";
|
import { editResourceStore } from "./edit-resource.store";
|
||||||
import { InfoPanel, InfoPanelProps } from "./info-panel";
|
import { InfoPanel, InfoPanelProps } from "./info-panel";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { TabKind } from "./dock.store";
|
import { TabKind } from "./dock.store";
|
||||||
import { dockViewsManager } from "./dock.views-manager";
|
import { dockViewsManager } from "./dock.views-manager";
|
||||||
|
import { createPatch } from "rfc6902";
|
||||||
|
|
||||||
interface Props extends InfoPanelProps {
|
interface Props extends InfoPanelProps {
|
||||||
}
|
}
|
||||||
@ -42,50 +43,43 @@ export class EditResourceInfoPanel extends React.Component<Props> {
|
|||||||
return editResourceStore.getResource(this.tabId);
|
return editResourceStore.getResource(this.tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get draft(): string {
|
get draft(): string {
|
||||||
if (!this.isReadyForEditing) {
|
return editResourceStore.getData(this.tabId)?.draft;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
private saveDraft(draft: string | KubeObject) {
|
||||||
saveDraft(draft: string | object) {
|
|
||||||
if (typeof draft === "object") {
|
if (typeof draft === "object") {
|
||||||
draft = draft ? yaml.dump(draft) : undefined;
|
draft = draft ? yaml.dump(draft.toPlainObject()) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
editResourceStore.setData(this.tabId, {
|
editResourceStore.getData(this.tabId).draft = draft;
|
||||||
firstDraft: draft, // this must be before the next line
|
|
||||||
...editResourceStore.getData(this.tabId),
|
|
||||||
draft,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (draft: string, error?: string) => {
|
|
||||||
this.error = error;
|
|
||||||
this.saveDraft(draft);
|
|
||||||
};
|
|
||||||
|
|
||||||
save = async () => {
|
save = async () => {
|
||||||
if (this.error) {
|
const store = editResourceStore.getStore(this.tabId);
|
||||||
return null;
|
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 (
|
||||||
|
<div className="resource-outdated flex column gaps">
|
||||||
|
<p>
|
||||||
|
{resource.kind} version <b>{resource.getName()}</b> is updated on the server while editing.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please backup your changes and <a onClick={() => this.saveDraft(resource)}>refresh resource</a> to the editor.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
const updatedResource = await store.patch(this.resource, patches);
|
||||||
|
|
||||||
editResourceStore.clearInitialDraft(this.tabId);
|
this.saveDraft(updatedResource); // dump latest resource version for editing
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import "./info-panel.scss";
|
|||||||
import React, { Component, ReactNode } from "react";
|
import React, { Component, ReactNode } from "react";
|
||||||
import { makeObservable, observable, reaction } from "mobx";
|
import { makeObservable, observable, reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames, isReactNode } from "../../utils";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
@ -85,7 +85,9 @@ export class InfoPanel extends Component<InfoPanelProps> {
|
|||||||
|
|
||||||
if (showNotifications) Notifications.ok(result);
|
if (showNotifications) Notifications.ok(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (showNotifications) Notifications.error(error.toString());
|
const errorMessage = isReactNode(error) ? error : String(error);
|
||||||
|
|
||||||
|
if (showNotifications) Notifications.error(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
this.waiting = false;
|
this.waiting = false;
|
||||||
}
|
}
|
||||||
@ -126,12 +128,12 @@ export class InfoPanel extends Component<InfoPanelProps> {
|
|||||||
</div>
|
</div>
|
||||||
{showStatusPanel && (
|
{showStatusPanel && (
|
||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
{waiting ? <><Spinner /> {submittingMessage}</> : this.renderErrorIcon()}
|
{waiting ? <><Spinner/> {submittingMessage}</> : this.renderErrorIcon()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showButtons && (
|
{showButtons && (
|
||||||
<>
|
<>
|
||||||
<Button plain label="Cancel" onClick={close} />
|
<Button plain label="Cancel" onClick={close}/>
|
||||||
<Button
|
<Button
|
||||||
active
|
active
|
||||||
outlined={showSubmitClose}
|
outlined={showSubmitClose}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ registerCustomThemes(); // setup
|
|||||||
|
|
||||||
export interface MonacoEditorProps {
|
export interface MonacoEditorProps {
|
||||||
id?: string; // associating editor's ID with created model.uri
|
id?: string; // associating editor's ID with created model.uri
|
||||||
value?: string; // initial text value for editor
|
value?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
||||||
import { action, comparer, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction, toJS, when, } from "mobx";
|
import { action, comparer, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction, toJS, when, } from "mobx";
|
||||||
import produce, { Draft, isDraft } from "immer";
|
import produce, { Draft, isDraft } from "immer";
|
||||||
import { isEqual } from "lodash";
|
import { isEqual, isPlainObject } from "lodash";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { disposer } from "../../common/utils";
|
import { disposer } from "../../common/utils";
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ export class StorageHelper<T> {
|
|||||||
if (newValue && !isDraft(newValue)) {
|
if (newValue && !isDraft(newValue)) {
|
||||||
Object.assign(draft, newValue);
|
Object.assign(draft, newValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (isPlainObject(value)) {
|
||||||
Object.assign(draft, value);
|
Object.assign(draft, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user