1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

cluster-manager view

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-07-10 12:47:50 +03:00
parent b05ed9822d
commit 6df56b5471
24 changed files with 167 additions and 141 deletions

View File

@ -7,7 +7,6 @@ export const isWindows = process.platform === "win32"
export const isDebugging = process.env.DEBUG === "true";
export const isProduction = process.env.NODE_ENV === "production"
export const isDevelopment = isDebugging || !isProduction;
export const buildVersion = process.env.BUILD_VERSION;
export const isTestEnv = !!process.env.JEST_WORKER_ID;
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`

View File

@ -78,8 +78,7 @@ export class Cluster implements ClusterModel {
this.contextHandler = new ContextHandler(this);
const proxyPort = await this.contextHandler.resolveProxyPort();
this.kubeconfigManager = new KubeconfigManager(this, proxyPort);
this.url = this.contextHandler.url
// todo: verify api url
this.url = this.contextHandler.url;
// this.apiUrl = kubeConfig.getCurrentCluster().server;
this.initialized = true;
logger.debug(`[CLUSTER]: init done (id="${this.id}", context="${this.contextName}")`);

View File

@ -111,12 +111,7 @@ export class ContextHandler {
async resolveProxyPort(): Promise<number> {
if (!this.proxyPort) {
try {
this.proxyPort = await getFreePort()
} catch (error) {
logger.error(error)
throw(error)
}
this.proxyPort = await getFreePort()
}
return this.proxyPort
}
@ -133,7 +128,7 @@ export class ContextHandler {
if (!this.proxyServer) {
const proxyPort = await this.resolveProxyPort()
const proxyEnv = Object.assign({}, process.env)
if (this.cluster?.preferences.httpsProxy) {
if (this.cluster.preferences.httpsProxy) {
proxyEnv.HTTPS_PROXY = this.cluster.preferences.httpsProxy
}
this.proxyServer = new KubeAuthProxy(this.cluster, proxyPort, proxyEnv)

View File

@ -6,7 +6,7 @@ import { app, dialog } from "electron"
import { appName, appProto, isMac, staticDir, staticProto } from "../common/vars";
import path from "path"
import initMenu from "./menu"
import { LensProxy, listen } from "./proxy"
import { LensProxy, listen } from "./lens-proxy"
import { WindowManager } from "./window-manager";
import { ClusterManager } from "./cluster-manager";
import AppUpdater from "./app-updater"

View File

@ -4,27 +4,20 @@ import { Socket } from "net";
import * as url from "url";
import * as WebSocket from "ws"
import { ContextHandler } from "./context-handler";
import logger from "./logger"
import * as shell from "./node-shell-session"
import { ClusterManager } from "./cluster-manager"
import { Router } from "./router"
import { apiPrefix } from "../common/vars";
import logger from "./logger"
export class LensProxy {
public static readonly localShellSessions = true
public port: number;
protected clusterUrl: url.UrlWithStringQuery
protected clusterManager: ClusterManager
protected retryCounters: Map<string, number> = new Map()
protected router: Router
protected proxyServer: http.Server
protected router: Router
protected closed = false
protected retryCounters = new Map<string, number>()
constructor(port: number, clusterManager: ClusterManager) {
this.port = port
this.clusterManager = clusterManager
this.router = new Router()
constructor(public port: number, protected clusterManager: ClusterManager) {
this.router = new Router();
}
public run() {
@ -34,7 +27,7 @@ export class LensProxy {
}
public close() {
logger.info("Closing proxy server")
logger.info(`Closing proxy server at port ${this.port}`);
this.proxyServer.close()
this.closed = true
}
@ -77,9 +70,7 @@ export class LensProxy {
}
})
proxy.on("error", (error, req, res, target) => {
if(this.closed) {
return
}
if (this.closed) return;
if (target) {
logger.debug("Failed proxy to target: " + JSON.stringify(target))
if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) {
@ -142,14 +133,13 @@ export class LensProxy {
return
}
const contextHandler = cluster.contextHandler
contextHandler.ensureServer().then(async () => {
const proxyTarget = await this.getProxyTarget(req, contextHandler)
if (proxyTarget) {
proxy.web(req, res, proxyTarget)
} else {
this.router.route(cluster, req, res)
}
})
await contextHandler.ensureServer();
const proxyTarget = await this.getProxyTarget(req, contextHandler)
if (proxyTarget) {
proxy.web(req, res, proxyTarget)
} else {
this.router.route(cluster, req, res)
}
}
protected async handleWsUpgrade(req: http.IncomingMessage, socket: Socket, head: Buffer) {

View File

@ -3,7 +3,7 @@ import { autobind, base64, EventEmitter, interval } from "../utils";
import { WebSocketApi } from "./websocket-api";
import { configStore } from "../config.store";
import isEqual from "lodash/isEqual"
import { apiPrefix, isDevelopment } from "../../common/vars";
import { isDevelopment } from "../../common/vars";
export enum TerminalChannels {
STDIN = 0,

View File

@ -0,0 +1 @@
export * from "./workspaces"

View File

@ -1,33 +1,3 @@
.Workspaces {
display: grid;
grid-template-areas: "draggable draggable" "menu lens-view" "bottom-bar bottom-bar";
grid-template-rows: auto 1fr min-content;
grid-template-columns: min-content 1fr;
height: 100%;
.draggable-top {
@include set-draggable;
grid-area: draggable;
height: 25px;
}
#lens-view {
grid-area: lens-view;
}
.ClusterMenu {
grid-area: menu;
}
.WorkspacesBottomBar {
grid-area: bottom-bar;
}
}
.WorkspacesMenu {
border-radius: $radius;
.workspaces-title {
padding: $padding;
}
}
}

View File

@ -1,19 +1,12 @@
import "./workspaces.scss"
import React from "react";
import { ClustersMenu } from "./clusters-menu";
import { WorkspacesBottomBar } from "./bottom-bar";
// todo: support `workspaceId` in URL
export class Workspaces extends React.Component {
render() {
return (
<div className="Workspaces">
<div className="draggable-top"/>
<div id="lens-view"/>
<ClustersMenu/>
<WorkspacesBottomBar/>
Workspaces
</div>
)
);
}
}

View File

@ -1,15 +1,13 @@
import "./app.scss";
import React from "react";
import { render } from "react-dom";
import { Redirect, Route, Router, Switch } from "react-router";
import React, { Fragment } from "react";
import { observer } from "mobx-react";
import { I18nProvider } from '@lingui/react'
import { _i18n, i18nStore } from "../i18n";
import { browserHistory } from "../navigation";
import { i18nStore } from "../i18n";
import { configStore } from "../config.store";
import { Terminal } from "./dock/terminal";
import { Redirect, Route, Switch } from "react-router";
import { Notifications } from "./notifications";
import { NotFound } from "./+404";
import { configStore } from "../config.store";
import { UserManagement } from "./+user-management/user-management";
import { ConfirmDialog } from "./confirm-dialog";
import { usersManagementRoute } from "./+user-management/user-management.routes";
@ -24,7 +22,6 @@ import { Cluster } from "./+cluster/cluster";
import { Config, configRoute } from "./+config";
import { Events } from "./+events/events";
import { eventRoute } from "./+events";
import { ErrorBoundary } from "./error-boundary";
import { Apps, appsRoute } from "./+apps";
import { KubeObjectDetails } from "./kube-object/kube-object-details";
import { AddRoleBindingDialog } from "./+user-management-roles-bindings";
@ -33,7 +30,6 @@ import { DeploymentScaleDialog } from "./+workloads-deployments/deployment-scale
import { CustomResources } from "./+custom-resources/custom-resources";
import { crdRoute } from "./+custom-resources";
import { isAllowedResource } from "../api/rbac";
import { Terminal } from "./dock/terminal";
@observer
export class App extends React.Component {
@ -43,42 +39,40 @@ export class App extends React.Component {
await i18nStore.init();
await configStore.init();
await Terminal.preloadFonts();
render(<App/>, App.rootElem);
}
render() {
const homeUrl = isAllowedResource(["events", "nodes", "pods"]) ? clusterURL() : workloadsURL();
return (
<I18nProvider i18n={_i18n}>
<Router history={browserHistory}>
<ErrorBoundary>
<Switch>
<Switch>
<Route component={Cluster} {...clusterRoute}/>
<Route component={Nodes} {...nodesRoute}/>
<Route component={Workloads} {...workloadsRoute}/>
<Route component={Config} {...configRoute}/>
<Route component={Network} {...networkRoute}/>
<Route component={Storage} {...storageRoute}/>
<Route component={Namespaces} {...namespacesRoute}/>
<Route component={Events} {...eventRoute}/>
<Route component={CustomResources} {...crdRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/>
<Redirect exact from="/" to={homeUrl}/>
<Route path="*" component={NotFound}/>
</Switch>
</Switch>
<KubeObjectDetails/>
<Notifications/>
<ConfirmDialog/>
<KubeConfigDialog/>
<AddRoleBindingDialog/>
<PodLogsDialog/>
<DeploymentScaleDialog/>
</ErrorBoundary>
</Router>
</I18nProvider>
<Fragment>
<Switch>
<Switch>
{/* todo: remove */}
<Route children={() => <p className="info">App is running!</p>}/>
<Route component={Cluster} {...clusterRoute}/>
<Route component={Nodes} {...nodesRoute}/>
<Route component={Workloads} {...workloadsRoute}/>
<Route component={Config} {...configRoute}/>
<Route component={Network} {...networkRoute}/>
<Route component={Storage} {...storageRoute}/>
<Route component={Namespaces} {...namespacesRoute}/>
<Route component={Events} {...eventRoute}/>
<Route component={CustomResources} {...crdRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/>
<Redirect exact from="/" to={homeUrl}/>
<Route path="*" component={NotFound}/>
</Switch>
</Switch>
<KubeObjectDetails/>
<Notifications/>
<ConfirmDialog/>
<KubeConfigDialog/>
<AddRoleBindingDialog/>
<PodLogsDialog/>
<DeploymentScaleDialog/>
</Fragment>
)
}
}

View File

@ -1,4 +1,4 @@
.WorkspacesBottomBar {
.BottomBar {
font-size: $font-size-small;
background-color: #3d90ce;
padding: $padding / 2 $padding;
@ -8,3 +8,11 @@
cursor: pointer;
}
}
#workspace-menu {
border-radius: $radius;
.workspaces-title {
padding: $padding;
}
}

View File

@ -13,26 +13,26 @@ import { workspaceStore } from "../../../common/workspace-store";
// todo: remove dummy actions + console.log
@observer
export class WorkspacesBottomBar extends React.Component {
export class BottomBar extends React.Component {
@observable menuVisible = false;
render() {
const { currentWorkspace, workspacesList } = workspaceStore;
return (
<div className="WorkspacesBottomBar flex gaps">
<div className="BottomBar flex gaps">
<div id="workspace" className="workspace flex align-center box right">
<Icon small material="layers"/> {currentWorkspace}
</div>
<Menu
usePortal
htmlFor="workspace"
className="WorkspacesMenu"
id="workspace-menu"
isOpen={this.menuVisible}
open={() => this.menuVisible = true}
close={() => this.menuVisible = false}
>
<Link
to="/workspaces"
to="#"
className="workspaces-title"
onClick={prevDefault(() => console.log('/navigate: workspaces page'))}>
<Trans>Workspaces</Trans>

View File

@ -0,0 +1,30 @@
import React from "react";
import { observer } from "mobx-react";
import { ClusterId, clusterStore } from "../../../common/cluster-store";
import { WorkspaceId, workspaceStore } from "../../../common/workspace-store";
export const clusterContext = React.createContext(getClusterContext());
export interface ClusterContextValue {
workspaceId: WorkspaceId;
clusterId?: ClusterId;
}
export function getClusterContext(): ClusterContextValue {
return {
clusterId: clusterStore.activeCluster,
workspaceId: workspaceStore.currentWorkspace,
}
}
@observer
export class ClusterContext extends React.Component {
render() {
const { Provider } = clusterContext;
return (
<Provider value={getClusterContext()}>
{this.props.children}
</Provider>
)
}
}

View File

@ -0,0 +1,26 @@
.ClusterManager {
display: grid;
grid-template-areas: "draggable draggable" "menu lens-view" "bottom-bar bottom-bar";
grid-template-rows: auto 1fr min-content;
grid-template-columns: min-content 1fr;
height: 100%;
.draggable-top {
@include set-draggable;
grid-area: draggable;
height: 25px;
}
#lens-view {
position: relative;
grid-area: lens-view;
}
.ClustersMenu {
grid-area: menu;
}
.BottomBar {
grid-area: bottom-bar;
}
}

View File

@ -0,0 +1,18 @@
import "./cluster-manager.scss"
import React from "react";
import { ClustersMenu } from "./clusters-menu";
import { BottomBar } from "./bottom-bar";
export class ClusterManager extends React.Component {
render() {
const { children: lensView } = this.props;
return (
<div className="ClusterManager">
<div className="draggable-top"/>
<div id="lens-view">{lensView}</div>
<ClustersMenu/>
<BottomBar/>
</div>
)
}
}

View File

@ -43,7 +43,7 @@
color: white;
background: $colorSuccess;
font-weight: normal;
border-radius: 50%;
border-radius: $radius;
padding: 0;
}
}

View File

@ -1,14 +1,13 @@
import type { Cluster } from "../../../main/cluster";
import "./clusters-menu.scss"
import { remote } from "electron"
import React from "react";
import { observer } from "mobx-react";
import { _i18n } from "../../i18n";
import { t, Trans } from "@lingui/macro";
import type { Cluster } from "../../../main/cluster";
import { userStore } from "../../../common/user-store";
import { clusterStore } from "../../../common/cluster-store";
import { WorkspaceId } from "../../../common/workspace-store";
import { workspaceStore } from "../../../common/workspace-store";
import { ClusterIcon } from "../+cluster-settings/cluster-icon";
import { Icon } from "../icon";
import { cssNames, IClassName } from "../../utils";
@ -19,7 +18,6 @@ import { Badge } from "../badge";
interface Props {
className?: IClassName;
workspaceId?: WorkspaceId;
}
@observer
@ -54,9 +52,10 @@ export class ClustersMenu extends React.Component<Props> {
}
render() {
const { workspaceId, className } = this.props;
const clusters = clusterStore.getByWorkspaceId(workspaceId);
const { className } = this.props;
const { newContexts } = userStore;
const { currentWorkspace } = workspaceStore;
const clusters = clusterStore.getByWorkspaceId(currentWorkspace);
return (
<div className={cssNames("ClustersMenu flex gaps column", className)}>
{clusters.map(cluster => {

View File

@ -0,0 +1 @@
export * from "./cluster-manager"

View File

@ -7,7 +7,7 @@ import { t, Trans } from "@lingui/macro";
import { Button } from "../button";
import { navigation } from "../../navigation";
import { _i18n } from "../../i18n";
import { issuesTrackerUrl, slackUrl, buildVersion } from "../../../common/vars";
import { issuesTrackerUrl, slackUrl } from "../../../common/vars";
interface Props {
}
@ -45,7 +45,6 @@ export class ErrorBoundary extends React.Component<Props, State> {
<div className="ErrorBoundary flex column gaps">
<h5>
<Trans>App crash at <span className="contrast">{pageUrl}</span></Trans>
{buildVersion && <p><Trans>Build version</Trans>: {buildVersion}</p>}
</h5>
<p>
<Trans>

View File

@ -21,6 +21,7 @@ export interface MenuProps {
isOpen?: boolean;
open(): void;
close(): void;
id?: string;
className?: string;
htmlFor?: string;
autoFocus?: boolean;

View File

@ -1,38 +1,41 @@
// todo: remove when app.tsx re-used
import "./components/app.scss"
import "./theme.store";
import "../common/system-ca"
import React from "react";
import ReactDOM from "react-dom";
import { render } from "react-dom";
import { Router } from "react-router";
import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store";
import { clusterStore } from "../common/cluster-store";
import { Workspaces } from "./components/+workspaces/workspaces";
import { I18nProvider } from "@lingui/react";
import { _i18n } from "./i18n";
import { browserHistory } from "./navigation";
import { _i18n } from "./i18n";
import { App } from "./components/app";
import { ClusterManager } from "./components/cluster-manager";
import { ErrorBoundary } from "./components/error-boundary";
class App extends React.Component {
class LensApp extends React.Component {
static async init() {
await Promise.all([
userStore.load(),
workspaceStore.load(),
clusterStore.load(),
]);
ReactDOM.render(<App/>, document.getElementById("app"),)
await App.init();
render(<LensApp/>, App.rootElem);
}
render() {
return (
<I18nProvider i18n={_i18n}>
<Router history={browserHistory}>
<Workspaces/>
<ErrorBoundary>
<ClusterManager>
<App/>
</ClusterManager>
</ErrorBoundary>
</Router>
</I18nProvider>
)
}
}
window.addEventListener("load", App.init);
window.addEventListener("load", LensApp.init);