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:
parent
b05ed9822d
commit
6df56b5471
@ -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" : ""}`
|
||||
|
||||
@ -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}")`);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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) {
|
||||
@ -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,
|
||||
|
||||
1
src/renderer/components/+workspaces/index.ts
Normal file
1
src/renderer/components/+workspaces/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./workspaces"
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
30
src/renderer/components/cluster-manager/cluster-context.tsx
Normal file
30
src/renderer/components/cluster-manager/cluster-context.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
26
src/renderer/components/cluster-manager/cluster-manager.scss
Normal file
26
src/renderer/components/cluster-manager/cluster-manager.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
18
src/renderer/components/cluster-manager/cluster-manager.tsx
Normal file
18
src/renderer/components/cluster-manager/cluster-manager.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -43,7 +43,7 @@
|
||||
color: white;
|
||||
background: $colorSuccess;
|
||||
font-weight: normal;
|
||||
border-radius: 50%;
|
||||
border-radius: $radius;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@ -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 => {
|
||||
1
src/renderer/components/cluster-manager/index.tsx
Normal file
1
src/renderer/components/cluster-manager/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from "./cluster-manager"
|
||||
@ -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>
|
||||
|
||||
@ -21,6 +21,7 @@ export interface MenuProps {
|
||||
isOpen?: boolean;
|
||||
open(): void;
|
||||
close(): void;
|
||||
id?: string;
|
||||
className?: string;
|
||||
htmlFor?: string;
|
||||
autoFocus?: boolean;
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user