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

fix: @observable static decorators doesn't work without makeObservable(this)

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-04-26 16:46:19 +03:00
parent 3323c25be3
commit a93b0d08bc
22 changed files with 197 additions and 175 deletions

View File

@ -1,25 +1,13 @@
// Converts mobx-observable or partially observable object to corresponding plain JS-structure. // Converts mobx-observable or partially observable object to corresponding plain JS-structure.
// Since mobx >= 6.x toJS() recursively converts only top-level observables. // Since mobx >= 6.x toJS() recursively converts only top-level observables.
import * as mobx from "mobx"; import * as mobx from "mobx";
import { isObservable, observable } from "mobx";
export function toJS<T>(data: T): T { export function toJS<T>(data: T): T {
if (mobx.isObservable(data)) { if (isObservable(data)) {
return mobx.toJS(data); // data recursively converted, nothing to worry about. return mobx.toJS(data);
} }
// convert top-level plain array or object // make data observable for recursive toJS()-call
if (typeof data === "object" && data !== null) { return mobx.toJS(observable.box(data).get());
let convertedData: any[] | T;
if (Array.isArray(data)) {
convertedData = data.map(toJS);
} else {
convertedData = Object.fromEntries(
Object.entries(data).map(([key, value]) => [key, toJS(value)])
) as T;
}
Object.assign(data, convertedData);
}
return data;
} }

View File

@ -125,7 +125,7 @@ export class HelmRepoManager extends Singleton {
return stdout; return stdout;
} }
public static async addСustomRepo(repoAttributes : HelmRepo) { public static async addCustomRepo(repoAttributes : HelmRepo) {
logger.info(`[HELM]: adding repo "${repoAttributes.name}" from ${repoAttributes.url}`); logger.info(`[HELM]: adding repo "${repoAttributes.name}" from ${repoAttributes.url}`);
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();

View File

@ -1,7 +1,7 @@
import "./release-rollback-dialog.scss"; import "./release-rollback-dialog.scss";
import React from "react"; import React from "react";
import { observable, makeObservable } from "mobx"; import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -16,8 +16,10 @@ interface Props extends DialogProps {
@observer @observer
export class ReleaseRollbackDialog extends React.Component<Props> { export class ReleaseRollbackDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable.ref static release: HelmRelease = null; isOpen: false,
release: null as HelmRelease,
});
@observable isLoading = false; @observable isLoading = false;
@observable revision: IReleaseRevision; @observable revision: IReleaseRevision;
@ -29,16 +31,16 @@ export class ReleaseRollbackDialog extends React.Component<Props> {
} }
static open(release: HelmRelease) { static open(release: HelmRelease) {
ReleaseRollbackDialog.isOpen = true; ReleaseRollbackDialog.metadata.isOpen = true;
ReleaseRollbackDialog.release = release; ReleaseRollbackDialog.metadata.release = release;
} }
static close() { static close() {
ReleaseRollbackDialog.isOpen = false; ReleaseRollbackDialog.metadata.isOpen = false;
} }
get release(): HelmRelease { get release(): HelmRelease {
return ReleaseRollbackDialog.release; return ReleaseRollbackDialog.metadata.release;
} }
onOpen = async () => { onOpen = async () => {
@ -99,7 +101,7 @@ export class ReleaseRollbackDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="ReleaseRollbackDialog" className="ReleaseRollbackDialog"
isOpen={ReleaseRollbackDialog.isOpen} isOpen={ReleaseRollbackDialog.metadata.isOpen}
onOpen={this.onOpen} onOpen={this.onOpen}
close={this.close} close={this.close}
> >

View File

@ -1,7 +1,7 @@
import { action, makeObservable, observable, reaction, when } from "mobx"; import { action, makeObservable, observable, reaction, when } from "mobx";
import { KubeObjectStore } from "../../kube-object.store"; import { KubeObjectStore } from "../../kube-object.store";
import { Cluster, clusterApi, IClusterMetrics } from "../../api/endpoints"; import { Cluster, clusterApi, IClusterMetrics } from "../../api/endpoints";
import { createStorage } from "../../utils"; import { createStorage, StorageHelper } from "../../utils";
import { IMetricsReqParams, normalizeMetrics } from "../../api/endpoints/metrics.api"; import { IMetricsReqParams, normalizeMetrics } from "../../api/endpoints/metrics.api";
import { nodesStore } from "../+nodes/nodes.store"; import { nodesStore } from "../+nodes/nodes.store";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
@ -24,13 +24,21 @@ export interface ClusterOverviewStorageState {
export class ClusterOverviewStore extends KubeObjectStore<Cluster> implements ClusterOverviewStorageState { export class ClusterOverviewStore extends KubeObjectStore<Cluster> implements ClusterOverviewStorageState {
api = clusterApi; api = clusterApi;
private storage: StorageHelper<ClusterOverviewStorageState>;
@observable metrics: Partial<IClusterMetrics> = {}; @observable metrics: Partial<IClusterMetrics> = {};
@observable metricsLoaded = false; @observable metricsLoaded = false;
private storage = createStorage<ClusterOverviewStorageState>("cluster_overview", { constructor() {
metricType: MetricType.CPU, // setup defaults super();
metricNodeRole: MetricNodeRole.WORKER,
}); this.storage = createStorage<ClusterOverviewStorageState>("cluster_overview", {
metricType: MetricType.CPU, // setup defaults
metricNodeRole: MetricNodeRole.WORKER,
});
makeObservable(this);
this.init();
}
get metricType(): MetricType { get metricType(): MetricType {
return this.storage.get().metricType; return this.storage.get().metricType;
@ -48,12 +56,6 @@ export class ClusterOverviewStore extends KubeObjectStore<Cluster> implements Cl
this.storage.merge({ metricNodeRole: value }); this.storage.merge({ metricNodeRole: value });
} }
constructor() {
super();
makeObservable(this);
this.init();
}
private init() { private init() {
// TODO: refactor, seems not a correct place to be // TODO: refactor, seems not a correct place to be
// auto-refresh metrics on user-action // auto-refresh metrics on user-action

View File

@ -1,7 +1,7 @@
import "./add-quota-dialog.scss"; import "./add-quota-dialog.scss";
import React from "react"; import React from "react";
import { computed, observable, makeObservable } from "mobx"; import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -20,7 +20,9 @@ interface Props extends DialogProps {
@observer @observer
export class AddQuotaDialog extends React.Component<Props> { export class AddQuotaDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
isOpen: false,
});
static defaultQuotas: IResourceQuotaValues = { static defaultQuotas: IResourceQuotaValues = {
"limits.cpu": "", "limits.cpu": "",
@ -57,11 +59,11 @@ export class AddQuotaDialog extends React.Component<Props> {
} }
static open() { static open() {
AddQuotaDialog.isOpen = true; AddQuotaDialog.metadata.isOpen = true;
} }
static close() { static close() {
AddQuotaDialog.isOpen = false; AddQuotaDialog.metadata.isOpen = false;
} }
@computed get quotaEntries() { @computed get quotaEntries() {
@ -77,7 +79,7 @@ export class AddQuotaDialog extends React.Component<Props> {
const icon = isCompute ? "memory" : isStorage ? "storage" : isCount ? "looks_one" : ""; const icon = isCompute ? "memory" : isStorage ? "storage" : isCount ? "looks_one" : "";
return { return {
label: icon ? <span className="nobr"><Icon material={icon} /> {quota}</span> : quota, label: icon ? <span className="nobr"><Icon material={icon}/> {quota}</span> : quota,
value: quota, value: quota,
}; };
}); });
@ -138,7 +140,7 @@ export class AddQuotaDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="AddQuotaDialog" className="AddQuotaDialog"
isOpen={AddQuotaDialog.isOpen} isOpen={AddQuotaDialog.metadata.isOpen}
close={this.close} close={this.close}
> >
<Wizard header={header} done={this.close}> <Wizard header={header} done={this.close}>
@ -158,7 +160,7 @@ export class AddQuotaDialog extends React.Component<Props> {
/> />
</div> </div>
<SubTitle title="Namespace" /> <SubTitle title="Namespace"/>
<NamespaceSelect <NamespaceSelect
value={this.namespace} value={this.namespace}
placeholder="Namespace" placeholder="Namespace"
@ -167,7 +169,7 @@ export class AddQuotaDialog extends React.Component<Props> {
onChange={({ value }) => this.namespace = value} onChange={({ value }) => this.namespace = value}
/> />
<SubTitle title="Values" /> <SubTitle title="Values"/>
<div className="flex gaps align-center"> <div className="flex gaps align-center">
<Select <Select
className="quota-select" className="quota-select"
@ -198,7 +200,7 @@ export class AddQuotaDialog extends React.Component<Props> {
<div key={quota} className="quota flex gaps inline align-center"> <div key={quota} className="quota flex gaps inline align-center">
<div className="name">{quota}</div> <div className="name">{quota}</div>
<div className="value">{value}</div> <div className="value">{value}</div>
<Icon material="clear" onClick={() => this.quotas[quota] = ""} /> <Icon material="clear" onClick={() => this.quotas[quota] = ""}/>
</div> </div>
); );
})} })}

View File

@ -38,19 +38,21 @@ type ISecretField = keyof ISecretTemplate;
@observer @observer
export class AddSecretDialog extends React.Component<Props> { export class AddSecretDialog extends React.Component<Props> {
@observable static isOpen = false;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
static metadata = observable({
isOpen: false,
});
static open() { static open() {
AddSecretDialog.isOpen = true; AddSecretDialog.metadata.isOpen = true;
} }
static close() { static close() {
AddSecretDialog.isOpen = false; AddSecretDialog.metadata.isOpen = false;
} }
private secretTemplate: { [p: string]: ISecretTemplate } = { private secretTemplate: { [p: string]: ISecretTemplate } = {
@ -189,7 +191,7 @@ export class AddSecretDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="AddSecretDialog" className="AddSecretDialog"
isOpen={AddSecretDialog.isOpen} isOpen={AddSecretDialog.metadata.isOpen}
onOpen={this.reset} onOpen={this.reset}
close={this.close} close={this.close}
> >

View File

@ -18,7 +18,10 @@ interface Props extends DialogProps {
@observer @observer
export class AddNamespaceDialog extends React.Component<Props> { export class AddNamespaceDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
isOpen: false,
});
@observable namespace = ""; @observable namespace = "";
constructor(props: Props) { constructor(props: Props) {
@ -27,11 +30,11 @@ export class AddNamespaceDialog extends React.Component<Props> {
} }
static open() { static open() {
AddNamespaceDialog.isOpen = true; AddNamespaceDialog.metadata.isOpen = true;
} }
static close() { static close() {
AddNamespaceDialog.isOpen = false; AddNamespaceDialog.metadata.isOpen = false;
} }
reset = () => { reset = () => {
@ -62,7 +65,7 @@ export class AddNamespaceDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="AddNamespaceDialog" className="AddNamespaceDialog"
isOpen={AddNamespaceDialog.isOpen} isOpen={AddNamespaceDialog.metadata.isOpen}
onOpen={this.reset} onOpen={this.reset}
close={AddNamespaceDialog.close} close={AddNamespaceDialog.close}
> >

View File

@ -1,15 +1,15 @@
import "./add-helm-repo-dialog.scss"; import "./add-helm-repo-dialog.scss";
import React from "react"; import React from "react";
import { remote, FileFilter } from "electron"; import { FileFilter, remote } from "electron";
import { observable, makeObservable } from "mobx"; import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
import { Input } from "../input"; import { Input } from "../input";
import { Checkbox } from "../checkbox"; import { Checkbox } from "../checkbox";
import { Button } from "../button"; import { Button } from "../button";
import { systemName, isUrl, isPath } from "../input/input_validators"; import { isPath, isUrl, systemName } from "../input/input_validators";
import { SubTitle } from "../layout/sub-title"; import { SubTitle } from "../layout/sub-title";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
@ -27,12 +27,16 @@ enum FileType {
@observer @observer
export class AddHelmRepoDialog extends React.Component<Props> { export class AddHelmRepoDialog extends React.Component<Props> {
private emptyRepo = {name: "", url: "", username: "", password: "", insecureSkipTlsVerify: false, caFile:"", keyFile: "", certFile: ""}; static metadata = observable({
isOpen: false,
});
private static keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"]; private static keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"];
private static certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c" , "p7s", "p12", "pfx", "pem"]; private static certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c", "p7s", "p12", "pfx", "pem"];
@observable static isOpen = false; private emptyRepo = {
name: "", url: "", username: "", password: "", insecureSkipTlsVerify: false, caFile: "", keyFile: "", certFile: ""
};
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -40,14 +44,14 @@ export class AddHelmRepoDialog extends React.Component<Props> {
} }
static open() { static open() {
AddHelmRepoDialog.isOpen = true; AddHelmRepoDialog.metadata.isOpen = true;
} }
static close() { static close() {
AddHelmRepoDialog.isOpen = false; AddHelmRepoDialog.metadata.isOpen = false;
} }
@observable helmRepo : HelmRepo = this.emptyRepo; @observable helmRepo: HelmRepo = this.emptyRepo;
@observable showOptions = false; @observable showOptions = false;
close = () => { close = () => {
@ -60,7 +64,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
this.helmRepo[type] = value; this.helmRepo[type] = value;
} }
getFilePath(type: FileType) : string { getFilePath(type: FileType): string {
return this.helmRepo[type]; return this.helmRepo[type];
} }
@ -73,7 +77,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
buttonLabel: `Use file`, buttonLabel: `Use file`,
filters: [ filters: [
fileFilter, fileFilter,
{ name: "Any", extensions: ["*"]} { name: "Any", extensions: ["*"] }
] ]
}); });
@ -84,7 +88,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
async addCustomRepo() { async addCustomRepo() {
try { try {
await HelmRepoManager.addСustomRepo(this.helmRepo); await HelmRepoManager.addCustomRepo(this.helmRepo);
Notifications.ok(<>Helm repository <b>{this.helmRepo.name}</b> has added</>); Notifications.ok(<>Helm repository <b>{this.helmRepo.name}</b> has added</>);
this.props.onAddRepo(); this.props.onAddRepo();
this.close(); this.close();
@ -93,19 +97,19 @@ export class AddHelmRepoDialog extends React.Component<Props> {
} }
} }
renderFileInput(placeholder:string, fileType:FileType ,fileExtensions:string[]){ renderFileInput(placeholder: string, fileType: FileType, fileExtensions: string[]) {
return( return (
<div className="flex gaps align-center"> <div className="flex gaps align-center">
<Input <Input
placeholder={placeholder} placeholder={placeholder}
validators = {isPath} validators={isPath}
className="box grow" className="box grow"
value={this.getFilePath(fileType)} value={this.getFilePath(fileType)}
onChange={v => this.setFilepath(fileType, v)} onChange={v => this.setFilepath(fileType, v)}
/> />
<Icon <Icon
material="folder" material="folder"
onClick={() => this.selectFileDialog(fileType, {name: placeholder, extensions: fileExtensions})} onClick={() => this.selectFileDialog(fileType, { name: placeholder, extensions: fileExtensions })}
tooltip="Browse" tooltip="Browse"
/> />
</div>); </div>);
@ -114,7 +118,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
renderOptions() { renderOptions() {
return ( return (
<> <>
<SubTitle title="Security settings" /> <SubTitle title="Security settings"/>
<Checkbox <Checkbox
label="Skip TLS certificate checks for the repository" label="Skip TLS certificate checks for the repository"
value={this.helmRepo.insecureSkipTlsVerify} value={this.helmRepo.insecureSkipTlsVerify}
@ -123,10 +127,10 @@ export class AddHelmRepoDialog extends React.Component<Props> {
{this.renderFileInput(`Key file`, FileType.KeyFile, AddHelmRepoDialog.keyExtensions)} {this.renderFileInput(`Key file`, FileType.KeyFile, AddHelmRepoDialog.keyExtensions)}
{this.renderFileInput(`Ca file`, FileType.CaFile, AddHelmRepoDialog.certExtensions)} {this.renderFileInput(`Ca file`, FileType.CaFile, AddHelmRepoDialog.certExtensions)}
{this.renderFileInput(`Cerificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)} {this.renderFileInput(`Cerificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)}
<SubTitle title="Chart Repository Credentials" /> <SubTitle title="Chart Repository Credentials"/>
<Input <Input
placeholder="Username" placeholder="Username"
value={this.helmRepo.username} onChange= {v => this.helmRepo.username = v} value={this.helmRepo.username} onChange={v => this.helmRepo.username = v}
/> />
<Input <Input
type="password" type="password"
@ -145,11 +149,13 @@ export class AddHelmRepoDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="AddHelmRepoDialog" className="AddHelmRepoDialog"
isOpen={AddHelmRepoDialog.isOpen} isOpen={AddHelmRepoDialog.metadata.isOpen}
close={this.close} close={this.close}
> >
<Wizard header={header} done={this.close}> <Wizard header={header} done={this.close}>
<WizardStep contentClass="flow column" nextLabel="Add" next={()=>{this.addCustomRepo();}}> <WizardStep contentClass="flow column" nextLabel="Add" next={() => {
this.addCustomRepo();
}}>
<div className="flex column gaps"> <div className="flex column gaps">
<Input <Input
autoFocus required autoFocus required
@ -163,7 +169,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
validators={isUrl} validators={isUrl}
value={this.helmRepo.url} onChange={v => this.helmRepo.url = v} value={this.helmRepo.url} onChange={v => this.helmRepo.url = v}
/> />
<Button plain className="accordion" onClick={() => this.showOptions = !this.showOptions} > <Button plain className="accordion" onClick={() => this.showOptions = !this.showOptions}>
More More
<Icon <Icon
small small

View File

@ -1,7 +1,7 @@
import "./add-role-binding-dialog.scss"; import "./add-role-binding-dialog.scss";
import React from "react"; import React from "react";
import { computed, observable, makeObservable } from "mobx"; import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -32,8 +32,10 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class AddRoleBindingDialog extends React.Component<Props> { export class AddRoleBindingDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable static data: RoleBinding = null; isOpen: false,
data: null as RoleBinding,
});
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -41,16 +43,16 @@ export class AddRoleBindingDialog extends React.Component<Props> {
} }
static open(roleBinding?: RoleBinding) { static open(roleBinding?: RoleBinding) {
AddRoleBindingDialog.isOpen = true; AddRoleBindingDialog.metadata.isOpen = true;
AddRoleBindingDialog.data = roleBinding; AddRoleBindingDialog.metadata.data = roleBinding;
} }
static close() { static close() {
AddRoleBindingDialog.isOpen = false; AddRoleBindingDialog.metadata.isOpen = false;
} }
get roleBinding(): RoleBinding { get roleBinding(): RoleBinding {
return AddRoleBindingDialog.data; return AddRoleBindingDialog.metadata.data;
} }
@observable isLoading = false; @observable isLoading = false;
@ -276,7 +278,7 @@ export class AddRoleBindingDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="AddRoleBindingDialog" className="AddRoleBindingDialog"
isOpen={AddRoleBindingDialog.isOpen} isOpen={AddRoleBindingDialog.metadata.isOpen}
onOpen={this.onOpen} onOpen={this.onOpen}
close={this.close} close={this.close}
> >

View File

@ -1,7 +1,7 @@
import "./add-role-dialog.scss"; import "./add-role-dialog.scss";
import React from "react"; import React from "react";
import { observable, makeObservable } from "mobx"; import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -17,7 +17,9 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class AddRoleDialog extends React.Component<Props> { export class AddRoleDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
isOpen: false,
});
@observable roleName = ""; @observable roleName = "";
@observable namespace = ""; @observable namespace = "";
@ -28,11 +30,11 @@ export class AddRoleDialog extends React.Component<Props> {
} }
static open() { static open() {
AddRoleDialog.isOpen = true; AddRoleDialog.metadata.isOpen = true;
} }
static close() { static close() {
AddRoleDialog.isOpen = false; AddRoleDialog.metadata.isOpen = false;
} }
close = () => { close = () => {
@ -64,7 +66,7 @@ export class AddRoleDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="AddRoleDialog" className="AddRoleDialog"
isOpen={AddRoleDialog.isOpen} isOpen={AddRoleDialog.metadata.isOpen}
close={this.close} close={this.close}
> >
<Wizard header={header} done={this.close}> <Wizard header={header} done={this.close}>
@ -73,7 +75,7 @@ export class AddRoleDialog extends React.Component<Props> {
nextLabel="Create" nextLabel="Create"
next={this.createRole} next={this.createRole}
> >
<SubTitle title="Role Name" /> <SubTitle title="Role Name"/>
<Input <Input
required autoFocus required autoFocus
placeholder="Name" placeholder="Name"
@ -81,7 +83,7 @@ export class AddRoleDialog extends React.Component<Props> {
value={this.roleName} value={this.roleName}
onChange={v => this.roleName = v} onChange={v => this.roleName = v}
/> />
<SubTitle title="Namespace" /> <SubTitle title="Namespace"/>
<NamespaceSelect <NamespaceSelect
themeName="light" themeName="light"
value={this.namespace} value={this.namespace}

View File

@ -1,7 +1,7 @@
import "./create-service-account-dialog.scss"; import "./create-service-account-dialog.scss";
import React from "react"; import React from "react";
import { observable, makeObservable } from "mobx"; import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -18,7 +18,9 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class CreateServiceAccountDialog extends React.Component<Props> { export class CreateServiceAccountDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
isOpen: false,
});
@observable name = ""; @observable name = "";
@observable namespace = "default"; @observable namespace = "default";
@ -29,11 +31,11 @@ export class CreateServiceAccountDialog extends React.Component<Props> {
} }
static open() { static open() {
CreateServiceAccountDialog.isOpen = true; CreateServiceAccountDialog.metadata.isOpen = true;
} }
static close() { static close() {
CreateServiceAccountDialog.isOpen = false; CreateServiceAccountDialog.metadata.isOpen = false;
} }
close = () => { close = () => {
@ -63,19 +65,19 @@ export class CreateServiceAccountDialog extends React.Component<Props> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className="CreateServiceAccountDialog" className="CreateServiceAccountDialog"
isOpen={CreateServiceAccountDialog.isOpen} isOpen={CreateServiceAccountDialog.metadata.isOpen}
close={this.close} close={this.close}
> >
<Wizard header={header} done={this.close}> <Wizard header={header} done={this.close}>
<WizardStep nextLabel="Create" next={this.createAccount}> <WizardStep nextLabel="Create" next={this.createAccount}>
<SubTitle title="Account Name" /> <SubTitle title="Account Name"/>
<Input <Input
autoFocus required autoFocus required
placeholder="Enter a name" placeholder="Enter a name"
validators={systemName} validators={systemName}
value={name} onChange={v => this.name = v.toLowerCase()} value={name} onChange={v => this.name = v.toLowerCase()}
/> />
<SubTitle title="Namespace" /> <SubTitle title="Namespace"/>
<NamespaceSelect <NamespaceSelect
themeName="light" themeName="light"
value={namespace} value={namespace}

View File

@ -16,11 +16,12 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class CronJobTriggerDialog extends Component<Props> { export class CronJobTriggerDialog extends Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable static data: CronJob = null; isOpen: false,
data: null as CronJob,
});
@observable jobName = ""; @observable jobName = "";
@observable ready = false; @observable ready = false;
constructor(props: Props) { constructor(props: Props) {
@ -29,16 +30,16 @@ export class CronJobTriggerDialog extends Component<Props> {
} }
static open(cronjob: CronJob) { static open(cronjob: CronJob) {
CronJobTriggerDialog.isOpen = true; CronJobTriggerDialog.metadata.isOpen = true;
CronJobTriggerDialog.data = cronjob; CronJobTriggerDialog.metadata.data = cronjob;
} }
static close() { static close() {
CronJobTriggerDialog.isOpen = false; CronJobTriggerDialog.metadata.isOpen = false;
} }
get cronjob() { get cronjob() {
return CronJobTriggerDialog.data; return CronJobTriggerDialog.metadata.data;
} }
close = () => { close = () => {
@ -112,7 +113,7 @@ export class CronJobTriggerDialog extends Component<Props> {
return ( return (
<Dialog <Dialog
{...dialogProps} {...dialogProps}
isOpen={CronJobTriggerDialog.isOpen} isOpen={CronJobTriggerDialog.metadata.isOpen}
className={cssNames("CronJobTriggerDialog", className)} className={cssNames("CronJobTriggerDialog", className)}
onOpen={this.onOpen} onOpen={this.onOpen}
onClose={this.onClose} onClose={this.onClose}

View File

@ -1,7 +1,7 @@
import "./deployment-scale-dialog.scss"; import "./deployment-scale-dialog.scss";
import React, { Component } from "react"; import React, { Component } from "react";
import { computed, observable, makeObservable } from "mobx"; import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -16,8 +16,10 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class DeploymentScaleDialog extends Component<Props> { export class DeploymentScaleDialog extends Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable static data: Deployment = null; isOpen: false,
data: null as Deployment,
});
@observable ready = false; @observable ready = false;
@observable currentReplicas = 0; @observable currentReplicas = 0;
@ -29,16 +31,16 @@ export class DeploymentScaleDialog extends Component<Props> {
} }
static open(deployment: Deployment) { static open(deployment: Deployment) {
DeploymentScaleDialog.isOpen = true; DeploymentScaleDialog.metadata.isOpen = true;
DeploymentScaleDialog.data = deployment; DeploymentScaleDialog.metadata.data = deployment;
} }
static close() { static close() {
DeploymentScaleDialog.isOpen = false; DeploymentScaleDialog.metadata.isOpen = false;
} }
get deployment() { get deployment() {
return DeploymentScaleDialog.data; return DeploymentScaleDialog.metadata.data;
} }
close = () => { close = () => {
@ -149,7 +151,7 @@ export class DeploymentScaleDialog extends Component<Props> {
return ( return (
<Dialog <Dialog
{...dialogProps} {...dialogProps}
isOpen={DeploymentScaleDialog.isOpen} isOpen={DeploymentScaleDialog.metadata.isOpen}
className={cssNames("DeploymentScaleDialog", className)} className={cssNames("DeploymentScaleDialog", className)}
onOpen={this.onOpen} onOpen={this.onOpen}
onClose={this.onClose} onClose={this.onClose}

View File

@ -1,7 +1,7 @@
import "./replicaset-scale-dialog.scss"; import "./replicaset-scale-dialog.scss";
import React, { Component } from "react"; import React, { Component } from "react";
import { computed, observable, makeObservable } from "mobx"; import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -16,8 +16,10 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class ReplicaSetScaleDialog extends Component<Props> { export class ReplicaSetScaleDialog extends Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable static data: ReplicaSet = null; isOpen: false,
data: null as ReplicaSet,
});
@observable ready = false; @observable ready = false;
@observable currentReplicas = 0; @observable currentReplicas = 0;
@ -29,16 +31,16 @@ export class ReplicaSetScaleDialog extends Component<Props> {
} }
static open(replicaSet: ReplicaSet) { static open(replicaSet: ReplicaSet) {
ReplicaSetScaleDialog.isOpen = true; ReplicaSetScaleDialog.metadata.isOpen = true;
ReplicaSetScaleDialog.data = replicaSet; ReplicaSetScaleDialog.metadata.data = replicaSet;
} }
static close() { static close() {
ReplicaSetScaleDialog.isOpen = false; ReplicaSetScaleDialog.metadata.isOpen = false;
} }
get replicaSet() { get replicaSet() {
return ReplicaSetScaleDialog.data; return ReplicaSetScaleDialog.metadata.data;
} }
close = () => { close = () => {
@ -151,7 +153,7 @@ export class ReplicaSetScaleDialog extends Component<Props> {
return ( return (
<Dialog <Dialog
{...dialogProps} {...dialogProps}
isOpen={ReplicaSetScaleDialog.isOpen} isOpen={ReplicaSetScaleDialog.metadata.isOpen}
className={cssNames("ReplicaSetScaleDialog", className)} className={cssNames("ReplicaSetScaleDialog", className)}
onOpen={this.onOpen} onOpen={this.onOpen}
onClose={this.onClose} onClose={this.onClose}

View File

@ -2,7 +2,7 @@ import "./statefulset-scale-dialog.scss";
import { StatefulSet, statefulSetApi } from "../../api/endpoints"; import { StatefulSet, statefulSetApi } from "../../api/endpoints";
import React, { Component } from "react"; import React, { Component } from "react";
import { computed, observable, makeObservable } from "mobx"; import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
@ -16,8 +16,10 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class StatefulSetScaleDialog extends Component<Props> { export class StatefulSetScaleDialog extends Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable static data: StatefulSet = null; isOpen: false,
data: null as StatefulSet,
});
@observable ready = false; @observable ready = false;
@observable currentReplicas = 0; @observable currentReplicas = 0;
@ -29,16 +31,16 @@ export class StatefulSetScaleDialog extends Component<Props> {
} }
static open(statefulSet: StatefulSet) { static open(statefulSet: StatefulSet) {
StatefulSetScaleDialog.isOpen = true; StatefulSetScaleDialog.metadata.isOpen = true;
StatefulSetScaleDialog.data = statefulSet; StatefulSetScaleDialog.metadata.data = statefulSet;
} }
static close() { static close() {
StatefulSetScaleDialog.isOpen = false; StatefulSetScaleDialog.metadata.isOpen = false;
} }
get statefulSet() { get statefulSet() {
return StatefulSetScaleDialog.data; return StatefulSetScaleDialog.metadata.data;
} }
close = () => { close = () => {
@ -151,7 +153,7 @@ export class StatefulSetScaleDialog extends Component<Props> {
return ( return (
<Dialog <Dialog
{...dialogProps} {...dialogProps}
isOpen={StatefulSetScaleDialog.isOpen} isOpen={StatefulSetScaleDialog.metadata.isOpen}
className={cssNames("StatefulSetScaleDialog", className)} className={cssNames("StatefulSetScaleDialog", className)}
onOpen={this.onOpen} onOpen={this.onOpen}
onClose={this.onClose} onClose={this.onClose}

View File

@ -83,7 +83,7 @@ export class App extends React.Component {
whatInput.ask(); // Start to monitor user input device whatInput.ask(); // Start to monitor user input device
// Setup hosted cluster context // Setup hosted cluster context
KubeObjectStore.defaultContext = clusterContext; KubeObjectStore.metadata.context = clusterContext;
kubeWatchApi.context = clusterContext; kubeWatchApi.context = clusterContext;
} }

View File

@ -1,7 +1,7 @@
import "./confirm-dialog.scss"; import "./confirm-dialog.scss";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { observable, makeObservable } from "mobx"; import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { cssNames, noop, prevDefault } from "../../utils"; import { cssNames, noop, prevDefault } from "../../utils";
import { Button, ButtonProps } from "../button"; import { Button, ButtonProps } from "../button";
@ -23,8 +23,10 @@ export interface ConfirmDialogParams {
@observer @observer
export class ConfirmDialog extends React.Component<ConfirmDialogProps> { export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
@observable static isOpen = false; static metadata = observable({
@observable.ref static params: ConfirmDialogParams; isOpen: false,
params: null as ConfirmDialogParams,
});
@observable isSaving = false; @observable isSaving = false;
@ -34,12 +36,12 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
} }
static open(params: ConfirmDialogParams) { static open(params: ConfirmDialogParams) {
ConfirmDialog.isOpen = true; ConfirmDialog.metadata.isOpen = true;
ConfirmDialog.params = params; ConfirmDialog.metadata.params = params;
} }
static close() { static close() {
ConfirmDialog.isOpen = false; ConfirmDialog.metadata.isOpen = false;
} }
public defaultParams: ConfirmDialogParams = { public defaultParams: ConfirmDialogParams = {
@ -50,7 +52,7 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
}; };
get params(): ConfirmDialogParams { get params(): ConfirmDialogParams {
return Object.assign({}, this.defaultParams, ConfirmDialog.params); return Object.assign({}, this.defaultParams, ConfirmDialog.metadata.params);
} }
ok = async () => { ok = async () => {
@ -83,7 +85,7 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
<Dialog <Dialog
{...dialogProps} {...dialogProps}
className={cssNames("ConfirmDialog", className)} className={cssNames("ConfirmDialog", className)}
isOpen={ConfirmDialog.isOpen} isOpen={ConfirmDialog.metadata.isOpen}
onClose={this.onClose} onClose={this.onClose}
close={this.close} close={this.close}
> >

View File

@ -1,6 +1,6 @@
import MD5 from "crypto-js/md5"; import MD5 from "crypto-js/md5";
import { action, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx"; import { action, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx";
import { autobind, createStorage } from "../../utils"; import { autobind, createStorage, StorageHelper } from "../../utils";
import throttle from "lodash/throttle"; import throttle from "lodash/throttle";
export type TabId = string; export type TabId = string;
@ -30,20 +30,21 @@ export interface DockStorageState {
export class DockStore implements DockStorageState { export class DockStore implements DockStorageState {
constructor() { constructor() {
this.storage = createStorage<DockStorageState>("dock", {
height: 300,
tabs: [
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" },
],
});
makeObservable(this); makeObservable(this);
this.init(); this.init();
} }
readonly minHeight = 100; private storage: StorageHelper<DockStorageState>;
public readonly minHeight = 100;
@observable fullSize = false; @observable fullSize = false;
private storage = createStorage<DockStorageState>("dock", {
height: 300,
tabs: [
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" },
],
});
get isOpen(): boolean { get isOpen(): boolean {
return this.storage.get().isOpen; return this.storage.get().isOpen;
} }

View File

@ -1,24 +1,19 @@
import "./terminal-tab.scss"; import "./terminal-tab.scss";
import React from "react"; import React from "react";
import { reaction } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { autobind, cssNames } from "../../utils"; import { autobind, cssNames } from "../../utils";
import { DockTab, DockTabProps } from "./dock-tab"; import { DockTab, DockTabProps } from "./dock-tab";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { terminalStore } from "./terminal.store"; import { terminalStore } from "./terminal.store";
import { dockStore } from "./dock.store"; import { dockStore } from "./dock.store";
import { reaction, makeObservable } from "mobx";
interface Props extends DockTabProps { interface Props extends DockTabProps {
} }
@observer @observer
export class TerminalTab extends React.Component<Props> { export class TerminalTab extends React.Component<Props> {
constructor(props: Props) {
super(props);
makeObservable(this);
}
componentDidMount() { componentDidMount() {
reaction(() => this.isDisconnected === true, () => { reaction(() => this.isDisconnected === true, () => {
dockStore.closeTab(this.tabId); dockStore.closeTab(this.tabId);

View File

@ -8,7 +8,7 @@ import { Avatar } from "@material-ui/core";
import { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../../common/catalog"; import { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../../common/catalog";
import { Menu, MenuItem } from "../menu"; import { Menu, MenuItem } from "../menu";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { computed, observable, makeObservable } from "mobx"; import { computed, makeObservable, observable } from "mobx";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import { HotbarStore } from "../../../common/hotbar-store"; import { HotbarStore } from "../../../common/hotbar-store";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
@ -58,10 +58,12 @@ export class HotbarIcon extends React.Component<Props> {
} }
@computed get iconString() { @computed get iconString() {
const [rawFirst, rawSecond, rawThird] = getNameParts(this.props.entity.metadata.name); const { name, labels } = this.props.entity.metadata;
const iconSourceText = name ?? labels?.distro ?? "";
const [rawFirst, rawSecond, rawThird] = getNameParts(iconSourceText);
const splitter = new GraphemeSplitter(); const splitter = new GraphemeSplitter();
const first = splitter.iterateGraphemes(rawFirst); const first = splitter.iterateGraphemes(rawFirst);
const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first; const second = rawSecond ? splitter.iterateGraphemes(rawSecond) : first;
const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty(); const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty();
return [ return [
@ -76,13 +78,13 @@ export class HotbarIcon extends React.Component<Props> {
const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity); const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity);
if (!category) { if (!category) {
return <Icon material="bug_report" className={className} />; return <Icon material="bug_report" className={className}/>;
} }
if (category.metadata.icon.includes("<svg")) { if (category.metadata.icon.includes("<svg")) {
return <Icon svg={category.metadata.icon} className={className} />; return <Icon svg={category.metadata.icon} className={className}/>;
} else { } else {
return <Icon material={category.metadata.icon} className={className} />; return <Icon material={category.metadata.icon} className={className}/>;
} }
} }
@ -151,22 +153,22 @@ export class HotbarIcon extends React.Component<Props> {
> >
{this.iconString} {this.iconString}
</Avatar> </Avatar>
{ this.badgeIcon } {this.badgeIcon}
<Menu <Menu
usePortal={false} usePortal={false}
htmlFor={entityIconId} htmlFor={entityIconId}
className="HotbarIconMenu" className="HotbarIconMenu"
isOpen={this.menuOpen} isOpen={this.menuOpen}
toggleEvent="contextmenu" toggleEvent="contextmenu"
position={{right: true, bottom: true }} // FIXME: position does not work position={{ right: true, bottom: true }} // FIXME: position does not work
open={() => onOpen()} open={() => onOpen()}
close={() => this.toggleMenu()}> close={() => this.toggleMenu()}>
<MenuItem key="remove-from-hotbar" onClick={() => this.removeFromHotbar(entity) }> <MenuItem key="remove-from-hotbar" onClick={() => this.removeFromHotbar(entity)}>
<Icon material="clear" small interactive={true} title="Remove from hotbar"/> Remove from Hotbar <Icon material="clear" small interactive={true} title="Remove from hotbar"/> Remove from Hotbar
</MenuItem> </MenuItem>
{ this.contextMenu && menuItems.map((menuItem) => { {this.contextMenu && menuItems.map((menuItem) => {
return ( return (
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem) }> <MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>
<Icon material={menuItem.icon} small interactive={true} title={menuItem.title}/> {menuItem.title} <Icon material={menuItem.icon} small interactive={true} title={menuItem.title}/> {menuItem.title}
</MenuItem> </MenuItem>
); );

View File

@ -24,8 +24,10 @@ interface Props extends Partial<DialogProps> {
@observer @observer
export class KubeConfigDialog extends React.Component<Props> { export class KubeConfigDialog extends React.Component<Props> {
@observable static isOpen = false; static metadata = observable({
@observable static data: IKubeconfigDialogData = null; isOpen: false,
data: null as IKubeconfigDialogData,
});
@observable.ref configTextArea: HTMLTextAreaElement; // required for coping config text @observable.ref configTextArea: HTMLTextAreaElement; // required for coping config text
@observable config = ""; // parsed kubeconfig in yaml format @observable config = ""; // parsed kubeconfig in yaml format
@ -36,16 +38,16 @@ export class KubeConfigDialog extends React.Component<Props> {
} }
static open(data: IKubeconfigDialogData) { static open(data: IKubeconfigDialogData) {
KubeConfigDialog.isOpen = true; KubeConfigDialog.metadata.isOpen = true;
KubeConfigDialog.data = data; KubeConfigDialog.metadata.data = data;
} }
static close() { static close() {
KubeConfigDialog.isOpen = false; KubeConfigDialog.metadata.isOpen = false;
} }
get data(): IKubeconfigDialogData { get data(): IKubeconfigDialogData {
return KubeConfigDialog.data; return KubeConfigDialog.metadata.data;
} }
close = () => { close = () => {
@ -76,7 +78,7 @@ export class KubeConfigDialog extends React.Component<Props> {
}; };
render() { render() {
const { isOpen, data = {} } = KubeConfigDialog; const { isOpen, data = {} } = KubeConfigDialog.metadata;
const { ...dialogProps } = this.props; const { ...dialogProps } = this.props;
const yamlConfig = this.config; const yamlConfig = this.config;
const header = <h5>{data.title || "Kubeconfig File"}</h5>; const header = <h5>{data.title || "Kubeconfig File"}</h5>;

View File

@ -13,7 +13,9 @@ export interface KubeObjectStoreLoadingParams {
} }
export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> { export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> {
@observable static defaultContext: ClusterContext; // TODO: support multiple cluster contexts static metadata = observable({
context: null as ClusterContext, // TODO: support multiple cluster contexts
});
abstract api: KubeApi<T>; abstract api: KubeApi<T>;
public readonly limit?: number; public readonly limit?: number;
@ -29,7 +31,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
get context(): ClusterContext { get context(): ClusterContext {
return KubeObjectStore.defaultContext; return KubeObjectStore.metadata.context;
} }
@computed get contextItems(): T[] { @computed get contextItems(): T[] {