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:
parent
068ab855c1
commit
6193e7aacb
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
.WorkspaceOverview {
|
||||
.TableCell {
|
||||
display: flex;
|
||||
align-items: left;
|
||||
|
||||
&.cluster-icon {
|
||||
align-items: center;
|
||||
flex-grow: 0.2;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/renderer/components/+landing-page/workspace-overview.tsx
Normal file
69
src/renderer/components/+landing-page/workspace-overview.tsx
Normal 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}/>;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user