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
|
* Kubernetes version
|
||||||
*/
|
*/
|
||||||
get version(): string {
|
get version(): string {
|
||||||
return String(this.metadata?.version) || "";
|
return String(this.metadata?.version || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(model: ClusterModel) {
|
constructor(model: ClusterModel) {
|
||||||
|
|||||||
@ -3,58 +3,4 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 0;
|
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 "./landing-page.scss";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable } from "mobx";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { Workspace, workspaceStore } from "../../../common/workspace-store";
|
||||||
import { workspaceStore } from "../../../common/workspace-store";
|
import { WorkspaceOverview } from "./workspace-overview";
|
||||||
|
import { PageLayout } from "../layout/page-layout";
|
||||||
@observer
|
@observer
|
||||||
export class LandingPage extends React.Component {
|
export class LandingPage extends React.Component {
|
||||||
@observable showHint = true;
|
|
||||||
|
|
||||||
|
get workspace(): Workspace {
|
||||||
|
return workspaceStore.currentWorkspace;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
const header = <h2>Workspace: {this.workspace.name}</h2>;
|
||||||
const noClustersInScope = !clusters.length;
|
|
||||||
const showStartupHint = this.showHint && noClustersInScope;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="LandingPage flex">
|
<PageLayout className="LandingPage" header={header} provideBackButtonNavigation={false}>
|
||||||
{showStartupHint && (
|
<WorkspaceOverview workspace={this.workspace}/>
|
||||||
<div className="startup-hint flex column gaps" onMouseEnter={() => this.showHint = false}>
|
</PageLayout>
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode {
|
||||||
|
const { isSearchable, searchFilters } = this.props;
|
||||||
const { title, filters, search, info } = placeholders;
|
const { title, filters, search, info } = placeholders;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -331,7 +332,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
{this.isReady && info}
|
{this.isReady && info}
|
||||||
</div>
|
</div>
|
||||||
{filters}
|
{filters}
|
||||||
{search}
|
{isSearchable && searchFilters && search}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user