cleanup Lens repo with tighter linting

Signed-off-by: Sebastian Malton <smalton@mirantis.com>
This commit is contained in:
Sebastian Malton committed 2020-07-09 17:00:23 -04:00
1 parent e468105143
commit b1ff34879a
470 files changed
+8447 -7443

No files matched your search

@@ -1,6 +1,6 @@
import { autobind } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
import { dockStore, DockTabData, TabKind } from "./dock.store";
@autobind()
export class CreateResourceStore extends DockTabStore<string> {
@@ -13,7 +13,7 @@ export class CreateResourceStore extends DockTabStore<string> {
export const createResourceStore = new CreateResourceStore();
export function createResourceTab(tabParams: Partial<IDockTab> = {}) {
export function createResourceTab(tabParams: Partial<DockTabData> = {}): DockTabData {
return dockStore.createTab({
kind: TabKind.CREATE_RESOURCE,
title: "Create resource",
@@ -21,6 +21,6 @@ export function createResourceTab(tabParams: Partial<IDockTab> = {}) {
});
}
export function isCreateResourceTab(tab: IDockTab) {
return tab && tab.kind === TabKind.CREATE_RESOURCE;
export function isCreateResourceTab(tab: DockTabData): boolean {
return tab?.kind === TabKind.CREATE_RESOURCE;
}
@@ -1,13 +1,13 @@
import "./create-resource.scss";
import React from "react";
import jsYaml from "js-yaml"
import jsYaml from "js-yaml";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { Plural, t, Trans } from "@lingui/macro";
import { cssNames } from "../../utils";
import { createResourceStore } from "./create-resource.store";
import { IDockTab } from "./dock.store";
import { DockTabData } from "./dock.store";
import { EditorPanel } from "./editor-panel";
import { InfoPanel } from "./info-panel";
import { resourceApplierApi } from "../../api/endpoints/resource-applier.api";
@@ -17,52 +17,56 @@ import { Notifications } from "../notifications";
interface Props {
className?: string;
tab: IDockTab;
tab: DockTabData;
}
@observer
export class CreateResource extends React.Component<Props> {
@observable error = ""
get tabId() {
return this.props.tab.id;
get tabId(): string {
return this.props.tab.id || "";
}
get data() {
get data(): string {
return createResourceStore.getData(this.tabId);
}
onChange = (value: string, error?: string) => {
onChange = (value: string, error?: string): void => {
createResourceStore.setData(this.tabId, value);
this.error = error;
}
create = async () => {
if (this.error) return;
create = async (): Promise<JSX.Element> => {
if (this.error) {
return;
}
const resources = jsYaml.safeLoadAll(this.data)
.filter(v => !!v) // skip empty documents if "---" pasted at the beginning or end
.filter(v => !!v); // skip empty documents if "---" pasted at the beginning or end
const createdResources: string[] = [];
const errors: string[] = [];
await Promise.all(
resources.map(data => {
return resourceApplierApi.update(data)
.then(item => createdResources.push(item.getName()))
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()))
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
})
);
if (errors.length) {
errors.forEach(Notifications.error);
if (!createdResources.length) throw errors[0];
if (!createdResources.length) {
throw errors[0];
}
}
return (
<p>
<Plural value={createdResources.length} one="Resource" other="Resources"/>{" "}
<Trans><b>{createdResources.join(", ")}</b> successfully created</Trans>
</p>
)
);
}
render() {
render(): JSX.Element {
const { tabId, data, error, create, onChange } = this;
const { className } = this.props;
return (
@@ -80,6 +84,6 @@ export class CreateResource extends React.Component<Props> {
showNotifications={false}
/>
</div>
)
);
}
}
@@ -1,5 +1,5 @@
import { autorun, observable, reaction } from "mobx";
import { autobind, createStorage } from "../../utils";
import { autobind, StorageHelper } from "../../utils";
import { dockStore, TabId } from "./dock.store";
interface Options<T = any> {
@@ -16,7 +16,7 @@ export class DockTabStore<T = any> {
// auto-save to local-storage
if (storageName) {
const storage = createStorage<[TabId, T][]>(storageName, []);
const storage = new StorageHelper<[TabId, T][]>(storageName, []);
this.data.replace(storage.get());
reaction(() => this.serializeData(), (data: T | any) => storage.set(data));
}
@@ -28,31 +28,34 @@ export class DockTabStore<T = any> {
if (!currentTabs.includes(tabId)) {
this.clearData(tabId);
}
})
});
});
}
protected serializeData() {
protected serializeData(): [string, Partial<T>][] {
const { storageSerializer } = this.options;
return Array.from(this.data).map(([tabId, tabData]) => {
if (storageSerializer) return [tabId, storageSerializer(tabData)]
return [tabId, tabData];
})
return Array.from(this.data)
.map(([tabId, tabData]) => {
if (storageSerializer) {
return [tabId, storageSerializer(tabData)];
}
return [tabId, tabData];
});
}
getData(tabId: TabId) {
getData(tabId: TabId): T {
return this.data.get(tabId);
}
setData(tabId: TabId, data: T) {
setData(tabId: TabId, data: T): void {
this.data.set(tabId, data);
}
clearData(tabId: TabId) {
clearData(tabId: TabId): void {
this.data.delete(tabId);
}
reset() {
reset(): void {
this.data.clear();
}
}
+10 -10
View File
@@ -1,30 +1,30 @@
import "./dock-tab.scss"
import "./dock-tab.scss";
import React from "react"
import React from "react";
import { observer } from "mobx-react";
import { t } from "@lingui/macro";
import { autobind, cssNames, prevDefault } from "../../utils";
import { dockStore, IDockTab } from "./dock.store";
import { dockStore, DockTabData } from "./dock.store";
import { Tab, TabProps } from "../tabs";
import { Icon } from "../icon";
import { _i18n } from "../../i18n";
export interface DockTabProps extends TabProps<IDockTab> {
export interface DockTabProps extends TabProps<DockTabData> {
moreActions?: React.ReactNode;
}
@observer
export class DockTab extends React.Component<DockTabProps> {
get tabId() {
return this.props.value.id;
get tabId(): string {
return this.props.value.id || "";
}
@autobind()
close() {
close(): void {
dockStore.closeTab(this.tabId);
}
render() {
render(): JSX.Element {
const { className, moreActions, ...tabProps } = this.props;
const { title, pinned } = tabProps.value;
const label = (
@@ -39,13 +39,13 @@ export class DockTab extends React.Component<DockTabProps> {
/>
)}
</div>
)
);
return (
<Tab
{...tabProps}
className={cssNames("DockTab", className, { pinned })}
label={label}
/>
)
);
}
}
+44 -34
View File
@@ -1,7 +1,7 @@
import MD5 from "crypto-js/md5";
import { action, computed, IReactionOptions, observable, reaction } from "mobx";
import { autobind, createStorage } from "../../utils";
import throttle from "lodash/throttle"
import { action, computed, IReactionOptions, observable, reaction, IReactionDisposer } from "mobx";
import { autobind, StorageHelper } from "../../utils";
import throttle from "lodash/throttle";
export type TabId = string;
@@ -13,7 +13,7 @@ export enum TabKind {
UPGRADE_CHART = "upgrade-chart",
}
export interface IDockTab {
export interface DockTabData {
id?: TabId;
kind: TabKind;
title?: string;
@@ -22,29 +22,29 @@ export interface IDockTab {
@autobind()
export class DockStore {
protected initialTabs: IDockTab[] = [
protected initialTabs: DockTabData[] = [
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" },
];
protected storage = createStorage("dock", {}); // keep settings in localStorage
protected storage = new StorageHelper("dock", {}); // keep settings in localStorage
public defaultTabId = this.initialTabs[0].id;
public minHeight = 100;
@observable isOpen = false;
@observable fullSize = false;
@observable height = this.defaultHeight;
@observable tabs = observable.array<IDockTab>(this.initialTabs);
@observable tabs = observable.array<DockTabData>(this.initialTabs);
@observable selectedTabId = this.defaultTabId;
@computed get selectedTab() {
@computed get selectedTab(): DockTabData {
return this.tabs.find(tab => tab.id === this.selectedTabId);
}
get defaultHeight() {
get defaultHeight(): number {
return Math.round(window.innerHeight / 2.5);
}
get maxHeight() {
get maxHeight(): number {
const mainLayoutHeader = 40;
const mainLayoutTabs = 33;
const mainLayoutMargin = 16;
@@ -69,7 +69,7 @@ export class DockStore {
window.addEventListener("resize", throttle(this.checkMaxHeight, 250));
}
protected checkMaxHeight() {
protected checkMaxHeight(): void {
if (!this.height) {
this.setHeight(this.defaultHeight || this.minHeight);
}
@@ -78,20 +78,20 @@ export class DockStore {
}
}
onResize(callback: () => void, options?: IReactionOptions) {
onResize(callback: () => void, options?: IReactionOptions): IReactionDisposer {
return reaction(() => [this.height, this.fullSize], callback, options);
}
onTabChange(callback: (tabId: TabId) => void, options?: IReactionOptions) {
onTabChange(callback: (tabId: TabId) => void, options?: IReactionOptions): IReactionDisposer {
return reaction(() => this.selectedTabId, callback, options);
}
hasTabs() {
hasTabs(): boolean {
return this.tabs.length > 0;
}
@action
open(fullSize?: boolean) {
open(fullSize?: boolean): void {
this.isOpen = true;
if (typeof fullSize === "boolean") {
this.fullSize = fullSize;
@@ -99,27 +99,32 @@ export class DockStore {
}
@action
close() {
close(): void {
this.isOpen = false;
}
@action
toggle() {
if (this.isOpen) this.close();
else this.open();
toggle(): void {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
@action
toggleFillSize() {
if (!this.isOpen) this.open();
toggleFillSize(): void {
if (!this.isOpen) {
this.open();
}
this.fullSize = !this.fullSize;
}
getTabById(tabId: TabId) {
getTabById(tabId: TabId): DockTabData {
return this.tabs.find(tab => tab.id === tabId);
}
protected getNewTabNumber(kind: TabKind) {
protected getNewTabNumber(kind: TabKind): number {
const tabNumbers = this.tabs
.filter(tab => tab.kind === kind)
.map(tab => {
@@ -127,17 +132,21 @@ export class DockStore {
return tabNumber === 0 ? 1 : tabNumber; // tab without a number is first
});
for (let i = 1; ; i++) {
if (!tabNumbers.includes(i)) return i;
if (!tabNumbers.includes(i)) {
return i;
}
}
}
@action
createTab(anonTab: IDockTab, addNumber = true): IDockTab {
createTab(anonTab: DockTabData, addNumber = true): DockTabData {
const tabId = MD5(Math.random().toString() + Date.now()).toString();
const tab: IDockTab = { id: tabId, ...anonTab };
const tab: DockTabData = { id: tabId, ...anonTab };
if (addNumber) {
const tabNumber = this.getNewTabNumber(tab.kind);
if (tabNumber > 1) tab.title += ` (${tabNumber})`
if (tabNumber > 1) {
tab.title += ` (${tabNumber})`;
}
}
this.tabs.push(tab);
this.selectTab(tab.id);
@@ -146,7 +155,7 @@ export class DockStore {
}
@action
async closeTab(tabId: TabId) {
async closeTab(tabId: TabId): Promise<void> {
const tab = this.getTabById(tabId);
if (!tab || tab.pinned) {
return;
@@ -158,11 +167,12 @@ export class DockStore {
if (newTab.kind === TabKind.TERMINAL) {
// close the dock when selected sibling inactive terminal tab
const { terminalStore } = await import("./terminal.store");
if (!terminalStore.isConnected(newTab.id)) this.close();
if (!terminalStore.isConnected(newTab.id)) {
this.close();
}
}
this.selectTab(newTab.id);
}
else {
} else {
this.selectedTabId = null;
this.close();
}
@@ -170,18 +180,18 @@ export class DockStore {
}
@action
selectTab(tabId: TabId) {
selectTab(tabId: TabId): void {
const tab = this.getTabById(tabId);
this.selectedTabId = tab ? tab.id : null;
}
@action
setHeight(height: number) {
setHeight(height: number): void {
this.height = Math.max(0, Math.min(height, this.maxHeight));
}
@action
reset() {
reset(): void {
this.selectedTabId = this.defaultTabId;
this.tabs.replace(this.initialTabs);
this.height = this.defaultHeight;
+35 -24
View File
@@ -9,7 +9,7 @@ import { Icon } from "../icon";
import { Tabs } from "../tabs/tabs";
import { MenuItem } from "../menu";
import { MenuActions } from "../menu/menu-actions";
import { dockStore, IDockTab } from "./dock.store";
import { dockStore, DockTabData } from "./dock.store";
import { DockTab } from "./dock-tab";
import { TerminalTab } from "./terminal-tab";
import { TerminalWindow } from "./terminal-window";
@@ -29,7 +29,7 @@ interface Props {
@observer
export class Dock extends React.Component<Props> {
onResizeStart = () => {
onResizeStart = (): void => {
const { isOpen, open, setHeight, minHeight } = dockStore;
if (!isOpen) {
open();
@@ -37,53 +37,59 @@ export class Dock extends React.Component<Props> {
}
}
onResize = ({ offsetY }: DraggableState) => {
onResize = ({ offsetY }: DraggableState): void => {
const { isOpen, close, height, setHeight, minHeight, defaultHeight } = dockStore;
const newHeight = height + offsetY;
if (height > newHeight && newHeight < minHeight) {
setHeight(defaultHeight);
close();
}
else if (isOpen) {
} else if (isOpen) {
setHeight(newHeight);
}
}
onKeydown = (evt: React.KeyboardEvent<HTMLElement>) => {
const { close, closeTab, selectedTab, fullSize, toggleFillSize } = dockStore;
if (!selectedTab) return;
onKeydown = (evt: React.KeyboardEvent<HTMLElement>): void => {
const { close, closeTab, selectedTab } = dockStore;
if (!selectedTab) {
return;
}
const { code, ctrlKey, shiftKey } = evt.nativeEvent;
if (shiftKey && code === "Escape") {
close();
}
if (ctrlKey && code === "KeyW") {
if (selectedTab.pinned) close();
else closeTab(selectedTab.id);
if (selectedTab.pinned) {
close();
} else {
closeTab(selectedTab.id);
}
}
}
onChangeTab = (tab: IDockTab) => {
onChangeTab = (tab: DockTabData): void => {
const { open, selectTab } = dockStore;
open();
selectTab(tab.id);
}
@autobind()
renderTab(tab: IDockTab) {
renderTab(tab: DockTabData): JSX.Element {
if (isTerminalTab(tab)) {
return <TerminalTab value={tab}/>
return <TerminalTab value={tab}/>;
}
if (isCreateResourceTab(tab) || isEditResourceTab(tab)) {
return <DockTab value={tab} icon="edit"/>
return <DockTab value={tab} icon="edit"/>;
}
if (isInstallChartTab(tab) || isUpgradeChartTab(tab)) {
return <DockTab value={tab} icon={<Icon svg="install"/>}/>
return <DockTab value={tab} icon={<Icon svg="install"/>}/>;
}
}
renderTabContent() {
renderTabContent(): JSX.Element {
const { isOpen, height, selectedTab: tab } = dockStore;
if (!isOpen || !tab) return;
if (!isOpen || !tab) {
return;
}
return (
<div className="tab-content" style={{ flexBasis: height }}>
{isCreateResourceTab(tab) && <CreateResource tab={tab}/>}
@@ -92,10 +98,10 @@ export class Dock extends React.Component<Props> {
{isUpgradeChartTab(tab) && <UpgradeChart tab={tab}/>}
{isTerminalTab(tab) && <TerminalWindow tab={tab}/>}
</div>
)
);
}
render() {
render(): JSX.Element {
const { className } = this.props;
const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize } = dockStore;
return (
@@ -115,16 +121,21 @@ export class Dock extends React.Component<Props> {
autoFocus={isOpen}
className="dock-tabs"
value={selectedTab} onChange={this.onChangeTab}
children={tabs.map(tab => <Fragment key={tab.id}>{this.renderTab(tab)}</Fragment>)}
/>
>
{tabs.map(tab => <Fragment key={tab.id}>{this.renderTab(tab)}</Fragment>)}
</Tabs>
<div className="toolbar flex gaps align-center box grow">
<div className="dock-menu box grow">
<MenuActions usePortal triggerIcon={{ material: "add", className: "new-dock-tab", tooltip: <Trans>New tab</Trans> }} closeOnScroll={false}>
<MenuItem className="create-terminal-tab" onClick={() => createTerminalTab()}>
<MenuItem className="create-terminal-tab" onClick={(): void => {
createTerminalTab();
}}>
<Icon small svg="terminal" size={15}/>
<Trans>Terminal session</Trans>
</MenuItem>
<MenuItem className="create-resource-tab" onClick={() => createResourceTab()}>
<MenuItem className="create-resource-tab" onClick={(): void => {
createResourceTab();
}}>
<Icon small material="create"/>
<Trans>Create resource</Trans>
</MenuItem>
@@ -148,6 +159,6 @@ export class Dock extends React.Component<Props> {
</div>
{this.renderTabContent()}
</div>
)
);
}
}
@@ -1,7 +1,7 @@
import { autobind, noop } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { autorun, IReactionDisposer } from "mobx";
import { dockStore, IDockTab, TabKind } from "./dock.store";
import { dockStore, DockTabData, TabKind } from "./dock.store";
import { KubeObject } from "../../api/kube-object";
import { apiManager } from "../../api/api-manager";
@@ -17,7 +17,7 @@ export class EditResourceStore extends DockTabStore<KubeEditResource> {
constructor() {
super({
storageName: "edit_resource_store",
storageSerializer: ({ draft, ...data }) => data, // skip saving draft in local-storage
storageSerializer: ({ draft: _draft, ...data }) => data, // skip saving draft in local-storage
});
autorun(() => {
@@ -33,38 +33,37 @@ export class EditResourceStore extends DockTabStore<KubeEditResource> {
// preload resource for editing
if (!obj && !store.isLoaded && !store.isLoading && isActiveTab) {
store.loadFromPath(resource).catch(noop);
}
// auto-close tab when resource removed from store
else if (!obj && store.isLoaded) {
} else if (!obj && store.isLoaded) {
// auto-close tab when resource removed from store
dockStore.closeTab(tabId);
}
}
}, {
delay: 100 // make sure all stores initialized
}));
})
});
});
}
getTabByResource(object: KubeObject): IDockTab {
const [tabId] = Array.from(this.data).find(([tabId, { resource }]) => {
getTabByResource(object: KubeObject): DockTabData {
const [tabId] = Array.from(this.data).find(([_tabId, { resource }]) => {
return object.selfLink === resource;
}) || [];
return dockStore.getTabById(tabId);
}
reset() {
reset(): void {
super.reset();
Array.from(this.watchers).forEach(([tabId, dispose]) => {
this.watchers.delete(tabId);
dispose();
})
});
}
}
export const editResourceStore = new EditResourceStore();
export function editResourceTab(object: KubeObject, tabParams: Partial<IDockTab> = {}) {
export function editResourceTab(object: KubeObject, tabParams: Partial<DockTabData> = {}): DockTabData {
// use existing tab if already opened
let tab = editResourceStore.getTabByResource(object);
if (tab) {
@@ -85,6 +84,6 @@ export function editResourceTab(object: KubeObject, tabParams: Partial<IDockTab>
return tab;
}
export function isEditResourceTab(tab: IDockTab) {
return tab && tab.kind === TabKind.EDIT_RESOURCE;
export function isEditResourceTab(tab: DockTabData): boolean {
return tab?.kind === TabKind.EDIT_RESOURCE;
}
@@ -3,11 +3,11 @@ import "./edit-resource.scss";
import React from "react";
import { autorun, observable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import jsYaml from "js-yaml"
import jsYaml from "js-yaml";
import { t, Trans } from "@lingui/macro";
import { IDockTab } from "./dock.store";
import { DockTabData } from "./dock.store";
import { cssNames } from "../../utils";
import { editResourceStore } from "./edit-resource.store";
import { editResourceStore, KubeEditResource } from "./edit-resource.store";
import { InfoPanel } from "./info-panel";
import { Badge } from "../badge";
import { EditorPanel } from "./editor-panel";
@@ -18,7 +18,7 @@ import { KubeObject } from "../../api/kube-object";
interface Props {
className?: string;
tab: IDockTab;
tab: DockTabData;
}
@observer
@@ -27,17 +27,19 @@ export class EditResource extends React.Component<Props> {
@disposeOnUnmount
autoDumpResourceOnInit = autorun(() => {
if (!this.tabData) return;
if (!this.tabData) {
return;
}
if (this.tabData.draft === undefined && this.resource) {
this.saveDraft(this.resource);
}
});
get tabId() {
return this.props.tab.id;
get tabId(): string {
return this.props.tab.id || "";
}
get tabData() {
get tabData(): KubeEditResource {
return editResourceStore.getData(this.tabId);
}
@@ -49,7 +51,7 @@ export class EditResource extends React.Component<Props> {
}
}
saveDraft(draft: string | object) {
saveDraft(draft: string | object): void {
if (typeof draft === "object") {
draft = draft ? jsYaml.dump(draft) : undefined;
}
@@ -59,12 +61,12 @@ export class EditResource extends React.Component<Props> {
});
}
onChange = (draft: string, error?: string) => {
onChange = (draft: string, error?: string): void => {
this.error = error;
this.saveDraft(draft);
}
save = async () => {
save = async (): Promise<JSX.Element> => {
if (this.error) {
return;
}
@@ -81,7 +83,7 @@ export class EditResource extends React.Component<Props> {
);
}
render() {
render(): JSX.Element {
const { tabId, resource, tabData, error, onChange, save } = this;
const { draft } = tabData;
if (!resource || draft === undefined) {
@@ -110,6 +112,6 @@ export class EditResource extends React.Component<Props> {
)}
/>
</div>
)
);
}
}
@@ -1,5 +1,5 @@
import React from "react";
import jsYaml from "js-yaml"
import jsYaml from "js-yaml";
import { observable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames } from "../../utils";
@@ -23,17 +23,17 @@ export class EditorPanel extends React.Component<Props> {
@observable yamlError = ""
componentDidMount() {
componentDidMount(): void {
// validate and run callback with optional error
this.onChange(this.props.value || "");
disposeOnUnmount(this, [
dockStore.onTabChange(this.onTabChange, { delay: 250 }),
dockStore.onResize(this.onResize, { delay: 250 }),
])
]);
}
validate(value: string) {
validate(value: string): void {
try {
jsYaml.safeLoadAll(value);
this.yamlError = "";
@@ -42,26 +42,26 @@ export class EditorPanel extends React.Component<Props> {
}
}
onTabChange = () => {
onTabChange = (): void => {
this.editor.focus();
}
onResize = () => {
onResize = (): void => {
this.editor.resize();
}
onCursorPosChange = (pos: Ace.Point) => {
onCursorPosChange = (pos: Ace.Point): void => {
EditorPanel.cursorPos.setData(this.props.tabId, pos);
}
onChange = (value: string) => {
onChange = (value: string): void => {
this.validate(value);
if (this.props.onChange) {
this.props.onChange(value, this.yamlError);
}
}
render() {
render(): JSX.Element {
const { value, tabId } = this.props;
let { className } = this.props;
className = cssNames("EditorPanel", className);
@@ -74,8 +74,10 @@ export class EditorPanel extends React.Component<Props> {
cursorPos={cursorPos}
onChange={this.onChange}
onCursorPosChange={this.onCursorPosChange}
ref={e => this.editor = e}
ref={(e): void => {
this.editor = e;
}}
/>
)
);
}
}
+1 -1
View File
@@ -1 +1 @@
export * from "./dock"
export * from "./dock";
+18 -14
View File
@@ -42,21 +42,21 @@ export class InfoPanel extends Component<Props> {
@observable error = "";
@observable waiting = false;
componentDidMount() {
componentDidMount(): void {
disposeOnUnmount(this, [
reaction(() => this.props.tabId, () => {
this.result = ""
this.error = ""
this.waiting = false
this.result = "";
this.error = "";
this.waiting = false;
})
])
]);
}
@computed get errorInfo() {
@computed get errorInfo(): string {
return this.error || this.props.error;
}
submit = async () => {
submit = async (): Promise<void> => {
const { showNotifications } = this.props;
this.result = "";
this.error = "";
@@ -65,24 +65,28 @@ export class InfoPanel extends Component<Props> {
this.result = await this.props.submit().finally(() => {
this.waiting = false;
});
if (showNotifications) Notifications.ok(this.result);
if (showNotifications) {
Notifications.ok(this.result);
}
} catch (error) {
this.error = error.toString();
if (showNotifications) Notifications.error(this.error);
if (showNotifications) {
Notifications.error(this.error);
}
throw error;
}
}
submitAndClose = async () => {
submitAndClose = async (): Promise<void> => {
await this.submit();
this.close();
}
close = () => {
close = (): void => {
dockStore.closeTab(this.props.tabId);
}
renderInfo() {
renderInfo(): JSX.Element {
if (!this.props.showInlineInfo) {
return;
}
@@ -101,10 +105,10 @@ export class InfoPanel extends Component<Props> {
</div>
)}
</>
)
);
}
render() {
render(): JSX.Element {
const { className, controls, submitLabel, disableSubmit, error, submittingMessage, showSubmitClose } = this.props;
const { submit, close, submitAndClose, waiting } = this;
const isDisabled = !!(disableSubmit || waiting || error);
@@ -1,12 +1,12 @@
import { action, autorun } from "mobx";
import { dockStore, IDockTab, TabId, TabKind } from "./dock.store";
import { dockStore, DockTabData, TabId, TabKind } from "./dock.store";
import { DockTabStore } from "./dock-tab.store";
import { t } from "@lingui/macro";
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
import { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api";
import { ReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api";
import { _i18n } from "../../i18n";
export interface IChartInstallData {
export interface ChartInstallData {
name: string;
repo: string;
version: string;
@@ -17,9 +17,13 @@ export interface IChartInstallData {
lastVersion?: boolean;
}
export class InstallChartStore extends DockTabStore<IChartInstallData> {
export function isInstallChartTab(tab: DockTabData): boolean {
return tab?.kind === TabKind.INSTALL_CHART;
}
export class InstallChartStore extends DockTabStore<ChartInstallData> {
public versions = new DockTabStore<string[]>();
public details = new DockTabStore<IReleaseUpdateDetails>();
public details = new DockTabStore<ReleaseUpdateDetails>();
constructor() {
super({
@@ -27,25 +31,27 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
});
autorun(() => {
const { selectedTab, isOpen } = dockStore;
if (!isInstallChartTab(selectedTab)) return;
if (!isInstallChartTab(selectedTab)) {
return;
}
if (isOpen) {
this.loadData();
}
}, { delay: 250 })
}, { delay: 250 });
}
@action
async loadData(tabId = dockStore.selectedTabId) {
async loadData(tabId = dockStore.selectedTabId): Promise<void> {
const { values } = this.getData(tabId);
const versions = this.versions.getData(tabId);
return Promise.all([
await Promise.all([
!versions && this.loadVersions(tabId),
!values && this.loadValues(tabId),
])
]);
}
@action
async loadVersions(tabId: TabId) {
async loadVersions(tabId: TabId): Promise<void> {
const { repo, name } = this.getData(tabId);
this.versions.clearData(tabId); // reset
const charts = await helmChartsApi.get(repo, name);
@@ -54,13 +60,15 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
}
@action
async loadValues(tabId: TabId) {
async loadValues(tabId: TabId): Promise<void> {
const data = this.getData(tabId);
const { repo, name, version } = data;
let values = "";
const fetchValues = async (retry = 1, maxRetries = 3) => {
const fetchValues = async (retry = 1, maxRetries = 3): Promise<void> => {
values = await helmChartsApi.getValues(repo, name, version);
if (values || retry == maxRetries) return;
if (values || retry == maxRetries) {
return;
}
await fetchValues(retry + 1);
};
this.setData(tabId, { ...data, values: undefined }); // reset
@@ -71,7 +79,7 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
export const installChartStore = new InstallChartStore();
export function createInstallChartTab(chart: HelmChart, tabParams: Partial<IDockTab> = {}) {
export function createInstallChartTab(chart: HelmChart, tabParams: Partial<DockTabData> = {}): DockTabData {
const { name, repo, version } = chart;
const tab = dockStore.createTab({
@@ -91,7 +99,3 @@ export function createInstallChartTab(chart: HelmChart, tabParams: Partial<IDock
return tab;
}
export function isInstallChartTab(tab: IDockTab) {
return tab && tab.kind === TabKind.INSTALL_CHART;
}
@@ -4,12 +4,12 @@ import React, { Component } from "react";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { t, Trans } from "@lingui/macro";
import { dockStore, IDockTab } from "./dock.store";
import { dockStore, DockTabData } from "./dock.store";
import { InfoPanel } from "./info-panel";
import { Badge } from "../badge";
import { NamespaceSelect } from "../+namespaces/namespace-select";
import { autobind, prevDefault } from "../../utils";
import { IChartInstallData, installChartStore } from "./install-chart.store";
import { ChartInstallData, installChartStore } from "./install-chart.store";
import { Spinner } from "../spinner";
import { Icon } from "../icon";
import { Button } from "../button";
@@ -21,9 +21,10 @@ import { Input } from "../input";
import { EditorPanel } from "./editor-panel";
import { navigate } from "../../navigation";
import { _i18n } from "../../i18n";
import { ReleaseUpdateDetails } from "client/api/endpoints/helm-releases.api";
interface Props {
tab: IDockTab;
tab: DockTabData;
}
@observer
@@ -31,28 +32,28 @@ export class InstallChart extends Component<Props> {
@observable error = "";
@observable showNotes = false;
get values() {
return this.chartData.values;
get values(): string {
return this.chartData.values || "";
}
get chartData() {
get chartData(): ChartInstallData {
return installChartStore.getData(this.tabId);
}
get tabId() {
return this.props.tab.id;
get tabId(): string {
return this.props.tab.id || "";
}
get versions() {
get versions(): string[] {
return installChartStore.versions.getData(this.tabId);
}
get releaseDetails() {
get releaseDetails(): ReleaseUpdateDetails {
return installChartStore.details.getData(this.tabId);
}
@autobind()
viewRelease() {
viewRelease(): void {
const { release } = this.releaseDetails;
navigate(releaseURL({
params: {
@@ -64,35 +65,35 @@ export class InstallChart extends Component<Props> {
}
@autobind()
save(data: Partial<IChartInstallData>) {
save(data: Partial<ChartInstallData>): void {
const chart = { ...this.chartData, ...data };
installChartStore.setData(this.tabId, chart);
}
@autobind()
onVersionChange(option: SelectOption) {
onVersionChange(option: SelectOption): void {
const version = option.value;
this.save({ version, values: "" });
installChartStore.loadValues(this.tabId);
}
@autobind()
onValuesChange(values: string, error?: string) {
onValuesChange(values: string, error?: string): void {
this.error = error;
this.save({ values });
}
@autobind()
onNamespaceChange(opt: SelectOption) {
onNamespaceChange(opt: SelectOption): void {
this.save({ namespace: opt.value });
}
@autobind()
onReleaseNameChange(name: string) {
onReleaseNameChange(name: string): void {
this.save({ releaseName: name });
}
install = async () => {
install = async (): Promise<JSX.Element> => {
const { repo, name, version, namespace, values, releaseName } = this.chartData;
const details = await releaseStore.create({
name: releaseName || undefined,
@@ -105,7 +106,7 @@ export class InstallChart extends Component<Props> {
);
}
render() {
render(): JSX.Element {
const { tabId, chartData, values, versions, install } = this;
if (!chartData || chartData.values === undefined || !versions) {
return <Spinner center/>;
@@ -127,17 +128,21 @@ export class InstallChart extends Component<Props> {
<Button
plain active
label={_i18n._(t`Show Notes`)}
onClick={() => this.showNotes = true}
onClick={(): void => {
this.showNotes = true;
}}
/>
</div>
<LogsDialog
title={_i18n._(t`Helm Chart Install`)}
isOpen={this.showNotes}
close={() => this.showNotes = false}
close={(): void => {
this.showNotes = false;
}}
logs={this.releaseDetails.log}
/>
</div>
)
);
}
const { repo, name, version, namespace, releaseName } = chartData;
@@ -1,6 +1,6 @@
import "./terminal-tab.scss"
import "./terminal-tab.scss";
import React from "react"
import React from "react";
import { observer } from "mobx-react";
import { Trans } from "@lingui/macro";
import { autobind, cssNames } from "../../utils";
@@ -15,26 +15,26 @@ interface Props extends DockTabProps {
@observer
export class TerminalTab extends React.Component<Props> {
componentDidMount() {
componentDidMount(): void {
reaction(() => this.isDisconnected === true, () => {
dockStore.closeTab(this.tabId)
})
dockStore.closeTab(this.tabId);
});
}
get tabId() {
return this.props.value.id;
get tabId(): string {
return this.props.value.id || "";
}
get isDisconnected() {
get isDisconnected(): boolean {
return terminalStore.isDisconnected(this.tabId);
}
@autobind()
reconnect() {
reconnect(): void {
terminalStore.reconnect(this.tabId);
}
render() {
render(): JSX.Element {
const tabIcon = <Icon svg="terminal"/>;
const className = cssNames("TerminalTab", this.props.className, {
disconnected: this.isDisconnected,
@@ -55,6 +55,6 @@ export class TerminalTab extends React.Component<Props> {
/>
)}
/>
)
);
}
}
@@ -4,14 +4,14 @@ import React from "react";
import { reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames } from "../../utils";
import { IDockTab } from "./dock.store";
import { DockTabData } from "./dock.store";
import { Terminal } from "./terminal";
import { terminalStore } from "./terminal.store";
import { themeStore } from "../../theme.store";
interface Props {
className?: string;
tab: IDockTab;
tab: DockTabData;
}
@observer
@@ -19,27 +19,31 @@ export class TerminalWindow extends React.Component<Props> {
public elem: HTMLElement;
public terminal: Terminal;
componentDidMount() {
componentDidMount(): void {
disposeOnUnmount(this, [
reaction(() => this.props.tab.id, tabId => this.activate(tabId), {
fireImmediately: true
})
])
]);
}
activate(tabId = this.props.tab.id) {
if (this.terminal) this.terminal.detach(); // detach previous
activate(tabId = this.props.tab.id): void {
if (this.terminal) {
this.terminal.detach();
} // detach previous
this.terminal = terminalStore.getTerminal(tabId);
this.terminal.attachTo(this.elem);
}
render() {
render(): JSX.Element {
const { className } = this.props;
return (
<div
className={cssNames("TerminalWindow", className, themeStore.activeTheme.type)}
ref={e => this.elem = e}
ref={(e): void => {
this.elem = e;
}}
/>
)
);
}
}
@@ -3,21 +3,21 @@ import { t } from "@lingui/macro";
import { autobind } from "../../utils";
import { Terminal } from "./terminal";
import { TerminalApi } from "../../api/terminal-api";
import { dockStore, IDockTab, TabId, TabKind } from "./dock.store";
import { dockStore, DockTabData, TabId, TabKind } from "./dock.store";
import { WebSocketApiState } from "../../api/websocket-api";
import { _i18n } from "../../i18n";
import { themeStore } from "../../theme.store";
export interface ITerminalTab extends IDockTab {
export interface TerminalTabData extends DockTabData {
node?: string; // activate node shell mode
}
export function isTerminalTab(tab: IDockTab) {
return tab && tab.kind === TabKind.TERMINAL;
export function isTerminalTab(tab: DockTabData): boolean {
return tab?.kind === TabKind.TERMINAL;
}
export function createTerminalTab(tabParams: Partial<ITerminalTab> = {}) {
export function createTerminalTab(tabParams: Partial<TerminalTabData> = {}): DockTabData {
return dockStore.createTab({
kind: TabKind.TERMINAL,
title: _i18n._(t`Terminal`),
@@ -34,7 +34,9 @@ export class TerminalStore {
// connect active tab
autorun(() => {
const { selectedTab, isOpen } = dockStore;
if (!isTerminalTab(selectedTab)) return;
if (!isTerminalTab(selectedTab)) {
return;
}
if (isOpen) {
this.connect(selectedTab.id);
}
@@ -43,16 +45,18 @@ export class TerminalStore {
autorun(() => {
const currentTabs = dockStore.tabs.map(tab => tab.id);
for (const [tabId] of this.connections) {
if (!currentTabs.includes(tabId)) this.disconnect(tabId);
if (!currentTabs.includes(tabId)) {
this.disconnect(tabId);
}
}
});
}
async connect(tabId: TabId) {
async connect(tabId: TabId): Promise<void> {
if (this.isConnected(tabId)) {
return;
}
const tab: ITerminalTab = dockStore.getTabById(tabId);
const tab: TerminalTabData = dockStore.getTabById(tabId);
const api = new TerminalApi({
id: tabId,
node: tab.node,
@@ -63,7 +67,7 @@ export class TerminalStore {
this.terminals.set(tabId, terminal);
}
disconnect(tabId: TabId) {
disconnect(tabId: TabId): void {
if (!this.isConnected(tabId)) {
return;
}
@@ -75,29 +79,35 @@ export class TerminalStore {
this.terminals.delete(tabId);
}
reconnect(tabId: TabId) {
reconnect(tabId: TabId): void {
const terminalApi = this.connections.get(tabId);
if (terminalApi) terminalApi.connect();
if (terminalApi) {
terminalApi.connect();
}
}
isConnected(tabId: TabId) {
isConnected(tabId: TabId): boolean {
return !!this.connections.get(tabId);
}
isDisconnected(tabId: TabId) {
isDisconnected(tabId: TabId): boolean {
const terminalApi = this.connections.get(tabId);
if (terminalApi) {
return terminalApi.readyState === WebSocketApiState.CLOSED;
}
}
sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) {
sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}): void {
const { enter, newTab, tabId } = options;
const { selectTab, getTabById } = dockStore;
const tab = tabId && getTabById(tabId);
if (tab) selectTab(tabId);
if (newTab) createTerminalTab();
if (tab) {
selectTab(tabId);
}
if (newTab) {
createTerminalTab();
}
const terminalApi = this.connections.get(dockStore.selectedTabId);
if (terminalApi) {
@@ -105,11 +115,11 @@ export class TerminalStore {
}
}
getTerminal(tabId: TabId) {
getTerminal(tabId: TabId): Terminal {
return this.terminals.get(tabId);
}
reset() {
reset(): void {
[...this.connections].forEach(([tabId]) => {
this.disconnect(tabId);
});
+32 -24
View File
@@ -10,12 +10,12 @@ import { autobind } from "../../utils";
export class Terminal {
static spawningPool: HTMLElement;
static init() {
static init(): void {
// terminal element must be in DOM before attaching via xterm.open(elem)
// https://xtermjs.org/docs/api/terminal/classes/terminal/#open
const pool = document.createElement("div");
pool.className = "terminal-init";
pool.style.cssText = "position: absolute; top: 0; left: 0; height: 0; visibility: hidden; overflow: hidden"
pool.style.cssText = "position: absolute; top: 0; left: 0; height: 0; visibility: hidden; overflow: hidden";
document.body.appendChild(pool);
Terminal.spawningPool = pool;
}
@@ -26,10 +26,10 @@ export class Terminal {
public disposers: Function[] = [];
@autobind()
protected setTheme(colors = themeStore.activeTheme.colors) {
protected setTheme(colors = themeStore.activeTheme.colors): void {
// Replacing keys stored in styles to format accepted by terminal
// E.g. terminalBrightBlack -> brightBlack
const colorPrefix = "terminal"
const colorPrefix = "terminal";
const terminalColors = Object.entries(colors)
.filter(([name]) => name.startsWith(colorPrefix))
.reduce<any>((colors, [name, color]) => {
@@ -41,11 +41,11 @@ export class Terminal {
this.xterm.setOption("theme", terminalColors);
}
get elem() {
get elem(): HTMLElement {
return this.xterm.element;
}
get viewport() {
get viewport(): Element {
return this.xterm.element.querySelector(".xterm-viewport");
}
@@ -53,21 +53,21 @@ export class Terminal {
this.init();
}
get isActive() {
get isActive(): boolean {
const { isOpen, selectedTabId } = dockStore;
return isOpen && selectedTabId === this.tabId;
}
attachTo(parentElem: HTMLElement) {
attachTo(parentElem: HTMLElement): void {
parentElem.appendChild(this.elem);
this.onActivate();
}
detach() {
detach(): void {
Terminal.spawningPool.appendChild(this.elem);
}
init() {
init(): void {
if (this.xterm) {
return;
}
@@ -106,15 +106,17 @@ export class Terminal {
);
}
destroy() {
if (!this.xterm) return;
destroy(): void {
if (!this.xterm) {
return;
}
this.disposers.forEach(dispose => dispose());
this.disposers = [];
this.xterm.dispose();
this.xterm = null;
}
fit = () => {
fit = (): void => {
this.fitAddon.fit();
const { cols, rows } = this.xterm;
this.api.sendTerminalSize(cols, rows);
@@ -122,39 +124,43 @@ export class Terminal {
fitLazy = debounce(this.fit, 250);
focus = () => {
focus = (): void => {
this.xterm.focus();
}
onApiData = (data: string) => {
onApiData = (data: string): void => {
this.xterm.write(data);
}
onData = (data: string) => {
if (!this.api.isReady) return;
onData = (data: string): void => {
if (!this.api.isReady) {
return;
}
this.api.sendCommand(data);
}
onScroll = () => {
onScroll = (): void => {
this.scrollPos = this.viewport.scrollTop;
}
onClear = () => {
onClear = (): void => {
this.xterm.clear();
}
onResize = () => {
if (!this.isActive) return;
onResize = (): void => {
if (!this.isActive) {
return;
}
this.fitLazy();
}
onActivate = () => {
onActivate = (): void => {
this.fit();
setTimeout(() => this.focus(), 250); // delay used to prevent focus on active tab
this.viewport.scrollTop = this.scrollPos; // restore last scroll position
}
onClickLink = (evt: MouseEvent, link: string) => {
onClickLink = (evt: MouseEvent, link: string): void => {
window.open(link, "_blank");
}
@@ -166,7 +172,9 @@ export class Terminal {
switch (code) {
// Ctrl+C: prevent terminal exit on windows / linux (?)
case "KeyC":
if (this.xterm.hasSelection()) return false;
if (this.xterm.hasSelection()) {
return false;
}
break;
// Ctrl+W: prevent unexpected terminal tab closing, e.g. editing file in vim
@@ -1,17 +1,21 @@
import { action, autorun, IReactionDisposer, reaction } from "mobx";
import { t } from "@lingui/macro";
import { dockStore, IDockTab, TabId, TabKind } from "./dock.store";
import { dockStore, DockTabData, TabId, TabKind } from "./dock.store";
import { DockTabStore } from "./dock-tab.store";
import { HelmRelease, helmReleasesApi } from "../../api/endpoints/helm-releases.api";
import { releaseStore } from "../+apps-releases/release.store";
import { _i18n } from "../../i18n";
export interface IChartUpgradeData {
export interface ChartUpgradeData {
releaseName: string;
releaseNamespace: string;
}
export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
export function isUpgradeChartTab(tab: DockTabData): boolean {
return tab?.kind === TabKind.UPGRADE_CHART;
}
export class UpgradeChartStore extends DockTabStore<ChartUpgradeData> {
private watchers = new Map<string, IReactionDisposer>();
values = new DockTabStore<string>();
@@ -23,7 +27,9 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
autorun(() => {
const { selectedTab, isOpen } = dockStore;
if (!isUpgradeChartTab(selectedTab)) return;
if (!isUpgradeChartTab(selectedTab)) {
return;
}
if (isOpen) {
this.loadData(selectedTab.id);
}
@@ -35,27 +41,28 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
});
}
private createReleaseWatcher(releaseName: string) {
private createReleaseWatcher(releaseName: string): void {
if (this.watchers.get(releaseName)) {
return;
}
const dispose = reaction(() => {
const release = releaseStore.getByName(releaseName);
if (release) return release.getRevision(); // watch changes only by revision
if (release) {
return release.revision;
} // watch changes only by revision
},
release => {
const releaseTab = this.getTabByRelease(releaseName);
if (!releaseStore.isLoaded || !releaseTab) {
return;
}
// auto-reload values if was loaded before
if (release) {
// auto-reload values if was loaded before
if (dockStore.selectedTab === releaseTab && this.values.getData(releaseTab.id)) {
this.loadValues(releaseTab.id);
}
}
// clean up watcher, close tab if release not exists / was removed
else {
} else {
// clean up watcher, close tab if release not exists / was removed
dispose();
this.watchers.delete(releaseName);
dockStore.closeTab(releaseTab.id);
@@ -64,13 +71,13 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
this.watchers.set(releaseName, dispose);
}
isLoading(tabId = dockStore.selectedTabId) {
isLoading(tabId = dockStore.selectedTabId): boolean {
const values = this.values.getData(tabId);
return !releaseStore.isLoaded || values === undefined;
}
@action
async loadData(tabId: TabId) {
async loadData(tabId: TabId): Promise<void> {
const values = this.values.getData(tabId);
await Promise.all([
!releaseStore.isLoaded && releaseStore.loadAll(),
@@ -79,25 +86,25 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
}
@action
async loadValues(tabId: TabId) {
async loadValues(tabId: TabId): Promise<void> {
this.values.clearData(tabId); // reset
const { releaseName, releaseNamespace } = this.getData(tabId);
const values = await helmReleasesApi.getValues(releaseName, releaseNamespace);
this.values.setData(tabId, values);
}
getTabByRelease(releaseName: string): IDockTab {
getTabByRelease(releaseName: string): DockTabData {
const item = [...this.data].find(item => item[1].releaseName === releaseName);
if (item) {
const [tabId] = item;
return dockStore.getTabById(tabId)
return dockStore.getTabById(tabId);
}
}
}
export const upgradeChartStore = new UpgradeChartStore();
export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial<IDockTab> = {}) {
export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial<DockTabData> = {}): DockTabData {
let tab = upgradeChartStore.getTabByRelease(release.getName());
if (tab) {
dockStore.open();
@@ -112,12 +119,8 @@ export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial<I
upgradeChartStore.setData(tab.id, {
releaseName: release.getName(),
releaseNamespace: release.getNs()
})
releaseNamespace: release.namespace
});
}
return tab;
}
export function isUpgradeChartTab(tab: IDockTab) {
return tab && tab.kind === TabKind.UPGRADE_CHART;
}
@@ -1,34 +1,34 @@
import "./upgrade-chart.scss"
import "./upgrade-chart.scss";
import React from "react";
import { observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { t, Trans } from "@lingui/macro";
import { cssNames } from "../../utils";
import { IDockTab } from "./dock.store";
import { DockTabData } from "./dock.store";
import { InfoPanel } from "./info-panel";
import { upgradeChartStore } from "./upgrade-chart.store";
import { Spinner } from "../spinner";
import { releaseStore } from "../+apps-releases/release.store";
import { Badge } from "../badge";
import { EditorPanel } from "./editor-panel";
import { helmChartStore, IChartVersion } from "../+apps-helm-charts/helm-chart.store";
import { helmChartStore, ChartVersion } from "../+apps-helm-charts/helm-chart.store";
import { HelmRelease } from "../../api/endpoints/helm-releases.api";
import { Select, SelectOption } from "../select";
import { _i18n } from "../../i18n";
interface Props {
className?: string;
tab: IDockTab;
tab: DockTabData;
}
@observer
export class UpgradeChart extends React.Component<Props> {
@observable error: string;
@observable versions = observable.array<IChartVersion>();
@observable version: IChartVersion;
@observable versions = observable.array<ChartVersion>();
@observable version: ChartVersion;
componentDidMount() {
componentDidMount(): void {
this.loadVersions();
disposeOnUnmount(this, [
@@ -36,22 +36,26 @@ export class UpgradeChart extends React.Component<Props> {
]);
}
get tabId() {
return this.props.tab.id;
get tabId(): string {
return this.props.tab.id || "";
}
get release(): HelmRelease {
const tabData = upgradeChartStore.getData(this.tabId);
if (!tabData) return;
if (!tabData) {
return;
}
return releaseStore.getByName(tabData.releaseName);
}
get value() {
get value(): string {
return upgradeChartStore.values.getData(this.tabId);
}
async loadVersions() {
if (!this.release) return;
async loadVersions(): Promise<void> {
if (!this.release) {
return;
}
this.version = null;
this.versions.clear();
const versions = await helmChartStore.getVersions(this.release.getChart());
@@ -59,16 +63,18 @@ export class UpgradeChart extends React.Component<Props> {
this.version = this.versions[0];
}
onChange = (value: string, error?: string) => {
onChange = (value: string, error?: string): void => {
upgradeChartStore.values.setData(this.tabId, value);
this.error = error;
}
upgrade = async () => {
if (this.error) return;
upgrade = async (): Promise<JSX.Element> => {
if (this.error) {
return;
}
const { version, repo } = this.version;
const releaseName = this.release.getName();
const releaseNs = this.release.getNs()
const releaseNs = this.release.namespace;
await releaseStore.update(releaseName, releaseNs, {
chart: this.release.getChart(),
values: this.value,
@@ -78,16 +84,16 @@ export class UpgradeChart extends React.Component<Props> {
<p>
<Trans>Release <b>{releaseName}</b> successfully upgraded to version <b>{version}</b></Trans>
</p>
)
);
}
formatVersionLabel = ({ value }: SelectOption<IChartVersion>) => {
formatVersionLabel = ({ value }: SelectOption<ChartVersion>): string => {
const chartName = this.release.getChart();
const { repo, version } = value;
return `${repo}/${chartName}-${version}`;
}
render() {
render(): JSX.Element {
const { tabId, release, value, error, onChange, upgrade, versions, version } = this;
const { className } = this.props;
if (!release || upgradeChartStore.isLoading() || !version) {
@@ -97,7 +103,7 @@ export class UpgradeChart extends React.Component<Props> {
const controlsAndInfo = (
<div className="upgrade flex gaps align-center">
<span><Trans>Release</Trans></span> <Badge label={release.getName()}/>
<span><Trans>Namespace</Trans></span> <Badge label={release.getNs()}/>
<span><Trans>Namespace</Trans></span> <Badge label={release.namespace}/>
<span><Trans>Version</Trans></span> <Badge label={currentVersion}/>
<span><Trans>Upgrade version</Trans></span>
<Select
@@ -107,10 +113,12 @@ export class UpgradeChart extends React.Component<Props> {
value={version}
options={versions}
formatOptionLabel={this.formatVersionLabel}
onChange={({ value }: SelectOption) => this.version = value}
onChange={({ value }: SelectOption): void => {
this.version = value;
}}
/>
</div>
)
);
return (
<div className={cssNames("UpgradeChart flex column", className)}>
<EditorPanel
@@ -127,6 +135,6 @@ export class UpgradeChart extends React.Component<Props> {
controls={controlsAndInfo}
/>
</div>
)
);
}
}