1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

basic workspace overview

Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com>
This commit is contained in:
Jim Ehrismann 2021-01-13 18:30:26 -05:00
parent 068ab855c1
commit 6193e7aacb
8 changed files with 235 additions and 84 deletions

View File

@ -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) {

View File

@ -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;
}
}
}
}

View File

@ -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 = <h2>Workspace: {this.workspace.name}</h2>;
return (
<div className="LandingPage flex">
{showStartupHint && (
<div className="startup-hint flex column gaps" onMouseEnter={() => this.showHint = false}>
<p>This is the quick launch menu.</p>
<p>
Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button.
</p>
</div>
)}
{noClustersInScope && (
<div className="no-clusters flex column gaps box center">
<h1>
Welcome!
</h1>
<p>
Get started by associating one or more clusters to Lens.
</p>
</div>
)}
</div>
<PageLayout className="LandingPage" header={header} provideBackButtonNavigation={false}>
<WorkspaceOverview workspace={this.workspace}/>
</PageLayout>
);
}
}

View File

@ -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<Props> {
@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 (
<p>Remove cluster <b>{clusterItem.getName()}</b> from workspace {workspace.name}?</p>
);
}
renderContent() {
const { toolbar } = this.props;
return (
<>
{
<MenuItem onClick={this.settings}>
<Icon material="settings" interactive={toolbar} title={`Settings`}/>
<span className="title">Settings</span>
</MenuItem>
}
</>
);
}
render() {
const { clusterItem, className, ...menuProps } = this.props;
return (
<MenuActions
{...menuProps}
className={cssNames("WorkspaceClusterMenu", className)}
removeAction={clusterItem.cluster.isManaged ? null : this.remove}
removeConfirmationMessage={this.renderRemoveMessage}
>
{this.renderContent()}
</MenuActions>
);
}
}

View File

@ -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<ClusterItem> {
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));
}
}

View File

@ -0,0 +1,12 @@
.WorkspaceOverview {
.TableCell {
display: flex;
align-items: left;
&.cluster-icon {
align-items: center;
flex-grow: 0.2;
padding: 0;
}
}
}

View File

@ -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<Props> {
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 (
<ItemListLayout
renderHeaderTitle={<div>Clusters</div>}
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 <WorkspaceClusterMenu clusterItem={clusterItem} workspace={workspace} workspaceClusterStore={workspaceClusterStore}/>;
}}
/>
);
}
}

View File

@ -322,6 +322,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}
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<ItemListLayoutProps> {
{this.isReady && info}
</div>
{filters}
{search}
{isSearchable && searchFilters && search}
</>
);
}