1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

refactoring dock-tab stores -- part 2

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-10-04 16:07:49 +03:00
parent 5c41b8612e
commit 5038267bbc
13 changed files with 188 additions and 214 deletions

View File

@ -23,10 +23,10 @@ import fs from "fs-extra";
import path from "path"; import path from "path";
import os from "os"; import os from "os";
import { autoBind } from "../../utils"; import { autoBind } from "../../utils";
import { DockTabsStore } from "./dock-tabs.store"; import { DockTabStore } from "./dock-tab.store";
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store"; import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
export class CreateResourceStore extends DockTabsStore<string> { export class CreateResourceStore extends DockTabStore<string> {
constructor() { constructor() {
super({ super({
storageKey: "create_resource" storageKey: "create_resource"
@ -43,8 +43,8 @@ export class CreateResourceStore extends DockTabsStore<string> {
return path.resolve(os.homedir(), "~/.k8slens/templates"); return path.resolve(os.homedir(), "~/.k8slens/templates");
} }
async getBundledTemplate(fileName: string, ext = "yaml") { async getTemplate(fileName: string, ext = "yaml") {
return import(`../../../../templates/create-resource/${fileName}.${ext}`); return import(`../../../../templates/create-resource/${fileName}.${ext}?raw`);
} }
} }

View File

@ -51,7 +51,7 @@ export class CreateResource extends React.Component<Props> {
} }
// TODO // TODO
@computed get selectTemplateOptions(): SelectOption<string>[] { @computed get templateOptions(): SelectOption<string>[] {
return []; return [];
} }
@ -127,7 +127,7 @@ export class CreateResource extends React.Component<Props> {
autoConvertOptions={false} autoConvertOptions={false}
className="SelectResourceTemplate" className="SelectResourceTemplate"
placeholder="Select Template ..." placeholder="Select Template ..."
options={this.selectTemplateOptions} options={this.templateOptions}
menuPlacement="top" menuPlacement="top"
themeName="outlined" themeName="outlined"
onChange={v => this.onSelectTemplate(v)} onChange={v => this.onSelectTemplate(v)}

View File

@ -28,18 +28,20 @@ export interface DockTabStoreOptions {
storageKey?: string; // save data to persistent storage under the key storageKey?: string; // save data to persistent storage under the key
} }
export class DockTabsStore<T extends {}> { export class DockTabStore<T extends {}> {
@observable private _data: Record<TabId, T> = {}; @observable private _data: Record<TabId, T> = {};
@observable.ref private storage?: StorageHelper<Record<TabId, T>>; // available only with `options.storageKey` @observable.ref private storage?: StorageHelper<Record<TabId, T>>; // available only with `options.storageKey`
@observable dataReady = false; // dock-tab's data ready to interact, e.g. start editing resource
protected watchers = observable.map<TabId, IReactionDisposer | Disposer>(); protected watchers = observable.map<TabId, IReactionDisposer | Disposer>();
protected dispose = disposer(); protected dispose = disposer();
@computed get data() { @computed get data() {
if (this.options.storageKey) { if (this.options.storageKey) {
return this.storage.get(); return this.storage.get();
} else {
return this._data;
} }
return this._data;
} }
set data(value: Record<TabId, T>) { set data(value: Record<TabId, T>) {
@ -60,7 +62,7 @@ export class DockTabsStore<T extends {}> {
}; };
if (this.options.storageKey) { if (this.options.storageKey) {
this.storage = createStorage(this.options.storageKey, {}); // starts preloading json file via Node.fs right away this.storage = createStorage(this.options.storageKey, this._data);
} }
if (this.options.autoInit) { if (this.options.autoInit) {
@ -69,13 +71,15 @@ export class DockTabsStore<T extends {}> {
} }
get whenReady(): Promise<any> { get whenReady(): Promise<any> {
return Promise.allSettled([ return Promise.all([
dockStore.whenReady, dockStore.whenReady,
this.storage?.whenReady, this.storage?.whenReady,
]); ]);
} }
protected init() { protected init() {
this.dataReady = true;
this.dispose.push( this.dispose.push(
autorun(() => { autorun(() => {
const docTabIds = dockStore.tabs.map(tab => tab.id); const docTabIds = dockStore.tabs.map(tab => tab.id);
@ -110,16 +114,12 @@ export class DockTabsStore<T extends {}> {
return reaction(() => this.data[tabId], callback, opts); return reaction(() => this.data[tabId], callback, opts);
} }
isReady(tabId: TabId): boolean { getData(tabId: TabId): T {
return Boolean(this.getData(tabId) !== undefined); return this.data[tabId] ?? {} as T;
}
getData(tabId: TabId) {
return this.data[tabId];
} }
setData(tabId: TabId, data: T) { setData(tabId: TabId, data: T) {
this.data[tabId] = data; this.data[tabId] = { ...data };
} }
clearData(tabId: TabId) { clearData(tabId: TabId) {
@ -131,6 +131,7 @@ export class DockTabsStore<T extends {}> {
} }
destroy() { destroy() {
this.dataReady = false;
this.dispose(); this.dispose();
this.disposeWatchers(); this.disposeWatchers();
this.reset(); this.reset();

View File

@ -96,7 +96,7 @@ export interface DockTabChangeEvent {
export interface DockTabChangeEventOptions extends IReactionOptions { export interface DockTabChangeEventOptions extends IReactionOptions {
kind?: TabKind; // filter: matching by dockStore.selectedTab.kind kind?: TabKind; // filter: matching by dockStore.selectedTab.kind
isVisible?: boolean; // filter: dock and selected tab should be visible dockIsVisible?: boolean; // filter: dock and selected tab should be visible (default: true)
} }
export class DockStore implements DockStorageState { export class DockStore implements DockStorageState {
@ -190,11 +190,11 @@ export class DockStore implements DockStorageState {
} }
onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) { onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) {
const { kind, isVisible, ...reactionOpts } = options; const { kind, dockIsVisible = true, ...reactionOpts } = options;
return reaction(() => this.selectedTab, ((tab, prevTab) => { return reaction(() => this.selectedTab, ((tab, prevTab) => {
if (kind && kind !== tab.kind) return; if (kind && kind !== tab.kind) return;
if (isVisible && !dockStore.isOpen) return; if (dockIsVisible && !dockStore.isOpen) return;
callback({ callback({
tab, prevTab, tab, prevTab,

View File

@ -19,8 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { autoBind, noop } from "../../utils"; import { makeObservable } from "mobx";
import { DockTabsStore } from "./dock-tabs.store"; import { autoBind } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
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";
@ -31,39 +32,23 @@ export interface EditingResource {
draft?: string; // edited draft in yaml draft?: string; // edited draft in yaml
} }
export class EditResourceStore extends DockTabsStore<EditingResource> { export class EditResourceStore extends DockTabStore<EditingResource> {
constructor() { constructor() {
super({ super({
storageKey: "edit_resource_store", storageKey: "edit_resource_store",
}); });
autoBind(this); autoBind(this);
makeObservable(this);
} }
protected async init() { async loadResource(tabId: TabId) {
super.init(); const resource = this.getResource(tabId);
const store = this.getStore(tabId);
const data = this.getData(tabId);
this.dispose.push( if (!resource && store && data) {
dockStore.onTabChange(({ selectedTabId }) => { await store.loadFromPath(data.resource); // preloading resource for the first time
const { resource } = this.getData(selectedTabId); }
const store = apiManager.getStore(resource);
if (!store?.getByPath(resource)) {
store?.loadFromPath(resource).catch(noop); // preload resource by uri
}
}, {
fireImmediately: true,
})
);
}
protected finalizeDataForSave({ draft, ...data }: EditingResource): EditingResource {
return data; // skip saving draft to local-storage
}
isReady(tabId: TabId) {
const tabDataReady = super.isReady(tabId);
return Boolean(tabDataReady && this.getResource(tabId)); // ready to edit resource
} }
getStore(tabId: TabId): KubeObjectStore<KubeObject> | undefined { getStore(tabId: TabId): KubeObjectStore<KubeObject> | undefined {
@ -74,8 +59,8 @@ export class EditResourceStore extends DockTabsStore<EditingResource> {
return this.getStore(tabId)?.getByPath(this.getResourcePath(tabId)); return this.getStore(tabId)?.getByPath(this.getResourcePath(tabId));
} }
getResourcePath(tabId: TabId): string | undefined { getResourcePath(tabId: TabId): string {
return this.getData(tabId)?.resource; return this.getData(tabId)?.resource ?? "";
} }
getTabByResource(object: KubeObject): DockTab { getTabByResource(object: KubeObject): DockTab {

View File

@ -22,8 +22,8 @@
import "./edit-resource.scss"; import "./edit-resource.scss";
import React from "react"; import React from "react";
import { action, computed, makeObservable, observable } from "mobx"; import { action, autorun, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import jsYaml from "js-yaml"; import jsYaml from "js-yaml";
import type { DockTab } from "./dock.store"; import type { DockTab } from "./dock.store";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
@ -46,32 +46,32 @@ export class EditResource extends React.Component<Props> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
makeObservable(this); makeObservable(this);
disposeOnUnmount(this, [
autorun(() => editResourceStore.loadResource(this.tabId)),
]);
} }
get tabId() { get tabId() {
return this.props.tab.id; return this.props.tab.id;
} }
get isReadyForEditing() {
return editResourceStore.isReady(this.tabId);
}
get resource(): KubeObject | undefined { get resource(): KubeObject | undefined {
return editResourceStore.getResource(this.tabId); return editResourceStore.getResource(this.tabId);
} }
@computed get draft(): string { get draft(): string {
if (!this.isReadyForEditing) {
return ""; // wait until tab's data and kube-object resource are loaded
}
const { draft } = editResourceStore.getData(this.tabId); const { draft } = editResourceStore.getData(this.tabId);
if (typeof draft === "string") { if (typeof draft === "string") {
return draft; return draft; // get previously edited draft
} }
return jsYaml.safeDump(this.resource.toPlainObject()); // dump resource first time if (this.resource) {
return jsYaml.safeDump(this.resource.toPlainObject()); // dump resource first time
}
return "";
} }
@action @action
@ -113,12 +113,12 @@ export class EditResource extends React.Component<Props> {
}; };
render() { render() {
const { tabId, error, draft, isReadyForEditing, resource } = this; if (!editResourceStore.dataReady || !this.resource) {
if (!isReadyForEditing) {
return <Spinner center/>; return <Spinner center/>;
} }
const { tabId, error, draft, resource } = this;
return ( return (
<div className={cssNames("EditResource flex column", this.props.className)}> <div className={cssNames("EditResource flex column", this.props.className)}>
<InfoPanel <InfoPanel

View File

@ -21,10 +21,9 @@
import { action, makeObservable, observable } from "mobx"; import { action, makeObservable, observable } from "mobx";
import { dockStore, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { dockStore, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
import { DockTabsStore } from "./dock-tabs.store"; import { DockTabStore } from "./dock-tab.store";
import { getChartDetails, getChartValues, HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api"; import { getChartDetails, getChartValues, HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
import type { IReleaseUpdateDetails } from "../../../common/k8s-api/endpoints/helm-releases.api"; import type { IReleaseUpdateDetails } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { Notifications } from "../notifications";
export interface IChartInstallData { export interface IChartInstallData {
name: string; name: string;
@ -37,7 +36,7 @@ export interface IChartInstallData {
lastVersion?: boolean; lastVersion?: boolean;
} }
export class InstallChartStore extends DockTabsStore<IChartInstallData> { export class InstallChartStore extends DockTabStore<IChartInstallData> {
details = observable.map<TabId, IReleaseUpdateDetails>(); details = observable.map<TabId, IReleaseUpdateDetails>();
versions = observable.map<TabId, string[]>(); versions = observable.map<TabId, string[]>();
@ -48,22 +47,8 @@ export class InstallChartStore extends DockTabsStore<IChartInstallData> {
makeObservable(this); makeObservable(this);
} }
protected init() {
super.init();
this.dispose.push(
dockStore.onTabChange(({ selectedTabId }) => {
this.loadData(selectedTabId).catch(err => Notifications.error(String(err)));
}, {
kind: TabKind.INSTALL_CHART,
fireImmediately: true,
isVisible: true,
}),
);
}
@action @action
async loadData(tabId: string) { async loadData(tabId: TabId) {
const promises = []; const promises = [];
if (!this.getData(tabId).values) { if (!this.getData(tabId).values) {

View File

@ -22,8 +22,8 @@
import "./install-chart.scss"; import "./install-chart.scss";
import React, { Component } from "react"; import React, { Component } from "react";
import { makeObservable, observable } from "mobx"; import { autorun, computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { dockStore, DockTab } from "./dock.store"; import { dockStore, DockTab } from "./dock.store";
import { InfoPanel } from "./info-panel"; import { InfoPanel } from "./info-panel";
import { Badge } from "../badge"; import { Badge } from "../badge";
@ -53,20 +53,20 @@ export class InstallChart extends Component<Props> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
makeObservable(this); makeObservable(this);
}
get values() { disposeOnUnmount(this, [
return this.chartData.values; autorun(() => installChartStore.loadData(this.tabId)),
} ]);
get chartData() {
return installChartStore.getData(this.tabId);
} }
get tabId() { get tabId() {
return this.props.tab.id; return this.props.tab.id;
} }
get chartData(): IChartInstallData | undefined {
return installChartStore.getData(this.tabId);
}
get versions() { get versions() {
return installChartStore.versions.get(this.tabId); return installChartStore.versions.get(this.tabId);
} }
@ -75,6 +75,10 @@ export class InstallChart extends Component<Props> {
return installChartStore.details.get(this.tabId); return installChartStore.details.get(this.tabId);
} }
@computed get isReady() {
return [installChartStore.dataReady, this.versions].every(Boolean);
}
viewRelease = () => { viewRelease = () => {
const { release } = this.releaseDetails; const { release } = this.releaseDetails;
@ -127,42 +131,45 @@ export class InstallChart extends Component<Props> {
); );
}; };
render() { renderReleaseDetails(): React.ReactNode {
const { tabId, chartData, values, versions, install } = this; return (
<div className="InstallChartDone flex column gaps align-center justify-center">
<p>
<Icon material="check" big sticker/>
</p>
<p>Installation complete!</p>
<div className="flex gaps align-center">
<Button
autoFocus primary
label="View Helm Release"
onClick={prevDefault(this.viewRelease)}
/>
<Button
plain active
label="Show Notes"
onClick={() => this.showNotes = true}
/>
</div>
<LogsDialog
title="Helm Chart Install"
isOpen={this.showNotes}
close={() => this.showNotes = false}
logs={this.releaseDetails.log}
/>
</div>
);
}
if (chartData?.values === undefined || !versions) { render() {
if (!this.isReady) {
return <Spinner center/>; return <Spinner center/>;
} }
if (this.releaseDetails) { if (this.releaseDetails) {
return ( return this.renderReleaseDetails();
<div className="InstallChartDone flex column gaps align-center justify-center">
<p>
<Icon material="check" big sticker/>
</p>
<p>Installation complete!</p>
<div className="flex gaps align-center">
<Button
autoFocus primary
label="View Helm Release"
onClick={prevDefault(this.viewRelease)}
/>
<Button
plain active
label="Show Notes"
onClick={() => this.showNotes = true}
/>
</div>
<LogsDialog
title="Helm Chart Install"
isOpen={this.showNotes}
close={() => this.showNotes = false}
logs={this.releaseDetails.log}
/>
</div>
);
} }
const { tabId, chartData, install, versions } = this;
const { repo, name, version, namespace, releaseName } = chartData; const { repo, name, version, namespace, releaseName } = chartData;
const panelControls = ( const panelControls = (
<div className="install-controls flex gaps align-center"> <div className="install-controls flex gaps align-center">
@ -208,7 +215,7 @@ export class InstallChart extends Component<Props> {
/> />
<MonacoEditor <MonacoEditor
id={tabId} id={tabId}
value={values} value={chartData.values}
onChange={this.onValuesChange} onChange={this.onValuesChange}
onError={error => this.error = error} onError={error => this.error = error}
/> />

View File

@ -25,7 +25,7 @@ import { podsStore } from "../+workloads-pods/pods.store";
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints"; import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object"; import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object";
import { DockTabsStore } from "./dock-tabs.store"; import { DockTabStore } from "./dock-tab.store";
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store"; import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
export interface LogTabData { export interface LogTabData {
@ -45,7 +45,7 @@ interface WorkloadLogsTabData {
workload: WorkloadKubeObject workload: WorkloadKubeObject
} }
export class LogTabStore extends DockTabsStore<LogTabData> { export class LogTabStore extends DockTabStore<LogTabData> {
constructor() { constructor() {
super({ super({
storageKey: "pod_logs" storageKey: "pod_logs"

View File

@ -19,19 +19,22 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { action, makeObservable, observable, when } from "mobx"; import { action, makeObservable, observable } from "mobx";
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
import { DockTabsStore } from "./dock-tabs.store"; import { DockTabStore } from "./dock-tab.store";
import { getReleaseValues, HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api"; import { getReleaseValues, HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { releaseStore } from "../+apps-releases/release.store"; import { releaseStore } from "../+apps-releases/release.store";
import type { IChartVersion } from "../+apps-helm-charts/helm-chart.store";
import { helmChartStore } from "../+apps-helm-charts/helm-chart.store";
export interface IChartUpgradeData { export interface IChartUpgradeData {
releaseName: string; releaseName: string;
releaseNamespace: string; releaseNamespace: string;
} }
export class UpgradeChartStore extends DockTabsStore<IChartUpgradeData> { export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
public values = observable.map<TabId, string>(); public values = observable.map<TabId, string>();
public versions = observable.array<IChartVersion>();
constructor() { constructor() {
super({ super({
@ -41,64 +44,65 @@ export class UpgradeChartStore extends DockTabsStore<IChartUpgradeData> {
makeObservable(this); makeObservable(this);
} }
get whenReady() {
return Promise.all([
super.whenReady,
when(() => releaseStore.isLoaded),
]);
}
protected init() { protected init() {
super.init(); super.init();
releaseStore.loadFromContextNamespaces();
this.dispose.push(
dockStore.onTabChange(({ selectedTabId }) => {
if (!this.isLoaded(selectedTabId)) {
this.loadData(selectedTabId); // preload just once
}
}, {
kind: TabKind.UPGRADE_CHART,
isVisible: true,
fireImmediately: true,
}),
);
} }
isLoading(tabId = dockStore.selectedTabId) { hasValues(tabId: TabId) {
const values = this.values.get(tabId);
return !releaseStore.isLoaded || values === undefined;
}
isLoaded(tabId: TabId) {
return this.values.has(tabId); return this.values.has(tabId);
} }
@action async loadData(tabId: TabId) {
private async loadData(tabId: TabId) {
const values = this.values.get(tabId);
await Promise.all([ await Promise.all([
!releaseStore.isLoaded && releaseStore.loadFromContextNamespaces(), this.loadValues(tabId),
!values && this.reloadValues(tabId) this.loadVersions(tabId),
]); ]);
} }
@action getRelease(tabId: TabId) {
private async reloadValues(tabId: TabId) { const releaseName = this.getData(tabId)?.releaseName;
this.values.delete(tabId);
const { releaseName, releaseNamespace } = this.getData(tabId);
const values = await getReleaseValues(releaseName, releaseNamespace, true);
this.values.set(tabId, values); if (!releaseName) return null;
return releaseStore.getByName(releaseName);
} }
getTabByRelease(releaseName: string): DockTab { @action
const entry = Object async loadVersions(tabId: TabId) {
.entries(this.data) try {
.find(([, data]) => data.releaseName === releaseName); const { releaseName } = this.getData(tabId);
return dockStore.getTabById(entry?.[0]); this.versions.clear();
const versions = await helmChartStore.getVersions(releaseName);
this.versions.replace(versions);
} catch (error) {
console.error(`[UPGRADE-CHART]: loading versions has failed: ${error}`);
}
}
@action
async loadValues(tabId: TabId, { forceReload = false } = {}) {
if (this.hasValues(tabId) && !forceReload) {
return;
}
try {
this.values.delete(tabId);
const { releaseName, releaseNamespace } = this.getData(tabId);
const values = await getReleaseValues(releaseName, releaseNamespace, true);
this.values.set(tabId, values);
} catch (error) {
console.error(`[UPGRADE-CHART]: loading values has failed: ${error}`);
}
}
getTabByRelease(releaseName: string): DockTab | null {
const entry = Object.entries(this.data).find(([, data]) => data.releaseName === releaseName);
return entry ? dockStore.getTabById(entry[0]) : null;
} }
} }

View File

@ -22,7 +22,7 @@
import "./upgrade-chart.scss"; import "./upgrade-chart.scss";
import React from "react"; import React from "react";
import { makeObservable, observable, reaction } from "mobx"; import { autorun, computed, makeObservable, observable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import type { DockTab } from "./dock.store"; import type { DockTab } from "./dock.store";
@ -32,9 +32,9 @@ import { Spinner } from "../spinner";
import { releaseStore } from "../+apps-releases/release.store"; import { releaseStore } from "../+apps-releases/release.store";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { MonacoEditor } from "../monaco-editor"; import { MonacoEditor } from "../monaco-editor";
import { helmChartStore, IChartVersion } from "../+apps-helm-charts/helm-chart.store";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { Select, SelectOption } from "../select"; import { Select, SelectOption } from "../select";
import type { IChartVersion } from "../+apps-helm-charts/helm-chart.store";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
interface Props { interface Props {
className?: string; className?: string;
@ -44,19 +44,14 @@ interface Props {
@observer @observer
export class UpgradeChart extends React.Component<Props> { export class UpgradeChart extends React.Component<Props> {
@observable error: string; @observable error: string;
@observable versions = observable.array<IChartVersion>(); @observable selectedVersion: IChartVersion;
@observable version: IChartVersion;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
makeObservable(this); makeObservable(this);
}
componentDidMount() {
this.loadVersions();
disposeOnUnmount(this, [ disposeOnUnmount(this, [
reaction(() => this.release, () => this.loadVersions()) autorun(() => upgradeChartStore.loadData(this.tabId)),
]); ]);
} }
@ -64,28 +59,26 @@ export class UpgradeChart extends React.Component<Props> {
return this.props.tab.id; return this.props.tab.id;
} }
get release(): HelmRelease { get release(): HelmRelease | null {
const tabData = upgradeChartStore.getData(this.tabId); return upgradeChartStore.getRelease(this.tabId);
}
if (!tabData) return null; get isReady(){
return [
upgradeChartStore.dataReady,
this.release,
this.selectedVersion,
].every(Boolean);
}
return releaseStore.getByName(tabData.releaseName); @computed get versionOptions(): SelectOption<IChartVersion>[] {
return upgradeChartStore.versions.map(version => ({ value: version }));
} }
get value() { get value() {
return upgradeChartStore.values.get(this.tabId); return upgradeChartStore.values.get(this.tabId);
} }
async loadVersions() {
if (!this.release) return;
this.version = null;
this.versions.clear();
const versions = await helmChartStore.getVersions(this.release.getChart());
this.versions.replace(versions);
this.version = this.versions[0];
}
onChange = (value: string) => { onChange = (value: string) => {
upgradeChartStore.values.set(this.tabId, value); upgradeChartStore.values.set(this.tabId, value);
}; };
@ -96,7 +89,7 @@ export class UpgradeChart extends React.Component<Props> {
upgrade = async () => { upgrade = async () => {
if (this.error) return null; if (this.error) return null;
const { version, repo } = this.version; const { version, repo } = this.selectedVersion;
const releaseName = this.release.getName(); const releaseName = this.release.getName();
const releaseNs = this.release.getNs(); const releaseNs = this.release.getNs();
@ -121,13 +114,14 @@ export class UpgradeChart extends React.Component<Props> {
}; };
render() { render() {
const { tabId, release, value, error, versions, version } = this; if (!this.isReady) {
const { className } = this.props;
if (!release || upgradeChartStore.isLoading() || !version) {
return <Spinner center/>; return <Spinner center/>;
} }
const { className } = this.props;
const { tabId, release, value, error, versionOptions, selectedVersion } = this;
const currentVersion = release.getVersion(); const currentVersion = release.getVersion();
const controlsAndInfo = ( const controlsAndInfo = (
<div className="upgrade flex gaps align-center"> <div className="upgrade flex gaps align-center">
<span>Release</span> <Badge label={release.getName()}/> <span>Release</span> <Badge label={release.getName()}/>
@ -138,10 +132,10 @@ export class UpgradeChart extends React.Component<Props> {
className="chart-version" className="chart-version"
menuPlacement="top" menuPlacement="top"
themeName="outlined" themeName="outlined"
value={version} value={selectedVersion}
options={versions} options={versionOptions}
formatOptionLabel={this.formatVersionLabel} formatOptionLabel={this.formatVersionLabel}
onChange={({ value }: SelectOption) => this.version = value} onChange={({ value }: SelectOption) => this.selectedVersion = value}
/> />
</div> </div>
); );

View File

@ -21,7 +21,7 @@
import "./monaco-editor.scss"; import "./monaco-editor.scss";
import React from "react"; import React from "react";
import { action, computed, makeObservable, observable, reaction, when } from "mobx"; import { action, computed, makeObservable, observable, reaction } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { editor, Uri } from "monaco-editor"; import { editor, Uri } from "monaco-editor";
import { ThemeStore } from "../../theme.store"; import { ThemeStore } from "../../theme.store";
@ -65,7 +65,6 @@ export const defaultEditorProps: Partial<MonacoEditorProps> = {
} }
}; };
// FIXME: load resource templates via webpack's dynamic import for create-resource dock tab
// FIXME: apply changes of props.options and globalOptions.editor to active editor // FIXME: apply changes of props.options and globalOptions.editor to active editor
@observer @observer
@ -86,14 +85,6 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
makeObservable(this); makeObservable(this);
} }
get whenEditorReady() {
return when(() => Boolean(this.containerElem && this.editor));
}
get whenUnmounting() {
return when(() => this.unmounting);
}
/** /**
* Monitor editor's dom container element box-size and sync with monaco's dimensions * Monitor editor's dom container element box-size and sync with monaco's dimensions
* @private * @private
@ -141,7 +132,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
} }
console.info(`[MONACO]: editor did mounted`); console.info(`[MONACO]: editor did mounted`);
} catch (error) { } catch (error) {
console.error(`[MONACO]: mounting failed`, { error, editor: this }); console.error(`[MONACO]: mounting failed`, { error, editor, component: this });
} }
} }

View File

@ -88,6 +88,13 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
}, },
module: { module: {
rules: [ rules: [
{
// Allow to load raw file contents with dynamic imports, e.g. import("../some-path.txt?raw")
// See also: https://webpack.js.org/guides/asset-modules/
use: "raw-loader",
resourceQuery: /raw/,
enforce: "post",
},
{ {
test: /\.node$/, test: /\.node$/,
use: "node-loader" use: "node-loader"