mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
fix: namespace-store refactoring / saving selected-namespaces to external json-file
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
f96b789280
commit
3357269c5d
@ -3,12 +3,10 @@ import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { HelmCharts, helmChartsRoute, helmChartsURL } from "../+apps-helm-charts";
|
||||
import { HelmReleases, releaseRoute, releaseURL } from "../+apps-releases";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
|
||||
@observer
|
||||
export class Apps extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
|
||||
return [
|
||||
{
|
||||
@ -20,7 +18,7 @@ export class Apps extends React.Component {
|
||||
{
|
||||
title: "Releases",
|
||||
component: HelmReleases,
|
||||
url: releaseURL({ query }),
|
||||
url: releaseURL(),
|
||||
routePath: releaseRoute.path.toString(),
|
||||
},
|
||||
];
|
||||
|
||||
@ -3,7 +3,6 @@ import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { ConfigMaps, configMapsRoute, configMapsURL } from "../+config-maps";
|
||||
import { Secrets, secretsRoute, secretsURL } from "../+config-secrets";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config-resource-quotas";
|
||||
import { pdbRoute, pdbURL, PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
|
||||
import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers";
|
||||
@ -13,14 +12,13 @@ import { LimitRanges, limitRangesRoute, limitRangeURL } from "../+config-limit-r
|
||||
@observer
|
||||
export class Config extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
const routes: TabLayoutRoute[] = [];
|
||||
|
||||
if (isAllowedResource("configmaps")) {
|
||||
routes.push({
|
||||
title: "ConfigMaps",
|
||||
component: ConfigMaps,
|
||||
url: configMapsURL({ query }),
|
||||
url: configMapsURL(),
|
||||
routePath: configMapsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -29,7 +27,7 @@ export class Config extends React.Component {
|
||||
routes.push({
|
||||
title: "Secrets",
|
||||
component: Secrets,
|
||||
url: secretsURL({ query }),
|
||||
url: secretsURL(),
|
||||
routePath: secretsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -38,7 +36,7 @@ export class Config extends React.Component {
|
||||
routes.push({
|
||||
title: "Resource Quotas",
|
||||
component: ResourceQuotas,
|
||||
url: resourceQuotaURL({ query }),
|
||||
url: resourceQuotaURL(),
|
||||
routePath: resourceQuotaRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -47,7 +45,7 @@ export class Config extends React.Component {
|
||||
routes.push({
|
||||
title: "Limit Ranges",
|
||||
component: LimitRanges,
|
||||
url: limitRangeURL({ query }),
|
||||
url: limitRangeURL(),
|
||||
routePath: limitRangesRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -56,7 +54,7 @@ export class Config extends React.Component {
|
||||
routes.push({
|
||||
title: "HPA",
|
||||
component: HorizontalPodAutoscalers,
|
||||
url: hpaURL({ query }),
|
||||
url: hpaURL(),
|
||||
routePath: hpaRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -65,7 +63,7 @@ export class Config extends React.Component {
|
||||
routes.push({
|
||||
title: "Pod Disruption Budgets",
|
||||
component: PodDisruptionBudgets,
|
||||
url: pdbURL({ query }),
|
||||
url: pdbURL(),
|
||||
routePath: pdbRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,35 +1,18 @@
|
||||
import { action, comparer, computed, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction, } from "mobx";
|
||||
import { action, comparer, computed, IReactionDisposer, IReactionOptions, makeObservable, reaction, when, } from "mobx";
|
||||
import { autoBind, createStorage } from "../../utils";
|
||||
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store";
|
||||
import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api";
|
||||
import { createPageParam } from "../../navigation";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
|
||||
// FIXME: something fishy with sync selected-namespaces with URL
|
||||
const selectedNamespaces = createStorage<string[] | undefined>("selected_namespaces", undefined);
|
||||
|
||||
export const namespaceUrlParam = createPageParam<string[]>({
|
||||
name: "namespaces",
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
export function getDummyNamespace(name: string) {
|
||||
return new Namespace({
|
||||
kind: Namespace.kind,
|
||||
apiVersion: "v1",
|
||||
metadata: {
|
||||
name,
|
||||
uid: "",
|
||||
resourceVersion: "",
|
||||
selfLink: `/api/v1/namespaces/${name}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
api = namespacesApi;
|
||||
|
||||
@observable private contextNs = observable.set<string>();
|
||||
private defaultNamespaces: string[] = [];
|
||||
private storage = createStorage<string[]>("selected_namespaces", this.defaultNamespaces);
|
||||
|
||||
@computed get selectedNamespaces(): string[] {
|
||||
return this.storage.get();
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -41,29 +24,19 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
|
||||
private async init() {
|
||||
await this.contextReady;
|
||||
await selectedNamespaces.whenReady;
|
||||
await when(() => this.storage.initialized);
|
||||
|
||||
this.setContext(this.initialNamespaces);
|
||||
this.autoLoadAllowedNamespaces();
|
||||
this.autoUpdateUrlAndLocalStorage();
|
||||
}
|
||||
|
||||
public onContextChange(callback: (contextNamespaces: string[]) => void, opts: IReactionOptions = {}): IReactionDisposer {
|
||||
return reaction(() => Array.from(this.contextNs), callback, {
|
||||
public onContextChange(callback: (namespaces: string[]) => void, opts: IReactionOptions = {}): IReactionDisposer {
|
||||
return reaction(() => Array.from(this.selectedNamespaces), callback, {
|
||||
equals: comparer.shallow,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
private autoUpdateUrlAndLocalStorage(): IReactionDisposer {
|
||||
return this.onContextChange(namespaces => {
|
||||
selectedNamespaces.set(namespaces); // save to local-storage
|
||||
namespaceUrlParam.set(namespaces, { replaceHistory: true }); // update url
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
});
|
||||
}
|
||||
|
||||
private autoLoadAllowedNamespaces(): IReactionDisposer {
|
||||
return reaction(() => this.allowedNamespaces, namespaces => this.loadAll({ namespaces }), {
|
||||
fireImmediately: true,
|
||||
@ -72,19 +45,18 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
}
|
||||
|
||||
private get initialNamespaces(): string[] {
|
||||
const namespaces = new Set(this.allowedNamespaces);
|
||||
const prevSelectedNamespaces = selectedNamespaces.get();
|
||||
const { allowedNamespaces, selectedNamespaces, defaultNamespaces } = this;
|
||||
|
||||
// return previously saved namespaces from local-storage (if any)
|
||||
if (prevSelectedNamespaces) {
|
||||
return prevSelectedNamespaces.filter(namespace => namespaces.has(namespace));
|
||||
if (selectedNamespaces !== defaultNamespaces) {
|
||||
return selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace));
|
||||
}
|
||||
|
||||
// otherwise select "default" or first allowed namespace
|
||||
if (namespaces.has("default")) {
|
||||
if (allowedNamespaces.includes("default")) {
|
||||
return ["default"];
|
||||
} else if (namespaces.size) {
|
||||
return [Array.from(namespaces)[0]];
|
||||
} else if (allowedNamespaces.length) {
|
||||
return [allowedNamespaces[0]];
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -98,13 +70,11 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
}
|
||||
|
||||
@computed get contextNamespaces(): string[] {
|
||||
const namespaces = Array.from(this.contextNs);
|
||||
|
||||
if (!namespaces.length) {
|
||||
if (!this.selectedNamespaces.length) {
|
||||
return this.allowedNamespaces; // show all namespaces when nothing selected
|
||||
}
|
||||
|
||||
return namespaces;
|
||||
return this.selectedNamespaces;
|
||||
}
|
||||
|
||||
getSubscribeApis() {
|
||||
@ -132,30 +102,39 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
|
||||
@action
|
||||
setContext(namespace: string | string[]) {
|
||||
const namespaces = [namespace].flat();
|
||||
const namespaces = Array.from(new Set([namespace].flat()));
|
||||
|
||||
this.contextNs.replace(namespaces);
|
||||
this.storage.set(namespaces);
|
||||
}
|
||||
|
||||
@action
|
||||
resetContext() {
|
||||
this.contextNs.clear();
|
||||
resetContext(namespaces?: string | string[]) {
|
||||
if (namespaces) {
|
||||
const resettingNamespaces = [namespaces].flat();
|
||||
const newNamespaces = this.storage.get().filter(ns => !resettingNamespaces.includes(ns));
|
||||
|
||||
this.storage.set(newNamespaces);
|
||||
} else {
|
||||
this.storage.reset();
|
||||
}
|
||||
}
|
||||
|
||||
hasContext(namespaces: string | string[]) {
|
||||
return [namespaces].flat().every(namespace => this.contextNs.has(namespace));
|
||||
hasContext(namespaces: string | string[]): boolean {
|
||||
return [namespaces]
|
||||
.flat()
|
||||
.every(namespace => this.selectedNamespaces.includes(namespace));
|
||||
}
|
||||
|
||||
@computed get hasAllContexts(): boolean {
|
||||
return this.contextNs.size === this.allowedNamespaces.length;
|
||||
return this.selectedNamespaces.length === this.allowedNamespaces.length;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleContext(namespace: string) {
|
||||
if (this.hasContext(namespace)) {
|
||||
this.contextNs.delete(namespace);
|
||||
toggleContext(namespaces: string | string[]) {
|
||||
if (this.hasContext(namespaces)) {
|
||||
this.resetContext(namespaces);
|
||||
} else {
|
||||
this.contextNs.add(namespace);
|
||||
this.setContext([this.selectedNamespaces, namespaces].flat());
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,9 +154,22 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
@action
|
||||
async remove(item: Namespace) {
|
||||
await super.remove(item);
|
||||
this.contextNs.delete(item.getName());
|
||||
this.resetContext(item.getName());
|
||||
}
|
||||
}
|
||||
|
||||
export const namespaceStore = new NamespaceStore();
|
||||
apiManager.registerStore(namespaceStore);
|
||||
|
||||
export function getDummyNamespace(name: string) {
|
||||
return new Namespace({
|
||||
kind: Namespace.kind,
|
||||
apiVersion: "v1",
|
||||
metadata: {
|
||||
name,
|
||||
uid: "",
|
||||
resourceVersion: "",
|
||||
selfLink: `/api/v1/namespaces/${name}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,20 +7,18 @@ import { Services, servicesRoute, servicesURL } from "../+network-services";
|
||||
import { endpointRoute, Endpoints, endpointURL } from "../+network-endpoints";
|
||||
import { Ingresses, ingressRoute, ingressURL } from "../+network-ingresses";
|
||||
import { NetworkPolicies, networkPoliciesRoute, networkPoliciesURL } from "../+network-policies";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
import { isAllowedResource } from "../../../common/rbac";
|
||||
|
||||
@observer
|
||||
export class Network extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
const routes: TabLayoutRoute[] = [];
|
||||
|
||||
if (isAllowedResource("services")) {
|
||||
routes.push({
|
||||
title: "Services",
|
||||
component: Services,
|
||||
url: servicesURL({ query }),
|
||||
url: servicesURL(),
|
||||
routePath: servicesRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -29,7 +27,7 @@ export class Network extends React.Component {
|
||||
routes.push({
|
||||
title: "Endpoints",
|
||||
component: Endpoints,
|
||||
url: endpointURL({ query }),
|
||||
url: endpointURL(),
|
||||
routePath: endpointRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -38,7 +36,7 @@ export class Network extends React.Component {
|
||||
routes.push({
|
||||
title: "Ingresses",
|
||||
component: Ingresses,
|
||||
url: ingressURL({ query }),
|
||||
url: ingressURL(),
|
||||
routePath: ingressRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -47,7 +45,7 @@ export class Network extends React.Component {
|
||||
routes.push({
|
||||
title: "Network Policies",
|
||||
component: NetworkPolicies,
|
||||
url: networkPoliciesURL({ query }),
|
||||
url: networkPoliciesURL(),
|
||||
routePath: networkPoliciesRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,20 +6,18 @@ import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { PersistentVolumes, volumesRoute, volumesURL } from "../+storage-volumes";
|
||||
import { StorageClasses, storageClassesRoute, storageClassesURL } from "../+storage-classes";
|
||||
import { PersistentVolumeClaims, volumeClaimsRoute, volumeClaimsURL } from "../+storage-volume-claims";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
import { isAllowedResource } from "../../../common/rbac";
|
||||
|
||||
@observer
|
||||
export class Storage extends React.Component {
|
||||
static get tabRoutes() {
|
||||
const tabRoutes: TabLayoutRoute[] = [];
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
|
||||
if (isAllowedResource("persistentvolumeclaims")) {
|
||||
tabRoutes.push({
|
||||
title: "Persistent Volume Claims",
|
||||
component: PersistentVolumeClaims,
|
||||
url: volumeClaimsURL({ query }),
|
||||
url: volumeClaimsURL(),
|
||||
routePath: volumeClaimsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import { Roles } from "../+user-management-roles";
|
||||
import { RoleBindings } from "../+user-management-roles-bindings";
|
||||
import { ServiceAccounts } from "../+user-management-service-accounts";
|
||||
import { podSecurityPoliciesRoute, podSecurityPoliciesURL, roleBindingsRoute, roleBindingsURL, rolesRoute, rolesURL, serviceAccountsRoute, serviceAccountsURL } from "./user-management.route";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
import { PodSecurityPolicies } from "../+pod-security-policies";
|
||||
import { isAllowedResource } from "../../../common/rbac";
|
||||
|
||||
@ -14,25 +13,24 @@ import { isAllowedResource } from "../../../common/rbac";
|
||||
export class UserManagement extends React.Component {
|
||||
static get tabRoutes() {
|
||||
const tabRoutes: TabLayoutRoute[] = [];
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
|
||||
tabRoutes.push(
|
||||
{
|
||||
title: "Service Accounts",
|
||||
component: ServiceAccounts,
|
||||
url: serviceAccountsURL({ query }),
|
||||
url: serviceAccountsURL(),
|
||||
routePath: serviceAccountsRoute.path.toString(),
|
||||
},
|
||||
{
|
||||
title: "Role Bindings",
|
||||
component: RoleBindings,
|
||||
url: roleBindingsURL({ query }),
|
||||
url: roleBindingsURL(),
|
||||
routePath: roleBindingsRoute.path.toString(),
|
||||
},
|
||||
{
|
||||
title: "Roles",
|
||||
component: Roles,
|
||||
url: rolesURL({ query }),
|
||||
url: rolesURL(),
|
||||
routePath: rolesRoute.path.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
@ -5,7 +5,6 @@ import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { WorkloadsOverview } from "../+workloads-overview/overview";
|
||||
import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, replicaSetsRoute, replicaSetsURL, statefulSetsRoute, statefulSetsURL } from "./workloads.route";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
import { Pods } from "../+workloads-pods";
|
||||
import { Deployments } from "../+workloads-deployments";
|
||||
import { DaemonSets } from "../+workloads-daemonsets";
|
||||
@ -18,12 +17,11 @@ import { ReplicaSets } from "../+workloads-replicasets";
|
||||
@observer
|
||||
export class Workloads extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
const routes: TabLayoutRoute[] = [
|
||||
{
|
||||
title: "Overview",
|
||||
component: WorkloadsOverview,
|
||||
url: overviewURL({ query }),
|
||||
url: overviewURL(),
|
||||
routePath: overviewRoute.path.toString()
|
||||
}
|
||||
];
|
||||
@ -32,7 +30,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "Pods",
|
||||
component: Pods,
|
||||
url: podsURL({ query }),
|
||||
url: podsURL(),
|
||||
routePath: podsRoute.path.toString()
|
||||
});
|
||||
}
|
||||
@ -41,7 +39,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "Deployments",
|
||||
component: Deployments,
|
||||
url: deploymentsURL({ query }),
|
||||
url: deploymentsURL(),
|
||||
routePath: deploymentsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -50,7 +48,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "DaemonSets",
|
||||
component: DaemonSets,
|
||||
url: daemonSetsURL({ query }),
|
||||
url: daemonSetsURL(),
|
||||
routePath: daemonSetsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -59,7 +57,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "StatefulSets",
|
||||
component: StatefulSets,
|
||||
url: statefulSetsURL({ query }),
|
||||
url: statefulSetsURL(),
|
||||
routePath: statefulSetsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -68,7 +66,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "ReplicaSets",
|
||||
component: ReplicaSets,
|
||||
url: replicaSetsURL({ query }),
|
||||
url: replicaSetsURL(),
|
||||
routePath: replicaSetsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -77,7 +75,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "Jobs",
|
||||
component: Jobs,
|
||||
url: jobsURL({ query }),
|
||||
url: jobsURL(),
|
||||
routePath: jobsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
@ -86,7 +84,7 @@ export class Workloads extends React.Component {
|
||||
routes.push({
|
||||
title: "CronJobs",
|
||||
component: CronJobs,
|
||||
url: cronJobsURL({ query }),
|
||||
url: cronJobsURL(),
|
||||
routePath: cronJobsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import { clusterRoute, clusterURL } from "../+cluster";
|
||||
import { Config, configRoute, configURL } from "../+config";
|
||||
import { eventRoute, eventsURL } from "../+events";
|
||||
import { Apps, appsRoute, appsURL } from "../+apps";
|
||||
import { namespaceUrlParam } from "../+namespaces/namespace.store";
|
||||
import { Workloads } from "../+workloads";
|
||||
import { UserManagement } from "../+user-management";
|
||||
import { Storage } from "../+storage";
|
||||
@ -48,7 +47,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
if (crdStore.isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<Spinner />
|
||||
<Spinner/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -154,7 +153,6 @@ export class Sidebar extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { toggle, compact, className } = this.props;
|
||||
const query = namespaceUrlParam.toObjectParam();
|
||||
|
||||
return (
|
||||
<div className={cssNames(Sidebar.displayName, "flex column", { compact }, className)}>
|
||||
@ -193,7 +191,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
text="Workloads"
|
||||
isActive={isActiveRoute(workloadsRoute)}
|
||||
isHidden={Workloads.tabRoutes.length == 0}
|
||||
url={workloadsURL({ query })}
|
||||
url={workloadsURL()}
|
||||
icon={<Icon svg="workloads"/>}
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
|
||||
@ -203,7 +201,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
text="Configuration"
|
||||
isActive={isActiveRoute(configRoute)}
|
||||
isHidden={Config.tabRoutes.length == 0}
|
||||
url={configURL({ query })}
|
||||
url={configURL()}
|
||||
icon={<Icon material="list"/>}
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Config.tabRoutes)}
|
||||
@ -213,7 +211,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
text="Network"
|
||||
isActive={isActiveRoute(networkRoute)}
|
||||
isHidden={Network.tabRoutes.length == 0}
|
||||
url={networkURL({ query })}
|
||||
url={networkURL()}
|
||||
icon={<Icon material="device_hub"/>}
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Network.tabRoutes)}
|
||||
@ -223,7 +221,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
text="Storage"
|
||||
isActive={isActiveRoute(storageRoute)}
|
||||
isHidden={Storage.tabRoutes.length == 0}
|
||||
url={storageURL({ query })}
|
||||
url={storageURL()}
|
||||
icon={<Icon svg="storage"/>}
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Storage.tabRoutes)}
|
||||
@ -241,14 +239,14 @@ export class Sidebar extends React.Component<Props> {
|
||||
text="Events"
|
||||
isActive={isActiveRoute(eventRoute)}
|
||||
isHidden={!isAllowedResource("events")}
|
||||
url={eventsURL({ query })}
|
||||
url={eventsURL()}
|
||||
icon={<Icon material="access_time"/>}
|
||||
/>
|
||||
<SidebarItem
|
||||
id="apps"
|
||||
text="Apps" // helm charts
|
||||
isActive={isActiveRoute(appsRoute)}
|
||||
url={appsURL({ query })}
|
||||
url={appsURL()}
|
||||
icon={<Icon material="apps"/>}
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Apps.tabRoutes)}
|
||||
@ -257,7 +255,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
id="users"
|
||||
text="Access Control"
|
||||
isActive={isActiveRoute(usersManagementRoute)}
|
||||
url={usersManagementURL({ query })}
|
||||
url={usersManagementURL()}
|
||||
icon={<Icon material="security"/>}
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
|
||||
|
||||
@ -4,7 +4,7 @@ import { action, makeObservable } from "mobx";
|
||||
|
||||
export interface PageParamInit<V = any> {
|
||||
name: string;
|
||||
defaultValue?: V;
|
||||
defaultValue?: V; // multi-values param must be defined with array-value, e.g. []
|
||||
prefix?: string; // name prefix, for extensions it's `${extension.id}:`
|
||||
parse?(value: string | string[]): V; // from URL
|
||||
stringify?(value: V): string | string[]; // to URL
|
||||
@ -106,10 +106,4 @@ export class PageParam<V = any> {
|
||||
|
||||
return `${withPrefix ? "?" : ""}${searchParams}`;
|
||||
}
|
||||
|
||||
toObjectParam(): Record<string, V> {
|
||||
return {
|
||||
[this.name]: this.get(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,12 +8,10 @@ import { StorageHelper } from "./storageHelper";
|
||||
import { ClusterStore, getHostedClusterId } from "../../common/cluster-store";
|
||||
import logger from "../../main/logger";
|
||||
|
||||
const LOG_PREFIX = "[LocalStorageHelper]:";
|
||||
|
||||
const storage = observable({
|
||||
initialized: false,
|
||||
loaded: false,
|
||||
data: {} as { [key: string]: any }, // json-compatible state
|
||||
data: {} as Record<string/*key*/, any>, // json-serializable
|
||||
});
|
||||
|
||||
/**
|
||||
@ -22,6 +20,7 @@ const storage = observable({
|
||||
* @param defaultValue
|
||||
*/
|
||||
export function createStorage<T>(key: string, defaultValue: T) {
|
||||
const { logPrefix } = StorageHelper;
|
||||
const clusterId = getHostedClusterId();
|
||||
const folder = path.resolve((app || remote.app).getPath("userData"), "lens-local-storage");
|
||||
const fileName = `${clusterId ?? "app"}.json`;
|
||||
@ -39,7 +38,7 @@ export function createStorage<T>(key: string, defaultValue: T) {
|
||||
.then(data => storage.data = data)
|
||||
.catch(() => null) // ignore empty / non-existing / invalid json files
|
||||
.finally(() => {
|
||||
logger.info(`${LOG_PREFIX} loaded local-storage file "${filePath}"`);
|
||||
logger.info(`${logPrefix} loading finished for ${filePath}`);
|
||||
storage.loaded = true;
|
||||
});
|
||||
|
||||
@ -55,20 +54,20 @@ export function createStorage<T>(key: string, defaultValue: T) {
|
||||
}
|
||||
|
||||
async function saveFile(state: Record<string, any> = {}) {
|
||||
logger.info(`${LOG_PREFIX} saving ${filePath}`);
|
||||
logger.info(`${logPrefix} saving ${filePath}`);
|
||||
|
||||
try {
|
||||
await fse.ensureDir(folder, { mode: 0o755 });
|
||||
await fse.writeJson(filePath, state, { spaces: 2 });
|
||||
} catch (error) {
|
||||
logger.error(`${LOG_PREFIX} saving failed: ${error}`, {
|
||||
logger.error(`${logPrefix} saving failed: ${error}`, {
|
||||
json: state, jsonFilePath: filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile() {
|
||||
logger.debug(`${LOG_PREFIX} removing ${filePath}`);
|
||||
logger.debug(`${logPrefix} removing ${filePath}`);
|
||||
fse.unlink(filePath).catch(Function);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
||||
|
||||
import { action, comparer, IObservableValue, makeObservable, observable, toJS, when, } from "mobx";
|
||||
import { action, IObservableValue, makeObservable, observable, toJS, when, } from "mobx";
|
||||
import produce, { Draft } from "immer";
|
||||
import { isEqual, isFunction, isPlainObject } from "lodash";
|
||||
import logger from "../../main/logger";
|
||||
@ -20,33 +20,30 @@ export interface StorageHelperOptions<T> {
|
||||
}
|
||||
|
||||
export class StorageHelper<T> {
|
||||
static readonly defaultOptions: Partial<StorageHelperOptions<any>> = {
|
||||
autoInit: true,
|
||||
};
|
||||
static logPrefix = "[StorageHelper]:";
|
||||
|
||||
readonly storage: StorageAdapter<T>;
|
||||
private data: IObservableValue<T>;
|
||||
@observable initialized = false;
|
||||
whenReady = when(() => this.initialized);
|
||||
|
||||
public readonly storage: StorageAdapter<T>;
|
||||
public readonly defaultValue: T;
|
||||
@observable initialized = false;
|
||||
whenReady = when(() => this.initialized); // FIXME: invalid, use when() at the place of usage
|
||||
|
||||
get defaultValue(): T {
|
||||
// return as-is since options.defaultValue might be a getter too
|
||||
return this.options.defaultValue;
|
||||
}
|
||||
|
||||
constructor(readonly key: string, private options: StorageHelperOptions<T>) {
|
||||
const { storage, defaultValue, autoInit = true } = options;
|
||||
makeObservable(this);
|
||||
|
||||
this.storage = options.storage;
|
||||
this.defaultValue = options.defaultValue;
|
||||
this.data = observable.box<T>(this.defaultValue, {
|
||||
autoBind: true,
|
||||
deep: true,
|
||||
equals: comparer.structural,
|
||||
});
|
||||
|
||||
this.storage = storage;
|
||||
this.data = observable.box<T>(defaultValue);
|
||||
this.data.observe_(({ newValue, oldValue }) => {
|
||||
this.onChange(newValue as T, oldValue as T);
|
||||
});
|
||||
|
||||
if (this.options.autoInit) {
|
||||
if (autoInit) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
@ -63,7 +60,7 @@ export class StorageHelper<T> {
|
||||
};
|
||||
|
||||
private onError = (error: any): void => {
|
||||
logger.error(`[load]: ${error}`, this);
|
||||
logger.error(`${StorageHelper.logPrefix} loading error: ${error}`, this);
|
||||
};
|
||||
|
||||
@action
|
||||
@ -101,21 +98,21 @@ export class StorageHelper<T> {
|
||||
|
||||
this.storage.onChange?.({ value, oldValue, key: this.key });
|
||||
} catch (error) {
|
||||
logger.error(`[change]: ${error}`, this, { value, oldValue });
|
||||
logger.error(`${StorageHelper.logPrefix} updating storage: ${error}`, this, { value, oldValue });
|
||||
}
|
||||
}
|
||||
|
||||
get(): T {
|
||||
return this.data.get();
|
||||
const value = this.data.get();
|
||||
|
||||
// return real default-value (not a copy from observable when it's an object, e.g. [])
|
||||
// this will allow to compare values by link with == operator
|
||||
return this.isDefaultValue(value) ? this.defaultValue : value;
|
||||
}
|
||||
|
||||
@action
|
||||
set(value: T) {
|
||||
if (value == null) {
|
||||
this.reset();
|
||||
} else {
|
||||
this.data.set(value);
|
||||
}
|
||||
this.data.set(value);
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
Loading…
Reference in New Issue
Block a user