mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Refactor cluster settings to catalog entity settings (#2525)
* fix cluster settings page layout Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor cluster settings to pluggable entity settings Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix gh actions network timeout on yarn install Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * review changes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
adec401acd
commit
8dde4a1ecb
51 files changed
+437
-336
No files matched your search
@@ -0,0 +1,16 @@
|
||||
import { navigate } from "../../navigation";
|
||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { entitySettingsURL } from "../+entity-settings";
|
||||
|
||||
commandRegistry.add({
|
||||
id: "cluster.viewCurrentClusterSettings",
|
||||
title: "Cluster: View Settings",
|
||||
scope: "global",
|
||||
action: () => navigate(entitySettingsURL({
|
||||
params: {
|
||||
entityId: clusterStore.active.id
|
||||
}
|
||||
})),
|
||||
isActive: (context) => !!context.entity
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
import React from "react";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { ClusterProxySetting } from "./components/cluster-proxy-setting";
|
||||
import { ClusterNameSetting } from "./components/cluster-name-setting";
|
||||
import { ClusterHomeDirSetting } from "./components/cluster-home-dir-setting";
|
||||
import { ClusterAccessibleNamespaces } from "./components/cluster-accessible-namespaces";
|
||||
import { ClusterMetricsSetting } from "./components/cluster-metrics-setting";
|
||||
import { ShowMetricsSetting } from "./components/show-metrics";
|
||||
import { ClusterPrometheusSetting } from "./components/cluster-prometheus-setting";
|
||||
import { ClusterKubeconfig } from "./components/cluster-kubeconfig";
|
||||
import { entitySettingRegistry } from "../../../extensions/registries";
|
||||
import { CatalogEntity } from "../../api/catalog-entity";
|
||||
|
||||
|
||||
function getClusterForEntity(entity: CatalogEntity) {
|
||||
const cluster = clusterStore.getById(entity.metadata.uid);
|
||||
|
||||
if (!cluster?.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
entitySettingRegistry.add([
|
||||
{
|
||||
apiVersions: ["entity.k8slens.dev/v1alpha1"],
|
||||
kind: "KubernetesCluster",
|
||||
source: "local",
|
||||
title: "General",
|
||||
components: {
|
||||
View: (props: { entity: CatalogEntity }) => {
|
||||
const cluster = getClusterForEntity(props.entity);
|
||||
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section>
|
||||
<ClusterNameSetting cluster={cluster} />
|
||||
</section>
|
||||
<section>
|
||||
<ClusterKubeconfig cluster={cluster} />
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersions: ["entity.k8slens.dev/v1alpha1"],
|
||||
kind: "KubernetesCluster",
|
||||
title: "Proxy",
|
||||
components: {
|
||||
View: (props: { entity: CatalogEntity }) => {
|
||||
const cluster = getClusterForEntity(props.entity);
|
||||
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<ClusterProxySetting cluster={cluster} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersions: ["entity.k8slens.dev/v1alpha1"],
|
||||
kind: "KubernetesCluster",
|
||||
title: "Terminal",
|
||||
components: {
|
||||
View: (props: { entity: CatalogEntity }) => {
|
||||
const cluster = getClusterForEntity(props.entity);
|
||||
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<ClusterHomeDirSetting cluster={cluster} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersions: ["entity.k8slens.dev/v1alpha1"],
|
||||
kind: "KubernetesCluster",
|
||||
title: "Namespaces",
|
||||
components: {
|
||||
View: (props: { entity: CatalogEntity }) => {
|
||||
const cluster = getClusterForEntity(props.entity);
|
||||
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<ClusterAccessibleNamespaces cluster={cluster} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersions: ["entity.k8slens.dev/v1alpha1"],
|
||||
kind: "KubernetesCluster",
|
||||
source: "local",
|
||||
title: "Metrics",
|
||||
components: {
|
||||
View: (props: { entity: CatalogEntity }) => {
|
||||
const cluster = getClusterForEntity(props.entity);
|
||||
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section>
|
||||
<ClusterPrometheusSetting cluster={cluster} />
|
||||
</section>
|
||||
<section>
|
||||
<ClusterMetricsSetting cluster={cluster}/>
|
||||
</section>
|
||||
<section>
|
||||
<ShowMetricsSetting cluster={cluster}/>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { EditableList } from "../../editable-list";
|
||||
import { observable } from "mobx";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterAccessibleNamespaces extends React.Component<Props> {
|
||||
@observable namespaces = new Set(this.props.cluster.accessibleNamespaces);
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Accessible Namespaces" id="accessible-namespaces" />
|
||||
<EditableList
|
||||
placeholder="Add new namespace..."
|
||||
add={(newNamespace) => {
|
||||
this.namespaces.add(newNamespace);
|
||||
this.props.cluster.accessibleNamespaces = Array.from(this.namespaces);
|
||||
}}
|
||||
items={Array.from(this.namespaces)}
|
||||
remove={({ oldItem: oldNamesapce }) => {
|
||||
this.namespaces.delete(oldNamesapce);
|
||||
this.props.cluster.accessibleNamespaces = Array.from(this.namespaces);
|
||||
}}
|
||||
/>
|
||||
<small className="hint">
|
||||
This setting is useful for manually specifying which namespaces you have access to. This is useful when you do not have permissions to list namespaces.
|
||||
</small>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from "react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterHomeDirSetting extends React.Component<Props> {
|
||||
@observable directory = "";
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.directory = this.props.cluster.preferences.terminalCWD || "";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.terminalCWD = this.directory;
|
||||
};
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.directory = value;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Working Directory"/>
|
||||
<Input
|
||||
theme="round-black"
|
||||
value={this.directory}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.save}
|
||||
placeholder="$HOME"
|
||||
/>
|
||||
<small className="hint">
|
||||
An explicit start path where the terminal will be launched,{" "}
|
||||
this is used as the current working directory (cwd) for the shell process.
|
||||
</small>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { observer } from "mobx-react";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { autobind } from "../../../../common/utils";
|
||||
import { shell } from "electron";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterKubeconfig extends React.Component<Props> {
|
||||
|
||||
@autobind()
|
||||
openKubeconfig() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
shell.showItemInFolder(cluster.kubeConfigPath);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Kubeconfig" />
|
||||
|
||||
<span>
|
||||
<a className="link value" onClick={this.openKubeconfig}>{this.props.cluster.kubeConfigPath}</a>
|
||||
</span>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.MetricsSelect {
|
||||
$spacing: $padding;
|
||||
--flex-gap: #{$spacing};
|
||||
|
||||
.Badge {
|
||||
margin-top: $spacing;
|
||||
}
|
||||
|
||||
.Button {
|
||||
margin-top: $spacing;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import "./cluster-metrics-setting.scss";
|
||||
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { Select, SelectOption } from "../../select/select";
|
||||
import { Icon } from "../../icon/icon";
|
||||
import { Button } from "../../button/button";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { observable, reaction } from "mobx";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
export enum ResourceType {
|
||||
Cluster = "Cluster",
|
||||
Node = "Node",
|
||||
Pod = "Pod",
|
||||
Deployment = "Deployment",
|
||||
StatefulSet = "StatefulSet",
|
||||
Container = "Container",
|
||||
Ingress = "Ingress",
|
||||
VolumeClaim = "VolumeClaim",
|
||||
ReplicaSet = "ReplicaSet",
|
||||
DaemonSet = "DaemonSet",
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterMetricsSetting extends React.Component<Props> {
|
||||
@observable hiddenMetrics = observable.set<string>();
|
||||
|
||||
componentDidMount() {
|
||||
this.hiddenMetrics = observable.set<string>(this.props.cluster.preferences.hiddenMetrics ?? []);
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.props.cluster.preferences.hiddenMetrics, () => {
|
||||
this.hiddenMetrics = observable.set<string>(this.props.cluster.preferences.hiddenMetrics ?? []);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.hiddenMetrics = Array.from(this.hiddenMetrics);
|
||||
};
|
||||
|
||||
onChangeSelect = (values: SelectOption<ResourceType>[]) => {
|
||||
for (const { value } of values) {
|
||||
if (this.hiddenMetrics.has(value)) {
|
||||
this.hiddenMetrics.delete(value);
|
||||
} else {
|
||||
this.hiddenMetrics.add(value);
|
||||
}
|
||||
}
|
||||
this.save();
|
||||
};
|
||||
|
||||
onChangeButton = () => {
|
||||
Object.keys(ResourceType).map(value =>
|
||||
this.hiddenMetrics.add(value)
|
||||
);
|
||||
this.save();
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
this.hiddenMetrics.clear();
|
||||
this.save();
|
||||
};
|
||||
|
||||
formatOptionLabel = ({ value: resource }: SelectOption<ResourceType>) => (
|
||||
<div className="flex gaps align-center">
|
||||
<span>{resource}</span>
|
||||
{this.hiddenMetrics.has(resource) && <Icon smallest material="check" className="box right" />}
|
||||
</div>
|
||||
);
|
||||
|
||||
renderMetricsSelect() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
className="box grow"
|
||||
placeholder="Select metrics to hide..."
|
||||
isMulti
|
||||
isSearchable
|
||||
onMenuClose={this.save}
|
||||
closeMenuOnSelect={false}
|
||||
controlShouldRenderValue={false}
|
||||
options={Object.values(ResourceType)}
|
||||
onChange={this.onChangeSelect}
|
||||
formatOptionLabel={this.formatOptionLabel}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
label="Hide all metrics"
|
||||
onClick={this.onChangeButton}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
label="Reset"
|
||||
onClick={this.reset}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="MetricsSelect">
|
||||
<SubTitle title={"Hide metrics from the UI"}/>
|
||||
<div className="flex gaps">
|
||||
{this.renderMetricsSelect()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { isRequired } from "../../input/input_validators";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterNameSetting extends React.Component<Props> {
|
||||
@observable name = "";
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.name = this.props.cluster.preferences.clusterName;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.clusterName = this.name;
|
||||
};
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.name = value;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Cluster Name" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
validators={isRequired}
|
||||
value={this.name}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.save}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from "react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { prometheusProviders } from "../../../../common/prometheus-providers";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Select, SelectOption } from "../../select";
|
||||
import { Input } from "../../input";
|
||||
import { observable, computed, autorun } from "mobx";
|
||||
|
||||
const options: SelectOption<string>[] = [
|
||||
{ value: "", label: "Auto detect" },
|
||||
...prometheusProviders.map(pp => ({value: pp.id, label: pp.name}))
|
||||
];
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||
@observable path = "";
|
||||
@observable provider = "";
|
||||
|
||||
@computed get canEditPrometheusPath() {
|
||||
if (this.provider === "" || this.provider === "lens") return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
|
||||
|
||||
if (prometheus) {
|
||||
const prefix = prometheus.prefix || "";
|
||||
|
||||
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
|
||||
} else {
|
||||
this.path = "";
|
||||
}
|
||||
|
||||
if (prometheusProvider) {
|
||||
this.provider = prometheusProvider.type;
|
||||
} else {
|
||||
this.provider = "";
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
parsePrometheusPath = () => {
|
||||
if (!this.provider || !this.path) {
|
||||
return null;
|
||||
}
|
||||
const parsed = this.path.split(/\/|:/, 3);
|
||||
const apiPrefix = this.path.substring(parsed.join("/").length);
|
||||
|
||||
if (!parsed[0] || !parsed[1] || !parsed[2]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
namespace: parsed[0],
|
||||
service: parsed[1],
|
||||
port: parseInt(parsed[2]),
|
||||
prefix: apiPrefix
|
||||
};
|
||||
};
|
||||
|
||||
onSaveProvider = () => {
|
||||
this.props.cluster.preferences.prometheusProvider = this.provider ?
|
||||
{ type: this.provider } :
|
||||
null;
|
||||
};
|
||||
|
||||
onSavePath = () => {
|
||||
this.props.cluster.preferences.prometheus = this.parsePrometheusPath();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Prometheus installation method"/>
|
||||
<p>
|
||||
Use pre-installed Prometheus service for metrics. Please refer to the{" "}
|
||||
<a href="https://github.com/lensapp/lens/blob/master/troubleshooting/custom-prometheus.md" target="_blank" rel="noreferrer">guide</a>{" "}
|
||||
for possible configuration changes.
|
||||
</p>
|
||||
<Select
|
||||
value={this.provider}
|
||||
onChange={({value}) => {
|
||||
this.provider = value;
|
||||
this.onSaveProvider();
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
<small className="hint">What query format is used to fetch metrics from Prometheus</small>
|
||||
{this.canEditPrometheusPath && (
|
||||
<>
|
||||
<p>Prometheus service address.</p>
|
||||
<Input
|
||||
theme="round-black"
|
||||
value={this.path}
|
||||
onChange={(value) => this.path = value}
|
||||
onBlur={this.onSavePath}
|
||||
placeholder="<namespace>/<service>:<port>"
|
||||
/>
|
||||
<small className="hint">
|
||||
An address to an existing Prometheus installation{" "}
|
||||
({"<namespace>/<service>:<port>"}). Lens tries to auto-detect address if left empty.
|
||||
</small>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from "react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input, InputValidators } from "../../input";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterProxySetting extends React.Component<Props> {
|
||||
@observable proxy = "";
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.proxy = this.props.cluster.preferences.httpsProxy || "";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.httpsProxy = this.proxy;
|
||||
};
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.proxy = value;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="HTTP Proxy" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
value={this.proxy}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.save}
|
||||
placeholder="http://<address>:<port>"
|
||||
validators={this.proxy ? InputValidators.isUrl : undefined}
|
||||
/>
|
||||
<small className="hint">
|
||||
HTTP Proxy server. Used for communicating with Kubernetes API.
|
||||
</small>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { clusterStore } from "../../../../common/cluster-store";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { autobind } from "../../../utils";
|
||||
import { Button } from "../../button";
|
||||
import { ConfirmDialog } from "../../confirm-dialog";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class RemoveClusterButton extends React.Component<Props> {
|
||||
@autobind()
|
||||
confirmRemoveCluster() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
ConfirmDialog.open({
|
||||
message: <p>Are you sure you want to remove <b>{cluster.preferences.clusterName}</b> from Lens?</p>,
|
||||
labelOk: "Yes",
|
||||
labelCancel: "No",
|
||||
ok: async () => {
|
||||
await clusterStore.removeById(cluster.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
return (
|
||||
<Button accent onClick={this.confirmRemoveCluster} className="button-area" disabled={cluster.isManaged}>
|
||||
Remove Cluster
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import "./cluster-metrics-setting.scss";
|
||||
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { observable, reaction } from "mobx";
|
||||
import { Badge } from "../../badge/badge";
|
||||
import { Icon } from "../../icon/icon";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ShowMetricsSetting extends React.Component<Props> {
|
||||
@observable hiddenMetrics = observable.set<string>();
|
||||
|
||||
componentDidMount() {
|
||||
this.hiddenMetrics = observable.set<string>(this.props.cluster.preferences.hiddenMetrics ?? []);
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.props.cluster.preferences.hiddenMetrics, () => {
|
||||
this.hiddenMetrics = observable.set<string>(this.props.cluster.preferences.hiddenMetrics ?? []);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
removeMetric(metric: string) {
|
||||
this.hiddenMetrics.delete(metric);
|
||||
this.props.cluster.preferences.hiddenMetrics = Array.from(this.hiddenMetrics);
|
||||
}
|
||||
|
||||
renderMetrics() {
|
||||
|
||||
return (
|
||||
|
||||
Array.from(this.hiddenMetrics).map(name => {
|
||||
const tooltipId = `${name}`;
|
||||
|
||||
return (
|
||||
<Badge key={name}>
|
||||
<span id={tooltipId}>{name}</span>
|
||||
<Icon
|
||||
smallest
|
||||
material="clear"
|
||||
onClick={() => this.removeMetric(name)}
|
||||
tooltip="Remove"
|
||||
/>
|
||||
</Badge>
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="MetricsSelect flex wrap gaps">
|
||||
{this.renderMetrics()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./cluster-settings";
|
||||
export * from "./cluster-settings.command";
|
||||
Reference in new issue
Block a user