mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
1 parent
ef3962f8b5
commit
323a4c141e
18 files changed
+319
-379
No files matched your search
@@ -1,159 +0,0 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<div class="h-100">
|
||||
<div class="wrapper" v-if="status === 'LOADING'">
|
||||
<cube-spinner text="" />
|
||||
<div class="auth-output">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<pre class="auth-output" v-html="authOutput" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper" v-if="status === 'ERROR'">
|
||||
<div class="error">
|
||||
<i class="material-icons">{{ error_icon }}</i>
|
||||
<div class="text-center">
|
||||
<h2>{{ cluster.preferences.clusterName }}</h2>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<pre v-html="authOutput" />
|
||||
<b-button variant="link" @click="tryAgain">
|
||||
Reconnect
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CubeSpinner from "@/_vue/components/CubeSpinner";
|
||||
import { tracker } from "../../../common/tracker"
|
||||
|
||||
export default {
|
||||
name: "ClusterPage",
|
||||
components: {
|
||||
CubeSpinner
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
authOutput: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cluster: function() {
|
||||
return this.$store.getters.clusterById(this.$route.params.id);
|
||||
},
|
||||
online: function() {
|
||||
if (!this.cluster) { return false }
|
||||
return this.cluster.online;
|
||||
},
|
||||
accessible: function() {
|
||||
if (!this.cluster) { return false }
|
||||
return this.cluster.accessible;
|
||||
},
|
||||
lens: function() {
|
||||
return this.$store.getters.lensById(this.cluster.id);
|
||||
},
|
||||
status: function() {
|
||||
if (this.cluster) {
|
||||
if (this.cluster.accessible && this.lens.loaded === true) {
|
||||
return "SUCCESS";
|
||||
} else if (this.cluster.accessible === false) {
|
||||
return "ERROR";
|
||||
}
|
||||
return "LOADING";
|
||||
}
|
||||
return "ERROR";
|
||||
},
|
||||
error_icon: function() {
|
||||
if (!this.cluster.online) {
|
||||
return "cloud_off"
|
||||
} else {
|
||||
return "https"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tryAgain: function() {
|
||||
this.authOutput = ""
|
||||
this.cluster.accessible = null
|
||||
setTimeout(() => {
|
||||
this.loadLens()
|
||||
}, 1000)
|
||||
|
||||
},
|
||||
loadLens: function() {
|
||||
this.authOutput = "Connecting ...\n";
|
||||
this.$promiseIpc.on(`kube-auth:${this.cluster.id}`, (output) => {
|
||||
this.authOutput += output.data;
|
||||
})
|
||||
this.toggleLens();
|
||||
return this.$store.dispatch("refineCluster", this.$route.params.id);
|
||||
},
|
||||
lensLoaded: function() {
|
||||
console.log("lens loaded")
|
||||
this.lens.loaded = true;
|
||||
this.$store.commit("updateLens", this.lens);
|
||||
},
|
||||
// Called only when online state changes
|
||||
toggleLens: function() {
|
||||
if (!this.lens) { return }
|
||||
if (this.accessible) {
|
||||
setTimeout(this.activateLens, 0); // see: https://github.com/electron/electron/issues/10016
|
||||
} else {
|
||||
this.hideLens();
|
||||
}
|
||||
},
|
||||
activateLens: async function() {
|
||||
console.log("activate lens")
|
||||
if (!this.lens.webview) {
|
||||
console.log("creating an iframe")
|
||||
const webview = document.createElement('iframe');
|
||||
webview.addEventListener('load', this.lensLoaded);
|
||||
webview.src = this.cluster.url;
|
||||
this.lens.webview = webview;
|
||||
}
|
||||
this.$store.dispatch("attachWebview", this.lens);
|
||||
tracker.event("cluster", "open");
|
||||
},
|
||||
hideLens: function() {
|
||||
this.$store.dispatch("hideWebviews");
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadLens();
|
||||
},
|
||||
destroyed() {
|
||||
this.hideLens();
|
||||
},
|
||||
watch: {
|
||||
"$route": "loadLens",
|
||||
"online": "toggleLens",
|
||||
"cluster": "toggleLens",
|
||||
"accessible": function(newStatus, oldStatus) {
|
||||
console.log("accessible watch, vals:", newStatus, oldStatus);
|
||||
if(newStatus === false) { // accessble == false
|
||||
tracker.event("cluster", "open-failed");
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
div.auth-output {
|
||||
padding-top: 250px;
|
||||
width: 70%;
|
||||
pre {
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
width: 90%;
|
||||
}
|
||||
pre {
|
||||
font-size: 80%;
|
||||
overflow: auto;
|
||||
max-height: 150px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
import "./app.scss";
|
||||
import React, { Fragment } from "react";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { i18nStore } from "../i18n";
|
||||
import { configStore } from "../config.store";
|
||||
@@ -34,11 +34,10 @@ import { LandingPage, landingRoute, landingURL } from "./+landing-page";
|
||||
import { clusterStore } from "../../common/cluster-store";
|
||||
import { ClusterSettings, clusterSettingsRoute } from "./+cluster-settings";
|
||||
import { Workspaces, workspacesRoute } from "./+workspaces";
|
||||
import { ErrorBoundary } from "./error-boundary";
|
||||
|
||||
@observer
|
||||
export class App extends React.Component {
|
||||
static rootElem = document.getElementById('app');
|
||||
|
||||
static async init() {
|
||||
await i18nStore.init();
|
||||
await configStore.init();
|
||||
@@ -57,27 +56,25 @@ export class App extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<ErrorBoundary>
|
||||
<Switch>
|
||||
<Switch>
|
||||
<Route component={LandingPage} {...landingRoute}/>
|
||||
<Route component={AddCluster} {...addClusterRoute}/>
|
||||
<Route component={Workspaces} {...workspacesRoute}/>
|
||||
<Route component={ClusterSettings} {...clusterSettingsRoute}/>
|
||||
<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={this.startURL}/>
|
||||
<Route component={NotFound}/>
|
||||
</Switch>
|
||||
<Route component={LandingPage} {...landingRoute}/>
|
||||
<Route component={AddCluster} {...addClusterRoute}/>
|
||||
<Route component={Workspaces} {...workspacesRoute}/>
|
||||
<Route component={ClusterSettings} {...clusterSettingsRoute}/>
|
||||
<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={this.startURL}/>
|
||||
<Route component={NotFound}/>
|
||||
</Switch>
|
||||
<KubeObjectDetails/>
|
||||
<Notifications/>
|
||||
@@ -86,7 +83,7 @@ export class App extends React.Component {
|
||||
<AddRoleBindingDialog/>
|
||||
<PodLogsDialog/>
|
||||
<DeploymentScaleDialog/>
|
||||
</Fragment>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,40 @@
|
||||
import "./cluster-manager.scss"
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { computed } from "mobx";
|
||||
import { App } from "../app";
|
||||
import { ClusterStatus } from "./cluster-status";
|
||||
import { ClustersMenu } from "./clusters-menu";
|
||||
import { BottomBar } from "./bottom-bar";
|
||||
import { App } from "../app";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { invokeIpc } from "../../../common/ipc";
|
||||
import { ClusterIpcChannel } from "../../../main/cluster";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
|
||||
interface Props {
|
||||
className?: IClassName;
|
||||
contentClass?: IClassName;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterManager extends React.Component<Props> {
|
||||
@computed get isReady() {
|
||||
return clusterStore.activeCluster?.isReady
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await invokeIpc(ClusterIpcChannel.INIT)
|
||||
await App.init();
|
||||
}
|
||||
|
||||
export class ClusterManager extends React.Component {
|
||||
render() {
|
||||
const { className, contentClass } = this.props;
|
||||
return (
|
||||
<div className="ClusterManager">
|
||||
<div className={cssNames("ClusterManager", className)}>
|
||||
<div id="draggable-top"/>
|
||||
<div id="lens-view">
|
||||
<App/>
|
||||
<div id="lens-view" className={cssNames("flex", contentClass)}>
|
||||
{this.isReady && <App/>}
|
||||
{!this.isReady && <ClusterStatus/>}
|
||||
</div>
|
||||
<ClustersMenu/>
|
||||
<BottomBar/>
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
.ClusterStatus {
|
||||
--flex-gap: #{$padding * 2};
|
||||
|
||||
min-width: 350px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
|
||||
pre {
|
||||
@include custom-scrollbar;
|
||||
max-width: 70vw;
|
||||
max-height: 40vh;
|
||||
//text-align: left;
|
||||
}
|
||||
|
||||
.Icon {
|
||||
--size: 70px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,32 @@
|
||||
import "./cluster-manager.scss"
|
||||
import type { KubeAuthProxyResponse } from "../../../main/kube-auth-proxy";
|
||||
import { Cluster, ClusterIpcEvent } from "../../../main/cluster";
|
||||
import "./cluster-status.scss"
|
||||
import React from "react";
|
||||
import type { KubeAuthProxyResponse } from "../../../main/kube-auth-proxy";
|
||||
import { ClusterIpcChannel } from "../../../main/cluster";
|
||||
import { invokeIpc } from "../../../common/ipc";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { computed, observable } from "mobx";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Icon } from "../icon";
|
||||
import { Button } from "../button";
|
||||
import { Trans } from "@lingui/macro";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
import { cssNames } from "../../utils";
|
||||
|
||||
@observer
|
||||
export class ClusterStatus extends React.Component<Props> {
|
||||
@observable authProxyOutput = "Connecting ...\n"
|
||||
export class ClusterStatus extends React.Component {
|
||||
@observable authOutput: string[] = [];
|
||||
|
||||
@computed get clusterId() {
|
||||
return this.props.cluster.id;
|
||||
get cluster() {
|
||||
return clusterStore.activeCluster;
|
||||
}
|
||||
|
||||
get clusterId() {
|
||||
return clusterStore.activeClusterId;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, authResponse: KubeAuthProxyResponse) => {
|
||||
this.authProxyOutput += authResponse.data;
|
||||
this.authOutput = ["Connecting ...\n"];
|
||||
ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, { data, stream }: KubeAuthProxyResponse) => {
|
||||
this.authOutput.push(`[${stream}]: ${data}`);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,22 +34,32 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
ipcRenderer.removeAllListeners(`kube-auth:${this.clusterId}`);
|
||||
}
|
||||
|
||||
reconnectCluster = () => {
|
||||
ipcRenderer.send(ClusterIpcEvent.RECONNECT, this.clusterId);
|
||||
reconnect = () => {
|
||||
this.authOutput = ["Reconnecting ...\n"];
|
||||
invokeIpc(ClusterIpcChannel.RECONNECT, this.clusterId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { authProxyOutput } = this;
|
||||
const { contextName, online } = this.props.cluster;
|
||||
const { authOutput, cluster } = this;
|
||||
const isError = cluster?.accessible === false;
|
||||
return (
|
||||
<div className="ClusterStatus flex column">
|
||||
<Icon sticker className="status-icon" material={online ? "https" : "cloud_off"}/>
|
||||
<h2>{contextName}</h2>
|
||||
<pre className="kube-auth-stdout">{authProxyOutput}</pre>
|
||||
<Button
|
||||
primary label={<Trans>Reconnect</Trans>}
|
||||
onClick={this.reconnectCluster}
|
||||
/>
|
||||
<div className="ClusterStatus flex column gaps">
|
||||
{!isError && <Icon material="cloud_queue"/>}
|
||||
{isError && <Icon material="cloud_off" className="error"/>}
|
||||
<h2>{cluster?.contextName}</h2>
|
||||
<pre className="kube-auth-out">
|
||||
{authOutput.map((data, index) => {
|
||||
const error = data.startsWith("[stderr]");
|
||||
return <p key={index} className={cssNames({ error })}>{data}</p>
|
||||
})}
|
||||
</pre>
|
||||
{isError && (
|
||||
<Button
|
||||
primary className="box center"
|
||||
label="Reconnect"
|
||||
onClick={this.reconnect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
label: _i18n._(t`Settings`),
|
||||
click: () => navigate(clusterSettingsURL())
|
||||
}));
|
||||
if (cluster.initialized) {
|
||||
if (cluster.online) {
|
||||
menu.append(new MenuItem({
|
||||
label: _i18n._(t`Disconnect`),
|
||||
click: () => {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { I18nProvider } from "@lingui/react";
|
||||
import { browserHistory } from "./navigation";
|
||||
import { isMac } from "../common/vars";
|
||||
import { _i18n } from "./i18n";
|
||||
import { App } from "./components/app";
|
||||
import { ClusterManager } from "./components/cluster-manager";
|
||||
import { ErrorBoundary } from "./components/error-boundary";
|
||||
import { WhatsNew, whatsNewRoute } from "./components/+whats-new";
|
||||
@@ -19,14 +18,14 @@ import { Preferences, preferencesRoute } from "./components/+preferences";
|
||||
@observer
|
||||
class LensApp extends React.Component {
|
||||
static async init() {
|
||||
App.rootElem.classList.toggle("is-mac", isMac);
|
||||
await Promise.all([
|
||||
userStore.load(),
|
||||
workspaceStore.load(),
|
||||
clusterStore.load(),
|
||||
]);
|
||||
await App.init();
|
||||
render(<LensApp/>, App.rootElem);
|
||||
const elem = document.getElementById("app");
|
||||
elem.classList.toggle("is-mac", isMac);
|
||||
render(<LensApp/>, elem);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
Reference in new issue
Block a user