mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
refactoring & fixes
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
2054f38d08
commit
3a115ae485
@ -153,10 +153,8 @@ export class Cluster implements ClusterModel {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
async refresh() {
|
async refresh() {
|
||||||
logger.info(`[CLUSTER]: refreshing status`, this.getMeta());
|
logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||||
const connectionStatus = await this.getConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
this.online = connectionStatus > ClusterStatus.Offline;
|
|
||||||
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
this.kubeCtl = new Kubectl(this.version)
|
this.kubeCtl = new Kubectl(this.version)
|
||||||
this.distribution = this.detectKubernetesDistribution(this.version)
|
this.distribution = this.detectKubernetesDistribution(this.version)
|
||||||
@ -169,12 +167,19 @@ export class Cluster implements ClusterModel {
|
|||||||
this.features = features;
|
this.features = features;
|
||||||
this.isAdmin = isAdmin;
|
this.isAdmin = isAdmin;
|
||||||
this.nodes = nodesCount;
|
this.nodes = nodesCount;
|
||||||
}
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.refreshEvents(),
|
this.refreshEvents(),
|
||||||
this.refreshAllowedResources(),
|
this.refreshAllowedResources(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async refreshConnectionStatus() {
|
||||||
|
const connectionStatus = await this.getConnectionStatus();
|
||||||
|
this.online = connectionStatus > ClusterStatus.Offline;
|
||||||
|
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async refreshAllowedResources() {
|
async refreshAllowedResources() {
|
||||||
@ -219,7 +224,7 @@ export class Cluster implements ClusterModel {
|
|||||||
const apiUrl = this.kubeProxyUrl + path;
|
const apiUrl = this.kubeProxyUrl + path;
|
||||||
return request(apiUrl, {
|
return request(apiUrl, {
|
||||||
json: true,
|
json: true,
|
||||||
timeout: 10000,
|
timeout: 5000,
|
||||||
headers: {
|
headers: {
|
||||||
...(options.headers || {}),
|
...(options.headers || {}),
|
||||||
Host: new URL(this.webContentUrl).host,
|
Host: new URL(this.webContentUrl).host,
|
||||||
@ -442,6 +447,9 @@ export class Cluster implements ClusterModel {
|
|||||||
{ resource: "storageclasses", group: "storage.k8s.io" },
|
{ resource: "storageclasses", group: "storage.k8s.io" },
|
||||||
]
|
]
|
||||||
try {
|
try {
|
||||||
|
if (!this.allowedNamespaces.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const resourceAccessStatuses = await Promise.all(
|
const resourceAccessStatuses = await Promise.all(
|
||||||
apiResources.map(apiResource => this.canI({
|
apiResources.map(apiResource => this.canI({
|
||||||
resource: apiResource.resource,
|
resource: apiResource.resource,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { Cluster } from "./cluster"
|
|||||||
import { bundledKubectl, Kubectl } from "./kubectl"
|
import { bundledKubectl, Kubectl } from "./kubectl"
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
|
|
||||||
export interface KubeAuthProxyResponse {
|
export interface KubeAuthProxyLog {
|
||||||
data: string;
|
data: string;
|
||||||
error?: boolean; // stream=stderr
|
error?: boolean; // stream=stderr
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ export class KubeAuthProxy {
|
|||||||
return errorMsg
|
return errorMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async sendIpcLogMessage(res: KubeAuthProxyResponse) {
|
protected async sendIpcLogMessage(res: KubeAuthProxyLog) {
|
||||||
const channel = `kube-auth:${this.cluster.id}`
|
const channel = `kube-auth:${this.cluster.id}`
|
||||||
logger.info(`[KUBE-AUTH]: out-channel "${channel}"`, { ...res, meta: this.cluster.getMeta() });
|
logger.info(`[KUBE-AUTH]: out-channel "${channel}"`, { ...res, meta: this.cluster.getMeta() });
|
||||||
broadcastIpc({
|
broadcastIpc({
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import "./nodes.scss";
|
import "./nodes.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
@ -15,7 +14,7 @@ import { NodeMenu } from "./node-menu";
|
|||||||
import { LineProgress } from "../line-progress";
|
import { LineProgress } from "../line-progress";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { bytesToUnits } from "../../utils/convertMemory";
|
import { bytesToUnits } from "../../utils/convertMemory";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip, TooltipPosition } from "../tooltip";
|
||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import upperFirst from "lodash/upperFirst";
|
import upperFirst from "lodash/upperFirst";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { apiManager } from "../../api/api-manager";
|
||||||
@ -57,7 +56,10 @@ export class Nodes extends React.Component<Props> {
|
|||||||
<LineProgress
|
<LineProgress
|
||||||
max={cores}
|
max={cores}
|
||||||
value={usage}
|
value={usage}
|
||||||
tooltip={_i18n._(t`CPU:`) + ` ${Math.ceil(usage * 100) / cores}\%, ` + _i18n._(t`cores:`) + ` ${cores}`}
|
tooltip={{
|
||||||
|
position: TooltipPosition.BOTTOM,
|
||||||
|
children: _i18n._(t`CPU:`) + ` ${Math.ceil(usage * 100) / cores}\%, ` + _i18n._(t`cores:`) + ` ${cores}`
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -71,7 +73,10 @@ export class Nodes extends React.Component<Props> {
|
|||||||
<LineProgress
|
<LineProgress
|
||||||
max={capacity}
|
max={capacity}
|
||||||
value={usage}
|
value={usage}
|
||||||
tooltip={_i18n._(t`Memory:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`}
|
tooltip={{
|
||||||
|
position: TooltipPosition.BOTTOM,
|
||||||
|
children: _i18n._(t`Memory:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -85,7 +90,10 @@ export class Nodes extends React.Component<Props> {
|
|||||||
<LineProgress
|
<LineProgress
|
||||||
max={capacity}
|
max={capacity}
|
||||||
value={usage}
|
value={usage}
|
||||||
tooltip={_i18n._(t`Disk:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`}
|
tooltip={{
|
||||||
|
position: TooltipPosition.BOTTOM,
|
||||||
|
children: _i18n._(t`Disk:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,20 +51,19 @@ export class App extends React.Component {
|
|||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.cluster) {
|
if (this.cluster) {
|
||||||
await clusterIpc.activate.invokeFromRenderer(); // refresh state, reconnect, etc.
|
await clusterIpc.activate.invokeFromRenderer(); // refresh state, reconnect, etc.
|
||||||
}
|
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
reaction(() => this.startURL, this.onStartUrlChange, {
|
reaction(() => this.cluster.accessible, this.onClusterAccessChange, {
|
||||||
fireImmediately: true
|
fireImmediately: true
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
|
}
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onStartUrlChange = (startURL: string) => {
|
protected onClusterAccessChange = (accessible: boolean) => {
|
||||||
const path = navigation.getPath();
|
const path = navigation.getPath();
|
||||||
const redirectRequired = ["/", clusterStatusURL()].includes(path);
|
if (!accessible || path === "/") {
|
||||||
if (redirectRequired || !this.cluster?.accessible) {
|
navigate(this.startURL);
|
||||||
navigate(startURL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,32 +1,40 @@
|
|||||||
import type { KubeAuthProxyResponse } from "../../../main/kube-auth-proxy";
|
import type { KubeAuthProxyLog } from "../../../main/kube-auth-proxy";
|
||||||
|
|
||||||
import "./cluster-status.scss"
|
import "./cluster-status.scss"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { computed, observable } from "mobx";
|
import { autorun, computed, observable } from "mobx";
|
||||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||||
import { getHostedCluster } from "../../../common/cluster-store";
|
import { getHostedCluster } from "../../../common/cluster-store";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
|
import { navigate } from "../../navigation";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterStatus extends React.Component {
|
export class ClusterStatus extends React.Component {
|
||||||
@observable authOutput: KubeAuthProxyResponse[] = [];
|
@observable authOutput: KubeAuthProxyLog[] = [];
|
||||||
@observable isReconnecting = false;
|
@observable isReconnecting = false;
|
||||||
|
|
||||||
@computed get hasErrors() {
|
|
||||||
return this.authOutput.some(({ error }) => error)
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get cluster() {
|
@computed get cluster() {
|
||||||
return getHostedCluster();
|
return getHostedCluster();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get hasErrors(): boolean {
|
||||||
|
return this.authOutput.some(({ error }) => error) || !!this.cluster.failureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@disposeOnUnmount
|
||||||
|
autoRedirectToMain = autorun(() => {
|
||||||
|
if (this.cluster.accessible && !this.hasErrors) {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
this.authOutput = [{ data: "Connecting..." }];
|
this.authOutput = [{ data: "Connecting..." }];
|
||||||
ipcRenderer.on(`kube-auth:${this.cluster.id}`, (evt, res: KubeAuthProxyResponse) => {
|
ipcRenderer.on(`kube-auth:${this.cluster.id}`, (evt, res: KubeAuthProxyLog) => {
|
||||||
this.authOutput.push({
|
this.authOutput.push({
|
||||||
data: res.data.trimRight(),
|
data: res.data.trimRight(),
|
||||||
error: res.error,
|
error: res.error,
|
||||||
@ -48,10 +56,11 @@ export class ClusterStatus extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const { authOutput, cluster, hasErrors } = this;
|
const { authOutput, cluster, hasErrors } = this;
|
||||||
const isDisconnected = !!cluster.disconnected;
|
const isDisconnected = !!cluster.disconnected;
|
||||||
const isInactive = hasErrors || isDisconnected;
|
const failureReason = cluster.failureReason;
|
||||||
|
const isError = hasErrors || isDisconnected;
|
||||||
return (
|
return (
|
||||||
<div className="ClusterStatus flex column gaps">
|
<div className="ClusterStatus flex column gaps">
|
||||||
{isInactive && (
|
{isError && (
|
||||||
<Icon
|
<Icon
|
||||||
material="cloud_off"
|
material="cloud_off"
|
||||||
className={cssNames({ error: hasErrors })}
|
className={cssNames({ error: hasErrors })}
|
||||||
@ -67,7 +76,10 @@ export class ClusterStatus extends React.Component {
|
|||||||
})}
|
})}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
{isInactive && (
|
{failureReason && (
|
||||||
|
<div className="failure-reason error">{failureReason}</div>
|
||||||
|
)}
|
||||||
|
{isError && (
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
label="Reconnect"
|
label="Reconnect"
|
||||||
|
|||||||
@ -6,13 +6,18 @@ import { observer } from "mobx-react";
|
|||||||
import { autobind, cssNames, IClassName } from "../../utils";
|
import { autobind, cssNames, IClassName } from "../../utils";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
|
|
||||||
export type TooltipPosition = "top" | "left" | "right" | "bottom";
|
export enum TooltipPosition {
|
||||||
|
TOP = "top",
|
||||||
|
BOTTOM = "bottom",
|
||||||
|
LEFT = "left",
|
||||||
|
RIGHT = "right",
|
||||||
|
}
|
||||||
|
|
||||||
export interface TooltipProps {
|
export interface TooltipProps {
|
||||||
targetId: string; // "id" of target html-element to bind
|
targetId: string; // html-id of target element to bind for
|
||||||
visible?: boolean;
|
visible?: boolean; // initial visibility
|
||||||
offset?: number; // px
|
offset?: number; // offset from target element in pixels (all sides)
|
||||||
usePortal?: boolean;
|
usePortal?: boolean; // renders element outside of parent (in body), disable for "easy-styling", default: true
|
||||||
position?: TooltipPosition;
|
position?: TooltipPosition;
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
formatters?: TooltipContentFormatters;
|
formatters?: TooltipContentFormatters;
|
||||||
@ -71,7 +76,12 @@ export class Tooltip extends React.Component<TooltipProps> {
|
|||||||
const { position } = this.props;
|
const { position } = this.props;
|
||||||
const { elem, targetElem } = this;
|
const { elem, targetElem } = this;
|
||||||
|
|
||||||
let allPositions: TooltipPosition[] = ["right", "bottom", "top", "left"];
|
let allPositions: TooltipPosition[] = [
|
||||||
|
TooltipPosition.RIGHT,
|
||||||
|
TooltipPosition.BOTTOM,
|
||||||
|
TooltipPosition.LEFT,
|
||||||
|
TooltipPosition.RIGHT,
|
||||||
|
];
|
||||||
if (allPositions.includes(position)) {
|
if (allPositions.includes(position)) {
|
||||||
allPositions = [
|
allPositions = [
|
||||||
position, // put first as priority side for positioning
|
position, // put first as priority side for positioning
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user