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:
parent
5c41b8612e
commit
5038267bbc
@ -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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)}
|
||||||
|
|||||||
@ -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();
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user