Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 124 additions & 17 deletions src/components/molecules/Terminals/NodeTerminal/NodeTerminal.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,152 @@
/* eslint-disable no-console */
import React, { FC, useState } from 'react'
import { Select } from 'antd'
import React, { FC, useEffect, useMemo, useState } from 'react'
import { Flex, Select } from 'antd'
import { filterSelectOptions } from 'utils/filterSelectOptions'
import { Spacer } from 'components/atoms'
import { useListWatch } from 'hooks/useListThenWatch'
import { XTerminal } from './molecules'
import { Styled } from './styled'

const PREDEFINED_PROFILES = ['legacy', 'general', 'baseline', 'netadmin', 'restricted', 'sysadmin'] as const

type TPodTemplateData = {
metadata?: { name?: string }
template?: {
spec?: {
containers?: Array<{ name?: string }>
}
}
}

export type TNodeTerminalProps = {
cluster: string
nodeName: string
substractHeight: number
defaultProfile?: string
listPodTemplatesNs?: string
}

export const NodeTerminal: FC<TNodeTerminalProps> = ({ cluster, nodeName, substractHeight, defaultProfile }) => {
export const NodeTerminal: FC<TNodeTerminalProps> = ({
cluster,
nodeName,
substractHeight,
defaultProfile,
listPodTemplatesNs,
}) => {
const [currentProfile, setCurrentProfile] = useState<string>(defaultProfile || 'general')
const [currentContainer, setCurrentContainer] = useState<string | undefined>()

const endpoint = `/api/clusters/${cluster}/openapi-bff-ws/terminal/terminalNode/terminalNode`

const profiles = ['legacy', 'general', 'baseline', 'netadmin', 'restricted', 'sysadmin']
const isUsingPodTemplates = Boolean(listPodTemplatesNs && listPodTemplatesNs.length > 0)

const podTemplatesWsUrl = `/api/clusters/${cluster}/openapi-bff-ws/listThenWatch/listWatchWs`
const podTemplates = useListWatch({
wsUrl: podTemplatesWsUrl,
query: {
apiVersion: 'v1',
plural: 'podtemplates',
namespace: listPodTemplatesNs,
},
isEnabled: isUsingPodTemplates,
})

const podTemplateNames = useMemo(() => {
const values = Object.values(podTemplates.state.byKey ?? {})
.map(it => String((it as TPodTemplateData)?.metadata?.name ?? ''))
.filter(Boolean)
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b))
}, [podTemplates.state.byKey])

const hasPodTemplates = podTemplateNames.length > 0

const isPredefinedProfile = PREDEFINED_PROFILES.some(p => p === currentProfile)
const isCustomTemplate = isUsingPodTemplates && hasPodTemplates && !isPredefinedProfile

const containerNames = useMemo(() => {
if (!isCustomTemplate) return []

const selectedTemplate = Object.values(podTemplates.state.byKey ?? {}).find(
it => (it as TPodTemplateData)?.metadata?.name === currentProfile,
) as TPodTemplateData | undefined

const containers = selectedTemplate?.template?.spec?.containers ?? []
return containers.map(c => c.name).filter((name): name is string => Boolean(name))
}, [isCustomTemplate, podTemplates.state.byKey, currentProfile])

const hasMultipleContainers = containerNames.length > 1

useEffect(() => {
if (isCustomTemplate && containerNames.length > 0) {
setCurrentContainer(containerNames[0])
} else {
setCurrentContainer(undefined)
}
}, [isCustomTemplate, containerNames])

const selectOptions = useMemo(() => {
const predefinedOptions = PREDEFINED_PROFILES.map(profile => ({
value: profile,
label: profile,
}))

if (!hasPodTemplates) {
return predefinedOptions
}

return [
{
label: 'Predefined Profiles',
options: predefinedOptions,
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to remove predefined if we have specific pod templates. You will need an option to hide predifend options if you keep it

{
label: 'Custom PodTemplates',
options: podTemplateNames.map(name => ({ value: name, label: name })),
},
]
}, [hasPodTemplates, podTemplateNames])

const canShowTerminal = currentProfile && (!isCustomTemplate || currentContainer)

return (
<>
<Styled.CustomSelect>
<Select
placeholder="Select profile"
options={profiles.map(profile => ({ value: profile, label: profile }))}
filterOption={filterSelectOptions}
showSearch
value={currentProfile}
onChange={value => setCurrentProfile(value)}
/>
</Styled.CustomSelect>
<Flex gap={16}>
<Styled.CustomSelect>
<Select
placeholder="Select profile"
options={selectOptions}
filterOption={filterSelectOptions}
showSearch
value={currentProfile}
onChange={value => {
setCurrentProfile(value)
setCurrentContainer(undefined)
}}
/>
</Styled.CustomSelect>
{isCustomTemplate && hasMultipleContainers && (
<Styled.CustomSelect>
<Select
placeholder="Select container"
options={containerNames.map(name => ({ value: name, label: name }))}
filterOption={filterSelectOptions}
showSearch
value={currentContainer}
onChange={value => setCurrentContainer(value)}
/>
</Styled.CustomSelect>
)}
</Flex>
<Spacer $space={16} $samespace />
{currentProfile && (
{canShowTerminal && (
<XTerminal
endpoint={endpoint}
nodeName={nodeName}
profile={currentProfile}
isCustomTemplate={isCustomTemplate}
podTemplateNamespace={isCustomTemplate ? listPodTemplatesNs : undefined}
containerName={isCustomTemplate ? currentContainer : undefined}
substractHeight={substractHeight}
key={`${cluster}-${nodeName}-${currentProfile}`}
key={`${cluster}-${nodeName}-${listPodTemplatesNs}-${currentProfile}-${currentContainer}`}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ type TXTerminalProps = {
endpoint: string
nodeName: string
profile: string
isCustomTemplate?: boolean
podTemplateNamespace?: string
containerName?: string
substractHeight: number
}

export const XTerminal: FC<TXTerminalProps> = ({ endpoint, nodeName, profile, substractHeight }) => {
export const XTerminal: FC<TXTerminalProps> = ({
endpoint,
nodeName,
profile,
isCustomTemplate,
podTemplateNamespace,
containerName,
substractHeight,
}) => {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [error, setError] = useState<Event>()

Expand Down Expand Up @@ -77,12 +88,13 @@ export const XTerminal: FC<TXTerminalProps> = ({ endpoint, nodeName, profile, su
socketRef.current = socket

socket.onopen = () => {
socket.send(
JSON.stringify({
type: 'init',
payload: { nodeName, profile },
}),
)
const payload: Record<string, unknown> = { nodeName, profile }
if (isCustomTemplate) {
payload.podTemplateName = profile
payload.podTemplateNamespace = podTemplateNamespace
payload.containerName = containerName
}
socket.send(JSON.stringify({ type: 'init', payload }))
console.log(`[${nodeName}/${profile}]: WebSocket Client Connected`)
setIsLoading(false)
}
Expand Down Expand Up @@ -174,7 +186,7 @@ export const XTerminal: FC<TXTerminalProps> = ({ endpoint, nodeName, profile, su
socket.close()
}
}
}, [terminal, endpoint, nodeName, profile])
}, [terminal, endpoint, nodeName, profile, isCustomTemplate, podTemplateNamespace, containerName])

return (
<>
Expand Down
1 change: 1 addition & 0 deletions src/components/organisms/DynamicComponents/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export type TDynamicComponentsAppTypeMap = {
cluster: string
nodeName: string
substractHeight?: number
listPodTemplatesNs?: string
}
PodLogs: {
id: number | string
Expand Down