search wip: tables

This commit is contained in:
typescreep
2025-09-17 10:51:27 +03:00
parent 24d2d90ec2
commit 3aeb00da64
26 changed files with 584 additions and 562 deletions

8
package-lock.json generated
View File

@@ -11,7 +11,7 @@
"@ant-design/icons": "5.6.0",
"@monaco-editor/react": "4.6.0",
"@originjs/vite-plugin-federation": "1.3.6",
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.118",
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.119",
"@readme/openapi-parser": "4.0.0",
"@reduxjs/toolkit": "2.2.5",
"@tanstack/react-query": "5.62.2",
@@ -2802,9 +2802,9 @@
}
},
"node_modules/@prorobotech/openapi-k8s-toolkit": {
"version": "0.0.1-alpha.118",
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.118.tgz",
"integrity": "sha512-DGEug01G2lioiRHNmjSRecL0tOtBMuSOK3tV6EMwsOm8Cwl+HwQvEzEdpIhMU+r8/iUPQ1Q+ZGeoi5dYMmPWpA==",
"version": "0.0.1-alpha.119",
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.119.tgz",
"integrity": "sha512-e0AwwmMBAyjrpTPWMC9eSiyUimTzxskaNhAzs/+7DV7u0MqjJLOPehsko/AIpm/RkOoU6qOYjAHTjWZHV1hLhA==",
"license": "MIT",
"dependencies": {
"@monaco-editor/react": "4.6.0",

View File

@@ -20,7 +20,7 @@
"@ant-design/icons": "5.6.0",
"@monaco-editor/react": "4.6.0",
"@originjs/vite-plugin-federation": "1.3.6",
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.118",
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.119",
"@readme/openapi-parser": "4.0.0",
"@reduxjs/toolkit": "2.2.5",
"@tanstack/react-query": "5.62.2",

View File

@@ -0,0 +1,341 @@
/* eslint-disable max-lines-per-function */
import React, { FC, useState, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { Spin, Alert, Button, Flex } from 'antd'
import { PlusOutlined, ClearOutlined, MinusOutlined } from '@ant-design/icons'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import {
EnrichedTableProvider,
usePermissions,
DeleteModal,
DeleteModalMany,
checkIfBuiltInInstanceNamespaceScoped,
checkIfApiInstanceNamespaceScoped,
useBuiltinResources,
useApiResources,
} from '@prorobotech/openapi-k8s-toolkit'
import { FlexGrow, PaddingContainer } from 'components'
import { TABLE_PROPS } from 'constants/tableProps'
import {
HEAD_FIRST_ROW,
HEAD_SECOND_ROW,
FOOTER_HEIGHT,
NAV_HEIGHT,
CONTENT_CARD_PADDING,
TABLE_ADD_BUTTON_HEIGHT,
} from 'constants/blocksSizes'
import { OverflowContainer } from './atoms'
import { getBackLinkToTable, getLinkToForm } from './utils'
type TTableApiBuiltinProps = {
namespace?: string
resourceType: 'builtin' | 'api'
apiGroup?: string // api
apiVersion?: string // api
typeName: string
limit: string | null
inside?: boolean
customizationIdPrefix: string
searchMount?: boolean
}
export const TableApiBuiltin: FC<TTableApiBuiltinProps> = ({
namespace,
resourceType,
apiGroup,
apiVersion,
typeName,
limit,
inside,
customizationIdPrefix,
searchMount,
}) => {
const location = useLocation()
const navigate = useNavigate()
const params = useParams()
const cluster = useSelector((state: RootState) => state.cluster.cluster)
const theme = useSelector((state: RootState) => state.openapiTheme.theme)
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<false | { name: string; endpoint: string }>(false)
const [isDeleteModalManyOpen, setIsDeleteModalManyOpen] = useState<false | { name: string; endpoint: string }[]>(
false,
)
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
const [selectedRowsData, setSelectedRowsData] = useState<{ name: string; endpoint: string }[]>([])
const [isNamespaced, setIsNamespaced] = useState<boolean>()
const [height, setHeight] = useState(0)
useEffect(() => {
const height =
window.innerHeight -
HEAD_FIRST_ROW -
HEAD_SECOND_ROW -
NAV_HEIGHT -
CONTENT_CARD_PADDING * 2 -
FOOTER_HEIGHT -
TABLE_ADD_BUTTON_HEIGHT
setHeight(height)
const handleResize = () => {
setHeight(height)
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
useEffect(() => {
if (resourceType === 'builtin') {
checkIfBuiltInInstanceNamespaceScoped({
typeName,
clusterName: cluster,
}).then(({ isNamespaceScoped }) => {
if (isNamespaceScoped) {
setIsNamespaced(isNamespaceScoped)
}
})
}
if (resourceType === 'api' && apiGroup && apiVersion) {
checkIfApiInstanceNamespaceScoped({
apiGroup,
apiVersion,
typeName,
clusterName: cluster,
}).then(({ isNamespaceScoped }) => {
if (isNamespaceScoped) {
setIsNamespaced(true)
}
})
}
}, [resourceType, cluster, typeName, apiGroup, apiVersion])
const createPermission = usePermissions({
apiGroup: apiGroup || '',
typeName,
namespace: '',
clusterName: cluster,
verb: 'create',
refetchInterval: false,
})
const updatePermission = usePermissions({
apiGroup: apiGroup || '',
typeName,
namespace: '',
clusterName: cluster,
verb: 'update',
refetchInterval: false,
})
const deletePermission = usePermissions({
apiGroup: apiGroup || '',
typeName,
namespace: '',
clusterName: cluster,
verb: 'delete',
refetchInterval: false,
})
const {
isPending: isPendingBuiltin,
error: errorBuiltin,
data: dataBuiltin,
} = useBuiltinResources({
clusterName: cluster,
namespace,
typeName,
limit,
isEnabled: resourceType === 'builtin',
})
const {
isPending: isPendingApi,
error: errorApi,
data: dataApi,
} = useApiResources({
clusterName: cluster,
namespace,
apiGroup: apiGroup || '',
apiVersion: apiVersion || '',
typeName,
limit,
isEnabled: resourceType === 'api' && !!apiGroup && !!apiVersion,
})
const onDeleteHandle = (name: string, endpoint: string) => {
setIsDeleteModalOpen({ name, endpoint })
}
const clearSelected = () => {
setSelectedRowKeys([])
setSelectedRowsData([])
}
const replaceValuesPartsOfUrls = location.pathname
.split('/')
.reduce<Record<string, string | undefined>>((acc, value, index) => {
acc[index.toString()] = value
return acc
}, {})
return (
<>
{((resourceType === 'builtin' && isPendingBuiltin) || (resourceType === 'api' && isPendingApi)) && <Spin />}
{resourceType === 'builtin' && errorBuiltin && (
<Alert message={`An error has occurred: ${errorBuiltin?.message} `} type="error" />
)}
{resourceType === 'api' && errorApi && (
<Alert message={`An error has occurred: ${errorApi?.message} `} type="error" />
)}
<OverflowContainer height={height} searchMount={searchMount}>
{!errorBuiltin &&
!errorApi &&
((resourceType === 'builtin' && dataBuiltin) || (resourceType === 'api' && dataApi)) && (
<EnrichedTableProvider
key={resourceType === 'builtin' ? `/v1/${typeName}` : `/${apiGroup}/${apiVersion}/${typeName}`}
customizationId={
resourceType === 'builtin'
? `${customizationIdPrefix}/v1/${typeName}`
: `${customizationIdPrefix}/${apiGroup}/${apiVersion}/${typeName}`
}
tableMappingsReplaceValues={{
clusterName: params.clusterName,
projectName: params.projectName,
instanceName: params.instanceName,
namespace: params.namespace,
syntheticProject: params.syntheticProject,
entryType: params.entryType,
apiGroup: params.apiGroup,
apiVersion: params.apiVersion,
typeName: params.typeName,
entryName: params.entryName,
apiExtensionVersion: params.apiExtensionVersion,
crdName: params.crdName,
...replaceValuesPartsOfUrls,
}}
cluster={cluster}
theme={theme}
baseprefix={inside ? `${baseprefix}/inside` : baseprefix}
dataItems={resourceType === 'builtin' ? dataBuiltin?.items || [] : dataApi?.items || []}
dataForControls={{
cluster,
syntheticProject: params.syntheticProject,
pathPrefix: resourceType === 'builtin' ? 'forms/builtin' : 'forms/apis',
typeName,
apiVersion: resourceType === 'builtin' ? 'v1' : `${apiGroup}/${apiVersion}`,
backlink: getBackLinkToTable({
resourceType,
cluster,
baseprefix,
namespace,
syntheticProject: params.syntheticProject,
apiGroup,
apiVersion,
typeName,
inside,
}),
deletePathPrefix:
resourceType === 'builtin' ? `/api/clusters/${cluster}/k8s/api` : `/api/clusters/${cluster}/k8s/apis`,
onDeleteHandle,
permissions: {
canUpdate: isNamespaced ? true : updatePermission.data?.status.allowed,
canDelete: isNamespaced ? true : deletePermission.data?.status.allowed,
},
}}
selectData={{
selectedRowKeys,
onChange: (selectedRowKeys: React.Key[], selectedRowsData: { name: string; endpoint: string }[]) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRowsData(selectedRowsData)
},
}}
tableProps={{ ...TABLE_PROPS, disablePagination: !searchMount }}
// maxHeight={height - 65}
/>
)}
{/* {selectedRowKeys.length > 0 && (
<MarginTopContainer $top={-40}>
<Flex gap={16}>
<Button type="primary" onClick={clearSelected}>
<ClearOutlined />
Clear
</Button>
<Button type="primary" onClick={() => setIsDeleteModalManyOpen(selectedRowsData)}>
<MinusOutlined />
Delete
</Button>
</Flex>
</MarginTopContainer>
)} */}
</OverflowContainer>
{!searchMount && (
<>
<FlexGrow />
<PaddingContainer $padding="4px">
<Flex justify="space-between">
<Button
type="primary"
onClick={() => {
const url = getLinkToForm({
resourceType,
cluster,
baseprefix,
namespace,
syntheticProject: params.syntheticProject,
apiGroup,
apiVersion,
typeName,
inside,
})
navigate(url)
}}
loading={isNamespaced ? false : createPermission.isPending}
disabled={isNamespaced ? false : !createPermission.data?.status.allowed}
>
<PlusOutlined />
Add
</Button>
{selectedRowKeys.length > 0 && (
<Flex gap={16}>
<Button type="primary" onClick={clearSelected}>
<ClearOutlined />
Clear
</Button>
<Button type="primary" onClick={() => setIsDeleteModalManyOpen(selectedRowsData)}>
<MinusOutlined />
Delete
</Button>
</Flex>
)}
</Flex>
</PaddingContainer>
</>
)}
{isDeleteModalOpen && (
<DeleteModal
name={isDeleteModalOpen.name}
onClose={() => {
setIsDeleteModalOpen(false)
clearSelected()
}}
endpoint={isDeleteModalOpen.endpoint}
/>
)}
{isDeleteModalManyOpen !== false && (
<DeleteModalMany
data={isDeleteModalManyOpen}
onClose={() => {
setIsDeleteModalManyOpen(false)
clearSelected()
}}
/>
)}
</>
)
}

View File

@@ -0,0 +1,17 @@
/* eslint-disable max-lines-per-function */
import React, { FC, PropsWithChildren } from 'react'
import { OverflowMaxHeightContainer } from 'components'
type TOverflowContainerProps = PropsWithChildren<{
height: number
searchMount?: boolean
}>
export const OverflowContainer: FC<TOverflowContainerProps> = ({ height, searchMount, children }) => {
if (searchMount) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>
}
return <OverflowMaxHeightContainer $maxHeight={height}>{children}</OverflowMaxHeightContainer>
}

View File

@@ -0,0 +1 @@
export * from './OverflowContainer'

View File

@@ -0,0 +1 @@
export * from './OverflowContainer'

View File

@@ -0,0 +1 @@
export * from './TableApiBuiltin'

View File

@@ -0,0 +1,136 @@
export const getBackLinkToBuiltinTable = ({
cluster,
baseprefix,
namespace,
syntheticProject,
typeName,
inside,
}: {
cluster: string
baseprefix?: string
namespace?: string
syntheticProject?: string
typeName: string
inside?: boolean
}): string => {
const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}`
const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}`
return `${mainRoute}/builtin-table/${typeName}`
}
export const getBackLinkToApiTable = ({
cluster,
baseprefix,
namespace,
syntheticProject,
apiGroup,
apiVersion,
typeName,
inside,
}: {
cluster: string
baseprefix?: string
namespace?: string
syntheticProject?: string
apiGroup?: string // api
apiVersion?: string // api
typeName: string
inside?: boolean
}): string => {
const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}`
const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}`
return `${mainRoute}/api-table/${apiGroup}/${apiVersion}/${typeName}`
}
export const getBackLinkToTable = ({
resourceType,
...rest
}: {
resourceType: 'builtin' | 'api'
cluster: string
baseprefix?: string
namespace?: string
syntheticProject?: string
apiGroup?: string // api
apiVersion?: string // api
typeName: string
inside?: boolean
}): string => {
return resourceType === 'builtin' ? getBackLinkToBuiltinTable({ ...rest }) : getBackLinkToApiTable({ ...rest })
}
export const getLinkToBuiltinForm = ({
cluster,
baseprefix,
namespace,
syntheticProject,
typeName,
inside,
}: {
cluster: string
baseprefix?: string
namespace?: string
syntheticProject?: string
typeName: string
inside?: boolean
}): string => {
const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}`
const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}`
const backlink = getBackLinkToBuiltinTable({ cluster, baseprefix, namespace, syntheticProject, typeName, inside })
return `${mainRoute}/forms/builtin/v1/${typeName}?backlink=${backlink}`
}
export const getLinkToApiForm = ({
cluster,
baseprefix,
namespace,
syntheticProject,
apiGroup,
apiVersion,
typeName,
inside,
}: {
cluster: string
baseprefix?: string
namespace?: string
syntheticProject?: string
apiGroup?: string // api
apiVersion?: string // api
typeName: string
inside?: boolean
}): string => {
const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}`
const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}`
const backlink = getBackLinkToApiTable({
cluster,
baseprefix,
namespace,
syntheticProject,
apiGroup,
apiVersion,
typeName,
inside,
})
return `${mainRoute}/forms/apis/${apiGroup}/${apiVersion}/${typeName}?backlink=${backlink}`
}
export const getLinkToForm = ({
resourceType,
...rest
}: {
resourceType: 'builtin' | 'api'
cluster: string
baseprefix?: string
namespace?: string
syntheticProject?: string
apiGroup?: string // api
apiVersion?: string // api
typeName: string
inside?: boolean
}): string => {
return resourceType === 'builtin' ? getLinkToBuiltinForm({ ...rest }) : getLinkToApiForm({ ...rest })
}

View File

@@ -1,3 +1,5 @@
export * from './BlackholeForm'
export * from './ManageableBreadcrumbs'
export * from './ManageableSidebar'
export * from './TableCrdInfo'
export * from './TableApiBuiltin'

View File

@@ -1,11 +1,24 @@
/* eslint-disable max-lines-per-function */
import React, { FC } from 'react'
import { Search as PackageSearch } from '@prorobotech/openapi-k8s-toolkit'
import React, { FC, Fragment, useState } from 'react'
import { Search as PackageSearch, Spacer } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import { SearchEntry } from './molecules'
export const Search: FC = () => {
const cluster = useSelector((state: RootState) => state.cluster.cluster)
return <PackageSearch cluster={cluster} />
const [currentSearch, setCurrentSearch] = useState<string[]>()
return (
<>
<PackageSearch cluster={cluster} updateCurrentSearch={value => setCurrentSearch(value)} />
{currentSearch?.map(item => (
<Fragment key={item}>
<SearchEntry resource={item} />
<Spacer $space={50} $samespace />
</Fragment>
))}
</>
)
}

View File

@@ -0,0 +1,45 @@
import React, { FC } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
import { Typography } from 'antd'
import { TableApiBuiltin } from 'components'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
type TSearchEntryProps = {
resource: string
}
export const SearchEntry: FC<TSearchEntryProps> = ({ resource }) => {
const { namespace, syntheticProject } = useParams()
const [searchParams] = useSearchParams()
const [apiGroup, apiVersion, typeName] = resource.split('~')
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
search: true,
})
return (
<div>
<Typography.Title level={4}>
{apiGroup.length > 0 ? `${apiGroup}/${apiVersion}/` : 'v1/'}
{typeName}
</Typography.Title>
{typeName && apiGroup && apiVersion && (
<TableApiBuiltin
resourceType={apiGroup ? 'api' : 'builtin'}
namespace={namespace}
apiGroup={apiGroup.length > 0 ? apiGroup : undefined}
apiVersion={apiGroup.length > 0 ? apiVersion : undefined}
typeName={typeName}
limit={searchParams.get('limit')}
customizationIdPrefix={tableCustomizationIdPrefix}
searchMount
/>
)}
</div>
)
}

View File

@@ -0,0 +1 @@
export * from './SearchEntry'

View File

@@ -0,0 +1 @@
export * from './SearchEntry'

View File

@@ -1,267 +0,0 @@
import React, { FC, useState, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { Spin, Alert, Button, Flex } from 'antd'
import { PlusOutlined, ClearOutlined, MinusOutlined } from '@ant-design/icons'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import {
EnrichedTableProvider,
usePermissions,
DeleteModal,
DeleteModalMany,
checkIfBuiltInInstanceNamespaceScoped,
useBuiltinResources,
} from '@prorobotech/openapi-k8s-toolkit'
import { FlexGrow, OverflowMaxHeightContainer, PaddingContainer } from 'components'
import { TABLE_PROPS } from 'constants/tableProps'
import {
HEAD_FIRST_ROW,
HEAD_SECOND_ROW,
FOOTER_HEIGHT,
NAV_HEIGHT,
CONTENT_CARD_PADDING,
TABLE_ADD_BUTTON_HEIGHT,
} from 'constants/blocksSizes'
type TTableBuiltinInfoProps = {
namespace?: string
typeName: string
limit: string | null
inside?: boolean
customizationIdPrefix: string
}
export const TableBuiltinInfo: FC<TTableBuiltinInfoProps> = ({
namespace,
typeName,
limit,
inside,
customizationIdPrefix,
}) => {
const location = useLocation()
const navigate = useNavigate()
const params = useParams()
const cluster = useSelector((state: RootState) => state.cluster.cluster)
const theme = useSelector((state: RootState) => state.openapiTheme.theme)
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<false | { name: string; endpoint: string }>(false)
const [isDeleteModalManyOpen, setIsDeleteModalManyOpen] = useState<false | { name: string; endpoint: string }[]>(
false,
)
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
const [selectedRowsData, setSelectedRowsData] = useState<{ name: string; endpoint: string }[]>([])
const [isNamespaced, setIsNamespaced] = useState<boolean>()
const [height, setHeight] = useState(0)
useEffect(() => {
const height =
window.innerHeight -
HEAD_FIRST_ROW -
HEAD_SECOND_ROW -
NAV_HEIGHT -
CONTENT_CARD_PADDING * 2 -
FOOTER_HEIGHT -
TABLE_ADD_BUTTON_HEIGHT
setHeight(height)
const handleResize = () => {
setHeight(height)
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
const createPermission = usePermissions({
apiGroup: '',
typeName,
namespace: '',
clusterName: cluster,
verb: 'create',
refetchInterval: false,
})
const updatePermission = usePermissions({
apiGroup: '',
typeName,
namespace: '',
clusterName: cluster,
verb: 'update',
refetchInterval: false,
})
const deletePermission = usePermissions({
apiGroup: '',
typeName,
namespace: '',
clusterName: cluster,
verb: 'delete',
refetchInterval: false,
})
useEffect(() => {
checkIfBuiltInInstanceNamespaceScoped({
typeName,
clusterName: cluster,
}).then(({ isNamespaceScoped }) => {
if (isNamespaceScoped) {
setIsNamespaced(isNamespaceScoped)
}
})
}, [cluster, typeName])
const { isPending, error, data } = useBuiltinResources({
clusterName: cluster,
namespace,
typeName,
limit,
})
const onDeleteHandle = (name: string, endpoint: string) => {
setIsDeleteModalOpen({ name, endpoint })
}
const clearSelected = () => {
setSelectedRowKeys([])
setSelectedRowsData([])
}
const replaceValuesPartsOfUrls = location.pathname
.split('/')
.reduce<Record<string, string | undefined>>((acc, value, index) => {
acc[index.toString()] = value
return acc
}, {})
return (
<>
{isPending && <Spin />}
{error && <Alert message={`An error has occurred: ${error?.message} `} type="error" />}
<OverflowMaxHeightContainer $maxHeight={height}>
{!error && data && (
<EnrichedTableProvider
key={`/v1/${typeName}`}
customizationId={`${customizationIdPrefix}/v1/${typeName}`}
tableMappingsReplaceValues={{
clusterName: params.clusterName,
projectName: params.projectName,
instanceName: params.instanceName,
namespace: params.namespace,
syntheticProject: params.syntheticProject,
entryType: params.entryType,
apiGroup: params.apiGroup,
apiVersion: params.apiVersion,
typeName: params.typeName,
entryName: params.entryName,
apiExtensionVersion: params.apiExtensionVersion,
crdName: params.crdName,
...replaceValuesPartsOfUrls,
}}
cluster={cluster}
theme={theme}
baseprefix={inside ? `${baseprefix}/inside` : baseprefix}
dataItems={data.items}
dataForControls={{
cluster,
syntheticProject: params.syntheticProject,
pathPrefix: 'forms/builtin',
typeName,
apiVersion: 'v1',
backlink: `${baseprefix}${inside ? '/inside' : ''}/${cluster}${namespace ? `/${namespace}` : ''}${
params.syntheticProject ? `/${params.syntheticProject}` : ''
}/builtin-table/${typeName}`,
deletePathPrefix: `/api/clusters/${cluster}/k8s/api`,
onDeleteHandle,
permissions: {
canUpdate: isNamespaced ? true : updatePermission.data?.status.allowed,
canDelete: isNamespaced ? true : deletePermission.data?.status.allowed,
},
}}
selectData={{
selectedRowKeys,
onChange: (selectedRowKeys: React.Key[], selectedRowsData: { name: string; endpoint: string }[]) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRowsData(selectedRowsData)
},
}}
tableProps={{ ...TABLE_PROPS, disablePagination: true }}
// maxHeight={height - 65}
/>
)}
{/* {selectedRowKeys.length > 0 && (
<MarginTopContainer $top={-40}>
<Flex gap={16}>
<Button type="primary" onClick={clearSelected}>
<ClearOutlined />
Clear
</Button>
<Button type="primary" onClick={() => setIsDeleteModalManyOpen(selectedRowsData)}>
<MinusOutlined />
Delete
</Button>
</Flex>
</MarginTopContainer>
)} */}
</OverflowMaxHeightContainer>
<FlexGrow />
<PaddingContainer $padding="4px">
<Flex justify="space-between">
<Button
type="primary"
onClick={() =>
navigate(
`${baseprefix}${inside ? '/inside' : ''}/${cluster}${namespace ? `/${namespace}` : ''}${
params.syntheticProject ? `/${params.syntheticProject}` : ''
}/forms/builtin/v1/${typeName}?backlink=${baseprefix}${inside ? '/inside' : ''}/${cluster}${
namespace ? `/${namespace}` : ''
}${params.syntheticProject ? `/${params.syntheticProject}` : ''}/builtin-table/${typeName}`,
)
}
loading={isNamespaced ? false : createPermission.isPending}
disabled={isNamespaced ? false : !createPermission.data?.status.allowed}
>
<PlusOutlined />
Add
</Button>
{selectedRowKeys.length > 0 && (
<Flex gap={16}>
<Button type="primary" onClick={clearSelected}>
<ClearOutlined />
Clear
</Button>
<Button type="primary" onClick={() => setIsDeleteModalManyOpen(selectedRowsData)}>
<MinusOutlined />
Delete
</Button>
</Flex>
)}
</Flex>
</PaddingContainer>
{isDeleteModalOpen && (
<DeleteModal
name={isDeleteModalOpen.name}
onClose={() => {
setIsDeleteModalOpen(false)
clearSelected()
}}
endpoint={isDeleteModalOpen.endpoint}
/>
)}
{isDeleteModalManyOpen !== false && (
<DeleteModalMany
data={isDeleteModalManyOpen}
onClose={() => {
setIsDeleteModalManyOpen(false)
clearSelected()
}}
/>
)}
</>
)
}

View File

@@ -1 +0,0 @@
export * from './TableBuiltinInfo'

View File

@@ -1,277 +0,0 @@
/* eslint-disable max-lines-per-function */
import React, { FC, useState, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { Spin, Alert, Button, Flex } from 'antd'
import { PlusOutlined, ClearOutlined, MinusOutlined } from '@ant-design/icons'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import {
EnrichedTableProvider,
usePermissions,
DeleteModal,
DeleteModalMany,
checkIfApiInstanceNamespaceScoped,
useApiResources,
} from '@prorobotech/openapi-k8s-toolkit'
import { FlexGrow, OverflowMaxHeightContainer, PaddingContainer } from 'components'
import { TABLE_PROPS } from 'constants/tableProps'
import {
HEAD_FIRST_ROW,
HEAD_SECOND_ROW,
FOOTER_HEIGHT,
NAV_HEIGHT,
CONTENT_CARD_PADDING,
TABLE_ADD_BUTTON_HEIGHT,
} from 'constants/blocksSizes'
type TTableNonCrdInfoProps = {
namespace?: string
apiGroup: string
apiVersion: string
typeName: string
limit: string | null
inside?: boolean
customizationIdPrefix: string
}
export const TableNonCrdInfo: FC<TTableNonCrdInfoProps> = ({
namespace,
apiGroup,
apiVersion,
typeName,
limit,
inside,
customizationIdPrefix,
}) => {
const location = useLocation()
const navigate = useNavigate()
const params = useParams()
const cluster = useSelector((state: RootState) => state.cluster.cluster)
const theme = useSelector((state: RootState) => state.openapiTheme.theme)
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<false | { name: string; endpoint: string }>(false)
const [isDeleteModalManyOpen, setIsDeleteModalManyOpen] = useState<false | { name: string; endpoint: string }[]>(
false,
)
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
const [selectedRowsData, setSelectedRowsData] = useState<{ name: string; endpoint: string }[]>([])
const [isNamespaced, setIsNamespaced] = useState<boolean>()
const [height, setHeight] = useState(0)
useEffect(() => {
const height =
window.innerHeight -
HEAD_FIRST_ROW -
HEAD_SECOND_ROW -
NAV_HEIGHT -
CONTENT_CARD_PADDING * 2 -
FOOTER_HEIGHT -
TABLE_ADD_BUTTON_HEIGHT
setHeight(height)
const handleResize = () => {
setHeight(height)
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
useEffect(() => {
checkIfApiInstanceNamespaceScoped({
apiGroup,
apiVersion,
typeName,
clusterName: cluster,
}).then(({ isNamespaceScoped }) => {
if (isNamespaceScoped) {
setIsNamespaced(true)
}
})
}, [cluster, typeName, apiGroup, apiVersion])
const createPermission = usePermissions({
apiGroup,
typeName,
namespace: '',
clusterName: cluster,
verb: 'create',
refetchInterval: false,
})
const updatePermission = usePermissions({
apiGroup,
typeName,
namespace: '',
clusterName: cluster,
verb: 'update',
refetchInterval: false,
})
const deletePermission = usePermissions({
apiGroup,
typeName,
namespace: '',
clusterName: cluster,
verb: 'delete',
refetchInterval: false,
})
const { isPending, error, data } = useApiResources({
clusterName: cluster,
namespace,
apiGroup,
apiVersion,
typeName,
limit,
})
const onDeleteHandle = (name: string, endpoint: string) => {
setIsDeleteModalOpen({ name, endpoint })
}
const clearSelected = () => {
setSelectedRowKeys([])
setSelectedRowsData([])
}
const replaceValuesPartsOfUrls = location.pathname
.split('/')
.reduce<Record<string, string | undefined>>((acc, value, index) => {
acc[index.toString()] = value
return acc
}, {})
return (
<>
{isPending && <Spin />}
{error && <Alert message={`An error has occurred: ${error?.message} `} type="error" />}
<OverflowMaxHeightContainer $maxHeight={height}>
{!error && data && (
<EnrichedTableProvider
key={`/${apiGroup}/${apiVersion}/${typeName}`}
customizationId={`${customizationIdPrefix}/${apiGroup}/${apiVersion}/${typeName}`}
tableMappingsReplaceValues={{
clusterName: params.clusterName,
projectName: params.projectName,
instanceName: params.instanceName,
namespace: params.namespace,
syntheticProject: params.syntheticProject,
entryType: params.entryType,
apiGroup: params.apiGroup,
apiVersion: params.apiVersion,
typeName: params.typeName,
entryName: params.entryName,
apiExtensionVersion: params.apiExtensionVersion,
crdName: params.crdName,
...replaceValuesPartsOfUrls,
}}
cluster={cluster}
theme={theme}
baseprefix={inside ? `${baseprefix}/inside` : baseprefix}
dataItems={data.items}
dataForControls={{
cluster,
syntheticProject: params.syntheticProject,
pathPrefix: 'forms/apis',
typeName,
apiVersion: `${apiGroup}/${apiVersion}`,
backlink: `${baseprefix}${inside ? '/inside' : ''}/${cluster}${namespace ? `/${namespace}` : ''}${
params.syntheticProject ? `/${params.syntheticProject}` : ''
}/api-table/${apiGroup}/${apiVersion}/${typeName}`,
deletePathPrefix: `/api/clusters/${cluster}/k8s/apis`,
onDeleteHandle,
permissions: {
canUpdate: isNamespaced ? true : updatePermission.data?.status.allowed,
canDelete: isNamespaced ? true : deletePermission.data?.status.allowed,
},
}}
selectData={{
selectedRowKeys,
onChange: (selectedRowKeys: React.Key[], selectedRowsData: { name: string; endpoint: string }[]) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRowsData(selectedRowsData)
},
}}
tableProps={{ ...TABLE_PROPS, disablePagination: true }}
// maxHeight={height - 65}
/>
)}
{/* {selectedRowKeys.length > 0 && (
<MarginTopContainer $top={-40}>
<Flex gap={16}>
<Button type="primary" onClick={clearSelected}>
<ClearOutlined />
Clear
</Button>
<Button type="primary" onClick={() => setIsDeleteModalManyOpen(selectedRowsData)}>
<MinusOutlined />
Delete
</Button>
</Flex>
</MarginTopContainer>
)} */}
</OverflowMaxHeightContainer>
<FlexGrow />
<PaddingContainer $padding="4px">
<Flex justify="space-between">
<Button
type="primary"
onClick={() =>
navigate(
`${baseprefix}${inside ? '/inside' : ''}/${cluster}${namespace ? `/${namespace}` : ''}${
params.syntheticProject ? `/${params.syntheticProject}` : ''
}/forms/apis/${apiGroup}/${apiVersion}/${typeName}?backlink=${baseprefix}${
inside ? '/inside' : ''
}/${cluster}${namespace ? `/${namespace}` : ''}${
params.syntheticProject ? `/${params.syntheticProject}` : ''
}/api-table/${apiGroup}/${apiVersion}/${typeName}`,
)
}
loading={isNamespaced ? false : createPermission.isPending}
disabled={isNamespaced ? false : !createPermission.data?.status.allowed}
>
<PlusOutlined />
Add
</Button>
{selectedRowKeys.length > 0 && (
<Flex gap={16}>
<Button type="primary" onClick={clearSelected}>
<ClearOutlined />
Clear
</Button>
<Button type="primary" onClick={() => setIsDeleteModalManyOpen(selectedRowsData)}>
<MinusOutlined />
Delete
</Button>
</Flex>
)}
</Flex>
</PaddingContainer>
{isDeleteModalOpen && (
<DeleteModal
name={isDeleteModalOpen.name}
onClose={() => {
setIsDeleteModalOpen(false)
clearSelected()
}}
endpoint={isDeleteModalOpen.endpoint}
/>
)}
{isDeleteModalManyOpen !== false && (
<DeleteModalMany
data={isDeleteModalManyOpen}
onClose={() => {
setIsDeleteModalManyOpen(false)
clearSelected()
}}
/>
)}
</>
)
}

View File

@@ -1 +0,0 @@
export * from './TableNonCrdInfo'

View File

@@ -3,9 +3,6 @@ export * from './ListInsideClusterAndNs'
export * from './ListInsideAllResources'
export * from './ListInsideCrdsByApiGroup'
export * from './ListInsideApisByApiGroup'
export * from './TableCrdInfo'
export * from './TableNonCrdInfo'
export * from './TableBuiltinInfo'
export * from './Forms'
export * from './Factory'
export * from './Header'

View File

@@ -3,7 +3,7 @@ import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableNonCrdInfo, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { TableApiBuiltin, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
@@ -79,7 +79,8 @@ export const TableApiPage: FC<TTableApiPageProps> = ({ forcedTheme, inside }) =>
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{typeName && apiGroup && apiVersion && (
<TableNonCrdInfo
<TableApiBuiltin
resourceType="api"
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}

View File

@@ -3,7 +3,7 @@ import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableBuiltinInfo, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { TableApiBuiltin, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
@@ -66,7 +66,8 @@ export const TableBuiltinPage: FC<TTableBuiltinPageProps> = ({ forcedTheme, insi
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{typeName && (
<TableBuiltinInfo
<TableApiBuiltin
resourceType="builtin"
namespace={namespace}
typeName={typeName}
limit={searchParams.get('limit')}

View File

@@ -3,13 +3,23 @@ export const getTableCustomizationIdPrefix = ({
instance,
namespace,
inside,
search,
}: {
project?: boolean
instance?: boolean
namespace?: boolean
inside?: boolean
search?: boolean
}): string => {
let result = inside ? 'inside-' : 'stock-'
let result: string
if (inside) {
result = 'inside-'
} else if (search) {
result = 'search-'
} else {
result = 'stock-'
}
if (instance) {
result += 'instance-'