From 6193e7aacb872418fec73bd2c0241abba2b04d1d Mon Sep 17 00:00:00 2001 From: Jim Ehrismann Date: Wed, 13 Jan 2021 18:30:26 -0500 Subject: [PATCH] basic workspace overview Signed-off-by: Jim Ehrismann --- src/main/cluster.ts | 2 +- .../+landing-page/landing-page.scss | 54 ------------- .../components/+landing-page/landing-page.tsx | 39 +++------- .../+landing-page/workspace-cluster-menu.tsx | 76 +++++++++++++++++++ .../+landing-page/workspace-cluster.store.ts | 64 ++++++++++++++++ .../+landing-page/workspace-overview.scss | 12 +++ .../+landing-page/workspace-overview.tsx | 69 +++++++++++++++++ .../item-object-list/item-list-layout.tsx | 3 +- 8 files changed, 235 insertions(+), 84 deletions(-) create mode 100644 src/renderer/components/+landing-page/workspace-cluster-menu.tsx create mode 100644 src/renderer/components/+landing-page/workspace-cluster.store.ts create mode 100644 src/renderer/components/+landing-page/workspace-overview.scss create mode 100644 src/renderer/components/+landing-page/workspace-overview.tsx diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 956164e10c..223deb8227 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -245,7 +245,7 @@ export class Cluster implements ClusterModel, ClusterState { * Kubernetes version */ get version(): string { - return String(this.metadata?.version) || ""; + return String(this.metadata?.version || ""); } constructor(model: ClusterModel) { diff --git a/src/renderer/components/+landing-page/landing-page.scss b/src/renderer/components/+landing-page/landing-page.scss index 4874b37c72..2e482d2b49 100644 --- a/src/renderer/components/+landing-page/landing-page.scss +++ b/src/renderer/components/+landing-page/landing-page.scss @@ -3,58 +3,4 @@ height: 100%; text-align: center; z-index: 0; - - &::after { - content: ""; - background: url(../../components/icon/crane.svg) no-repeat; - background-position: 0 35%; - background-size: 85%; - background-clip: content-box; - opacity: .75; - top: 0; - left: 0; - bottom: 0; - right: 0; - position: absolute; - z-index: -1; - - .theme-light & { - opacity: 0.2; - } - } - - .startup-hint { - $bgc: $mainBackground; - $arrowSize: 10px; - - position: absolute; - left: 0; - top: 25px; - margin: $padding; - padding: $padding * 2; - width: 320px; - background: $bgc; - color: $textColorAccent; - filter: drop-shadow(0 0px 2px #ffffff33); - - &:before { - content: ""; - position: absolute; - width: 0; - height: 0; - border-top: $arrowSize solid transparent; - border-bottom: $arrowSize solid transparent; - border-right: $arrowSize solid $bgc; - right: 100%; - } - - .theme-light & { - filter: drop-shadow(0 0px 2px #777); - background: white; - - &:before { - border-right-color: white; - } - } - } } \ No newline at end of file diff --git a/src/renderer/components/+landing-page/landing-page.tsx b/src/renderer/components/+landing-page/landing-page.tsx index ea0b24bb87..853d1e3486 100644 --- a/src/renderer/components/+landing-page/landing-page.tsx +++ b/src/renderer/components/+landing-page/landing-page.tsx @@ -1,40 +1,23 @@ import "./landing-page.scss"; import React from "react"; -import { observable } from "mobx"; import { observer } from "mobx-react"; -import { clusterStore } from "../../../common/cluster-store"; -import { workspaceStore } from "../../../common/workspace-store"; - +import { Workspace, workspaceStore } from "../../../common/workspace-store"; +import { WorkspaceOverview } from "./workspace-overview"; +import { PageLayout } from "../layout/page-layout"; @observer export class LandingPage extends React.Component { - @observable showHint = true; + get workspace(): Workspace { + return workspaceStore.currentWorkspace; + } + render() { - const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId); - const noClustersInScope = !clusters.length; - const showStartupHint = this.showHint && noClustersInScope; + const header =

Workspace: {this.workspace.name}

; return ( -
- {showStartupHint && ( -
this.showHint = false}> -

This is the quick launch menu.

-

- Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button. -

-
- )} - {noClustersInScope && ( -
-

- Welcome! -

-

- Get started by associating one or more clusters to Lens. -

-
- )} -
+ + + ); } } diff --git a/src/renderer/components/+landing-page/workspace-cluster-menu.tsx b/src/renderer/components/+landing-page/workspace-cluster-menu.tsx new file mode 100644 index 0000000000..83d542f258 --- /dev/null +++ b/src/renderer/components/+landing-page/workspace-cluster-menu.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import { ClusterItem, WorkspaceClusterStore } from "./workspace-cluster.store"; +import { autobind, cssNames } from "../../utils"; +import { MenuActions, MenuActionsProps } from "../menu/menu-actions"; +import { MenuItem } from "../menu"; +import { Icon } from "../icon"; +import { Workspace } from "../../../common/workspace-store"; +import { clusterSettingsURL } from "../+cluster-settings"; +import { navigate } from "../../navigation"; + +interface Props extends MenuActionsProps { + clusterItem: ClusterItem; + workspace: Workspace; + workspaceClusterStore: WorkspaceClusterStore; +} + +export class WorkspaceClusterMenu extends React.Component { + + @autobind() + remove() { + const { clusterItem, workspaceClusterStore } = this.props; + + return workspaceClusterStore.remove(clusterItem); + } + + @autobind() + settings() { + const { clusterItem } = this.props; + + navigate(clusterSettingsURL({ + params: { + clusterId: clusterItem.getId() + } + })); + } + + @autobind() + renderRemoveMessage() { + const { clusterItem, workspace } = this.props; + + return ( +

Remove cluster {clusterItem.getName()} from workspace {workspace.name}?

+ ); + } + + + renderContent() { + const { toolbar } = this.props; + + return ( + <> + { + + + Settings + + } + + ); + } + + render() { + const { clusterItem, className, ...menuProps } = this.props; + + return ( + + {this.renderContent()} + + ); + } +} diff --git a/src/renderer/components/+landing-page/workspace-cluster.store.ts b/src/renderer/components/+landing-page/workspace-cluster.store.ts new file mode 100644 index 0000000000..810683e128 --- /dev/null +++ b/src/renderer/components/+landing-page/workspace-cluster.store.ts @@ -0,0 +1,64 @@ +import { WorkspaceId } from "../../../common/workspace-store"; +import { Cluster } from "../../../main/cluster"; +import { clusterStore } from "../../../common/cluster-store"; +import { ItemObject, ItemStore } from "../../item.store"; +import { autobind } from "../../utils"; + +export class ClusterItem implements ItemObject { + cluster: Cluster; + + getName() { + return this.cluster.name; + } + + getId() { + return this.cluster.id; + } +} + +/** an ItemStore of the clusters belonging to a given workspace */ +@autobind() +export class WorkspaceClusterStore extends ItemStore { + + workspaceId: WorkspaceId; + + constructor(workspaceId: WorkspaceId) { + super(); + this.workspaceId = workspaceId; + } + + loadAll() { + return this.loadItems(() => clusterStore.getByWorkspaceId(this.workspaceId).map(cluster => { + const clusterItem = new ClusterItem(); + + clusterItem.cluster = cluster; + + return clusterItem; + })); + } + + async remove(clusterItem: ClusterItem) { + const { cluster } = clusterItem; + + if (cluster.isManaged) { + return; + } + + const clusterId = cluster.id; + + return super.removeItem(clusterItem, async () => { + if (clusterStore.activeClusterId === clusterId) { + clusterStore.setActive(null); + } + clusterStore.removeById(clusterId); + }); + } + + async removeSelectedItems() { + if (!this.selectedItems.length) { + return; + } + + return Promise.all(this.selectedItems.map(this.remove)); + } +} diff --git a/src/renderer/components/+landing-page/workspace-overview.scss b/src/renderer/components/+landing-page/workspace-overview.scss new file mode 100644 index 0000000000..1d6e16a383 --- /dev/null +++ b/src/renderer/components/+landing-page/workspace-overview.scss @@ -0,0 +1,12 @@ +.WorkspaceOverview { + .TableCell { + display: flex; + align-items: left; + + &.cluster-icon { + align-items: center; + flex-grow: 0.2; + padding: 0; + } + } +} \ No newline at end of file diff --git a/src/renderer/components/+landing-page/workspace-overview.tsx b/src/renderer/components/+landing-page/workspace-overview.tsx new file mode 100644 index 0000000000..d6bca05d49 --- /dev/null +++ b/src/renderer/components/+landing-page/workspace-overview.tsx @@ -0,0 +1,69 @@ +import "./workspace-overview.scss"; + +import React, { Component } from "react"; +import { Workspace } from "../../../common/workspace-store"; +import { observer } from "mobx-react"; +import { ItemListLayout } from "../item-object-list/item-list-layout"; +import { ClusterItem, WorkspaceClusterStore } from "./workspace-cluster.store"; +import { navigate } from "../../navigation"; +import { clusterViewURL } from "../cluster-manager/cluster-view.route"; +import { WorkspaceClusterMenu } from "./workspace-cluster-menu"; + +interface Props { + workspace: Workspace; +} + +enum sortBy { + name = "name", + contextName = "contextName", + version = "version", +} + +@observer +export class WorkspaceOverview extends Component { + + showCluster = (clusterItem: ClusterItem) => { + const clusterId = clusterItem.getId(); + + navigate(clusterViewURL({ params: { clusterId } })); + }; + + render() { + const { workspace } = this.props; + const workspaceClusterStore = new WorkspaceClusterStore(workspace.id); + + workspaceClusterStore.loadAll(); + + return ( + Clusters} + isClusterScoped + isSearchable={false} + isSelectable={false} + className="WorkspaceOverview" + store={workspaceClusterStore} + sortingCallbacks={{ + [sortBy.name]: (item: ClusterItem) => item.getName(), + [sortBy.contextName]: (item: ClusterItem) => item.cluster.contextName, + [sortBy.version]: (item: ClusterItem) => item.cluster.version, + }} + renderTableHeader={[ + { title: "Name", className: "name", sortBy: sortBy.name }, + { title: "Context", className: "context", sortBy: sortBy.contextName }, + { title: "Version", className: "version", sortBy: sortBy.version }, + { title: "Status", className: "status" }, + ]} + renderTableContents={(item: ClusterItem) => [ + item.getName(), + item.cluster.contextName, + item.cluster.version, + item.cluster.online ? "online" : "offline" + ]} + onDetails={this.showCluster} + renderItemMenu={(clusterItem: ClusterItem) => { + return ; + }} + /> + ); + } +} diff --git a/src/renderer/components/item-object-list/item-list-layout.tsx b/src/renderer/components/item-object-list/item-list-layout.tsx index b13d496064..afdf57c73e 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -322,6 +322,7 @@ export class ItemListLayout extends React.Component { } renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode { + const { isSearchable, searchFilters } = this.props; const { title, filters, search, info } = placeholders; return ( @@ -331,7 +332,7 @@ export class ItemListLayout extends React.Component { {this.isReady && info} {filters} - {search} + {isSearchable && searchFilters && search} ); }