mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
parent
162b4108dd
commit
c1dc1e6463
@ -12,6 +12,7 @@ import isEqual from "lodash/isEqual";
|
||||
export interface BaseStoreParams<T = any> extends ConfOptions<T> {
|
||||
autoLoad?: boolean;
|
||||
syncEnabled?: boolean;
|
||||
syncDelayMs?: number;
|
||||
}
|
||||
|
||||
export class BaseStore<T = any> extends Singleton {
|
||||
@ -27,6 +28,7 @@ export class BaseStore<T = any> extends Singleton {
|
||||
this.params = {
|
||||
autoLoad: false,
|
||||
syncEnabled: true,
|
||||
syncDelayMs: 100,
|
||||
...params,
|
||||
}
|
||||
this.init();
|
||||
@ -73,7 +75,9 @@ export class BaseStore<T = any> extends Singleton {
|
||||
|
||||
enableSync() {
|
||||
this.syncDisposers.push(
|
||||
reaction(() => this.toJSON(), model => this.onModelChange(model)),
|
||||
reaction(() => this.toJSON(), model => this.onModelChange(model), {
|
||||
delay: this.params.syncDelayMs,
|
||||
}),
|
||||
);
|
||||
if (ipcMain) {
|
||||
const callback = (event: IpcMainEvent, model: T) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import { app, ipcRenderer, remote, webFrame, webContents } from "electron";
|
||||
import { app, ipcRenderer, remote, webFrame } from "electron";
|
||||
import { unlink } from "fs-extra";
|
||||
import { action, computed, observable, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
@ -108,7 +108,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
@action
|
||||
setActive(id: ClusterId) {
|
||||
this.activeClusterId = id;
|
||||
this.activeClusterId = this.clusters.has(id) ? id : null;
|
||||
}
|
||||
|
||||
@action
|
||||
@ -155,7 +155,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
if (cluster) {
|
||||
this.clusters.delete(clusterId);
|
||||
if (this.activeClusterId === clusterId) {
|
||||
this.activeClusterId = null;
|
||||
this.setActive(null);
|
||||
}
|
||||
// remove only custom kubeconfigs (pasted as text)
|
||||
if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import path from "path"
|
||||
import sharp from "sharp";
|
||||
import packageInfo from "../../package.json"
|
||||
import { app, dialog, Menu, nativeImage, Tray } from "electron"
|
||||
import { dialog, Menu, nativeImage, Tray } from "electron"
|
||||
import { isDevelopment, isMac } from "../common/vars";
|
||||
import { autorun } from "mobx";
|
||||
import { showAbout } from "./menu";
|
||||
@ -21,17 +21,39 @@ export const trayIcon = isDevelopment
|
||||
: path.resolve(__static, "logo.svg") // electron-builder's extraResources
|
||||
|
||||
export function initTray(windowManager: WindowManager) {
|
||||
return autorun(() => buildTrayMenu(windowManager), {
|
||||
delay: 100
|
||||
return autorun(() => {
|
||||
const menu = createTrayMenu(windowManager);
|
||||
buildTray(menu);
|
||||
})
|
||||
}
|
||||
|
||||
export async function buildTrayMenu(windowManager: WindowManager) {
|
||||
// note: browserWindow not available within menuItem.click() as argument[1] when app is not focused / hidden
|
||||
const trayMenu = Menu.buildFromTemplate([
|
||||
export async function buildTray(menu: Menu) {
|
||||
logger.info("[TRAY]: build start");
|
||||
const iconSize = isMac ? 16 : 32; // todo: verify on windows/linux
|
||||
const pngIcon = await sharp(trayIcon).png().toBuffer();
|
||||
const icon = nativeImage.createFromBuffer(pngIcon).resize({
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
});
|
||||
|
||||
if (tray) {
|
||||
tray.destroy(); // remove old tray on update
|
||||
}
|
||||
|
||||
tray = new Tray(icon)
|
||||
tray.setToolTip(packageInfo.description)
|
||||
tray.setIgnoreDoubleClickEvents(true);
|
||||
tray.setContextMenu(menu);
|
||||
|
||||
return tray;
|
||||
}
|
||||
|
||||
export function createTrayMenu(windowManager: WindowManager): Menu {
|
||||
return Menu.buildFromTemplate([
|
||||
{
|
||||
label: "About Lens",
|
||||
click() {
|
||||
// note: argument[1] (browserWindow) not available when app is not focused / hidden
|
||||
windowManager.bringToTop();
|
||||
showAbout(windowManager.mainView);
|
||||
},
|
||||
@ -45,22 +67,25 @@ export async function buildTrayMenu(windowManager: WindowManager) {
|
||||
},
|
||||
{
|
||||
label: "Clusters",
|
||||
submenu: workspaceStore.workspacesList.map(workspace => {
|
||||
const clusters = clusterStore.getByWorkspaceId(workspace.id);
|
||||
return {
|
||||
label: workspace.name,
|
||||
toolTip: workspace.description,
|
||||
submenu: clusters.map(({ id: clusterId, contextName: label }) => {
|
||||
return {
|
||||
label,
|
||||
click() {
|
||||
windowManager.bringToTop();
|
||||
windowManager.navigate(clusterViewURL({ params: { clusterId } }));
|
||||
submenu: workspaceStore.workspacesList
|
||||
.filter(workspace => clusterStore.getByWorkspaceId(workspace.id).length > 0) // hide empty workspaces
|
||||
.map(workspace => {
|
||||
const clusters = clusterStore.getByWorkspaceId(workspace.id);
|
||||
return {
|
||||
label: workspace.name,
|
||||
toolTip: workspace.description,
|
||||
submenu: clusters.map(({ id: clusterId, preferences: { clusterName: label }, online }) => {
|
||||
return {
|
||||
label: `${label}${online ? " (online)" : ""}`,
|
||||
toolTip: clusterId,
|
||||
click() {
|
||||
windowManager.bringToTop();
|
||||
windowManager.navigate(clusterViewURL({ params: { clusterId } }));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Check for updates",
|
||||
@ -76,26 +101,4 @@ export async function buildTrayMenu(windowManager: WindowManager) {
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// note: all "await"-s must be defined *AFTER* getting observables for proper mobx reactions
|
||||
await app.whenReady();
|
||||
logger.info('[TRAY]: building tray icon and menu');
|
||||
|
||||
const iconSize = isMac ? 16 : 32; // todo: verify on windows/linux
|
||||
const pngIcon = await sharp(trayIcon).png().toBuffer();
|
||||
const icon = nativeImage.createFromBuffer(pngIcon).resize({
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
});
|
||||
|
||||
if (tray) {
|
||||
tray.destroy(); // remove old tray on update
|
||||
}
|
||||
|
||||
tray = new Tray(icon)
|
||||
tray.setToolTip(packageInfo.description)
|
||||
tray.setIgnoreDoubleClickEvents(true);
|
||||
tray.setContextMenu(trayMenu);
|
||||
|
||||
return tray;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import "./add-cluster.scss"
|
||||
import os from "os";
|
||||
import React, { Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { action, observable, runInAction } from "mobx";
|
||||
import { action, observable } from "mobx";
|
||||
import { remote } from "electron";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { _i18n } from "../../i18n";
|
||||
@ -45,6 +45,7 @@ export class AddCluster extends React.Component {
|
||||
@observable dropAreaActive = false;
|
||||
|
||||
componentDidMount() {
|
||||
clusterStore.setActive(null);
|
||||
this.setKubeConfig(userStore.kubeConfigPath);
|
||||
}
|
||||
|
||||
@ -117,6 +118,7 @@ export class AddCluster extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
addClusters = () => {
|
||||
try {
|
||||
if (!this.selectedContexts.length) {
|
||||
@ -125,6 +127,7 @@ export class AddCluster extends React.Component {
|
||||
}
|
||||
this.error = ""
|
||||
this.isWaiting = true
|
||||
|
||||
const newClusters: ClusterModel[] = this.selectedContexts.map(context => {
|
||||
const clusterId = uuid();
|
||||
const kubeConfig = this.kubeContexts.get(context);
|
||||
@ -142,19 +145,17 @@ export class AddCluster extends React.Component {
|
||||
},
|
||||
}
|
||||
});
|
||||
runInAction(() => {
|
||||
clusterStore.addCluster(...newClusters);
|
||||
if (newClusters.length === 1) {
|
||||
const clusterId = newClusters[0].id;
|
||||
clusterStore.setActive(clusterId);
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
} else {
|
||||
Notifications.ok(
|
||||
<Trans>Successfully imported <b>{newClusters.length}</b> cluster(s)</Trans>
|
||||
);
|
||||
}
|
||||
})
|
||||
this.refreshContexts();
|
||||
|
||||
clusterStore.addCluster(...newClusters);
|
||||
|
||||
if (newClusters.length === 1) {
|
||||
const clusterId = newClusters[0].id;
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
} else {
|
||||
Notifications.ok(
|
||||
<Trans>Successfully imported <b>{newClusters.length}</b> cluster(s)</Trans>
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
this.error = String(err);
|
||||
Notifications.error(<Trans>Error while adding cluster(s): {this.error}</Trans>);
|
||||
@ -206,7 +207,7 @@ export class AddCluster extends React.Component {
|
||||
<Tab
|
||||
value={KubeConfigSourceTab.FILE}
|
||||
label={<Trans>Select kubeconfig file</Trans>}
|
||||
active={this.sourceTab == KubeConfigSourceTab.FILE} />
|
||||
active={this.sourceTab == KubeConfigSourceTab.FILE}/>
|
||||
<Tab
|
||||
value={KubeConfigSourceTab.TEXT}
|
||||
label={<Trans>Paste as text</Trans>}
|
||||
@ -320,8 +321,8 @@ export class AddCluster extends React.Component {
|
||||
return (
|
||||
<div className={cssNames("kube-context flex gaps align-center", context)}>
|
||||
<span>{context}</span>
|
||||
{isNew && <Icon small material="fiber_new" />}
|
||||
{isSelected && <Icon small material="check" className="box right" />}
|
||||
{isNew && <Icon small material="fiber_new"/>}
|
||||
{isSelected && <Icon small material="check" className="box right"/>}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import "./cluster-settings.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { Features } from "./features";
|
||||
import { Removal } from "./removal";
|
||||
import { Status } from "./status";
|
||||
@ -13,30 +15,35 @@ import { Icon } from "../icon";
|
||||
import { navigate } from "../../navigation";
|
||||
import { IClusterSettingsRouteParams } from "./cluster-settings.route";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { autorun } from "mobx";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterSettingsRouteParams> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterSettings extends React.Component<Props> {
|
||||
get clusterId() {
|
||||
return this.props.match.params.clusterId
|
||||
}
|
||||
|
||||
get cluster(): Cluster {
|
||||
return clusterStore.getById(this.props.match.params.clusterId);
|
||||
return clusterStore.getById(this.clusterId);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
window.addEventListener('keydown', this.onEscapeKey);
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.refreshCluster();
|
||||
window.addEventListener("keydown", this.onEscapeKey);
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.cluster, this.refreshCluster, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
|
||||
fireImmediately: true,
|
||||
})
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.onEscapeKey);
|
||||
window.removeEventListener("keydown", this.onEscapeKey);
|
||||
}
|
||||
|
||||
onEscapeKey = (evt: KeyboardEvent) => {
|
||||
@ -46,10 +53,9 @@ export class ClusterSettings extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
refreshCluster = () => {
|
||||
if(this.cluster) {
|
||||
clusterIpc.refresh.invokeFromRenderer(this.cluster.id);
|
||||
}
|
||||
refreshCluster = (cluster: Cluster) => {
|
||||
if (!cluster) return;
|
||||
clusterIpc.refresh.invokeFromRenderer(cluster.id);
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
@ -1,14 +1,37 @@
|
||||
import "./cluster-view.scss"
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { getMatchedCluster } from "./cluster-view.route";
|
||||
import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { IClusterViewRouteParams } from "./cluster-view.route";
|
||||
import { ClusterStatus } from "./cluster-status";
|
||||
import { hasLoadedView } from "./lens-views";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterView extends React.Component {
|
||||
export class ClusterView extends React.Component<Props> {
|
||||
get clusterId() {
|
||||
return this.props.match.params.clusterId;
|
||||
}
|
||||
|
||||
get cluster(): Cluster {
|
||||
return clusterStore.getById(this.clusterId);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
|
||||
fireImmediately: true,
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
render() {
|
||||
const cluster = getMatchedCluster();
|
||||
const { cluster } = this;
|
||||
const showStatus = cluster && (!cluster.available || !hasLoadedView(cluster.id))
|
||||
return (
|
||||
<div className="ClusterView">
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import "./clusters-menu.scss"
|
||||
|
||||
import type { Cluster } from "../../../main/cluster";
|
||||
import { remote } from "electron"
|
||||
import React from "react";
|
||||
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
|
||||
import { observer } from "mobx-react";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
@ -9,7 +12,7 @@ import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { ClusterIcon } from "../cluster-icon";
|
||||
import { Icon } from "../icon";
|
||||
import { cssNames, IClassName, autobind } from "../../utils";
|
||||
import { autobind, cssNames, IClassName } from "../../utils";
|
||||
import { Badge } from "../badge";
|
||||
import { navigate } from "../../navigation";
|
||||
import { addClusterURL } from "../+add-cluster";
|
||||
@ -19,8 +22,6 @@ import { Tooltip } from "../tooltip";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { clusterViewURL } from "./cluster-view.route";
|
||||
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
|
||||
import type { Cluster } from "../../../main/cluster";
|
||||
|
||||
interface Props {
|
||||
className?: IClassName;
|
||||
@ -29,13 +30,11 @@ interface Props {
|
||||
@observer
|
||||
export class ClustersMenu extends React.Component<Props> {
|
||||
showCluster = (clusterId: ClusterId) => {
|
||||
clusterStore.setActive(clusterId);
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
}
|
||||
|
||||
addCluster = () => {
|
||||
navigate(addClusterURL());
|
||||
clusterStore.setActive(null);
|
||||
}
|
||||
|
||||
showContextMenu = (cluster: Cluster) => {
|
||||
@ -45,7 +44,6 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
menu.append(new MenuItem({
|
||||
label: _i18n._(t`Settings`),
|
||||
click: () => {
|
||||
clusterStore.setActive(cluster.id);
|
||||
navigate(clusterSettingsURL({
|
||||
params: {
|
||||
clusterId: cluster.id
|
||||
@ -110,35 +108,29 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
<div className="clusters flex column gaps">
|
||||
<DragDropContext onDragEnd={this.swapClusterIconOrder}>
|
||||
<Droppable droppableId="cluster-menu" type="CLUSTER">
|
||||
{(provided: DroppableProvided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{({ innerRef, droppableProps, placeholder }: DroppableProvided) => (
|
||||
<div ref={innerRef} {...droppableProps}>
|
||||
{clusters.map((cluster, index) => {
|
||||
const isActive = cluster.id === clusterStore.activeClusterId;
|
||||
return (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{(provided: DraggableProvided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={isActive}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)}
|
||||
const isActive = cluster.id === clusterStore.activeClusterId;
|
||||
return (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{({ draggableProps, dragHandleProps, innerRef }: DraggableProvided) => (
|
||||
<div ref={innerRef} {...draggableProps} {...dragHandleProps}>
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={isActive}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)
|
||||
}
|
||||
)}
|
||||
{provided.placeholder}
|
||||
{placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
@ -148,9 +140,9 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
<Tooltip targetId="add-cluster-icon">
|
||||
<Trans>Add Cluster</Trans>
|
||||
</Tooltip>
|
||||
<Icon big material="add" id="add-cluster-icon" />
|
||||
<Icon big material="add" id="add-cluster-icon"/>
|
||||
{newContexts.size > 0 && (
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>} />
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user