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