mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
fix namespace filter to not jump after clicking
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
13b99afa21
commit
39cdb9e4ff
@ -10,7 +10,7 @@ import { namespaceStore } from "../+namespaces/namespace.store";
|
|||||||
@observer
|
@observer
|
||||||
export class Apps extends React.Component {
|
export class Apps extends React.Component {
|
||||||
static get tabRoutes(): TabRoute[] {
|
static get tabRoutes(): TabRoute[] {
|
||||||
const query = namespaceStore.getContextParams();
|
const query = namespaceStore.contextParams;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: <Trans>Charts</Trans>,
|
title: <Trans>Charts</Trans>,
|
||||||
@ -32,8 +32,8 @@ export class Apps extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<TabLayout className="Apps" tabs={tabRoutes}>
|
<TabLayout className="Apps" tabs={tabRoutes}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{tabRoutes.map((route, index) => <Route key={index} {...route}/>)}
|
{tabRoutes.map((route, index) => <Route key={index} {...route} />)}
|
||||||
<Redirect to={tabRoutes[0].url}/>
|
<Redirect to={tabRoutes[0].url} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export const clusterIssuersURL = buildURL("/clusterissuers");
|
|||||||
@observer
|
@observer
|
||||||
export class Config extends React.Component {
|
export class Config extends React.Component {
|
||||||
static get tabRoutes(): TabRoute[] {
|
static get tabRoutes(): TabRoute[] {
|
||||||
const query = namespaceStore.getContextParams()
|
const query = namespaceStore.contextParams
|
||||||
const routes: TabRoute[] = []
|
const routes: TabRoute[] = []
|
||||||
if (isAllowedResource("configmaps")) {
|
if (isAllowedResource("configmaps")) {
|
||||||
routes.push({
|
routes.push({
|
||||||
@ -70,8 +70,8 @@ export class Config extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<TabLayout className="Config" tabs={tabRoutes}>
|
<TabLayout className="Config" tabs={tabRoutes}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{tabRoutes.map((route, index) => <Route key={index} {...route}/>)}
|
{tabRoutes.map((route, index) => <Route key={index} {...route} />)}
|
||||||
<Redirect to={configURL({ query: namespaceStore.getContextParams() })}/>
|
<Redirect to={configURL({ query: namespaceStore.contextParams })} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,9 +14,22 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__option {
|
||||||
|
&--is-selected {
|
||||||
|
.Icon {
|
||||||
|
visibility: visible!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Icon {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.NamespaceSelectMenu {
|
.NamespaceSelectMenu {
|
||||||
@include namespaceSelectCommon;
|
@include namespaceSelectCommon;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,18 +5,16 @@ import { computed } from "mobx";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { Select, SelectOption, SelectProps } from "../select";
|
import { Select, SelectOption, SelectProps } from "../select";
|
||||||
import { cssNames, noop } from "../../utils";
|
import ReactSelect, { ActionMeta, components, OptionTypeBase, ValueType } from "react-select"
|
||||||
|
import { autobind, cssNames, noop } from "../../utils";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { namespaceStore } from "./namespace.store";
|
import { namespaceStore } from "./namespace.store";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { FilterIcon } from "../item-object-list/filter-icon";
|
|
||||||
import { FilterType } from "../item-object-list/page-filters.store";
|
|
||||||
|
|
||||||
interface Props extends SelectProps {
|
interface Props extends SelectProps {
|
||||||
showIcons?: boolean;
|
showIcons?: boolean;
|
||||||
showClusterOption?: boolean; // show cluster option on the top (default: false)
|
showClusterOption?: boolean; // show cluster option on the top (default: false)
|
||||||
clusterOptionLabel?: React.ReactNode; // label for cluster option (default: "Cluster")
|
clusterOptionLabel?: React.ReactNode; // label for cluster option (default: "Cluster")
|
||||||
customizeOptions?(nsOptions: SelectOption[]): SelectOption[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps: Partial<Props> = {
|
const defaultProps: Partial<Props> = {
|
||||||
@ -44,12 +42,15 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get options(): SelectOption[] {
|
@computed get options(): SelectOption[] {
|
||||||
const { customizeOptions, showClusterOption, clusterOptionLabel } = this.props;
|
const options: SelectOption[] = namespaceStore.items
|
||||||
let options: SelectOption[] = namespaceStore.items.map(ns => ({ value: ns.getName() }));
|
.map(ns => ({ value: ns.getName() }))
|
||||||
options = customizeOptions ? customizeOptions(options) : options;
|
.map(opt => ({ ...opt, label: this.formatOptionLabel(opt) }))
|
||||||
|
|
||||||
|
const { showClusterOption, clusterOptionLabel } = this.props;
|
||||||
if (showClusterOption) {
|
if (showClusterOption) {
|
||||||
options.unshift({ value: null, label: clusterOptionLabel });
|
options.unshift({ value: null, label: clusterOptionLabel });
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
const { value, label } = option;
|
const { value, label } = option;
|
||||||
return label || (
|
return label || (
|
||||||
<>
|
<>
|
||||||
{showIcons && <Icon small material="layers"/>}
|
{showIcons && <Icon small material="layers" />}
|
||||||
{value}
|
{value}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -70,7 +71,7 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
<Select
|
<Select
|
||||||
className={cssNames("NamespaceSelect", className)}
|
className={cssNames("NamespaceSelect", className)}
|
||||||
menuClass="NamespaceSelectMenu"
|
menuClass="NamespaceSelectMenu"
|
||||||
formatOptionLabel={this.formatOptionLabel}
|
autoConvertOptions={false}
|
||||||
options={this.options}
|
options={this.options}
|
||||||
{...selectProps}
|
{...selectProps}
|
||||||
/>
|
/>
|
||||||
@ -78,30 +79,53 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BasicNS {
|
||||||
|
label: React.ReactElement;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class NamespaceSelectFilter extends React.Component {
|
export class NamespaceSelectFilter extends React.Component {
|
||||||
|
async componentDidMount() {
|
||||||
|
if (!namespaceStore.isLoaded) {
|
||||||
|
await namespaceStore.loadAll();
|
||||||
|
namespaceStore.contextNs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind()
|
||||||
|
onChange(value: BasicNS, actionMeta: ActionMeta<BasicNS>) {
|
||||||
|
switch (actionMeta.action) {
|
||||||
|
case "select-option":
|
||||||
|
namespaceStore.contextNs.add(actionMeta.option.value)
|
||||||
|
break
|
||||||
|
case "clear":
|
||||||
|
namespaceStore.contextNs.clear()
|
||||||
|
break
|
||||||
|
case "deselect-option":
|
||||||
|
namespaceStore.contextNs.delete(actionMeta.option.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { contextNs, hasContext, toggleContext } = namespaceStore;
|
|
||||||
let placeholder = <Trans>All namespaces</Trans>;
|
|
||||||
if (contextNs.length == 1) placeholder = <Trans>Namespace: {contextNs[0]}</Trans>
|
|
||||||
if (contextNs.length >= 2) placeholder = <Trans>Namespaces: {contextNs.join(", ")}</Trans>
|
|
||||||
return (
|
return (
|
||||||
<NamespaceSelect
|
<ReactSelect
|
||||||
placeholder={placeholder}
|
placeholder={<Trans>Filter by namespace...</Trans>}
|
||||||
|
isMulti
|
||||||
closeMenuOnSelect={false}
|
closeMenuOnSelect={false}
|
||||||
isOptionSelected={() => false}
|
hideSelectedOptions={false}
|
||||||
controlShouldRenderValue={false}
|
className={cssNames("Select", "NamespaceSelect", "theme-dark")}
|
||||||
onChange={({ value: namespace }: SelectOption) => toggleContext(namespace)}
|
classNamePrefix="Select"
|
||||||
formatOptionLabel={({ value: namespace }: SelectOption) => {
|
components={{
|
||||||
const isSelected = hasContext(namespace);
|
Menu(props) {
|
||||||
return (
|
return <components.Menu {...props} className="NamespaceSelectMenu" />
|
||||||
<div className="flex gaps align-center">
|
}
|
||||||
<FilterIcon type={FilterType.NAMESPACE}/>
|
|
||||||
<span>{namespace}</span>
|
|
||||||
{isSelected && <Icon small material="check" className="box right"/>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
|
onChange={this.onChange}
|
||||||
|
options={namespaceStore.Options}
|
||||||
|
value={namespaceStore.SelectedValues}
|
||||||
|
controlShouldRenderValue={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +1,50 @@
|
|||||||
import { action, observable, reaction } from "mobx";
|
import React from "react";
|
||||||
|
import { action, computed, observable, ObservableSet, reaction } from "mobx";
|
||||||
import { autobind, createStorage } from "../../utils";
|
import { autobind, createStorage } from "../../utils";
|
||||||
import { KubeObjectStore } from "../../kube-object.store";
|
import { KubeObjectStore } from "../../kube-object.store";
|
||||||
import { Namespace, namespacesApi } from "../../api/endpoints";
|
import { Namespace, namespacesApi } from "../../api/endpoints";
|
||||||
import { IQueryParams, navigation, setQueryParams } from "../../navigation";
|
import { IQueryParams, navigation, setQueryParams } from "../../navigation";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { apiManager } from "../../api/api-manager";
|
||||||
import { isAllowedResource } from "../../../common/rbac";
|
import { isAllowedResource } from "../../../common/rbac";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||||
api = namespacesApi;
|
api = namespacesApi;
|
||||||
contextNs = observable.array<string>();
|
contextNs = observable.set<string>();
|
||||||
|
|
||||||
protected storage = createStorage<string[]>("context_ns", this.contextNs);
|
protected storage = createStorage<Set<string>>("context_ns", this.contextNs, {
|
||||||
|
parse(from: string) {
|
||||||
get initNamespaces() {
|
return new Set(JSON.parse(from))
|
||||||
const fromUrl = navigation.searchParams.getAsArray("namespaces");
|
},
|
||||||
return fromUrl.length ? fromUrl : this.storage.get();
|
stringify(from: Set<string>) {
|
||||||
}
|
return JSON.stringify([...from.values()])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// restore context namespaces
|
// restore context namespaces
|
||||||
const { initNamespaces: namespaces } = this;
|
const fromUrl = navigation.searchParams.getAsArray("namespaces")
|
||||||
this.setContext(namespaces);
|
const namespaces = fromUrl.length ? fromUrl : this.storage.get();
|
||||||
this.updateUrl(namespaces);
|
this.context = [...namespaces];
|
||||||
|
this.updateUrl(...namespaces);
|
||||||
|
|
||||||
// sync with local-storage & url-search-params
|
// sync with local-storage & url-search-params
|
||||||
reaction(() => this.contextNs.toJS(), namespaces => {
|
reaction(() => this.contextNs.toJS(), namespaces => {
|
||||||
this.storage.set(namespaces);
|
this.storage.set(namespaces);
|
||||||
this.updateUrl(namespaces);
|
this.updateUrl(...namespaces.values());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextParams(): Partial<IQueryParams> {
|
@computed
|
||||||
return {
|
get contextParams(): IQueryParams {
|
||||||
namespaces: this.contextNs
|
const namespaces = [...this.contextNs.values()]
|
||||||
}
|
return { namespaces }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateUrl(namespaces: string[]) {
|
protected updateUrl(...namespaces: string[]) {
|
||||||
setQueryParams({ namespaces }, { replace: true })
|
setQueryParams({ namespaces }, { replace: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,18 +73,42 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext(namespaces: string[]) {
|
@action
|
||||||
|
set context(namespaces: string[]) {
|
||||||
this.contextNs.replace(namespaces);
|
this.contextNs.replace(namespaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
hasContext(namespace: string | string[]) {
|
hasContext(namespace: string | string[]) {
|
||||||
const context = Array.isArray(namespace) ? namespace : [namespace];
|
const context = Array.isArray(namespace) ? namespace : [namespace];
|
||||||
return context.every(namespace => this.contextNs.includes(namespace));
|
return context.every(namespace => this.contextNs.has(namespace));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
toggleContext(namespace: string) {
|
toggleContext(namespace: string) {
|
||||||
if (this.hasContext(namespace)) this.contextNs.remove(namespace);
|
if (this.contextNs.has(namespace)) {
|
||||||
else this.contextNs.push(namespace);
|
this.contextNs.delete(namespace)
|
||||||
|
} else {
|
||||||
|
this.contextNs.add(namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get Options() {
|
||||||
|
return this.items.map(namespace => ({
|
||||||
|
value: namespace.getName(),
|
||||||
|
label: (
|
||||||
|
<div className="flex gaps align-center">
|
||||||
|
<span>{namespace.getName()}</span>
|
||||||
|
<Icon small material="check" className="box right" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get SelectedValues() {
|
||||||
|
return this.Options.filter(({ value }) => this.contextNs.has(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -20,7 +20,7 @@ interface Props extends RouteComponentProps<{}> {
|
|||||||
@observer
|
@observer
|
||||||
export class Network extends React.Component<Props> {
|
export class Network extends React.Component<Props> {
|
||||||
static get tabRoutes(): TabRoute[] {
|
static get tabRoutes(): TabRoute[] {
|
||||||
const query = namespaceStore.getContextParams()
|
const query = namespaceStore.contextParams
|
||||||
const routes: TabRoute[] = [];
|
const routes: TabRoute[] = [];
|
||||||
if (isAllowedResource("services")) {
|
if (isAllowedResource("services")) {
|
||||||
routes.push({
|
routes.push({
|
||||||
@ -62,8 +62,8 @@ export class Network extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<TabLayout className="Network" tabs={tabRoutes}>
|
<TabLayout className="Network" tabs={tabRoutes}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{tabRoutes.map((route, index) => <Route key={index} {...route}/>)}
|
{tabRoutes.map((route, index) => <Route key={index} {...route} />)}
|
||||||
<Redirect to={networkURL({ query: namespaceStore.getContextParams() })}/>
|
<Redirect to={networkURL({ query: namespaceStore.contextParams })} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ interface Props extends RouteComponentProps<{}> {
|
|||||||
export class Storage extends React.Component<Props> {
|
export class Storage extends React.Component<Props> {
|
||||||
static get tabRoutes() {
|
static get tabRoutes() {
|
||||||
const tabRoutes: TabRoute[] = [];
|
const tabRoutes: TabRoute[] = [];
|
||||||
const query = namespaceStore.getContextParams()
|
const query = namespaceStore.contextParams
|
||||||
|
|
||||||
tabRoutes.push({
|
tabRoutes.push({
|
||||||
title: <Trans>Persistent Volume Claims</Trans>,
|
title: <Trans>Persistent Volume Claims</Trans>,
|
||||||
@ -54,8 +54,8 @@ export class Storage extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<TabLayout className="Storage" tabs={tabRoutes}>
|
<TabLayout className="Storage" tabs={tabRoutes}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{tabRoutes.map((route, index) => <Route key={index} {...route}/>)}
|
{tabRoutes.map((route, index) => <Route key={index} {...route} />)}
|
||||||
<Redirect to={storageURL({ query: namespaceStore.getContextParams() })}/>
|
<Redirect to={storageURL({ query: namespaceStore.contextParams })} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ interface Props extends RouteComponentProps<{}> {
|
|||||||
export class UserManagement extends React.Component<Props> {
|
export class UserManagement extends React.Component<Props> {
|
||||||
static get tabRoutes() {
|
static get tabRoutes() {
|
||||||
const tabRoutes: TabRoute[] = [];
|
const tabRoutes: TabRoute[] = [];
|
||||||
const query = namespaceStore.getContextParams()
|
const query = namespaceStore.contextParams
|
||||||
tabRoutes.push(
|
tabRoutes.push(
|
||||||
{
|
{
|
||||||
title: <Trans>Service Accounts</Trans>,
|
title: <Trans>Service Accounts</Trans>,
|
||||||
@ -57,8 +57,8 @@ export class UserManagement extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<TabLayout className="UserManagement" tabs={tabRoutes}>
|
<TabLayout className="UserManagement" tabs={tabRoutes}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{tabRoutes.map((route, index) => <Route key={index} {...route}/>)}
|
{tabRoutes.map((route, index) => <Route key={index} {...route} />)}
|
||||||
<Redirect to={usersManagementURL({ query: namespaceStore.getContextParams() })}/>
|
<Redirect to={usersManagementURL({ query: namespaceStore.contextParams })} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -21,55 +21,55 @@ import { isAllowedResource } from "../../../common/rbac";
|
|||||||
export class OverviewStatuses extends React.Component {
|
export class OverviewStatuses extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const { contextNs } = namespaceStore;
|
const { contextNs } = namespaceStore;
|
||||||
const pods = isAllowedResource("pods") ? podsStore.getAllByNs(contextNs) : [];
|
const pods = isAllowedResource("pods") ? podsStore.getAllByNs([...contextNs]) : [];
|
||||||
const deployments = isAllowedResource("deployments") ? deploymentStore.getAllByNs(contextNs) : [];
|
const deployments = isAllowedResource("deployments") ? deploymentStore.getAllByNs([...contextNs]) : [];
|
||||||
const statefulSets = isAllowedResource("statefulsets") ? statefulSetStore.getAllByNs(contextNs) : [];
|
const statefulSets = isAllowedResource("statefulsets") ? statefulSetStore.getAllByNs([...contextNs]) : [];
|
||||||
const daemonSets = isAllowedResource("daemonsets") ? daemonSetStore.getAllByNs(contextNs) : [];
|
const daemonSets = isAllowedResource("daemonsets") ? daemonSetStore.getAllByNs([...contextNs]) : [];
|
||||||
const jobs = isAllowedResource("jobs") ? jobStore.getAllByNs(contextNs) : [];
|
const jobs = isAllowedResource("jobs") ? jobStore.getAllByNs([...contextNs]) : [];
|
||||||
const cronJobs = isAllowedResource("cronjobs") ? cronJobStore.getAllByNs(contextNs) : [];
|
const cronJobs = isAllowedResource("cronjobs") ? cronJobStore.getAllByNs([...contextNs]) : [];
|
||||||
return (
|
return (
|
||||||
<div className="OverviewStatuses">
|
<div className="OverviewStatuses">
|
||||||
<div className="header flex gaps align-center">
|
<div className="header flex gaps align-center">
|
||||||
<h5 className="box grow"><Trans>Overview</Trans></h5>
|
<h5 className="box grow"><Trans>Overview</Trans></h5>
|
||||||
<NamespaceSelectFilter/>
|
<NamespaceSelectFilter />
|
||||||
</div>
|
</div>
|
||||||
<PageFiltersList/>
|
<PageFiltersList />
|
||||||
<div className="workloads">
|
<div className="workloads">
|
||||||
{isAllowedResource("pods") &&
|
{isAllowedResource("pods") &&
|
||||||
<div className="workload">
|
<div className="workload">
|
||||||
<div className="title"><Link to={podsURL()}><Trans>Pods</Trans> ({pods.length})</Link></div>
|
<div className="title"><Link to={podsURL()}><Trans>Pods</Trans> ({pods.length})</Link></div>
|
||||||
<OverviewWorkloadStatus status={podsStore.getStatuses(pods)}/>
|
<OverviewWorkloadStatus status={podsStore.getStatuses(pods)} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{isAllowedResource("deployments") &&
|
{isAllowedResource("deployments") &&
|
||||||
<div className="workload">
|
<div className="workload">
|
||||||
<div className="title"><Link to={deploymentsURL()}><Trans>Deployments</Trans> ({deployments.length})</Link></div>
|
<div className="title"><Link to={deploymentsURL()}><Trans>Deployments</Trans> ({deployments.length})</Link></div>
|
||||||
<OverviewWorkloadStatus status={deploymentStore.getStatuses(deployments)}/>
|
<OverviewWorkloadStatus status={deploymentStore.getStatuses(deployments)} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{isAllowedResource("statefulsets") &&
|
{isAllowedResource("statefulsets") &&
|
||||||
<div className="workload">
|
<div className="workload">
|
||||||
<div className="title"><Link to={statefulSetsURL()}><Trans>StatefulSets</Trans> ({statefulSets.length})</Link></div>
|
<div className="title"><Link to={statefulSetsURL()}><Trans>StatefulSets</Trans> ({statefulSets.length})</Link></div>
|
||||||
<OverviewWorkloadStatus status={statefulSetStore.getStatuses(statefulSets)}/>
|
<OverviewWorkloadStatus status={statefulSetStore.getStatuses(statefulSets)} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{isAllowedResource("daemonsets") &&
|
{isAllowedResource("daemonsets") &&
|
||||||
<div className="workload">
|
<div className="workload">
|
||||||
<div className="title"><Link to={daemonSetsURL()}><Trans>DaemonSets</Trans> ({daemonSets.length})</Link></div>
|
<div className="title"><Link to={daemonSetsURL()}><Trans>DaemonSets</Trans> ({daemonSets.length})</Link></div>
|
||||||
<OverviewWorkloadStatus status={daemonSetStore.getStatuses(daemonSets)}/>
|
<OverviewWorkloadStatus status={daemonSetStore.getStatuses(daemonSets)} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{isAllowedResource("jobs") &&
|
{isAllowedResource("jobs") &&
|
||||||
<div className="workload">
|
<div className="workload">
|
||||||
<div className="title"><Link to={jobsURL()}><Trans>Jobs</Trans> ({jobs.length})</Link></div>
|
<div className="title"><Link to={jobsURL()}><Trans>Jobs</Trans> ({jobs.length})</Link></div>
|
||||||
<OverviewWorkloadStatus status={jobStore.getStatuses(jobs)}/>
|
<OverviewWorkloadStatus status={jobStore.getStatuses(jobs)} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{isAllowedResource("cronjobs") &&
|
{isAllowedResource("cronjobs") &&
|
||||||
<div className="workload">
|
<div className="workload">
|
||||||
<div className="title"><Link to={cronJobsURL()}><Trans>CronJobs</Trans> ({cronJobs.length})</Link></div>
|
<div className="title"><Link to={cronJobsURL()}><Trans>CronJobs</Trans> ({cronJobs.length})</Link></div>
|
||||||
<OverviewWorkloadStatus status={cronJobStore.getStatuses(cronJobs)}/>
|
<OverviewWorkloadStatus status={cronJobStore.getStatuses(cronJobs)} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ interface Props extends RouteComponentProps {
|
|||||||
@observer
|
@observer
|
||||||
export class Workloads extends React.Component<Props> {
|
export class Workloads extends React.Component<Props> {
|
||||||
static get tabRoutes(): TabRoute[] {
|
static get tabRoutes(): TabRoute[] {
|
||||||
const query = namespaceStore.getContextParams();
|
const query = namespaceStore.contextParams;
|
||||||
const routes: TabRoute[] = [
|
const routes: TabRoute[] = [
|
||||||
{
|
{
|
||||||
title: <Trans>Overview</Trans>,
|
title: <Trans>Overview</Trans>,
|
||||||
@ -88,8 +88,8 @@ export class Workloads extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<TabLayout className="Workloads" tabs={tabRoutes}>
|
<TabLayout className="Workloads" tabs={tabRoutes}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{tabRoutes.map((route, index) => <Route key={index} {...route}/>)}
|
{tabRoutes.map((route, index) => <Route key={index} {...route} />)}
|
||||||
<Redirect to={workloadsURL({ query: namespaceStore.getContextParams() })}/>
|
<Redirect to={workloadsURL({ query: namespaceStore.contextParams })} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -30,17 +30,17 @@ export class PageFiltersStore {
|
|||||||
protected syncWithContextNamespace() {
|
protected syncWithContextNamespace() {
|
||||||
const disposers = [
|
const disposers = [
|
||||||
reaction(() => this.getValues(FilterType.NAMESPACE), filteredNs => {
|
reaction(() => this.getValues(FilterType.NAMESPACE), filteredNs => {
|
||||||
if (filteredNs.length !== namespaceStore.contextNs.length) {
|
if (filteredNs.length !== namespaceStore.contextNs.size) {
|
||||||
namespaceStore.setContext(filteredNs);
|
namespaceStore.context = filteredNs;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
reaction(() => namespaceStore.contextNs.toJS(), contextNs => {
|
reaction(() => namespaceStore.contextNs.toJS(), contextNs => {
|
||||||
const filteredNs = this.getValues(FilterType.NAMESPACE);
|
const filteredNs = this.getValues(FilterType.NAMESPACE);
|
||||||
const isChanged = contextNs.length !== filteredNs.length;
|
const isChanged = contextNs.size !== filteredNs.length;
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
this.filters.replace([
|
this.filters.replace([
|
||||||
...this.filters.filter(({ type }) => type !== FilterType.NAMESPACE),
|
...this.filters.filter(({ type }) => type !== FilterType.NAMESPACE),
|
||||||
...contextNs.map(ns => ({ type: FilterType.NAMESPACE, value: ns })),
|
...[...contextNs.values()].map(ns => ({ type: FilterType.NAMESPACE, value: ns })),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { toggle, isPinned, className } = this.props;
|
const { toggle, isPinned, className } = this.props;
|
||||||
const query = namespaceStore.getContextParams();
|
const query = namespaceStore.contextParams;
|
||||||
return (
|
return (
|
||||||
<SidebarContext.Provider value={{ pinned: isPinned }}>
|
<SidebarContext.Provider value={{ pinned: isPinned }}>
|
||||||
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
|
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
$menuBackgroundColor: $contentColor;
|
$menuBackgroundColor: $contentColor;
|
||||||
$menuSelectedOptionBgc: $layoutBackground;
|
$menuSelectedOptionBgc: $selectedBackground;
|
||||||
|
|
||||||
--select-menu-bgc: #{$menuBackgroundColor};
|
--select-menu-bgc: #{$menuBackgroundColor};
|
||||||
--select-menu-border-color: #{$halfGray};
|
--select-menu-border-color: #{$halfGray};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { createStorage, IStorageHelperOptions } from "../utils";
|
import { createStorage, StorageHelperOptions } from "../utils";
|
||||||
|
|
||||||
export function useStorage<T>(key: string, initialValue?: T, options?: IStorageHelperOptions) {
|
export function useStorage<T>(key: string, initialValue?: T, options?: StorageHelperOptions<T>) {
|
||||||
const storage = createStorage(key, initialValue, options);
|
const storage = createStorage(key, initialValue, options);
|
||||||
const [storageValue, setStorageValue] = useState(storage.get());
|
const [storageValue, setStorageValue] = useState(storage.get());
|
||||||
const setValue = (value: T) => {
|
const setValue = (value: T) => {
|
||||||
@ -9,4 +9,4 @@ export function useStorage<T>(key: string, initialValue?: T, options?: IStorageH
|
|||||||
storage.set(value);
|
storage.set(value);
|
||||||
};
|
};
|
||||||
return [storageValue, setValue] as [T, (value: T) => void];
|
return [storageValue, setValue] as [T, (value: T) => void];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"borderFaintColor": "#373a3e",
|
"borderFaintColor": "#373a3e",
|
||||||
"mainBackground": "#1e2124",
|
"mainBackground": "#1e2124",
|
||||||
"contentColor": "#262b2f",
|
"contentColor": "#262b2f",
|
||||||
|
"selectedBackground": "#444444",
|
||||||
"layoutBackground": "#2e3136",
|
"layoutBackground": "#2e3136",
|
||||||
"layoutTabsBackground": "#252729",
|
"layoutTabsBackground": "#252729",
|
||||||
"layoutTabsActiveColor": "#ffffff",
|
"layoutTabsActiveColor": "#ffffff",
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"borderFaintColor": "#dfdfdf",
|
"borderFaintColor": "#dfdfdf",
|
||||||
"mainBackground": "#f1f1f1",
|
"mainBackground": "#f1f1f1",
|
||||||
"contentColor": "#ffffff",
|
"contentColor": "#ffffff",
|
||||||
|
"selectedBackground": "#cccccc",
|
||||||
"layoutBackground": "#e8e8e8",
|
"layoutBackground": "#e8e8e8",
|
||||||
"layoutTabsBackground": "#f8f8f8",
|
"layoutTabsBackground": "#f8f8f8",
|
||||||
"layoutTabsActiveColor": "#333333",
|
"layoutTabsActiveColor": "#333333",
|
||||||
@ -52,7 +53,7 @@
|
|||||||
"helmDescriptionPreColor": "#555555",
|
"helmDescriptionPreColor": "#555555",
|
||||||
"colorSuccess": "#206923",
|
"colorSuccess": "#206923",
|
||||||
"colorOk": "#399c3d",
|
"colorOk": "#399c3d",
|
||||||
"colorInfo": "#2d71a4",
|
"colorInfo": "#deebff",
|
||||||
"colorError": "#ce3933",
|
"colorError": "#ce3933",
|
||||||
"colorSoftError": "#e85555",
|
"colorSoftError": "#e85555",
|
||||||
"colorWarning": "#ff9800",
|
"colorWarning": "#ff9800",
|
||||||
@ -91,7 +92,7 @@
|
|||||||
"drawerSubtitleBackground": "#f1f1f1",
|
"drawerSubtitleBackground": "#f1f1f1",
|
||||||
"drawerItemNameColor": "#727272",
|
"drawerItemNameColor": "#727272",
|
||||||
"drawerItemValueColor": "#555555",
|
"drawerItemValueColor": "#555555",
|
||||||
"clusterMenuBackground": "#e8e8e8",
|
"clusterMenuBackground": "#cccccc",
|
||||||
"clusterMenuBorderColor": "#c9cfd3",
|
"clusterMenuBorderColor": "#c9cfd3",
|
||||||
"clusterSettingsBackground": "#ffffff",
|
"clusterSettingsBackground": "#ffffff",
|
||||||
"addClusterIconColor": "#8d8d8d",
|
"addClusterIconColor": "#8d8d8d",
|
||||||
|
|||||||
@ -123,6 +123,7 @@ $iconActiveBackground: var(--iconActiveBackground);
|
|||||||
$filterAreaBackground: var(--filterAreaBackground);
|
$filterAreaBackground: var(--filterAreaBackground);
|
||||||
|
|
||||||
$selectOptionHoveredColor: var(--selectOptionHoveredColor);
|
$selectOptionHoveredColor: var(--selectOptionHoveredColor);
|
||||||
|
$selectedBackground: var(--selectedBackground);
|
||||||
$lineProgressBackground: var(--lineProgressBackground);
|
$lineProgressBackground: var(--lineProgressBackground);
|
||||||
$radioActiveBackground: var(--radioActiveBackground);
|
$radioActiveBackground: var(--radioActiveBackground);
|
||||||
$menuActiveBackground: var(--menuActiveBackground);
|
$menuActiveBackground: var(--menuActiveBackground);
|
||||||
|
|||||||
@ -1,23 +1,27 @@
|
|||||||
// Helper to work with browser's local/session storage api
|
// Helper to work with browser's local/session storage api
|
||||||
|
|
||||||
export interface IStorageHelperOptions {
|
export interface StorageHelperOptions<T> {
|
||||||
addKeyPrefix?: boolean;
|
addKeyPrefix?: boolean;
|
||||||
useSession?: boolean; // use `sessionStorage` instead of `localStorage`
|
useSession?: boolean; // use `sessionStorage` instead of `localStorage`
|
||||||
|
parse?(from: string): T;
|
||||||
|
stringify?(from: T): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createStorage<T>(key: string, defaultValue?: T, options?: IStorageHelperOptions) {
|
export function createStorage<T>(key: string, defaultValue?: T, options?: StorageHelperOptions<T>) {
|
||||||
return new StorageHelper(key, defaultValue, options);
|
return new StorageHelper(key, defaultValue, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StorageHelper<T> {
|
export class StorageHelper<T> {
|
||||||
static keyPrefix = "lens_";
|
static keyPrefix = "lens_";
|
||||||
|
|
||||||
static defaultOptions: IStorageHelperOptions = {
|
static defaultOptions: StorageHelperOptions<any> = {
|
||||||
addKeyPrefix: true,
|
addKeyPrefix: true,
|
||||||
useSession: false,
|
useSession: false,
|
||||||
|
parse: JSON.parse,
|
||||||
|
stringify: JSON.stringify,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(protected key: string, protected defaultValue?: T, protected options?: IStorageHelperOptions) {
|
constructor(protected key: string, protected defaultValue?: T, protected options?: StorageHelperOptions<T>) {
|
||||||
this.options = Object.assign({}, StorageHelper.defaultOptions, options);
|
this.options = Object.assign({}, StorageHelper.defaultOptions, options);
|
||||||
|
|
||||||
if (this.options.addKeyPrefix) {
|
if (this.options.addKeyPrefix) {
|
||||||
@ -34,16 +38,16 @@ export class StorageHelper<T> {
|
|||||||
const strValue = this.storage.getItem(this.key);
|
const strValue = this.storage.getItem(this.key);
|
||||||
if (strValue != null) {
|
if (strValue != null) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(strValue);
|
return this.options.parse(strValue)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Parsing json failed for pair: ${this.key}=${strValue}`)
|
console.error(`Parsing failed for pair: ${this.key}=${strValue}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.defaultValue;
|
return this.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value: T) {
|
set(value: T) {
|
||||||
this.storage.setItem(this.key, JSON.stringify(value));
|
this.storage.setItem(this.key, this.options.stringify(value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user