mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Catalog & Hotbar - initial groundwork (#2418)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
e18a041b13
commit
99a464c61d
109 files changed
+1604
-2612
No files matched your search
@@ -0,0 +1,81 @@
|
||||
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity";
|
||||
import { ItemObject, ItemStore } from "../../item.store";
|
||||
import { autobind } from "../../utils";
|
||||
import { CatalogCategory } from "../../../common/catalog-entity";
|
||||
|
||||
export class CatalogEntityItem implements ItemObject {
|
||||
constructor(public entity: CatalogEntity) {}
|
||||
|
||||
get name() {
|
||||
return this.entity.metadata.name;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.entity.metadata.name;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.entity.metadata.uid;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@computed get phase() {
|
||||
return this.entity.status.phase;
|
||||
}
|
||||
|
||||
get labels() {
|
||||
const labels: string[] = [];
|
||||
|
||||
Object.keys(this.entity.metadata.labels).forEach((key) => {
|
||||
const value = this.entity.metadata.labels[key];
|
||||
|
||||
labels.push(`${key}=${value}`);
|
||||
});
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this.entity.metadata.source || "unknown";
|
||||
}
|
||||
|
||||
onRun(ctx: CatalogEntityActionContext) {
|
||||
this.entity.onRun(ctx);
|
||||
}
|
||||
|
||||
@action
|
||||
async onContextMenuOpen(ctx: any) {
|
||||
return this.entity.onContextMenuOpen(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind()
|
||||
export class CatalogEntityStore extends ItemStore<CatalogEntityItem> {
|
||||
@observable activeCategory: CatalogCategory;
|
||||
|
||||
@computed get entities() {
|
||||
if (!this.activeCategory) return [];
|
||||
|
||||
console.log("computing entities", this.activeCategory);
|
||||
|
||||
return catalogEntityRegistry.getItemsForCategory(this.activeCategory).map(entity => new CatalogEntityItem(entity));
|
||||
}
|
||||
|
||||
watch() {
|
||||
const disposers: IReactionDisposer[] = [
|
||||
reaction(() => this.entities, () => this.loadAll()),
|
||||
reaction(() => this.activeCategory, () => this.loadAll(), { delay: 100})
|
||||
];
|
||||
|
||||
return () => disposers.forEach((dispose) => dispose());
|
||||
}
|
||||
|
||||
loadAll() {
|
||||
return this.loadItems(() => this.entities);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const catalogRoute: RouteProps = {
|
||||
path: "/catalog"
|
||||
};
|
||||
|
||||
export const catalogURL = buildURL(catalogRoute.path);
|
||||
@@ -0,0 +1,26 @@
|
||||
.CatalogPage {
|
||||
--width: 100%;
|
||||
--height: 100%;
|
||||
--nav-column-width: 230px;
|
||||
text-align: left;
|
||||
|
||||
.sidebarRegion {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.contentRegion {
|
||||
.content {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.TableCell.status {
|
||||
&.connected {
|
||||
color: var(--colorSuccess);
|
||||
}
|
||||
|
||||
&.disconnected {
|
||||
color: var(--halfGray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import "./catalog.scss";
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { ItemListLayout } from "../item-object-list";
|
||||
import { observable, reaction } from "mobx";
|
||||
import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
|
||||
import { navigate } from "../../navigation";
|
||||
import { kebabCase } from "lodash";
|
||||
import { PageLayout } from "../layout/page-layout";
|
||||
import { MenuItem, MenuActions } from "../menu";
|
||||
import { Icon } from "../icon";
|
||||
import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
|
||||
import { Badge } from "../badge";
|
||||
import { hotbarStore } from "../../../common/hotbar-store";
|
||||
import { addClusterURL } from "../+add-cluster";
|
||||
import { autobind } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Tab, Tabs } from "../tabs";
|
||||
import { catalogCategoryRegistry } from "../../../common/catalog-category-registry";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
source = "source",
|
||||
status = "status"
|
||||
}
|
||||
@observer
|
||||
export class Catalog extends React.Component {
|
||||
@observable private catalogEntityStore?: CatalogEntityStore;
|
||||
@observable.deep private contextMenu: CatalogEntityContextMenuContext;
|
||||
@observable activeTab: string;
|
||||
|
||||
async componentDidMount() {
|
||||
this.contextMenu = {
|
||||
menuItems: [],
|
||||
navigate: (url: string) => navigate(url)
|
||||
};
|
||||
this.catalogEntityStore = new CatalogEntityStore();
|
||||
disposeOnUnmount(this, [
|
||||
this.catalogEntityStore.watch(),
|
||||
reaction(() => catalogCategoryRegistry.items, (items) => {
|
||||
if (!this.activeTab && items.length > 0) {
|
||||
this.activeTab = items[0].getId();
|
||||
this.catalogEntityStore.activeCategory = items[0];
|
||||
}
|
||||
}, { fireImmediately: true })
|
||||
]);
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.catalogEntityStore.items.length === 0) {
|
||||
Notifications.info(<><b>Welcome!</b><p>Get started by associating one or more clusters to Lens</p></>, {
|
||||
timeout: 30_000,
|
||||
id: "catalog-welcome"
|
||||
});
|
||||
}
|
||||
}, 2_000);
|
||||
}
|
||||
|
||||
addToHotbar(item: CatalogEntityItem) {
|
||||
const hotbar = hotbarStore.getByName("default"); // FIXME
|
||||
|
||||
if (!hotbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
hotbar.items.push({ entity: { uid: item.id }});
|
||||
}
|
||||
|
||||
removeFromHotbar(item: CatalogEntityItem) {
|
||||
const hotbar = hotbarStore.getByName("default"); // FIXME
|
||||
|
||||
if (!hotbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
hotbar.items = hotbar.items.filter((i) => i.entity.uid !== item.id);
|
||||
}
|
||||
|
||||
onDetails(item: CatalogEntityItem) {
|
||||
item.onRun(catalogEntityRunContext);
|
||||
}
|
||||
|
||||
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||
if (menuItem.confirm) {
|
||||
ConfirmDialog.open({
|
||||
okButtonProps: {
|
||||
primary: false,
|
||||
accent: true,
|
||||
},
|
||||
ok: () => {
|
||||
menuItem.onClick();
|
||||
},
|
||||
message: menuItem.confirm.message
|
||||
});
|
||||
} else {
|
||||
menuItem.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
get categories() {
|
||||
return catalogCategoryRegistry.items;
|
||||
}
|
||||
|
||||
onTabChange = (tabId: string) => {
|
||||
this.activeTab = tabId;
|
||||
|
||||
const activeCategory = this.categories.find((category) => category.getId() === tabId);
|
||||
|
||||
if (activeCategory) {
|
||||
this.catalogEntityStore.activeCategory = activeCategory;
|
||||
}
|
||||
};
|
||||
|
||||
renderNavigation() {
|
||||
return (
|
||||
<Tabs className="flex column" scrollable={false} onChange={this.onTabChange} value={this.activeTab}>
|
||||
<div className="header">Catalog</div>
|
||||
{ this.categories.map((category, index) => {
|
||||
return <Tab value={category.getId()} key={index} label={category.metadata.name} data-testid={`${category.getId()}-tab`} />;
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
renderItemMenu(item: CatalogEntityItem) {
|
||||
const onOpen = async () => {
|
||||
await item.onContextMenuOpen(this.contextMenu);
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuActions onOpen={() => onOpen()}>
|
||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
|
||||
<Icon material="add" small interactive={true} title="Add to hotbar"/> Add to Hotbar
|
||||
</MenuItem>
|
||||
<MenuItem key="remove-from-hotbar" onClick={() => this.removeFromHotbar(item) }>
|
||||
<Icon material="clear" small interactive={true} title="Remove from hotbar"/> Remove from Hotbar
|
||||
</MenuItem>
|
||||
{ this.contextMenu.menuItems.map((menuItem, index) => {
|
||||
return (
|
||||
<MenuItem key={index} onClick={() => this.onMenuItemClick(menuItem)}>
|
||||
<Icon material={menuItem.icon} small interactive={true} title={menuItem.title}/> {menuItem.title}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuActions>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.catalogEntityStore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
className="CatalogPage"
|
||||
navigation={this.renderNavigation()}
|
||||
provideBackButtonNavigation={false}
|
||||
contentGaps={false}>
|
||||
<ItemListLayout
|
||||
isClusterScoped
|
||||
isSearchable={true}
|
||||
isSelectable={false}
|
||||
className="CatalogItemList"
|
||||
store={this.catalogEntityStore}
|
||||
tableId="catalog-items"
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: (item: CatalogEntityItem) => item.name,
|
||||
[sortBy.source]: (item: CatalogEntityItem) => item.source,
|
||||
[sortBy.status]: (item: CatalogEntityItem) => item.phase,
|
||||
}}
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: sortBy.name },
|
||||
{ title: "Source", className: "source" },
|
||||
{ title: "Labels", className: "labels" },
|
||||
{ title: "Status", className: "status", sortBy: sortBy.status },
|
||||
]}
|
||||
renderTableContents={(item: CatalogEntityItem) => [
|
||||
item.name,
|
||||
item.source,
|
||||
item.labels.map((label) => <Badge key={label} label={label} title={label} />),
|
||||
{ title: item.phase, className: kebabCase(item.phase) }
|
||||
]}
|
||||
onDetails={(item: CatalogEntityItem) => this.onDetails(item) }
|
||||
renderItemMenu={this.renderItemMenu}
|
||||
addRemoveButtons={{
|
||||
addTooltip: "Add Kubernetes Cluster",
|
||||
onAdd: () => navigate(addClusterURL()),
|
||||
}}
|
||||
/>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./catalog.route";
|
||||
export * from "./catalog";
|
||||
Reference in new issue
Block a user