/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import "../item-object-list/item-list-layout.scss"; import "./releases.scss"; import React, { Component } from "react"; import type { RouteComponentProps } from "react-router"; import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api"; import { navigation } from "../../navigation"; import type { ReleaseRouteParams } from "../../../common/routes"; import { releaseURL } from "../../../common/routes"; import { withInjectables } from "@ogre-tools/injectable-react"; import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; import { ItemListLayout } from "../item-object-list"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { kebabCase } from "lodash/fp"; import { HelmReleaseMenu } from "./release-menu"; import type { ItemStore } from "../../../common/item.store"; import { ReleaseRollbackDialog } from "./release-rollback-dialog"; import { ReleaseDetails } from "./release-details/release-details"; import removableReleasesInjectable from "./removable-releases.injectable"; import type { RemovableHelmRelease } from "./removable-releases"; import type { IComputedValue } from "mobx"; import releasesInjectable from "./releases.injectable"; enum columnId { name = "name", namespace = "namespace", revision = "revision", chart = "chart", version = "version", appVersion = "app-version", status = "status", updated = "update", } export interface HelmReleasesProps extends RouteComponentProps { } interface Dependencies { releases: IComputedValue; releasesArePending: IComputedValue; selectNamespace: (namespace: string) => void; } class NonInjectedHelmReleases extends Component { componentDidMount() { const { match: { params: { namespace }}} = this.props; if (namespace) { this.props.selectNamespace(namespace); } } onDetails = (item: HelmRelease) => { this.showDetails(item); }; showDetails = (item: HelmRelease) => { navigation.push(releaseURL({ params: { name: item.getName(), namespace: item.getNs(), }, })); }; hideDetails = () => { navigation.push(releaseURL()); }; renderRemoveDialogMessage(selectedItems: HelmRelease[]) { const releaseNames = selectedItems.map(item => item.getName()).join(", "); return (
<>Remove {releaseNames}?

Note: StatefulSet Volumes won't be deleted automatically

); } render() { const releases = this.props.releases; const releasesArePending = this.props.releasesArePending; // TODO: Implement ItemListLayout without stateful stores const legacyReleaseStore = { get items() { return releases.get(); }, loadAll: () => Promise.resolve(), get isLoaded() { return !releasesArePending.get(); }, failedLoading: false, getTotalCount: () => releases.get().length, toggleSelection: (item) => { item.toggle(); }, isSelectedAll: (visibleItems: RemovableHelmRelease[]) => ( visibleItems.length > 0 && visibleItems.every((release) => release.isSelected) ), toggleSelectionAll: (visibleItems: RemovableHelmRelease[]) => { let selected = false; if (!legacyReleaseStore.isSelectedAll(visibleItems)) { selected = true; } visibleItems.forEach((release) => { if (release.isSelected !== selected) { release.toggle(); } }); }, isSelected: (item) => item.isSelected, get selectedItems() { return releases.get().filter((release) => release.isSelected); }, pickOnlySelected: (releases) => { return releases.filter(release => release.isSelected); }, removeItems: async (releases) => { await Promise.all(releases.map(release => release.delete())); }, } as ItemStore; return ( <> legacyReleaseStore.items} preloadStores={false} isConfigurable tableId="helm_releases" className="HelmReleases" sortingCallbacks={{ [columnId.name]: release => release.getName(), [columnId.namespace]: release => release.getNs(), [columnId.revision]: release => release.getRevision(), [columnId.chart]: release => release.getChart(), [columnId.status]: release => release.getStatus(), [columnId.updated]: release => release.getUpdated(false, false), }} searchFilters={[ release => release.getName(), release => release.getNs(), release => release.getChart(), release => release.getStatus(), release => release.getVersion(), ]} customizeHeader={({ filters, searchProps, ...headerPlaceholders }) => ({ filters: ( <> {filters} ), searchProps: { ...searchProps, placeholder: "Search Releases...", }, ...headerPlaceholders, })} renderHeaderTitle="Releases" renderTableHeader={[ { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, { title: "Chart", className: "chart", sortBy: columnId.chart, id: columnId.chart }, { title: "Revision", className: "revision", sortBy: columnId.revision, id: columnId.revision }, { title: "Version", className: "version", id: columnId.version }, { title: "App Version", className: "app-version", id: columnId.appVersion }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Updated", className: "updated", sortBy: columnId.updated, id: columnId.updated }, ]} renderTableContents={release => [ release.getName(), release.getNs(), release.getChart(), release.getRevision(), release.getVersion(), release.appVersion, { title: release.getStatus(), className: kebabCase(release.getStatus()) }, release.getUpdated(), ]} renderItemMenu={release => ( )} customizeRemoveDialog={selectedItems => ({ message: this.renderRemoveDialogMessage(selectedItems), })} onDetails={this.onDetails} /> ); } } export const HelmReleases = withInjectables( NonInjectedHelmReleases, { getProps: (di, props) => ({ releases: di.inject(removableReleasesInjectable), releasesArePending: di.inject(releasesInjectable).pending, selectNamespace: di.inject(namespaceStoreInjectable).selectNamespaces, ...props, }), }, );