Merge branch 'master' into extensions-api

# Conflicts:
#	package.json
#	src/common/cluster-store.ts
#	src/renderer/bootstrap.tsx
#	src/renderer/components/cluster-manager/clusters-menu.tsx
#	static/RELEASE_NOTES.md
#	webpack.dll.ts
This commit is contained in:
Roman committed 2020-09-11 12:29:38 +03:00
commit a3dc928430
34 files changed
+273 -139

No files matched your search

@@ -1,6 +1,6 @@
import React from "react";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { observable, autorun } from "mobx";
import { observer, disposeOnUnmount } from "mobx-react";
import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input";
import { SubTitle } from "../../layout/sub-title";
@@ -11,7 +11,15 @@ interface Props {
@observer
export class ClusterHomeDirSetting extends React.Component<Props> {
@observable directory = this.props.cluster.preferences.terminalCWD || "";
@observable directory = "";
componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
this.directory = this.props.cluster.preferences.terminalCWD || "";
})
);
}
save = () => {
this.props.cluster.preferences.terminalCWD = this.directory;
@@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component<Props> {
try {
if (file) {
const buf = Buffer.from(await file.arrayBuffer());
cluster.preferences.icon = `data:${file.type};base64, ${buf.toString('base64')}`;
cluster.preferences.icon = `data:${file.type};base64,${buf.toString('base64')}`;
} else {
// this has to be done as a seperate branch (and not always) because `cluster`
// is observable and triggers an update loop.
@@ -73,4 +73,4 @@ export class ClusterIconSetting extends React.Component<Props> {
</>
);
}
}
}
@@ -1,8 +1,8 @@
import React from "react";
import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { observable, autorun } from "mobx";
import { observer, disposeOnUnmount } from "mobx-react";
import { SubTitle } from "../../layout/sub-title";
import { isRequired } from "../../input/input.validators";
@@ -12,7 +12,15 @@ interface Props {
@observer
export class ClusterNameSetting extends React.Component<Props> {
@observable name = this.props.cluster.preferences.clusterName || "";
@observable name = "";
componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
this.name = this.props.cluster.preferences.clusterName;
})
);
}
save = () => {
this.props.cluster.preferences.clusterName = this.name;
@@ -1,11 +1,11 @@
import React from "react";
import { observer } from "mobx-react";
import { observer, disposeOnUnmount } from "mobx-react";
import { prometheusProviders } from "../../../../common/prometheus-providers";
import { Cluster } from "../../../../main/cluster";
import { SubTitle } from "../../layout/sub-title";
import { Select, SelectOption } from "../../select";
import { Input } from "../../input";
import { observable, computed } from "mobx";
import { observable, computed, autorun } from "mobx";
const options: SelectOption<string>[] = [
{ value: "", label: "Auto detect" },
@@ -27,14 +27,22 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
}
componentDidMount() {
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
if (prometheus) {
const prefix = prometheus.prefix || "";
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
}
if (prometheusProvider) {
this.provider = prometheusProvider.type;
}
disposeOnUnmount(this,
autorun(() => {
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
if (prometheus) {
const prefix = prometheus.prefix || "";
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
} else {
this.path = "";
}
if (prometheusProvider) {
this.provider = prometheusProvider.type;
} else {
this.provider = "";
}
})
);
}
parsePrometheusPath = () => {
@@ -1,6 +1,6 @@
import React from "react";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { observable, autorun } from "mobx";
import { observer, disposeOnUnmount } from "mobx-react";
import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input";
import { isUrl } from "../../input/input.validators";
@@ -12,7 +12,15 @@ interface Props {
@observer
export class ClusterProxySetting extends React.Component<Props> {
@observable proxy = this.props.cluster.preferences.httpsProxy || "";
@observable proxy = "";
componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
this.proxy = this.props.cluster.preferences.httpsProxy || "";
})
);
}
save = () => {
this.props.cluster.preferences.httpsProxy = this.proxy;
@@ -12,6 +12,7 @@ import { Icon } from "../icon";
import { Input } from "../input";
import { cssNames, prevDefault } from "../../utils";
import { Button } from "../button";
import { isRequired, Validator } from "../input/input.validators";
@observer
export class Workspaces extends React.Component {
@@ -41,9 +42,9 @@ export class Workspaces extends React.Component {
saveWorkspace = (id: WorkspaceId) => {
const draft = toJS(this.editingWorkspaces.get(id));
if (draft) {
const workspace = workspaceStore.saveWorkspace(draft);
if (workspace) {
this.clearEditing(id);
workspaceStore.saveWorkspace(draft);
}
}
@@ -90,6 +91,15 @@ export class Workspaces extends React.Component {
})
}
onInputKeypress = (evt: React.KeyboardEvent<any>, workspaceId: WorkspaceId) => {
if (evt.key == 'Enter') {
// Trigget input validation
evt.currentTarget.blur();
evt.currentTarget.focus();
this.saveWorkspace(workspaceId);
}
}
render() {
return (
<WizardLayout className="Workspaces" infoPanel={this.renderInfo()}>
@@ -102,11 +112,15 @@ export class Workspaces extends React.Component {
const isDefault = workspaceStore.isDefault(workspaceId);
const isEditing = this.editingWorkspaces.has(workspaceId);
const editingWorkspace = this.editingWorkspaces.get(workspaceId);
const className = cssNames("workspace flex gaps align-center", {
const className = cssNames("workspace flex gaps", {
active: isActive,
editing: isEditing,
default: isDefault,
});
const existenceValidator: Validator = {
message: () => `Workspace '${name}' already exists`,
validate: value => !workspaceStore.getByName(value.trim())
}
return (
<div key={workspaceId} className={className}>
{!isEditing && (
@@ -139,23 +153,27 @@ export class Workspaces extends React.Component {
placeholder={_i18n._(t`Name`)}
value={editingWorkspace.name}
onChange={v => editingWorkspace.name = v}
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
validators={[isRequired, existenceValidator]}
autoFocus
/>
<Input
className="description"
placeholder={_i18n._(t`Description`)}
value={editingWorkspace.description}
onChange={v => editingWorkspace.description = v}
/>
<Icon
material="cancel"
tooltip={<Trans>Cancel</Trans>}
onClick={() => this.clearEditing(workspaceId)}
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
/>
<Icon
material="save"
tooltip={<Trans>Save</Trans>}
onClick={() => this.saveWorkspace(workspaceId)}
/>
<Icon
material="cancel"
tooltip={<Trans>Cancel</Trans>}
onClick={() => this.clearEditing(workspaceId)}
/>
</Fragment>
)}
</div>
+2 -2
View File
@@ -44,8 +44,8 @@ export class App extends React.Component {
const clusterId = getHostedClusterId();
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`)
await Terminal.preloadFonts()
await clusterIpc.initView.invokeFromRenderer(clusterId, frameId);
await getHostedCluster().whenInitialized;
await clusterIpc.activate.invokeFromRenderer(clusterId, frameId);
await getHostedCluster().whenReady; // cluster.refresh() is done at this point
}
get startURL() {
@@ -1,7 +1,7 @@
import "./cluster-manager.scss"
import React from "react";
import { Redirect, Route, Switch } from "react-router";
import { reaction } from "mobx";
import { comparer, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { ClustersMenu } from "./clusters-menu";
import { BottomBar } from "./bottom-bar";
@@ -25,11 +25,14 @@ export class ClusterManager extends React.Component {
fireImmediately: true
}),
reaction(() => [
getMatchedClusterId(), // refresh when active cluster-view changed
hasLoadedView(getMatchedClusterId()), // refresh when cluster's webview loaded
getMatchedCluster()?.available, // refresh on disconnect active-cluster
getMatchedCluster()?.ready, // refresh when cluster ready-state change
], refreshViews, {
fireImmediately: true
})
fireImmediately: true,
equals: comparer.shallow,
}),
])
}
@@ -38,7 +38,7 @@ export class ClusterStatus extends React.Component<Props> {
error: res.error,
});
})
if (!this.cluster.initialized || this.cluster.disconnected) {
if (this.cluster.disconnected) {
await this.refreshCluster();
}
}
@@ -63,7 +63,7 @@ export class ClusterStatus extends React.Component<Props> {
if (!hasErrors || this.isReconnecting) {
return (
<>
<CubeSpinner />
<CubeSpinner/>
<pre className="kube-auth-out">
<p>{this.isReconnecting ? "Reconnecting..." : "Connecting..."}</p>
{authOutput.map(({ data, error }, index) => {
@@ -75,7 +75,7 @@ export class ClusterStatus extends React.Component<Props> {
}
return (
<>
<Icon material="cloud_off" className="error" />
<Icon material="cloud_off" className="error"/>
<h2>
{cluster.preferences.clusterName}
</h2>
@@ -2,7 +2,7 @@ import { reaction } from "mobx";
import { ipcRenderer } from "electron";
import { matchPath, RouteProps } from "react-router";
import { buildURL, navigation } from "../../navigation";
import { clusterStore, getHostedClusterId } from "../../../common/cluster-store";
import { clusterStore } from "../../../common/cluster-store";
import { clusterSettingsRoute } from "../+cluster-settings/cluster-settings.route";
export interface IClusterViewRouteParams {
@@ -34,11 +34,10 @@ export function getMatchedCluster() {
}
if (ipcRenderer) {
// Refresh global menu depending on active route's type (common/cluster view)
const isMainView = !getHostedClusterId();
if (isMainView) {
if (process.isMainFrame) {
// Keep track of active cluster-id for handling IPC/menus/etc.
reaction(() => getMatchedClusterId(), clusterId => {
ipcRenderer.send("cluster-view:change", clusterId);
ipcRenderer.send("cluster-view:current-id", clusterId);
}, {
fireImmediately: true
})
@@ -1,5 +1,5 @@
import { observable, when } from "mobx";
import { ClusterId, clusterStore } from "../../../common/cluster-store";
import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
import { getMatchedCluster } from "./cluster-view.route"
import logger from "../../../main/logger";
@@ -21,29 +21,38 @@ export async function initView(clusterId: ClusterId) {
}
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
const cluster = clusterStore.getById(clusterId);
await cluster.whenReady;
const parentElem = document.getElementById("lens-views");
const iframe = document.createElement("iframe");
iframe.name = cluster.contextName;
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
iframe.addEventListener("load", async () => {
iframe.setAttribute("src", getClusterFrameUrl(clusterId))
iframe.addEventListener("load", () => {
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
lensViews.get(clusterId).isLoaded = true;
})
}, { once: true });
lensViews.set(clusterId, { clusterId, view: iframe });
parentElem.appendChild(iframe);
// auto-clean when cluster removed
await autoCleanOnRemove(clusterId, iframe);
}
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
await when(() => !clusterStore.getById(clusterId));
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`)
parentElem.removeChild(iframe)
lensViews.delete(clusterId)
// Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed.
// In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx)
// Issue: https://github.com/lensapp/lens/issues/811
iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`;
iframe.removeAttribute("src")
iframe.removeAttribute("name")
}
export function refreshViews() {
const cluster = getMatchedCluster();
lensViews.forEach(({ clusterId, view, isLoaded }) => {
const isVisible = cluster && cluster.available && cluster.id === clusterId;
view.style.display = isLoaded && isVisible ? "flex" : "none"
const isCurrent = clusterId === cluster?.id;
const isReady = cluster?.available && cluster?.ready;
const isVisible = isCurrent && isLoaded && isReady;
view.style.display = isVisible ? "flex" : "none"
})
}
+3 -2
View File
@@ -92,7 +92,8 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
this.props.onOpen();
if (!this.props.pinned) {
if (this.elem) this.elem.addEventListener('click', this.onClickOutside);
window.addEventListener('keydown', this.onEscapeKey);
// Using document.body target to handle keydown event before Drawer does
document.body.addEventListener('keydown', this.onEscapeKey);
}
}
@@ -100,7 +101,7 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
this.props.onClose();
if (!this.props.pinned) {
if (this.elem) this.elem.removeEventListener('click', this.onClickOutside);
window.removeEventListener('keydown', this.onEscapeKey);
document.body.removeEventListener('keydown', this.onEscapeKey);
}
}
@@ -40,6 +40,7 @@ export class Drawer extends React.Component<DrawerProps> {
});
componentDidMount() {
// Using window target for events to make sure they will be catched after other places (e.g. Dialog)
window.addEventListener("mousedown", this.onMouseDown)
window.addEventListener("click", this.onClickOutside)
window.addEventListener("keydown", this.onEscapeKey)
+17 -1
View File
@@ -1 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.cls-2{fill:#3c90ce;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-2" d="M256,496C123.67,496,16,388.33,16,256S123.67,16,256,16,496,123.67,496,256,388.33,496,256,496Zm0-470C129.17,26,26,129.18,26,256S129.17,486,256,486,486,382.82,486,256,382.83,26,256,26Z"/><path class="cls-2" d="M403.22,282l65.66-81.68A220.52,220.52,0,0,0,351.61,57.81Z"/><path class="cls-2" d="M476,256a220.86,220.86,0,0,0-4-41.5L334.74,385.3H434A219,219,0,0,0,476,256Z"/><path class="cls-2" d="M247.63,121.17,139.38,69.48A220,220,0,0,0,38.81,221Z"/><path class="cls-2" d="M140.75,184.81,37.07,234.35C36.38,241.48,36,248.69,36,256A219.13,219.13,0,0,0,91,401.4Z"/><path class="cls-2" d="M210.57,396.65l63.2,78.57a219.52,219.52,0,0,0,151.38-78.57Z"/><path class="cls-2" d="M124.21,307.38l-23.91,104A219.33,219.33,0,0,0,256,476c1.26,0,2.5-.07,3.76-.1Z"/><path class="cls-2" d="M364.52,164.42,338.67,52.12A220.21,220.21,0,0,0,151.16,62.54Z"/><path class="cls-2" d="M351.71,276.74c-.46-.11-1.12-.29-1.58-.37a45.28,45.28,0,0,0-5.2-.42,50.71,50.71,0,0,1-9.73-1.6,6.13,6.13,0,0,1-2.35-2.36l-2.19-.63a70.52,70.52,0,0,0-11.31-48.85l1.93-1.73a4.3,4.3,0,0,1,1-3.08,50.88,50.88,0,0,1,8.07-5.67,43.85,43.85,0,0,0,4.51-2.63c.35-.26.83-.67,1.2-1,2.6-2.08,3.2-5.66,1.33-8s-5.49-2.57-8.09-.49c-.38.29-.88.67-1.21,1a43.42,43.42,0,0,0-3.58,3.79,51.27,51.27,0,0,1-7.32,6.63,6.1,6.1,0,0,1-3.3.36l-2.06,1.47a71.09,71.09,0,0,0-45.06-21.77c-.05-.72-.11-2-.12-2.42a4.33,4.33,0,0,1-1.78-2.72,50.16,50.16,0,0,1,.62-9.84,45.29,45.29,0,0,0,.74-5.17c0-.43,0-1.07,0-1.54,0-3.33-2.43-6-5.43-6s-5.43,2.7-5.43,6c0,.05,0,.1,0,.15,0,.45,0,1,0,1.39a45.29,45.29,0,0,0,.74,5.17,50.87,50.87,0,0,1,.61,9.84,5.9,5.9,0,0,1-1.77,2.81l-.12,2.29a71.38,71.38,0,0,0-9.82,1.51,69.83,69.83,0,0,0-35.46,20.26c-.6-.41-1.65-1.16-2-1.39a4.32,4.32,0,0,1-3.23-.31,50.46,50.46,0,0,1-7.31-6.61,45,45,0,0,0-3.58-3.79c-.33-.29-.83-.67-1.2-1a6.42,6.42,0,0,0-3.78-1.42,5.22,5.22,0,0,0-4.33,1.91c-1.87,2.34-1.27,5.93,1.33,8l.09.06c.35.29.79.66,1.12.91a46.81,46.81,0,0,0,4.5,2.63,49.61,49.61,0,0,1,8.07,5.67,6,6,0,0,1,1.09,3.13l1.74,1.55a70.25,70.25,0,0,0-11.07,49l-2.28.66a7.61,7.61,0,0,1-2.33,2.36,51.5,51.5,0,0,1-9.73,1.6,45.38,45.38,0,0,0-5.21.41c-.41.08-1,.23-1.45.34h0l-.08,0c-3.2.78-5.26,3.72-4.6,6.61a5.75,5.75,0,0,0,7,4h.08l.1,0c.45-.1,1-.21,1.41-.32a45.65,45.65,0,0,0,4.87-1.87,51.49,51.49,0,0,1,9.46-2.78,6.07,6.07,0,0,1,3.12,1.1l2.37-.4A70.59,70.59,0,0,0,225,322.21l-1,2.37a5.46,5.46,0,0,1,.48,3.07,53.5,53.5,0,0,1-4.92,8.83,46,46,0,0,0-2.91,4.33c-.21.41-.48,1-.69,1.47a5.73,5.73,0,0,0,2.31,7.71c2.69,1.3,6-.07,7.49-3.06v0h0c.21-.42.5-1,.67-1.38a45,45,0,0,0,1.57-5c1.43-3.61,2.22-7.4,4.2-9.75a4.31,4.31,0,0,1,2.34-1.14l1.23-2.23a70.15,70.15,0,0,0,40.78,2.93,71.11,71.11,0,0,0,9.31-2.8l1.16,2.09a4.26,4.26,0,0,1,2.77,1.68,50.16,50.16,0,0,1,3.72,9.12,44.6,44.6,0,0,0,1.58,5c.18.4.47,1,.67,1.39,1.45,3,4.81,4.38,7.51,3.07s3.7-4.72,2.3-7.71c-.2-.43-.49-1.05-.7-1.46A45.8,45.8,0,0,0,302,336.4a51.18,51.18,0,0,1-4.82-8.62,4.27,4.27,0,0,1,.42-3.2,20.73,20.73,0,0,1-.9-2.19A70.66,70.66,0,0,0,328,283c.7.11,1.92.33,2.32.41a4.28,4.28,0,0,1,3-1.13,50.68,50.68,0,0,1,9.47,2.79,45.66,45.66,0,0,0,4.87,1.88c.39.1,1,.2,1.4.3l.11,0h.08a5.72,5.72,0,0,0,7-4C357,280.45,354.91,277.51,351.71,276.74Zm-51.57-55.29-23.2,16.45-.08,0a4.79,4.79,0,0,1-6.56-.88,4.71,4.71,0,0,1-1.05-2.77h0l-1.61-28.43A56.44,56.44,0,0,1,300.14,221.45Zm-43.7,31.16h8.73l5.43,6.79-2,8.46-7.84,3.77L253,267.85,251,259.39Zm-8.22-45.84a56.58,56.58,0,0,1,5.79-1l-1.61,28.47-.12.06a4.79,4.79,0,0,1-7.59,3.67l-.05,0-23.35-16.55A55.92,55.92,0,0,1,248.22,206.77Zm-35.39,25.3,21.32,19.07,0,.12a4.79,4.79,0,0,1-1.88,8.22l0,.09-27.32,7.89A55.83,55.83,0,0,1,212.83,232.07Zm28.54,50.65L230.51,309A56.27,56.27,0,0,1,208,280.73L236,276l0,.06A4.37,4.37,0,0,1,237,276a4.78,4.78,0,0,1,4.33,6.67Zm32,33.74a56.07,56.07,0,0,1-30.62-1.58L256.56,290h0a4.77,4.77,0,0,1,4-2.53A4.86,4.86,0,0,1,265,290h.1l13.82,25Q276.24,315.82,273.41,316.46Zm17.81-7.4-11-26.5,0-.05a4.78,4.78,0,0,1,2.32-6.2,4.65,4.65,0,0,1,1.83-.48,5.05,5.05,0,0,1,1.1.08l.05-.05,28.26,4.77A56,56,0,0,1,291.22,309.06Zm25.59-41.69-27.46-7.91,0-.12a4.79,4.79,0,0,1-3.4-5.68,4.7,4.7,0,0,1,1.52-2.54v-.06l21.2-19a56.92,56.92,0,0,1,8.17,35.28Z"/></g></g></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.rect{fill:none!important;}
</style>
<rect class="rect" width="512" height="512"/>
<g>
<path d="M237,496h184.8l-42.6-181.7L237,496z"/>
<path d="M16,392.3V496h194.2l81.2-103.7H16z"/>
<path d="M280.1,130.3L496,235.4v-209L280.1,130.3z"/>
<path d="M443.6,496H496V258.8l-122-59.4L443.6,496z"/>
<path d="M469,16H200.1l-32.4,145L469,16z"/>
<path d="M16,170v201.1h160L16,170z"/>
<path d="M178.5,16H16v120.1l105.9,133.1L178.5,16z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 730 B

+3 -2
View File
@@ -40,18 +40,19 @@
div.logo-text {
position: absolute;
left: 42px;
top: 11.5px;
top: 11px;
}
.logo-icon {
width: 28px;
height: 28px;
margin-left: 2px;
margin-top: 3px;
margin-top: 2px;
margin-right: 10px;
svg {
--size: 28px;
padding: 2px;
}
}