import React from "react"; import { Renderer } from "@k8slens/extensions"; import { observable, makeObservable, computed, reaction, runInAction, toJS } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react" import { KubescapeControl } from "../kubescape/types"; import { docsUrl, getRelatedObjectsFromControl } from "../kubescape/controlUtils"; import { KubescapeControlSeverity } from "./KubescapeControlSeverity"; import { KubescapeReportStore } from "../stores"; const { Component: { Drawer, DrawerItem, DrawerTitle, Icon, Table, TableHead, TableCell, TableRow } } = Renderer; class RelatedResource { id: string name: string kind: string namespace: string; detailsUrl: string; public constructor(init?: Partial) { Object.assign(this, init); } } class RoleBasedResource extends RelatedResource { role: RelatedResource roleBinding: RelatedResource } @observer export class KubescapeControlDetails extends React.Component<{ control?: KubescapeControl, onClose?: () => void }> { constructor(props) { super(props); makeObservable(this); } @observable failedResources: RelatedResource[] | RoleBasedResource[] = null; @computed get control() { return this.props.control; } @computed get store() { return KubescapeReportStore.getInstance(); } async componentDidMount() { disposeOnUnmount(this, [ reaction(() => this.control, async () => { this.failedResources = null; const result = await this.resolveFailedResources(); runInAction(() => { this.failedResources = result; }) } )] ); } resolveFailedResources = async () => { if (!this.control) { return null; } const relatedObjects = getRelatedObjectsFromControl(this.control, this.store.kubescapeResultMap, this.store.kubescapeRawResourceMap) return await Promise.all(relatedObjects.map(async (entity) => { if (entity.metadata?.uid) { const kubeObject = await this.store.getKubeObject(entity.metadata.namespace, entity.kind, entity.apiVersion, entity.metadata.uid) return new RelatedResource({ id: entity.metadata.uid, name: entity.metadata.name, kind: entity.kind, namespace: entity.metadata.namespace, detailsUrl: kubeObject ? Renderer.Navigation.getDetailsUrl(kubeObject.selfLink) : null }); } const roleBasedResource: RoleBasedResource = new RoleBasedResource({ kind: entity.kind, name: entity.name, namespace: entity.namespace }); for (const relatedObject of entity.relatedObjects) { const kubeObject = await this.store.getKubeObject(relatedObject.metadata.namespace, relatedObject.kind, relatedObject.apiVersion, relatedObject.metadata.uid) if (relatedObject.kind.includes("RoleBinding")) { roleBasedResource.roleBinding = new RelatedResource({ name: kubeObject?.getName() ?? relatedObject.metadata.name, detailsUrl: kubeObject ? Renderer.Navigation.getDetailsUrl(kubeObject.selfLink) : null }); } else { roleBasedResource.role = new RelatedResource({ name: kubeObject?.getName() ?? relatedObject.metadata.name, detailsUrl: kubeObject ? Renderer.Navigation.getDetailsUrl(kubeObject.selfLink) : null }); } } return roleBasedResource; })); } @computed get title() { return this.control ? `${this.control.id} - ${this.control.name}` : null; } @computed get controlDetails() { if (!this.control) { return null; } return (<> {this.control.name} {this.control.description} {this.control.remediation} ); } navigationLink = (resource: RelatedResource) => resource.detailsUrl ? {e.preventDefault(); Renderer.Navigation.navigate(resource.detailsUrl); this.props.onClose()}}>{resource.name} : resource.name @computed get failedResourcesTable() { if (!this.failedResources || this.failedResources.length == 0) { return null; } let columns; if (this.failedResources[0] instanceof RoleBasedResource) { columns = [ { title: "Namespace", value: (resource: RoleBasedResource) => resource.namespace }, { title: "Role", value: (resource: RoleBasedResource) => this.navigationLink(resource.role) }, { title: "RoleBinding", value: (resource: RoleBasedResource) => this.navigationLink(resource.roleBinding) }, { title: "Subject", value: (resource: RoleBasedResource) => resource.name }, ] } else { columns = [ { title: "Namespace", value: (resource: RelatedResource) => resource.namespace }, { title: "Kind", value: (resource: RelatedResource) => resource.kind }, { title: "Name", value: (resource: RelatedResource) => this.navigationLink(resource) }, ] } return (<>Failed Resources {columns.map(col => {col.title})} {this.failedResources.map(resource => {columns.map(col => {col.value(resource)})})}
); } render() { const { onClose } = this.props; return ( {this.controlDetails} {this.failedResourcesTable} ); } }