mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add notification to user to add accessible namespaces (#2173)
This commit is contained in:
parent
0b99377feb
commit
ee4d434d35
11
src/common/ipc/cluster.ipc.ts
Normal file
11
src/common/ipc/cluster.ipc.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* This channel is broadcast on whenever the cluster fails to list namespaces
|
||||
* during a refresh and no `accessibleNamespaces` have been set.
|
||||
*/
|
||||
export const ClusterListNamespaceForbiddenChannel = "cluster:list-namespace-forbidden";
|
||||
|
||||
export type ListNamespaceForbiddenArgs = [clusterId: string];
|
||||
|
||||
export function isListNamespaceForbiddenArgs(args: unknown[]): args is ListNamespaceForbiddenArgs {
|
||||
return args.length === 1 && typeof args[0] === "string";
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./ipc";
|
||||
export * from "./invalid-kubeconfig";
|
||||
export * from "./update-available";
|
||||
export * from "./update-available.ipc";
|
||||
export * from "./cluster.ipc";
|
||||
export * from "./type-enforced-ipc";
|
||||
|
||||
@ -3,10 +3,7 @@ import { UpdateInfo } from "electron-updater";
|
||||
export const UpdateAvailableChannel = "update-available";
|
||||
export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]";
|
||||
|
||||
/**
|
||||
* [<back-channel>, <update-info>]
|
||||
*/
|
||||
export type UpdateAvailableFromMain = [string, UpdateInfo];
|
||||
export type UpdateAvailableFromMain = [backChannel: string, updateInfo: UpdateInfo];
|
||||
|
||||
export function areArgsUpdateAvailableFromMain(args: unknown[]): args is UpdateAvailableFromMain {
|
||||
if (args.length !== 2) {
|
||||
@ -32,7 +29,7 @@ export type BackchannelArg = {
|
||||
now: boolean;
|
||||
};
|
||||
|
||||
export type UpdateAvailableToBackchannel = [BackchannelArg];
|
||||
export type UpdateAvailableToBackchannel = [updateDecision: BackchannelArg];
|
||||
|
||||
export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel {
|
||||
if (args.length !== 1) {
|
||||
@ -4,9 +4,9 @@ import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||
import type { WorkspaceId } from "../common/workspace-store";
|
||||
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
import { broadcastMessage, InvalidKubeconfigChannel } from "../common/ipc";
|
||||
import { broadcastMessage, InvalidKubeconfigChannel, ClusterListNamespaceForbiddenChannel } from "../common/ipc";
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||
import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||
import { Kubectl } from "./kubectl";
|
||||
import { KubeconfigManager } from "./kubeconfig-manager";
|
||||
import { loadConfig, validateKubeConfig } from "../common/kube-helpers";
|
||||
@ -690,10 +690,14 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
return namespaceList.body.items.map(ns => ns.metadata.name);
|
||||
} catch (error) {
|
||||
const ctx = (await this.getProxyKubeconfig()).getContextObject(this.contextName);
|
||||
const namespaceList = [ctx.namespace].filter(Boolean);
|
||||
|
||||
if (ctx.namespace) return [ctx.namespace];
|
||||
if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) {
|
||||
logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id });
|
||||
broadcastMessage(ClusterListNamespaceForbiddenChannel, this.id);
|
||||
}
|
||||
|
||||
return [];
|
||||
return namespaceList;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import { clusterStore } from "../../../common/cluster-store";
|
||||
import { PageLayout } from "../layout/page-layout";
|
||||
import { requestMain } from "../../../common/ipc";
|
||||
import { clusterActivateHandler, clusterRefreshHandler } from "../../../common/cluster-ipc";
|
||||
import { navigation } from "../../navigation";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterSettingsRouteParams> {
|
||||
}
|
||||
@ -30,6 +31,10 @@ export class ClusterSettings extends React.Component<Props> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { hash } = navigation.location;
|
||||
|
||||
document.getElementById(hash.slice(1))?.scrollIntoView();
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.cluster, this.refreshCluster, {
|
||||
fireImmediately: true,
|
||||
|
||||
@ -16,7 +16,7 @@ export class ClusterAccessibleNamespaces extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Accessible Namespaces" />
|
||||
<SubTitle title="Accessible Namespaces" id="accessible-namespaces" />
|
||||
<p>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.</p>
|
||||
<EditableList
|
||||
placeholder="Add new namespace..."
|
||||
|
||||
@ -50,6 +50,10 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
*:target {
|
||||
color: $textColorAccent;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 62.5%; // 1 rem == 10px
|
||||
color: $textColorPrimary;
|
||||
|
||||
@ -6,19 +6,18 @@ interface Props {
|
||||
className?: string;
|
||||
title: React.ReactNode;
|
||||
compact?: boolean; // no bottom padding
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export class SubTitle extends React.Component<Props> {
|
||||
render() {
|
||||
const { compact, title, children } = this.props;
|
||||
let { className } = this.props;
|
||||
|
||||
className = cssNames("SubTitle", className, {
|
||||
const { className, compact, title, children, id } = this.props;
|
||||
const classNames = cssNames("SubTitle", className, {
|
||||
compact,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={classNames} id={id}>
|
||||
{title} {children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import React from "react";
|
||||
import { ipcRenderer, IpcRendererEvent } from "electron";
|
||||
import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, UpdateAvailableFromMain, BackchannelArg } from "../../common/ipc";
|
||||
import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, UpdateAvailableFromMain, BackchannelArg, ClusterListNamespaceForbiddenChannel, isListNamespaceForbiddenArgs, ListNamespaceForbiddenArgs } from "../../common/ipc";
|
||||
import { Notifications, notificationsStore } from "../components/notifications";
|
||||
import { Button } from "../components/button";
|
||||
import { isMac } from "../../common/vars";
|
||||
import { invalidKubeconfigHandler } from "./invalid-kubeconfig-handler";
|
||||
import { clusterStore } from "../../common/cluster-store";
|
||||
import { navigate } from "../navigation";
|
||||
import { clusterSettingsURL } from "../components/+cluster-settings";
|
||||
|
||||
function sendToBackchannel(backchannel: string, notificationId: string, data: BackchannelArg): void {
|
||||
notificationsStore.remove(notificationId);
|
||||
@ -42,7 +45,8 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update
|
||||
<Button active outlined label="No" onClick={() => sendToBackchannel(backchannel, notificationId, { doUpdate: false })} />
|
||||
</div>
|
||||
</div>
|
||||
), {
|
||||
),
|
||||
{
|
||||
id: notificationId,
|
||||
onClose() {
|
||||
sendToBackchannel(backchannel, notificationId, { doUpdate: false });
|
||||
@ -51,6 +55,42 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update
|
||||
);
|
||||
}
|
||||
|
||||
const listNamespacesForbiddenHandlerDisplayedAt = new Map<string, number>();
|
||||
const intervalBetweenNotifications = 1000 * 60; // 60s
|
||||
|
||||
function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void {
|
||||
const lastDisplayedAt = listNamespacesForbiddenHandlerDisplayedAt.get(clusterId);
|
||||
const wasDisplayed = Boolean(lastDisplayedAt);
|
||||
const now = Date.now();
|
||||
|
||||
if (!wasDisplayed || (now - lastDisplayedAt) > intervalBetweenNotifications) {
|
||||
listNamespacesForbiddenHandlerDisplayedAt.set(clusterId, now);
|
||||
} else {
|
||||
// don't bother the user too often
|
||||
return;
|
||||
}
|
||||
|
||||
const notificationId = `list-namespaces-forbidden:${clusterId}`;
|
||||
|
||||
Notifications.info(
|
||||
(
|
||||
<div className="flex column gaps">
|
||||
<b>Add Accessible Namespaces</b>
|
||||
<p>Cluster <b>{clusterStore.active.name}</b> does not have permissions to list namespaces. Please add the namespaces you have access to.</p>
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<Button active outlined label="Go to Accessible Namespaces Settings" onClick={()=> {
|
||||
navigate(clusterSettingsURL({ params: { clusterId }, fragment: "accessible-namespaces" }));
|
||||
notificationsStore.remove(notificationId);
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
id: notificationId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function registerIpcHandlers() {
|
||||
onCorrect({
|
||||
source: ipcRenderer,
|
||||
@ -59,4 +99,10 @@ export function registerIpcHandlers() {
|
||||
verifier: areArgsUpdateAvailableFromMain,
|
||||
});
|
||||
onCorrect(invalidKubeconfigHandler);
|
||||
onCorrect({
|
||||
source: ipcRenderer,
|
||||
channel: ClusterListNamespaceForbiddenChannel,
|
||||
listener: ListNamespacesForbiddenHandler,
|
||||
verifier: isListNamespaceForbiddenArgs,
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user