Fully support displaying all supported PodVolume types (#4131)

This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-03-28 09:04:15 -04:00
1 parent b3574e1a21
commit 4c92f3f251
74 files changed
+2163 -472

No files matched your search

+5 -2
View File
@@ -141,8 +141,11 @@ export function isMetricsEmpty(metrics: Record<string, IMetrics>) {
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
}
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | void {
if (!metrics) return;
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | undefined {
if (!metrics) {
return undefined;
}
const itemMetrics = { ...metrics };
for (const metric in metrics) {
@@ -31,17 +31,25 @@ export interface IPvcMetrics<T = IMetrics> {
diskCapacity: T;
}
export interface PersistentVolumeClaim {
spec: {
accessModes: string[];
storageClassName: string;
selector: LabelSelector;
resources: {
requests: {
storage: string; // 8Gi
};
};
export interface PersistentVolumeClaimSpec {
accessModes: string[];
selector: LabelSelector;
resources: {
requests?: Record<string, string>;
limits?: Record<string, string>;
};
volumeName?: string;
storageClassName?: string;
volumeMode?: string;
dataSource?: {
apiGroup: string;
kind: string;
name: string;
};
}
export interface PersistentVolumeClaim {
spec: PersistentVolumeClaimSpec;
status: {
phase: string; // Pending
};
+446 -26
View File
@@ -9,6 +9,10 @@ import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
import type { RequireExactlyOne } from "type-fest";
import type { KubeObjectMetadata, LocalObjectReference } from "../kube-object";
import type { SecretReference } from "./secret.api";
import type { PersistentVolumeClaimSpec } from "./persistent-volume-claims.api";
export class PodsApi extends KubeApi<Pod> {
getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> => {
@@ -112,12 +116,8 @@ export interface IPodContainer extends Partial<Record<PodContainerProbe, IContai
};
}[];
envFrom?: {
configMapRef?: {
name: string;
};
secretRef?: {
name: string;
};
configMapRef?: LocalObjectReference;
secretRef?: LocalObjectReference;
}[];
volumeMounts?: {
name: string;
@@ -195,6 +195,444 @@ export interface IPodContainerStatus {
started?: boolean;
}
export interface AwsElasticBlockStoreSource {
volumeID: string;
fsType: string;
}
export interface AzureDiskSource {
/**
* The name of the VHD blob object OR the name of an Azure managed data disk if `kind` is `"Managed"`.
*/
diskName: string;
/**
* The URI of the vhd blob object OR the `resourceID` of an Azure managed data disk if `kind` is `"Managed"`.
*/
diskURI: string;
/**
* Kind of disk
* @default "Shared"
*/
kind?: "Shared" | "Dedicated" | "Managed";
/**
* Disk caching mode.
* @default "None"
*/
cachingMode?: "None" | "ReadOnly" | "ReadWrite";
/**
* The filesystem type to mount.
* @default "ext4"
*/
fsType?: string;
/**
* Whether the filesystem is used as readOnly.
* @default false
*/
readonly?: boolean;
}
export interface AzureFileSource {
/**
* The name of the secret that contains both Azure storage account name and key.
*/
secretName: string;
/**
* The share name to be used.
*/
shareName: string;
/**
* In case the secret is stored in a different namespace.
* @default "default"
*/
secretNamespace?: string;
/**
* Whether the filesystem is used as readOnly.
*/
readOnly: boolean;
}
export interface CephfsSource {
/**
* List of Ceph monitors
*/
monitors: string[];
/**
* Used as the mounted root, rather than the full Ceph tree.
* @default "/"
*/
path?: string;
/**
* The RADOS user name.
* @default "admin"
*/
user?: string;
/**
* The path to the keyring file.
* @default "/etc/ceph/user.secret"
*/
secretFile?: string;
/**
* Reference to Ceph authentication secrets. If provided, then the secret overrides `secretFile`
*/
secretRef?: SecretReference;
/**
* Whether the filesystem is used as readOnly.
*/
readOnly: boolean;
}
export interface CinderSource {
volumeID: string;
fsType: string;
/**
* @default false
*/
readOnly?: boolean;
secretRef?: SecretReference;
}
export interface ConfigMapSource {
name: string;
items: {
key: string;
path: string;
}[];
}
export interface DownwardApiSource {
items: {
path: string;
fieldRef: {
fieldPath: string;
};
}[];
}
export interface EphemeralSource {
volumeClaimTemplate: {
/**
* All the rest of the fields are ignored and rejected during validation
*/
metadata?: Pick<KubeObjectMetadata, "labels" | "annotations">;
spec: PersistentVolumeClaimSpec;
};
}
export interface EmptyDirSource {
medium?: string;
sizeLimit?: string;
}
export interface FiberChannelSource {
/**
* A list of World Wide Names
*/
targetWWNs: string[];
/**
* Logical Unit number
*/
lun: number;
/**
* The type of filesystem
* @default "ext4"
*/
fsType?: string;
readOnly: boolean;
}
export interface FlockerSource {
datasetName: string;
}
export interface FlexVolumeSource {
driver: string;
fsType?: string;
secretRef?: LocalObjectReference;
/**
* @default false
*/
readOnly?: boolean;
options?: Record<string, string>;
}
export interface GcePersistentDiskSource {
pdName: string;
fsType: string;
}
export interface GitRepoSource {
repository: string;
revision: string;
}
export interface GlusterFsSource {
/**
* The name of the Endpoints object that represents a Gluster cluster configuration.
*/
endpoints: string;
/**
* The Glusterfs volume name.
*/
path: string;
/**
* The boolean that sets the mountpoint readOnly or readWrite.
*/
readOnly: boolean;
}
export interface HostPathSource {
path: string;
/**
* Determines the sorts of checks that will be done
* @default ""
*/
type?: "" | "DirectoryOrCreate" | "Directory" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice";
}
export interface IScsiSource {
targetPortal: string;
iqn: string;
lun: number;
fsType: string;
readOnly: boolean;
chapAuthDiscovery?: boolean;
chapAuthSession?: boolean;
secretRef?: SecretReference;
}
export interface LocalSource {
path: string;
}
export interface NetworkFsSource {
server: string;
path: string;
readOnly?: boolean;
}
export interface PersistentVolumeClaimSource {
claimName: string;
}
export interface PhotonPersistentDiskSource {
pdID: string;
/**
* @default "ext4"
*/
fsType?: string;
}
export interface PortworxVolumeSource {
volumeID: string;
fsType?: string;
readOnly?: boolean;
}
export interface ProjectedSource {
sources: {
secret?: {
name: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
};
downwardAPI?: {
items?: {
path: string;
fieldRef?: {
fieldPath: string;
apiVersion?: string;
};
resourceFieldRef?: {
resource: string;
containerName?: string;
};
mode?: number;
}[];
};
configMap?: {
name: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
optional?: boolean;
};
serviceAccountToken?: {
audience?: string;
expirationSeconds?: number;
path: string;
};
}[];
defaultMode: number;
}
export interface QuobyteSource {
registry: string;
volume: string;
/**
* @default false
*/
readOnly?: boolean;
/**
* @default "serivceaccount"
*/
user?: string;
group?: string;
tenant?: string;
}
export interface RadosBlockDeviceSource {
monitors: string[];
image: string;
/**
* @default "ext4"
*/
fsType?: string;
/**
* @default "rbd"
*/
pool?: string;
/**
* @default "admin"
*/
user?: string;
/**
* @default "/etc/ceph/keyring"
*/
keyring?: string;
secretRef?: SecretReference;
/**
* @default false
*/
readOnly?: boolean;
}
export interface ScaleIoSource {
gateway: string;
system: string;
secretRef?: LocalObjectReference;
/**
* @default false
*/
sslEnabled?: boolean;
protectionDomain?: string;
storagePool?: string;
/**
* @default "ThinProvisioned"
*/
storageMode?: "ThickProvisioned" | "ThinProvisioned";
volumeName: string;
/**
* @default "xfs"
*/
fsType?: string;
/**
* @default false
*/
readOnly?: boolean;
}
export interface SecretSource {
secretName: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
defaultMode?: number;
optional?: boolean;
}
export interface StorageOsSource {
volumeName: string;
/**
* @default Pod.metadata.namespace
*/
volumeNamespace?: string;
/**
* @default "ext4"
*/
fsType?: string;
/**
* @default false
*/
readOnly?: boolean;
secretRef?: LocalObjectReference;
}
export interface VsphereVolumeSource {
volumePath: string;
/**
* @default "ext4"
*/
fsType?: string;
storagePolicyName?: string;
storagePolicyID?: string;
}
export interface ContainerStorageInterfaceSource {
driver: string;
/**
* @default false
*/
readOnly?: boolean;
/**
* @default "ext4"
*/
fsType?: string;
volumeAttributes?: Record<string, string>;
controllerPublishSecretRef?: SecretReference;
nodeStageSecretRef?: SecretReference;
nodePublishSecretRef?: SecretReference;
controllerExpandSecretRef?: SecretReference;
}
export interface PodVolumeVariants {
awsElasticBlockStore: AwsElasticBlockStoreSource;
azureDisk: AzureDiskSource;
azureFile: AzureFileSource;
cephfs: CephfsSource;
cinder: CinderSource;
configMap: ConfigMapSource;
csi: ContainerStorageInterfaceSource;
downwardAPI: DownwardApiSource;
emptyDir: EmptyDirSource;
ephemeral: EphemeralSource;
fc: FiberChannelSource;
flexVolume: FlexVolumeSource;
flocker: FlockerSource;
gcePersistentDisk: GcePersistentDiskSource;
gitRepo: GitRepoSource;
glusterfs: GlusterFsSource;
hostPath: HostPathSource;
iscsi: IScsiSource;
local: LocalSource;
nfs: NetworkFsSource;
persistentVolumeClaim: PersistentVolumeClaimSource;
photonPersistentDisk: PhotonPersistentDiskSource;
portworxVolume: PortworxVolumeSource;
projected: ProjectedSource;
quobyte: QuobyteSource;
rbd: RadosBlockDeviceSource;
scaleIO: ScaleIoSource;
secret: SecretSource;
storageos: StorageOsSource;
vsphereVolume: VsphereVolumeSource;
}
/**
* The valid kinds of volume
*/
export type PodVolumeKind = keyof PodVolumeVariants;
export type PodVolume = RequireExactlyOne<PodVolumeVariants> & {
name: string;
};
export class Pod extends WorkloadKubeObject {
static kind = "Pod";
static namespaced = true;
@@ -206,23 +644,7 @@ export class Pod extends WorkloadKubeObject {
}
declare spec?: {
volumes?: {
name: string;
persistentVolumeClaim: {
claimName: string;
};
emptyDir: {
medium?: string;
sizeLimit?: string;
};
configMap: {
name: string;
};
secret: {
secretName: string;
defaultMode: number;
};
}[];
volumes?: PodVolume[];
initContainers: IPodContainer[];
containers: IPodContainer[];
restartPolicy?: string;
@@ -239,9 +661,7 @@ export class Pod extends WorkloadKubeObject {
[selector: string]: string;
};
securityContext?: {};
imagePullSecrets?: {
name: string;
}[];
imagePullSecrets?: LocalObjectReference[];
hostNetwork?: boolean;
hostPID?: boolean;
hostIPC?: boolean;
@@ -25,6 +25,11 @@ export interface ISecretRef {
name: string;
}
export interface SecretReference {
name: string;
namespace?: string;
}
export interface SecretData extends KubeJsonApiData {
type: SecretType;
data?: Record<string, string>;
+7
View File
@@ -21,6 +21,13 @@ export type KubeObjectConstructor<K extends KubeObject> = (new (data: KubeJsonAp
apiBase?: string;
};
/**
* A reference to an object in the same namespace
*/
export interface LocalObjectReference {
name: string;
}
export interface KubeObjectMetadata {
uid: string;
name: string;
@@ -134,7 +134,7 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
})}
</DrawerItem>
<DrawerTitle title="Metrics"/>
<DrawerTitle>Metrics</DrawerTitle>
<div className="metrics">
{this.renderMetrics()}
</div>
@@ -85,7 +85,7 @@ export class ConfigMapDetails extends React.Component<ConfigMapDetailsProps> {
{
data.length > 0 && (
<>
<DrawerTitle title="Data"/>
<DrawerTitle>Data</DrawerTitle>
{
data.map(([name, value]) => (
<div key={name} className="data">
@@ -82,7 +82,7 @@ export class ResourceQuotaDetails extends React.Component<ResourceQuotaDetailsPr
{quota.getScopeSelector().length > 0 && (
<>
<DrawerTitle title="Scope Selector"/>
<DrawerTitle>Scope Selector</DrawerTitle>
<Table className="paths">
<TableHead>
<TableCell>Operator</TableCell>
@@ -116,7 +116,7 @@ export class SecretDetails extends React.Component<SecretDetailsProps> {
return (
<>
<DrawerTitle title="Data" />
<DrawerTitle>Data</DrawerTitle>
{secrets.map(this.renderSecret)}
<Button
primary
@@ -92,7 +92,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
})
}
</DrawerItem>
<DrawerTitle title="Names"/>
<DrawerTitle>Names</DrawerTitle>
<Table selectable className="names box grow">
<TableHead>
<TableCell>plural</TableCell>
@@ -109,7 +109,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
</Table>
{printerColumns.length > 0 &&
<>
<DrawerTitle title="Additional Printer Columns"/>
<DrawerTitle>Additional Printer Columns</DrawerTitle>
<Table selectable className="printer-columns box grow">
<TableHead>
<TableCell className="name">Name</TableCell>
@@ -136,7 +136,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
}
{validation &&
<>
<DrawerTitle title="Validation"/>
<DrawerTitle>Validation</DrawerTitle>
<MonacoEditor
readOnly
value={validation}
@@ -71,7 +71,7 @@ export class EventDetails extends React.Component<EventDetailsProps> {
<span className={kebabCase(type)}>{type}</span>
</DrawerItem>
<DrawerTitle title="Involved object"/>
<DrawerTitle>Involved object</DrawerTitle>
<Table>
<TableHead>
<TableCell>Name</TableCell>
@@ -113,7 +113,7 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
return (
<div className="values">
<DrawerTitle title="Values" />
<DrawerTitle>Values</DrawerTitle>
<div className="flex column gaps">
<Checkbox
label="User-supplied values only"
@@ -238,9 +238,9 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
/>
</DrawerItem>
{this.renderValues()}
<DrawerTitle title="Notes"/>
<DrawerTitle>Notes</DrawerTitle>
{this.renderNotes()}
<DrawerTitle title="Resources"/>
<DrawerTitle>Resources</DrawerTitle>
{resources && this.renderResources(resources)}
</div>
);
@@ -35,7 +35,7 @@ export class EndpointDetails extends React.Component<EndpointDetailsProps> {
return (
<div className="EndpointDetails">
<KubeObjectMeta object={endpoint}/>
<DrawerTitle title="Subsets"/>
<DrawerTitle>Subsets</DrawerTitle>
{
endpoint.getEndpointSubsets().map((subset) => (
<EndpointSubsetList key={subset.toString()} subset={subset} endpoint={endpoint} />
@@ -160,10 +160,10 @@ export class IngressDetails extends React.Component<IngressDetailsProps> {
{serviceName}:{servicePort}
</DrawerItem>
}
<DrawerTitle title="Rules"/>
<DrawerTitle>Rules</DrawerTitle>
{this.renderPaths(ingress)}
<DrawerTitle title="Load-Balancer Ingress Points"/>
<DrawerTitle>Load-Balancer Ingress Points</DrawerTitle>
{this.renderIngressPoints(ingressPoints)}
</div>
);
@@ -170,7 +170,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
{ingress && (
<>
<DrawerTitle title="Ingress"/>
<DrawerTitle>Ingress</DrawerTitle>
{ingress.map((ingress, i) => (
<div key={i} data-testid={`ingress-${i}`}>
{this.renderNetworkPolicyPorts(ingress.ports)}
@@ -182,7 +182,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
{egress && (
<>
<DrawerTitle title="Egress"/>
<DrawerTitle>Egress</DrawerTitle>
{egress.map((egress, i) => (
<div key={i} data-testid={`egress-${i}`}>
{this.renderNetworkPolicyPorts(egress.ports)}
@@ -86,7 +86,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
{spec.sessionAffinity}
</DrawerItem>
<DrawerTitle title="Connection"/>
<DrawerTitle>Connection</DrawerTitle>
<DrawerItem name="Cluster IP">
{spec.clusterIP}
@@ -129,7 +129,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
{spec.loadBalancerIP}
</DrawerItem>
)}
<DrawerTitle title="Endpoint"/>
<DrawerTitle>Endpoint</DrawerTitle>
<ServiceDetailsEndpoint endpoint={endpoint}/>
</div>
+2 -2
View File
@@ -169,9 +169,9 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
}
</DrawerItem>
}
<DrawerTitle title="Capacity"/>
<DrawerTitle>Capacity</DrawerTitle>
<NodeDetailsResources node={node} type={"capacity"}/>
<DrawerTitle title="Allocatable"/>
<DrawerTitle>Allocatable</DrawerTitle>
<NodeDetailsResources node={node} type={"allocatable"}/>
<PodDetailsList
pods={childPods}
@@ -28,26 +28,23 @@ interface RuleGroup {
@observer
export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps> {
renderRuleGroup( title: React.ReactNode, group: RuleGroup) {
if (!group) return null;
const { rule, ranges } = group;
return (
<>
<DrawerTitle title={title}/>
<DrawerItem name="Rule">
{rule}
renderRuleGroup = (title: React.ReactNode, { rule, ranges }: RuleGroup) => (
<>
<DrawerTitle>{title}</DrawerTitle>
<DrawerItem name="Rule">
{rule}
</DrawerItem>
{ranges && (
<DrawerItem name="Ranges (Min-Max)" labelsOnly>
{ranges.map(({ min, max }, index) => (
<Badge
key={index}
label={`${min} - ${max}`} />
))}
</DrawerItem>
{ranges && (
<DrawerItem name="Ranges (Min-Max)" labelsOnly>
{ranges.map(({ min, max }, index) => {
return <Badge key={index} label={`${min} - ${max}`}/>;
})}
</DrawerItem>
)}
</>
);
}
)}
</>
);
render() {
const { object: psp } = this.props;
@@ -161,7 +158,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
{allowedHostPaths && (
<>
<DrawerTitle title="Allowed Host Paths"/>
<DrawerTitle>Allowed Host Paths</DrawerTitle>
<Table>
<TableHead>
<TableCell>Path Prefix</TableCell>
@@ -179,14 +176,14 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
</>
)}
{this.renderRuleGroup("Fs Group", fsGroup)}
{this.renderRuleGroup("Run As Group", runAsGroup)}
{this.renderRuleGroup("Run As User", runAsUser)}
{this.renderRuleGroup("Supplemental Groups", supplementalGroups)}
{fsGroup && this.renderRuleGroup("Fs Group", fsGroup)}
{runAsGroup && this.renderRuleGroup("Run As Group", runAsGroup)}
{runAsUser && this.renderRuleGroup("Run As User", runAsUser)}
{supplementalGroups && this.renderRuleGroup("Supplemental Groups", supplementalGroups)}
{runtimeClass && (
<>
<DrawerTitle title="Runtime Class"/>
<DrawerTitle>Runtime Class</DrawerTitle>
<DrawerItem name="Allowed Runtime Class Names">
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
</DrawerItem>
@@ -198,7 +195,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
{seLinux && (
<>
<DrawerTitle title="Se Linux"/>
<DrawerTitle>Se Linux</DrawerTitle>
<DrawerItem name="Rule">
{seLinux.rule}
</DrawerItem>
@@ -80,7 +80,7 @@ class NonInjectedStorageClassDetails extends React.Component<StorageClassDetails
)}
{parameters && (
<>
<DrawerTitle title="Parameters"/>
<DrawerTitle>Parameters</DrawerTitle>
{
Object.entries(parameters).map(([name, value]) => (
<DrawerItem key={name + value} name={startCase(name)}>
@@ -102,7 +102,7 @@ export class PersistentVolumeClaimDetails extends React.Component<PersistentVolu
{volumeClaim.getStatus()}
</DrawerItem>
<DrawerTitle title="Selector"/>
<DrawerTitle>Selector</DrawerTitle>
<DrawerItem name="Match Labels" labelsOnly>
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
@@ -67,7 +67,7 @@ export class VolumeDetailsList extends React.Component<VolumeDetailsListProps> {
return (
<div className="VolumeDetailsList flex column">
<DrawerTitle title="Persistent Volumes"/>
<DrawerTitle>Persistent Volumes</DrawerTitle>
<Table
tableId="storage_volume_details_list"
items={persistentVolumes}
@@ -65,7 +65,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
{nfs && (
<>
<DrawerTitle title="Network File System"/>
<DrawerTitle>Network File System</DrawerTitle>
{
Object.entries(nfs).map(([name, value]) => (
<DrawerItem key={name} name={startCase(name)}>
@@ -78,7 +78,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
{flexVolume && (
<>
<DrawerTitle title="FlexVolume"/>
<DrawerTitle>FlexVolume</DrawerTitle>
<DrawerItem name="Driver">
{flexVolume.driver}
</DrawerItem>
@@ -94,7 +94,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
{claimRef && (
<>
<DrawerTitle title="Claim"/>
<DrawerTitle>Claim</DrawerTitle>
<DrawerItem name="Type">
{claimRef.kind}
</DrawerItem>
@@ -68,7 +68,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
<div className="ClusterRoleBindingDetails">
<KubeObjectMeta object={clusterRoleBinding} />
<DrawerTitle title="Reference" />
<DrawerTitle>Reference</DrawerTitle>
<Table>
<TableHead>
<TableCell>Kind</TableCell>
@@ -82,7 +82,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
</TableRow>
</Table>
<DrawerTitle title="Bindings" />
<DrawerTitle>Bindings</DrawerTitle>
{subjects.length > 0 && (
<Table selectable className="bindings box grow">
<TableHead>
@@ -28,7 +28,7 @@ export class ClusterRoleDetails extends React.Component<ClusterRoleDetailsProps>
<div className="ClusterRoleDetails">
<KubeObjectMeta object={clusterRole}/>
<DrawerTitle title="Rules"/>
<DrawerTitle>Rules</DrawerTitle>
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
return (
<div className="rule" key={index}>
@@ -64,7 +64,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
<div className="RoleBindingDetails">
<KubeObjectMeta object={roleBinding} />
<DrawerTitle title="Reference" />
<DrawerTitle>Reference</DrawerTitle>
<Table>
<TableHead>
<TableCell>Kind</TableCell>
@@ -78,7 +78,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
</TableRow>
</Table>
<DrawerTitle title="Bindings" />
<DrawerTitle>Bindings</DrawerTitle>
{subjects.length > 0 && (
<Table selectable className="bindings box grow">
<TableHead>
@@ -27,7 +27,7 @@ export class RoleDetails extends React.Component<RoleDetailsProps> {
return (
<div className="RoleDetails">
<KubeObjectMeta object={role}/>
<DrawerTitle title="Rules"/>
<DrawerTitle>Rules</DrawerTitle>
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
return (
<div className="rule" key={index}>
@@ -143,7 +143,7 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
</DrawerItem>
}
<DrawerTitle title="Mountable secrets"/>
<DrawerTitle>Mountable secrets</DrawerTitle>
<div className="secrets">
{this.renderSecrets()}
</div>
@@ -78,7 +78,7 @@ class NonInjectedCronJobDetails extends React.Component<CronJobDetailsProps & De
</DrawerItem>
{childJobs.length > 0 &&
<>
<DrawerTitle title="Jobs"/>
<DrawerTitle>Jobs</DrawerTitle>
{childJobs.map((job: Job) => {
const selectors = job.getSelectors();
const condition = job.getCondition();
@@ -53,7 +53,7 @@ export class DeploymentReplicaSets extends React.Component<DeploymentReplicaSets
return (
<div className="ReplicaSets flex column">
<DrawerTitle title="Deploy Revisions"/>
<DrawerTitle>Deploy Revisions</DrawerTitle>
<Table
selectable
tableId="deployment_replica_sets_view"
@@ -0,0 +1,40 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { Link } from "react-router-dom";
import type { PodVolumeVariants, Pod, SecretReference } from "../../../../../common/k8s-api/endpoints";
import type { KubeApi } from "../../../../../common/k8s-api/kube-api";
import type { LocalObjectReference, KubeObject } from "../../../../../common/k8s-api/kube-object";
import { DrawerItem } from "../../../drawer";
import { getDetailsUrl } from "../../../kube-detail-params";
export interface PodVolumeVariantSpecificProps<Kind extends keyof PodVolumeVariants> {
variant: PodVolumeVariants[Kind];
pod: Pod;
volumeName: string;
}
export type VolumeVariantComponent<Kind extends keyof PodVolumeVariants> = React.FunctionComponent<PodVolumeVariantSpecificProps<Kind>>;
export interface LocalRefProps {
pod: Pod;
title: string;
kubeRef: LocalObjectReference | SecretReference | undefined;
api: KubeApi<KubeObject>;
}
export const LocalRef = ({ pod, title, kubeRef: ref, api }: LocalRefProps) => {
if (!ref) {
return null;
}
return (
<DrawerItem name={title}>
<Link to={getDetailsUrl(api.getUrl({ namespace: pod.getNs(), ...ref }))}>
{ref.name}
</Link>
</DrawerItem>
);
};
@@ -0,0 +1,291 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { Pod, PodVolume, PodVolumeKind } from "../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../drawer";
import { Icon } from "../../../icon";
import { AwsElasticBlockStore } from "./variants/aws-elastic-block-store";
import { AzureDisk } from "./variants/azure-disk";
import { AzureFile } from "./variants/azure-file";
import { CephFs } from "./variants/ceph-fs";
import { Cinder } from "./variants/cinder";
import { ConfigMap } from "./variants/config-map";
import { ContainerStorageInterface } from "./variants/container-storage-interface";
import { DownwardAPI } from "./variants/downward-api";
import { EmptyDir } from "./variants/empty-dir";
import { Ephemeral } from "./variants/ephemeral";
import { FiberChannel } from "./variants/fiber-channel";
import { FlexVolume } from "./variants/flex-volume";
import { Flocker } from "./variants/flocker";
import { GcePersistentDisk } from "./variants/gce-persistent-disk";
import { GitRepo } from "./variants/git-repo";
import { GlusterFs } from "./variants/gluster-fs";
import { HostPath } from "./variants/host-path";
import { IScsi } from "./variants/i-scsi";
import { Local } from "./variants/local";
import { NetworkFs } from "./variants/network-fs";
import { PersistentVolumeClaim } from "./variants/persistent-volume-claim";
import { PhotonPersistentDisk } from "./variants/photon-persistent-disk";
import { PortworxVolume } from "./variants/portworx-volume";
import { Projected } from "./variants/projected";
import { Quobyte } from "./variants/quobyte";
import { RadosBlockDevice } from "./variants/rados-block-device";
import { ScaleIo } from "./variants/scale-io";
import { Secret } from "./variants/secret";
import { StorageOs } from "./variants/storage-os";
import { VsphereVolume } from "./variants/vsphere-volume";
const deprecatedVolumeTypes = new Set<PodVolumeKind>([
"flocker",
"gitRepo",
"quobyte",
"storageos",
]);
interface VolumeVariantProps {
pod: Pod;
volume: PodVolume;
}
interface VolumeVariantRender {
kind: PodVolumeKind;
element: JSX.Element;
}
function renderVolumeVariant({ pod, volume }: VolumeVariantProps): VolumeVariantRender | null {
if (volume.awsElasticBlockStore) {
return {
kind: "awsElasticBlockStore",
element: <AwsElasticBlockStore variant={volume.awsElasticBlockStore} pod={pod} volumeName={volume.name} />,
};
}
if (volume.azureDisk) {
return {
kind: "azureDisk",
element: <AzureDisk variant={volume.azureDisk} pod={pod} volumeName={volume.name} />,
};
}
if (volume.azureFile) {
return {
kind: "azureFile",
element: <AzureFile variant={volume.azureFile} pod={pod} volumeName={volume.name} />,
};
}
if (volume.cephfs) {
return {
kind: "cephfs",
element: <CephFs variant={volume.cephfs} pod={pod} volumeName={volume.name} />,
};
}
if (volume.cinder) {
return {
kind: "cinder",
element: <Cinder variant={volume.cinder} pod={pod} volumeName={volume.name} />,
};
}
if (volume.configMap) {
return {
kind: "configMap",
element: <ConfigMap variant={volume.configMap} pod={pod} volumeName={volume.name} />,
};
}
if (volume.csi) {
return {
kind: "csi",
element: <ContainerStorageInterface variant={volume.csi} pod={pod} volumeName={volume.name} />,
};
}
if (volume.downwardAPI) {
return {
kind: "downwardAPI",
element: <DownwardAPI variant={volume.downwardAPI} pod={pod} volumeName={volume.name} />,
};
}
if (volume.emptyDir) {
return {
kind: "emptyDir",
element: <EmptyDir variant={volume.emptyDir} pod={pod} volumeName={volume.name} />,
};
}
if (volume.ephemeral) {
return {
kind: "ephemeral",
element: <Ephemeral variant={volume.ephemeral} pod={pod} volumeName={volume.name} />,
};
}
if (volume.fc) {
return {
kind: "fc",
element: <FiberChannel variant={volume.fc} pod={pod} volumeName={volume.name} />,
};
}
if (volume.flexVolume) {
return {
kind: "flexVolume",
element: <FlexVolume variant={volume.flexVolume} pod={pod} volumeName={volume.name} />,
};
}
if (volume.flocker) {
return {
kind: "flocker",
element: <Flocker variant={volume.flocker} pod={pod} volumeName={volume.name} />,
};
}
if (volume.gcePersistentDisk) {
return {
kind: "gcePersistentDisk",
element: <GcePersistentDisk variant={volume.gcePersistentDisk} pod={pod} volumeName={volume.name} />,
};
}
if (volume.gitRepo) {
return {
kind: "gitRepo",
element: <GitRepo variant={volume.gitRepo} pod={pod} volumeName={volume.name} />,
};
}
if (volume.glusterfs) {
return {
kind: "glusterfs",
element: <GlusterFs variant={volume.glusterfs} pod={pod} volumeName={volume.name} />,
};
}
if (volume.hostPath) {
return {
kind: "hostPath",
element: <HostPath variant={volume.hostPath} pod={pod} volumeName={volume.name} />,
};
}
if (volume.iscsi) {
return {
kind: "iscsi",
element: <IScsi variant={volume.iscsi} pod={pod} volumeName={volume.name} />,
};
}
if (volume.local) {
return {
kind: "local",
element: <Local variant={volume.local} pod={pod} volumeName={volume.name} />,
};
}
if (volume.nfs) {
return {
kind: "nfs",
element: <NetworkFs variant={volume.nfs} pod={pod} volumeName={volume.name} />,
};
}
if (volume.persistentVolumeClaim) {
return {
kind: "persistentVolumeClaim",
element: <PersistentVolumeClaim variant={volume.persistentVolumeClaim} pod={pod} volumeName={volume.name} />,
};
}
if (volume.photonPersistentDisk) {
return {
kind: "photonPersistentDisk",
element: <PhotonPersistentDisk variant={volume.photonPersistentDisk} pod={pod} volumeName={volume.name} />,
};
}
if (volume.portworxVolume) {
return {
kind: "portworxVolume",
element: <PortworxVolume variant={volume.portworxVolume} pod={pod} volumeName={volume.name} />,
};
}
if (volume.projected) {
return {
kind: "projected",
element: <Projected variant={volume.projected} pod={pod} volumeName={volume.name} />,
};
}
if (volume.quobyte) {
return {
kind: "quobyte",
element: <Quobyte variant={volume.quobyte} pod={pod} volumeName={volume.name} />,
};
}
if (volume.rbd) {
return {
kind: "rbd",
element: <RadosBlockDevice variant={volume.rbd} pod={pod} volumeName={volume.name} />,
};
}
if (volume.scaleIO) {
return {
kind: "scaleIO",
element: <ScaleIo variant={volume.scaleIO} pod={pod} volumeName={volume.name} />,
};
}
if (volume.secret) {
return {
kind: "secret",
element: <Secret variant={volume.secret} pod={pod} volumeName={volume.name} />,
};
}
if (volume.storageos) {
return {
kind: "storageos",
element: <StorageOs variant={volume.storageos} pod={pod} volumeName={volume.name} />,
};
}
if (volume.vsphereVolume) {
return {
kind: "vsphereVolume",
element: <VsphereVolume variant={volume.vsphereVolume} pod={pod} volumeName={volume.name} />,
};
}
return null;
}
export function VolumeVariant(props: VolumeVariantProps) {
const result = renderVolumeVariant(props);
if (!result) {
return <p>Error! Unknown pod volume kind</p>;
}
const { kind, element } = result;
const isDeprecated = deprecatedVolumeTypes.has(kind);
return (
<>
<DrawerItem name="Kind">
{kind}
{isDeprecated && <Icon title="Deprecated" material="warning_amber" />}
</DrawerItem>
{element}
</>
);
}
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const AwsElasticBlockStore: VolumeVariantComponent<"awsElasticBlockStore"> = (
({ variant: { volumeID, fsType = "ext4" }}) => (
<>
<DrawerItem name="Volume ID">
{volumeID}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
</>
)
);
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const AzureDisk: VolumeVariantComponent<"azureDisk"> = (
({ variant: { diskName, diskURI, kind = "Shared", cachingMode = "None", fsType = "ext4", readonly = false }}) => (
<>
<DrawerItem name={kind === "Managed" ? "Disk Name" : "VHD blob Name"}>
{diskName}
</DrawerItem>
<DrawerItem name={kind === "Managed" ? "Resource ID" : "Disk URI"}>
{diskURI}
</DrawerItem>
<DrawerItem name="Kind">
{kind}
</DrawerItem>
<DrawerItem name="Caching Mode">
{cachingMode}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<DrawerItem name="Readonly">
{readonly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const AzureFile: VolumeVariantComponent<"azureFile"> = (
({ variant: { readOnly = false, secretName, shareName, secretNamespace = "default" }}) => (
<>
<DrawerItem name="Secret Name">
{secretName}
</DrawerItem>
<DrawerItem name="Share Name">
{shareName}
</DrawerItem>
<DrawerItem name="Namespace of Secret">
{secretNamespace}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,46 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const CephFs: VolumeVariantComponent<"cephfs"> = (
({ pod, variant: { monitors, path = "/", user = "admin", secretFile = "/etc/ceph/user.secret", secretRef, readOnly }}) => (
<>
<DrawerItem name="Monitors">
<ul>
{monitors.map(monitor => <li key={monitor}>{monitor}</li>)}
</ul>
</DrawerItem>
<DrawerItem name="Mount Path">
{path}
</DrawerItem>
<DrawerItem name="Username">
{user}
</DrawerItem>
{
secretRef
? (
<LocalRef
pod={pod}
title="Secret"
kubeRef={secretRef}
api={secretsApi}
/>
)
: (
<DrawerItem name="Secret Filepath">
{secretFile}
</DrawerItem>
)
}
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const Cinder: VolumeVariantComponent<"cinder"> = (
({ variant: { volumeID, fsType = "ext4" }}) => (
<>
<DrawerItem name="Volume ID">
{volumeID}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
</>
)
);
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { configMapApi } from "../../../../../../common/k8s-api/endpoints";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const ConfigMap: VolumeVariantComponent<"configMap"> = (
({ pod, variant: { name }}) => (
<LocalRef
pod={pod}
title="Name"
kubeRef={{ name }}
api={configMapApi}
/>
)
);
@@ -0,0 +1,67 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const ContainerStorageInterface: VolumeVariantComponent<"csi"> = ({
pod,
variant: {
driver,
readOnly = false,
fsType = "ext4",
volumeAttributes = {},
nodePublishSecretRef,
controllerPublishSecretRef,
nodeStageSecretRef,
controllerExpandSecretRef,
},
}) => (
<>
<DrawerItem name="Driver">
{driver}
</DrawerItem>
<DrawerItem name="ReadOnly">
{readOnly.toString()}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<LocalRef
pod={pod}
title="Controller Publish Secret"
kubeRef={controllerPublishSecretRef}
api={secretsApi}
/>
<LocalRef
pod={pod}
title="Controller Expand Secret"
kubeRef={controllerExpandSecretRef}
api={secretsApi}
/>
<LocalRef
pod={pod}
title="Node Publish Secret"
kubeRef={nodePublishSecretRef}
api={secretsApi}
/>
<LocalRef
pod={pod}
title="Node Stage Secret"
kubeRef={nodeStageSecretRef}
api={secretsApi}
/>
{
Object.entries(volumeAttributes)
.map(([key, value]) => (
<DrawerItem key={key} name={key}>
{value}
</DrawerItem>
))
}
</>
);
@@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const DownwardAPI: VolumeVariantComponent<"downwardAPI"> = (
({ variant: { items }}) => (
<>
<DrawerItem name="Items">
<ul>
{items.map(item => <li key={item.path}>{item.path}</li>)}
</ul>
</DrawerItem>
</>
)
);
@@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const EmptyDir: VolumeVariantComponent<"emptyDir"> = (
({ variant: { medium, sizeLimit }}) => (
<>
<DrawerItem name="Medium">
{medium || "<node's default medium>"}
</DrawerItem>
<DrawerItem name="Size Limit" hidden={!sizeLimit}>
{sizeLimit}
</DrawerItem>
</>
)
);
@@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { dump } from "js-yaml";
import React from "react";
import { DrawerItem, DrawerItemLabels } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const Ephemeral: VolumeVariantComponent<"ephemeral"> = (
({ pod, volumeName, variant: { volumeClaimTemplate: { metadata, spec }}}) => (
<>
<DrawerItem name="PVC Template Name">
{pod.getName()}-{volumeName}
</DrawerItem>
<DrawerItemLabels
name="Template Labels"
labels={metadata.labels}
/>
<DrawerItemLabels
name="Template Annotations"
labels={metadata.annotations}
/>
<DrawerItem name="Template PVC Spec">
{dump(spec)}
</DrawerItem>
</>
)
);
@@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const FiberChannel: VolumeVariantComponent<"fc"> = (
({ variant: { targetWWNs, lun, fsType = "ext4", readOnly = false }}) => (
<>
<DrawerItem name="Target World Wide Names">
<ul>
{targetWWNs.map(targetWWN => <li key={targetWWN}>{targetWWN}</li>)}
</ul>
</DrawerItem>
<DrawerItem name="Logical Unit Number">
{lun.toString()}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const FlexVolume: VolumeVariantComponent<"flexVolume"> = (
({ pod, variant: { driver, fsType, secretRef, readOnly = false, options }}) => (
<>
<DrawerItem name="Driver">
{driver}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType || "-- system default --"}
</DrawerItem>
<LocalRef
pod={pod}
title="Secret"
kubeRef={secretRef}
api={secretsApi}
/>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
{
...Object.entries(options)
.map(([key, value]) => (
<DrawerItem key={key} name={`Option: ${key}`}>
{value}
</DrawerItem>
))
}
</>
)
);
@@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const Flocker: VolumeVariantComponent<"flocker"> = (
({ variant: { datasetName }}) => (
<>
<DrawerItem name="Dataset Name">
{datasetName}
</DrawerItem>
</>
)
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const GcePersistentDisk: VolumeVariantComponent<"gcePersistentDisk"> = (
({ variant: { pdName, fsType = "ext4" }}) => (
<>
<DrawerItem name="Persistent Disk Name">
{pdName}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
</>
)
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const GitRepo: VolumeVariantComponent<"gitRepo"> = (
({ variant: { repository, revision }}) => (
<>
<DrawerItem name="Repository URL">
{repository}
</DrawerItem>
<DrawerItem name="Commit Hash">
{revision}
</DrawerItem>
</>
)
);
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const GlusterFs: VolumeVariantComponent<"glusterfs"> = (
({ variant: { endpoints, path, readOnly = false }}) => (
<>
<DrawerItem name="Endpoints object name">
{endpoints}
</DrawerItem>
<DrawerItem name="Glusterfs volume name">
{path}
</DrawerItem>
<DrawerItem name="Readonly Mountpoint">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const HostPath: VolumeVariantComponent<"hostPath"> = (
({ variant: { path, type }}) => (
<>
<DrawerItem name="Node's Host Filesystem Path">
{path}
</DrawerItem>
<DrawerItem name="Check Behaviour">
{type || "-- none --"}
</DrawerItem>
</>
)
);
@@ -0,0 +1,45 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const IScsi: VolumeVariantComponent<"iscsi"> = (
({ variant: { targetPortal, iqn, lun, fsType = "ext4", readOnly = false, chapAuthDiscovery, chapAuthSession, secretRef }}) => (
<>
<DrawerItem name="Target Address">
{targetPortal}
</DrawerItem>
<DrawerItem name="iSCSI qualified name">
{iqn}
</DrawerItem>
<DrawerItem name="Logical Unit Number">
{lun.toString()}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
{chapAuthDiscovery && (
<DrawerItem name="CHAP Discovery Authentication">
{chapAuthDiscovery.toString()}
</DrawerItem>
)}
{chapAuthSession && (
<DrawerItem name="CHAP Session Authentication">
{chapAuthSession.toString()}
</DrawerItem>
)}
{ secretRef && (
<DrawerItem name="CHAP Secret">
{secretRef.name}
</DrawerItem>
)}
</>
)
);
@@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const Local: VolumeVariantComponent<"local"> = (
({ variant: { path }}) => (
<>
<DrawerItem name="Path">
{path}
</DrawerItem>
</>
)
);
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const NetworkFs: VolumeVariantComponent<"nfs"> = (
({ variant: { server, path, readOnly = false }}) => (
<>
<DrawerItem name="Server">
{server}
</DrawerItem>
<DrawerItem name="Path">
{path}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { pvcApi } from "../../../../../../common/k8s-api/endpoints";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const PersistentVolumeClaim: VolumeVariantComponent<"persistentVolumeClaim"> = (
({ pod, variant: { claimName }}) => (
<LocalRef
pod={pod}
title="Name"
kubeRef={{ name: claimName }}
api={pvcApi}
/>
)
);
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const PhotonPersistentDisk: VolumeVariantComponent<"photonPersistentDisk"> = (
({ variant: { pdID, fsType = "ext4" }}) => (
<>
<DrawerItem name="Persistent Disk ID">
{pdID}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
</>
)
);
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const PortworxVolume: VolumeVariantComponent<"portworxVolume"> = (
({ variant: { volumeID, fsType = "ext4", readOnly = false }}) => (
<>
<DrawerItem name="Volume ID">
{volumeID}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,88 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem, DrawerTitle } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const Projected: VolumeVariantComponent<"projected"> = (
({ variant: { sources, defaultMode }}) => (
<>
<DrawerItem name="Default Mount Mode">
0o{defaultMode.toString(8)}
</DrawerItem>
<DrawerItem name="Sources">
{
sources.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => (
<React.Fragment key={index}>
{secret && (
<>
<DrawerTitle size="sub-title">Secret</DrawerTitle>
<DrawerItem name="Name">
{secret.name}
</DrawerItem>
<DrawerItem name="Items">
<ul>
{secret.items?.map(({ key, path }) => (
<li key={key}>
{key} ⇢ {path}
</li>
))}
</ul>
</DrawerItem>
</>
)}
{downwardAPI && (
<>
<DrawerTitle size="sub-title">Downward API</DrawerTitle>
<DrawerItem name="Items">
<ul>
{downwardAPI.items?.map(({ path }) => (
<li key={path}>
{path}
</li>
))}
</ul>
</DrawerItem>
</>
)}
{configMap && (
<>
<DrawerTitle size="sub-title">Config Map</DrawerTitle>
<DrawerItem name="Name">
{configMap.name}
</DrawerItem>
<DrawerItem name="Items">
<ul>
{configMap.items?.map(({ key, path }) => (
<li key={key}>
{key} ⇢ {path}
</li>
))}
</ul>
</DrawerItem>
</>
)}
{serviceAccountToken && (
<>
<DrawerTitle size="sub-title">Service Account Token</DrawerTitle>
<DrawerItem name="Audience" hidden={!serviceAccountToken.audience}>
{serviceAccountToken.audience}
</DrawerItem>
<DrawerItem name="Expiration">
{String(serviceAccountToken.expirationSeconds || (60*60 /* an hour */))}s
</DrawerItem>
<DrawerItem name="Path">
{serviceAccountToken.path}
</DrawerItem>
</>
)}
</React.Fragment>
))
}
</DrawerItem>
</>
)
);
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const Quobyte: VolumeVariantComponent<"quobyte"> = (
({ variant: { registry, volume, readOnly = false, user = "serviceaccount", group, tenant }}) => (
<>
<DrawerItem name="Registry">
{registry}
</DrawerItem>
<DrawerItem name="Volume">
{volume}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
<DrawerItem name="User">
{user}
</DrawerItem>
<DrawerItem name="Group">
{group ?? "-- no group --"}
</DrawerItem>
<DrawerItem name="Tenant" hidden={!tenant}>
{tenant}
</DrawerItem>
</>
)
);
@@ -0,0 +1,52 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const RadosBlockDevice: VolumeVariantComponent<"rbd"> = (
({ pod, variant: { monitors, image, fsType = "ext4", pool = "rbd", user = "admin", keyring = "/etc/ceph/keyright", secretRef, readOnly = false }}) => (
<>
<DrawerItem name="Ceph Monitors">
<ul>
{monitors.map(monitor => <li key={monitor}>{monitor}</li>)}
</ul>
</DrawerItem>
<DrawerItem name="Image">
{image}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<DrawerItem name="Pool">
{pool}
</DrawerItem>
<DrawerItem name="User">
{user}
</DrawerItem>
{
secretRef
? (
<LocalRef
pod={pod}
title="Authentication Secret"
kubeRef={secretRef}
api={secretsApi}
/>
)
: (
<DrawerItem name="Keyright Path">
{keyring}
</DrawerItem>
)
}
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,49 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const ScaleIo: VolumeVariantComponent<"scaleIO"> = (
({ pod, variant: { gateway, system, secretRef, sslEnabled = false, protectionDomain, storagePool, storageMode = "ThinProvisioned", volumeName, fsType = "xfs", readOnly = false }}) => (
<>
<DrawerItem name="Gateway">
{gateway}
</DrawerItem>
<DrawerItem name="System">
{system}
</DrawerItem>
<LocalRef
pod={pod}
title="Name"
kubeRef={secretRef}
api={secretsApi}
/>
<DrawerItem name="SSL Enabled">
{sslEnabled.toString()}
</DrawerItem>
<DrawerItem name="Protection Domain Name" hidden={!protectionDomain}>
{protectionDomain}
</DrawerItem>
<DrawerItem name="Storage Pool" hidden={!storagePool}>
{storagePool}
</DrawerItem>
<DrawerItem name="Storage Mode" hidden={!storageMode}>
{storageMode}
</DrawerItem>
<DrawerItem name="Volume Name">
{volumeName}
</DrawerItem>
<DrawerItem name="Filesystem Type">
{fsType}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const Secret: VolumeVariantComponent<"secret"> = (
({ pod, variant: { secretName, items = [], defaultMode = 0o644, optional = false }}) => (
<>
<LocalRef
pod={pod}
title="Name"
kubeRef={{ name: secretName }}
api={secretsApi}
/>
<DrawerItem name="Items" hidden={items.length === 0}>
<ul>
{items.map(({ key }) => <li key={key}>{key}</li>)}
</ul>
</DrawerItem>
<DrawerItem name="Default File Mode">
0o{defaultMode.toString(8)}
</DrawerItem>
<DrawerItem name="Optional">
{optional.toString()}
</DrawerItem>
</>
)
);
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { secretsApi } from "../../../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../../../drawer";
import { LocalRef, VolumeVariantComponent } from "../variant-helpers";
export const StorageOs: VolumeVariantComponent<"storageos"> = (
({ pod, variant: { volumeName, volumeNamespace, fsType = "ext4", readOnly = false, secretRef }}) => (
<>
<DrawerItem name="Volume Name">
{volumeName}
</DrawerItem>
<DrawerItem name="Volume Namespace" hidden={volumeNamespace === "default"}>
{
volumeNamespace === volumeName
? "- no default behaviour -"
: volumeNamespace || pod.getNs()
}
</DrawerItem>
<DrawerItem name="Filesystem type">
{fsType}
</DrawerItem>
<DrawerItem name="Readonly">
{readOnly.toString()}
</DrawerItem>
<LocalRef
pod={pod}
title="Secret"
kubeRef={secretRef}
api={secretsApi}
/>
</>
)
);
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerItem } from "../../../../drawer";
import type { VolumeVariantComponent } from "../variant-helpers";
export const VsphereVolume: VolumeVariantComponent<"vsphereVolume"> = (
({ variant: { volumePath, fsType = "ext4", storagePolicyName, storagePolicyID }}) => (
<>
<DrawerItem name="Virtual Machine Disk Volume">
{volumePath}
</DrawerItem>
<DrawerItem name="Filesystem type">
{fsType}
</DrawerItem>
<DrawerItem name="Storage Policy Based Management Profile Name" hidden={!storagePolicyName}>
{storagePolicyName}
</DrawerItem>
<DrawerItem name="Storage Policy Based Management Profile ID" hidden={!storagePolicyID}>
{storagePolicyID}
</DrawerItem>
</>
)
);
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { observer } from "mobx-react";
import React from "react";
import type { Pod } from "../../../../../common/k8s-api/endpoints";
import { DrawerTitle } from "../../../drawer";
import { Icon } from "../../../icon";
import { VolumeVariant } from "./variant";
export interface PodVolumesProps {
pod: Pod;
}
export const PodVolumes = observer(({ pod }: PodVolumesProps) => {
const volumes = pod.getVolumes() ?? [];
if (volumes.length === 0) {
return null;
}
return (
<>
<DrawerTitle>Volumes</DrawerTitle>
{volumes.map(volume => (
<div key={volume.name} className="volume">
<div className="title flex gaps">
<Icon small material="storage" />
<span>{volume.name}</span>
</div>
<VolumeVariant pod={pod} volume={volume} />
</div>
))}
</>
);
});
@@ -131,7 +131,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
return (
<div className="PodDetailsList flex column">
<DrawerTitle title="Pods" />
<DrawerTitle>Pods</DrawerTitle>
<Table
tableId="workloads_pod_details_list"
items={pods}
@@ -5,10 +5,10 @@
import "./pod-details-secrets.scss";
import React, { Component } from "react";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { autorun, observable, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { reaction } from "mobx";
import { observer } from "mobx-react";
import { Pod, Secret, secretsApi } from "../../../common/k8s-api/endpoints";
import { getDetailsUrl } from "../kube-detail-params";
@@ -16,59 +16,50 @@ export interface PodDetailsSecretsProps {
pod: Pod;
}
@observer
export class PodDetailsSecrets extends Component<PodDetailsSecretsProps> {
@observable secrets: Map<string, Secret> = observable.map<string, Secret>();
export const PodDetailsSecrets = observer(({ pod }: PodDetailsSecretsProps) => {
const [secrets, setSecrets] = useState(new Map<string, Secret>());
componentDidMount(): void {
disposeOnUnmount(this, [
autorun(async () => {
const { pod } = this.props;
const secrets = await Promise.all(
pod.getSecrets().map(secretName => secretsApi.get({
useEffect(() => (
reaction(
() => pod.getSecrets(),
async (secretNames) => {
const results = await Promise.allSettled(
secretNames.map(secretName => secretsApi.get({
name: secretName,
namespace: pod.getNs(),
})),
);
secrets.forEach(secret => secret && this.secrets.set(secret.getName(), secret));
}),
]);
}
setSecrets(new Map(
results
.filter(result => result.status === "fulfilled" && result.value)
.map(result => (result as PromiseFulfilledResult<Secret>).value)
.map(secret => [secret.getName(), secret]),
));
},
{
fireImmediately: true,
})
), []);
constructor(props: PodDetailsSecretsProps) {
super(props);
makeObservable(this);
}
const renderSecret = (name: string) => {
const secret = secrets.get(name);
render() {
const { pod } = this.props;
if (!secret) {
return <span key={name}>{name}</span>;
}
return (
<div className="PodDetailsSecrets">
{
pod.getSecrets().map(secretName => {
const secret = this.secrets.get(secretName);
if (secret) {
return this.renderSecretLink(secret);
} else {
return (
<span key={secretName}>{secretName}</span>
);
}
})
}
</div>
);
}
protected renderSecretLink(secret: Secret) {
return (
<Link key={secret.getId()} to={getDetailsUrl(secret.selfLink)}>
{secret.getName()}
</Link>
);
}
}
};
return (
<div className="PodDetailsSecrets">
{pod.getSecrets().map(renderSecret)}
</div>
);
});
@@ -10,14 +10,13 @@ import kebabCase from "lodash/kebabCase";
import { disposeOnUnmount, observer } from "mobx-react";
import { Link } from "react-router-dom";
import { observable, reaction, makeObservable } from "mobx";
import { type IPodMetrics, nodesApi, Pod, pvcApi, configMapApi, getMetricsForPods } from "../../../common/k8s-api/endpoints";
import { type IPodMetrics, nodesApi, Pod, getMetricsForPods } from "../../../common/k8s-api/endpoints";
import { DrawerItem, DrawerTitle } from "../drawer";
import { Badge } from "../badge";
import { boundMethod, cssNames, toJS } from "../../utils";
import { PodDetailsContainer } from "./pod-details-container";
import { PodDetailsAffinities } from "./pod-details-affinities";
import { PodDetailsTolerations } from "./pod-details-tolerations";
import { Icon } from "../icon";
import { PodDetailsSecrets } from "./pod-details-secrets";
import { ResourceMetrics } from "../resource-metrics";
import type { KubeObjectDetailsProps } from "../kube-object-details";
@@ -28,6 +27,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { getDetailsUrl } from "../kube-detail-params";
import logger from "../../../common/logger";
import { PodVolumes } from "./details/volumes/view";
export interface PodDetailsProps extends KubeObjectDetailsProps<Pod> {
}
@@ -73,12 +73,13 @@ export class PodDetails extends React.Component<PodDetailsProps> {
}
const { status, spec } = pod;
const { conditions, podIP } = status;
const { conditions = [], podIP } = status ?? {};
const podIPs = pod.getIPs();
const { nodeName } = spec;
const { nodeName } = spec ?? {};
const nodeSelector = pod.getNodeSelectors();
const volumes = pod.getVolumes();
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Pod);
const initContainers = pod.getInitContainers();
const containers = pod.getContainers();
return (
<div className="PodDetails">
@@ -90,26 +91,22 @@ export class PodDetails extends React.Component<PodDetailsProps> {
<PodCharts/>
</ResourceMetrics>
)}
<KubeObjectMeta object={pod}/>
<DrawerItem name="Status">
<span className={cssNames("status", kebabCase(pod.getStatusMessage()))}>{pod.getStatusMessage()}</span>
</DrawerItem>
<DrawerItem name="Node">
{nodeName && (
<Link to={getDetailsUrl(nodesApi.getUrl({ name: nodeName }))}>
{nodeName}
</Link>
)}
<DrawerItem name="Node" hidden={!nodeName}>
<Link to={getDetailsUrl(nodesApi.getUrl({ name: nodeName }))}>
{nodeName}
</Link>
</DrawerItem>
<DrawerItem name="Pod IP">
{podIP}
</DrawerItem>
<DrawerItem name="Pod IPs" hidden={!podIPs.length} labelsOnly>
{
podIPs.map(label => (
<Badge key={label} label={label}/>
))
}
<DrawerItem name="Pod IPs" hidden={podIPs.length === 0} labelsOnly>
{podIPs.map(label => <Badge key={label} label={label} />)}
</DrawerItem>
<DrawerItem name="Priority Class">
{pod.getPriorityClassName()}
@@ -117,129 +114,55 @@ export class PodDetails extends React.Component<PodDetailsProps> {
<DrawerItem name="QoS Class">
{pod.getQosClass()}
</DrawerItem>
{conditions &&
<DrawerItem name="Conditions" className="conditions" labelsOnly>
{
conditions.map(condition => {
const { type, status, lastTransitionTime } = condition;
return (
<Badge
key={type}
label={type}
disabled={status === "False"}
tooltip={`Last transition time: ${lastTransitionTime}`}
/>
);
})
}
</DrawerItem>
}
{nodeSelector.length > 0 &&
<DrawerItem name="Node Selector">
<DrawerItem name="Conditions" className="conditions" hidden={conditions.length === 0} labelsOnly>
{
nodeSelector.map(label => (
<Badge key={label} label={label}/>
conditions.map(({ type, status, lastTransitionTime }) => (
<Badge
key={type}
label={type}
disabled={status === "False"}
tooltip={`Last transition time: ${lastTransitionTime}`}
/>
))
}
</DrawerItem>
}
<DrawerItem name="Node Selector" hidden={nodeSelector.length === 0}>
{nodeSelector.map(label => <Badge key={label} label={label} />)}
</DrawerItem>
<PodDetailsTolerations workload={pod}/>
<PodDetailsAffinities workload={pod}/>
{pod.getSecrets().length > 0 && (
<DrawerItem name="Secrets">
<PodDetailsSecrets pod={pod}/>
</DrawerItem>
)}
<DrawerItem name="Secrets" hidden={pod.getSecrets().length === 0}>
<PodDetailsSecrets pod={pod}/>
</DrawerItem>
{pod.getInitContainers() && pod.getInitContainers().length > 0 &&
<DrawerTitle title="Init Containers"/>
}
{
pod.getInitContainers() && pod.getInitContainers().map(container => {
return <PodDetailsContainer key={container.name} pod={pod} container={container}/>;
})
}
<DrawerTitle title="Containers"/>
{
pod.getContainers().map(container => {
const { name } = container;
const metrics = getItemMetrics(toJS(this.containerMetrics), name);
return (
{initContainers.length > 0 && (
<>
<DrawerTitle>Init Containers</DrawerTitle>
{initContainers.map(container => (
<PodDetailsContainer
key={name}
key={container.name}
pod={pod}
container={container}
metrics={metrics || null}
/>
);
})
}
{volumes.length > 0 && (
<>
<DrawerTitle title="Volumes"/>
{volumes.map(volume => {
const claimName = volume.persistentVolumeClaim ? volume.persistentVolumeClaim.claimName : null;
const configMap = volume.configMap ? volume.configMap.name : null;
const type = Object.keys(volume)[1];
return (
<div key={volume.name} className="volume">
<div className="title flex gaps">
<Icon small material="storage"/>
<span>{volume.name}</span>
</div>
<DrawerItem name="Type">
{type}
</DrawerItem>
{ type == "configMap" && (
<div>
{configMap && (
<DrawerItem name="Name">
<Link
to={getDetailsUrl(configMapApi.getUrl({
name: configMap,
namespace: pod.getNs(),
}))}>{configMap}
</Link>
</DrawerItem>
)}
</div>
)}
{ type === "emptyDir" && (
<div>
{ volume.emptyDir.medium && (
<DrawerItem name="Medium">
{volume.emptyDir.medium}
</DrawerItem>
)}
{ volume.emptyDir.sizeLimit && (
<DrawerItem name="Size Limit">
{volume.emptyDir.sizeLimit}
</DrawerItem>
)}
</div>
)}
{claimName && (
<DrawerItem name="Claim Name">
<Link
to={getDetailsUrl(pvcApi.getUrl({
name: claimName,
namespace: pod.getNs(),
}))}
>{claimName}
</Link>
</DrawerItem>
)}
</div>
);
})}
))}
</>
)}
<DrawerTitle>Containers</DrawerTitle>
{containers.map(container => (
<PodDetailsContainer
key={container.name}
pod={pod}
container={container}
metrics={getItemMetrics(toJS(this.containerMetrics), container.name)}
/>
))}
<PodVolumes pod={pod} />
</div>
);
}
@@ -6,21 +6,30 @@
import React from "react";
import { DrawerItem, DrawerItemProps } from "./drawer-item";
import { Badge } from "../badge";
import { KubeObject } from "../../../common/k8s-api/kube-object";
export interface DrawerItemLabelsProps extends DrawerItemProps {
labels: string[];
labels: string[] | Record<string, string>;
}
export function DrawerItemLabels(props: DrawerItemLabelsProps) {
const { labels, ...itemProps } = props;
if (!labels || !labels.length) {
if (!labels || typeof labels !== "object") {
return null;
}
const labelStrings = Array.isArray(labels)
? labels
: KubeObject.stringifyLabels(labels);
if (labelStrings.length === 0) {
return null;
}
return (
<DrawerItem {...itemProps} labelsOnly>
{labels.map(label => <Badge key={label} label={label} title={label}/>)}
{labelStrings.map(label => <Badge key={label} label={label} title={label}/>)}
</DrawerItem>
);
}
+20 -17
View File
@@ -7,29 +7,32 @@ import "./drawer-item.scss";
import React from "react";
import { cssNames, displayBooleans } from "../../utils";
export interface DrawerItemProps extends React.HTMLAttributes<any> {
export interface DrawerItemProps extends React.HTMLAttributes<HTMLDivElement> {
name: React.ReactNode;
className?: string;
title?: string;
labelsOnly?: boolean;
hidden?: boolean;
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
}
export class DrawerItem extends React.Component<DrawerItemProps> {
render() {
const { name, title, labelsOnly, children, hidden, className, renderBoolean, ...elemProps } = this.props;
if (hidden) return null;
const classNames = cssNames("DrawerItem", className, { labelsOnly });
const content = displayBooleans(renderBoolean, children);
return (
<div {...elemProps} className={classNames} title={title}>
<span className="name">{name}</span>
<span className="value">{content}</span>
</div>
);
export function DrawerItem({
name,
title,
labelsOnly,
children,
hidden = false,
className,
renderBoolean,
...elemProps
}: DrawerItemProps) {
if (hidden) {
return null;
}
return (
<div {...elemProps} className={cssNames("DrawerItem", className, { labelsOnly })} title={title}>
<span className="name">{name}</span>
<span className="value">{displayBooleans(renderBoolean, children)}</span>
</div>
);
}
@@ -4,7 +4,14 @@
*/
.DrawerTitle {
padding: $padding * 1.5 $padding * 3;
margin: $margin * 3 (-$margin * 3);
background: var(--drawerSubtitleBackground);
}
.DrawerTitle.title {
padding: $padding * 1.5 $padding * 3;
}
.DrawerTitle.sub-title {
padding: $padding $padding * 2;
}
+18 -12
View File
@@ -3,23 +3,29 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./drawer-title.scss";
import styles from "./drawer-title.module.css";
import React from "react";
import { cssNames } from "../../utils";
export interface DrawerTitleProps {
className?: string;
title?: React.ReactNode;
children: React.ReactNode;
/**
* Specifies how large this title is
*
* @default "title"
*/
size?: "sub-title" | "title";
}
export class DrawerTitle extends React.Component<DrawerTitleProps> {
render() {
const { title, children, className } = this.props;
return (
<div className={cssNames("DrawerTitle", className)}>
{title || children}
</div>
);
}
export function DrawerTitle({ className, children, size = "title" }: DrawerTitleProps) {
return (
<div className={cssNames(styles.DrawerTitle, className, {
[styles.title]: size === "title",
[styles["sub-title"]]: size === "sub-title",
})}>
{children}
</div>
);
}
-2
View File
@@ -17,8 +17,6 @@ interface TabsContextValue<D = any> {
onChange?(value: D): void;
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export interface TabsProps<D = any> extends TabsContextValue<D>, Omit<DOMAttributes<HTMLElement>, "onChange"> {
className?: string;
center?: boolean;
@@ -17,7 +17,7 @@ export function initCatalogEntityDetailRegistry() {
components: {
Details: ({ entity }: CatalogEntityDetailsProps<KubernetesCluster>) => (
<>
<DrawerTitle title="Kubernetes Information" />
<DrawerTitle>Kubernetes Information</DrawerTitle>
<div className="box grow EntityMetadata">
<DrawerItem name="Distribution">
{entity.metadata.distro || "unknown"}
@@ -36,7 +36,7 @@ export function initCatalogEntityDetailRegistry() {
components: {
Details: ({ entity }: CatalogEntityDetailsProps<WebLink>) => (
<>
<DrawerTitle title="More Information" />
<DrawerTitle>More Information</DrawerTitle>
<DrawerItem name="URL">
{entity.spec.url}
</DrawerItem>