Merge pull request #133 from PRO-Robotech/feature/dev

search fixes
This commit is contained in:
typescreep
2025-09-30 15:21:29 +03:00
committed by GitHub
20 changed files with 214 additions and 113 deletions

2
.env
View File

@@ -31,3 +31,5 @@ VITE_REMOVE_BACKLINK=true
VITE_REMOVE_BACKLINK_TEXT=true
VITE_DOCS_URL=https://in-cloud.io/docs/tech-docs/introduction/
VITE_SEARCH_TABLE_CUSTOMIZATION_PREFIX=stock-

View File

@@ -33,3 +33,5 @@ REMOVE_BACKLINK=
REMOVE_BACKLINK_TEXT=
DOCS_URL=
SEARCH_TABLE_CUSTOMIZATION_PREFIX=

View File

@@ -35,3 +35,4 @@ This app can be configured through environment variables.
| `REMOVE_BACKLINK` | `boolean` | Remove backlink arrow from right-side navigation |
| `REMOVE_BACKLINK_TEXT` | `boolean` | Remove backlink text from right-side navigation |
| `DOCS_URL` | `string` | URL to navigate from question mark |
| `SEARCH_TABLE_CUSTOMIZATION_PREFIX` | `string` | Search tables Customization id prefix |

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.123",
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.124",
"@readme/openapi-parser": "4.0.0",
"@reduxjs/toolkit": "2.2.5",
"@tanstack/react-query": "5.62.2",
@@ -2804,9 +2804,9 @@
}
},
"node_modules/@prorobotech/openapi-k8s-toolkit": {
"version": "0.0.1-alpha.123",
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.123.tgz",
"integrity": "sha512-eTge/8JaNPxlSaOluIdSLSCw3I67b4SUzKNQnevrrErofxnUR16MFEJVRiN+M6StatpVlCleQyfcyYG/0St0Lw==",
"version": "0.0.1-alpha.124",
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.124.tgz",
"integrity": "sha512-/y3lgZKivdK/ra/7/n2HU4LTI7Z/u4GdQ+vwXhUj6839YDqL/cfMSBiZbjATo5U7sC8A1SNKGiuYH++nkxO13Q==",
"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.123",
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.124",
"@readme/openapi-parser": "4.0.0",
"@reduxjs/toolkit": "2.2.5",
"@tanstack/react-query": "5.62.2",

View File

@@ -68,6 +68,11 @@ const REMOVE_BACKLINK_TEXT =
const DOCS_URL = process.env.LOCAL === 'true' ? options?.DOCS_URL : process.env.DOCS_URL
const SEARCH_TABLE_CUSTOMIZATION_PREFIX =
process.env.LOCAL === 'true'
? options?.SEARCH_TABLE_CUSTOMIZATION_PREFIX
: process.env.SEARCH_TABLE_CUSTOMIZATION_PREFIX
const healthcheck = require('express-healthcheck')
const promBundle = require('express-prom-bundle')
@@ -192,6 +197,7 @@ app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => {
LOGOUT_URL: ${JSON.stringify(LOGOUT_URL) || '"check envs"'},
LOGIN_USERNAME_FIELD: ${JSON.stringify(LOGIN_USERNAME_FIELD) || '"check envs"'},
DOCS_URL: ${JSON.stringify(DOCS_URL) || '"/docs"'},
SEARCH_TABLE_CUSTOMIZATION_PREFIX: ${JSON.stringify(SEARCH_TABLE_CUSTOMIZATION_PREFIX) || '"search-"'},
REMOVE_BACKLINK: ${!!REMOVE_BACKLINK ? JSON.stringify(REMOVE_BACKLINK).toLowerCase() : '"false"'},
REMOVE_BACKLINK_TEXT: ${!!REMOVE_BACKLINK_TEXT ? JSON.stringify(REMOVE_BACKLINK_TEXT).toLowerCase() : '"false"'}
}

View File

@@ -35,7 +35,6 @@ type TTableApiBuiltinProps = {
apiGroup?: string // api
apiVersion?: string // api
typeName: string
specificName?: string
labels?: string[]
fields?: string[]
limit: string | null
@@ -51,7 +50,6 @@ export const TableApiBuiltin: FC<TTableApiBuiltinProps> = ({
apiGroup,
apiVersion,
typeName,
specificName,
labels,
fields,
limit,
@@ -159,7 +157,6 @@ export const TableApiBuiltin: FC<TTableApiBuiltinProps> = ({
clusterName: cluster,
namespace,
typeName,
specificName,
labels,
fields,
limit,
@@ -176,7 +173,6 @@ export const TableApiBuiltin: FC<TTableApiBuiltinProps> = ({
apiGroup: apiGroup || '',
apiVersion: apiVersion || '',
typeName,
specificName,
labels,
fields,
limit,
@@ -239,7 +235,7 @@ export const TableApiBuiltin: FC<TTableApiBuiltinProps> = ({
cluster={cluster}
theme={theme}
baseprefix={inside ? `${baseprefix}/inside` : baseprefix}
dataItems={getDataItems({ resourceType, dataBuiltin, dataApi, isSingle: !!specificName })}
dataItems={getDataItems({ resourceType, dataBuiltin, dataApi })}
dataForControls={{
cluster,
syntheticProject: params.syntheticProject,

View File

@@ -4,22 +4,11 @@ export const getDataItems = ({
resourceType,
dataBuiltin,
dataApi,
isSingle,
}: {
resourceType: 'builtin' | 'api'
dataBuiltin?: TBuiltinResources
dataApi?: TApiResources
isSingle?: boolean
}): TJSON[] => {
if (isSingle) {
if (resourceType === 'builtin') {
return dataBuiltin ? [dataBuiltin] : []
}
if (resourceType === 'api') {
return dataApi ? [dataApi] : []
}
}
return resourceType === 'builtin' ? dataBuiltin?.items || [] : dataApi?.items || []
}

View File

@@ -8,9 +8,10 @@ import { Styled } from './styled'
type THeaderProps = {
inside?: boolean
isSearch?: boolean
}
export const HeaderSecond: FC<THeaderProps> = ({ inside }) => {
export const HeaderSecond: FC<THeaderProps> = ({ inside, isSearch }) => {
// const { projectName, instanceName, clusterName, entryType, namespace, syntheticProject } = useParams()
const { projectName, instanceName, clusterName, namespace, syntheticProject } = useParams()
const { token } = theme.useToken()
@@ -24,14 +25,14 @@ export const HeaderSecond: FC<THeaderProps> = ({ inside }) => {
<Flex gap={18}>
{inside ? <SelectorClusterInside clusterName={clusterName} /> : <SelectorCluster clusterName={clusterName} />}
{inside && <SelectorInside clusterName={clusterName} namespace={namespace} />}
{!inside && BASE_USE_NAMESPACE_NAV !== 'true' && (
{!inside && !isSearch && BASE_USE_NAMESPACE_NAV !== 'true' && (
<Selector
clusterName={clusterName}
projectName={projectName || possibleProject}
instanceName={instanceName || possibleInstance}
/>
)}
{!inside && BASE_USE_NAMESPACE_NAV === 'true' && (
{!inside && (isSearch || BASE_USE_NAMESPACE_NAV === 'true') && (
<SelectorNamespace clusterName={clusterName} namespace={namespace} />
)}
</Flex>

View File

@@ -1,9 +1,12 @@
import React, { FC, useState } from 'react'
import { Flex, Typography } from 'antd'
import { useNavigate } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'
import { useDirectUnknownResource } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { useNavSelectorInside } from 'hooks/useNavSelectorInside'
import { useMountEffect } from 'hooks/useMountEffect'
import { useIsSearchPage } from 'hooks/useIsSearchPage'
import { EntrySelect } from 'components/atoms'
import {
BASE_API_GROUP,
@@ -19,6 +22,9 @@ type TSelectorNamespaceProps = {
export const SelectorNamespace: FC<TSelectorNamespaceProps> = ({ clusterName, namespace }) => {
const navigate = useNavigate()
const location = useLocation()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const [selectedClusterName, setSelectedClusterName] = useState(clusterName)
const [selectedNamespace, setSelectedNamespace] = useState(namespace)
@@ -34,7 +40,45 @@ export const SelectorNamespace: FC<TSelectorNamespaceProps> = ({ clusterName, na
isEnabled: clusterName !== undefined,
})
const isSearchPage = useIsSearchPage(baseprefix || '')
const handleNamepsaceChange = (value?: string) => {
if (isSearchPage) {
const { pathname, search, hash } = location
const segs = pathname.split('/')
// Assume pattern: /prefix/:clusterName/:namespace?/:syntheticProject?/search/*
// Find the "search" segment index
const searchIdx = segs.indexOf('search')
const clusterIdx = segs.indexOf(selectedClusterName || '')
if (clusterIdx === -1) {
return
} // bail if we can't find the cluster
const nsIdx = clusterIdx + 1 // where namespace would live if present
const spIdx = clusterIdx + 2 // where syntheticProject would live if present
const nsExists = nsIdx < searchIdx // true if something occupies ns slot
const spExists = spIdx < searchIdx // true if something occupies sp slot
if (value && value !== 'all') {
setSelectedNamespace(value)
if (nsExists) {
// replace namespace in place
segs[nsIdx] = value
} else {
// insert namespace before "search" (or before syntheticProject if present)
const insertAt = spExists ? spIdx : searchIdx
segs.splice(insertAt, 0, value)
}
} else if (nsExists) {
segs.splice(nsIdx, 1) // removes namespace; syntheticProject (if any) shifts left
}
// if ns didn't exist, nothing to clear
navigate(segs.join('/') + search + hash, { replace: true })
return
}
if (value && value !== 'all') {
setSelectedNamespace(value)
const changeUrl =

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-lines-per-function */
import React, { FC, Fragment, useState, useEffect } from 'react'
import React, { FC, Fragment, useState, useEffect, useLayoutEffect, useRef } from 'react'
import { useLocation, useSearchParams } from 'react-router-dom'
import {
Search as PackageSearch,
@@ -9,7 +9,6 @@ import {
TKindWithVersion,
getKinds,
getSortedKinds,
// kindByGvr,
} from '@prorobotech/openapi-k8s-toolkit'
import { ConfigProvider, theme as antdtheme, Form, Spin, Alert } from 'antd'
import { useSelector } from 'react-redux'
@@ -20,13 +19,22 @@ import {
FIELD_NAME_STRING,
FIELD_NAME_LABELS,
FIELD_NAME_FIELDS,
TYPE_SELECTOR,
QUERY_KEY,
NAME_QUERY_KEY,
LABELS_QUERY_KEY,
FIELDS_QUERY_KEY,
TYPE_SELECTOR,
TYPE_QUERY_KEY,
} from './constants'
import { useDebouncedCallback, getArrayParam, setArrayParam, getStringParam, setStringParam } from './utils'
import {
useDebouncedCallback,
getArrayParam,
setArrayParam,
getStringParam,
setStringParam,
getTypeParam,
setTypeParam,
} from './utils'
import { SearchEntry } from './molecules'
import { Styled } from './styled'
@@ -67,9 +75,7 @@ export const Search: FC = () => {
useEffect(() => {
setIsLoading(true)
setError(undefined)
getKinds({
clusterName: cluster,
})
getKinds({ clusterName: cluster })
.then(data => {
setKindIndex(data)
setKindWithVersion(getSortedKinds(data))
@@ -88,59 +94,79 @@ export const Search: FC = () => {
const watchedFields = Form.useWatch<string[] | undefined>(FIELD_NAME_FIELDS, form)
const watchedTypedSelector = Form.useWatch<string | undefined>(TYPE_SELECTOR, form)
// Apply current values from search params on mount / when URL changes
// —— hydration control to prevent “push empties” on first render ——
const isHydratingRef = useRef(true)
// First, synchronously hydrate form from URL so watchers arent undefined on paint
useLayoutEffect(() => {
const fromKinds = getArrayParam(searchParams, QUERY_KEY)
const fromName = getStringParam(searchParams, NAME_QUERY_KEY)
const fromLabels = getArrayParam(searchParams, LABELS_QUERY_KEY)
const fromFields = getArrayParam(searchParams, FIELDS_QUERY_KEY)
const explicitType = getTypeParam(searchParams, TYPE_QUERY_KEY)
const inferredFromValues =
(fromFields.length > 0 && 'fields') || (fromLabels.length > 0 && 'labels') || (fromName ? 'name' : undefined)
const nextType = explicitType ?? inferredFromValues
form.setFieldsValue({
[FIELD_NAME]: fromKinds,
[FIELD_NAME_STRING]: fromName,
[FIELD_NAME_LABELS]: fromLabels,
[FIELD_NAME_FIELDS]: fromFields,
[TYPE_SELECTOR]: nextType,
})
isHydratingRef.current = false
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// keep form in sync if URL changes later (back/forward/external edits) ——
useEffect(() => {
if (isHydratingRef.current) return
const fromKinds = getArrayParam(searchParams, QUERY_KEY)
const currentKinds = form.getFieldValue(FIELD_NAME)
const kindsDiffer =
(fromKinds.length || 0) !== (currentKinds?.length || 0) || fromKinds.some((v, i) => v !== currentKinds?.[i])
// name
const fromName = getStringParam(searchParams, NAME_QUERY_KEY)
const currentName = form.getFieldValue(FIELD_NAME_STRING) as string | undefined
const currentName = form.getFieldValue(FIELD_NAME_STRING)
const nameDiffer = (fromName || '') !== (currentName || '')
// labels
const fromLabels = getArrayParam(searchParams, LABELS_QUERY_KEY)
const currentLabels = form.getFieldValue(FIELD_NAME_LABELS) as string[] | undefined
const currentLabels = form.getFieldValue(FIELD_NAME_LABELS)
const labelsDiffer =
(fromLabels.length || 0) !== (currentLabels?.length || 0) || fromLabels.some((v, i) => v !== currentLabels?.[i])
// labels
const fromFields = getArrayParam(searchParams, FIELDS_QUERY_KEY)
const currentFields = form.getFieldValue(FIELD_NAME_FIELDS) as string[] | undefined
const currentFields = form.getFieldValue(FIELD_NAME_FIELDS)
const fieldsDiffer =
(fromFields.length || 0) !== (currentFields?.length || 0) || fromFields.some((v, i) => v !== currentFields?.[i])
// decide type from params
const explicitType = getTypeParam(searchParams, TYPE_QUERY_KEY)
const currentType = form.getFieldValue(TYPE_SELECTOR)
let inferredType: string | undefined
if (fromName) {
inferredType = 'name'
} else if (fromLabels.length > 0) {
inferredType = 'labels'
} else if (fromFields.length > 0) {
inferredType = 'fields'
}
const typeDiffer = inferredType !== currentType
const inferredFromValues =
(fromFields.length > 0 && 'fields') || (fromLabels.length > 0 && 'labels') || (fromName ? 'name' : undefined)
const nextType = explicitType ?? currentType ?? inferredFromValues
const typeDiffer = nextType !== currentType
// Only update the form if URL differs from form (prevents loops)
if (kindsDiffer || nameDiffer || labelsDiffer || fieldsDiffer) {
if (kindsDiffer || nameDiffer || labelsDiffer || fieldsDiffer || typeDiffer) {
form.setFieldsValue({
[FIELD_NAME]: kindsDiffer ? fromKinds : currentKinds,
[FIELD_NAME_STRING]: nameDiffer ? fromName : currentName,
[FIELD_NAME_LABELS]: labelsDiffer ? fromLabels : currentLabels,
[FIELD_NAME_FIELDS]: fieldsDiffer ? fromFields : currentFields,
[TYPE_SELECTOR]: typeDiffer ? inferredType : currentType,
...(typeDiffer ? { [TYPE_SELECTOR]: nextType } : {}),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.search]) // react to back/forward, external URL edits
}, [location.search])
// Watch field changes to push to URL (debounced)
const debouncedPush = useDebouncedCallback((values: string[]) => {
// debounced URL pushers (guarded against hydration & undefined) ——
const debouncedPushKinds = useDebouncedCallback((values: string[]) => {
const next = setArrayParam(searchParams, QUERY_KEY, values)
setSearchParams(next, { replace: true }) // replace to keep history cleaner
setSearchParams(next, { replace: true })
}, 250)
const debouncedPushName = useDebouncedCallback((value: string) => {
@@ -159,47 +185,43 @@ export const Search: FC = () => {
}, 250)
useEffect(() => {
debouncedPush(watchedKinds || [])
if (isHydratingRef.current || watchedKinds === undefined) {
return
}
debouncedPushKinds(watchedKinds)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedKinds])
useEffect(() => {
debouncedPushName((watchedName || '').trim())
if (isHydratingRef.current || watchedName === undefined) {
return
}
debouncedPushName(watchedName.trim())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedName])
useEffect(() => {
if (isHydratingRef.current || watchedLabels === undefined) {
return
}
debouncedPushLabels(watchedLabels || [])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedLabels])
useEffect(() => {
if (isHydratingRef.current || watchedFields === undefined) {
return
}
debouncedPushFields(watchedFields || [])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedFields])
useEffect(() => {
if (watchedTypedSelector === 'name') {
// Clear labels when switching to "name"
// const cur = form.getFieldValue(FIELD_NAME_LABELS) as string[] | undefined
// if (cur?.length) {
form.setFieldsValue({ [FIELD_NAME_LABELS]: [], [FIELD_NAME_FIELDS]: [] })
// }
} else if (watchedTypedSelector === 'labels') {
// Clear name when switching to "labels"
// const cur = (form.getFieldValue(FIELD_NAME_STRING) as string | undefined) ?? ''
// if (cur) {
form.setFieldsValue({ [FIELD_NAME_STRING]: '', [FIELD_NAME_FIELDS]: [] })
// }
} else if (watchedTypedSelector === 'fields') {
// Clear name when switching to "labels"
// const cur = (form.getFieldValue(FIELD_NAME_STRING) as string | undefined) ?? ''
// if (cur) {
form.setFieldsValue({ [FIELD_NAME_STRING]: '', [FIELD_NAME_LABELS]: [] })
// }
if (isHydratingRef.current || watchedTypedSelector === undefined) {
return
}
// Optional: if undefined (e.g., initial), choose a default behavior:
// else { form.setFieldsValue({ [FIELD_NAME_STRING]: '', [FIELD_NAME_MULTIPLE]: [] }) }
const next = setTypeParam(searchParams, TYPE_QUERY_KEY, watchedTypedSelector)
setSearchParams(next, { replace: true })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedTypedSelector])
@@ -240,22 +262,24 @@ export const Search: FC = () => {
},
}}
>
{watchedKinds?.map(item => (
<Fragment key={item}>
<Spacer $space={20} $samespace />
<SearchEntry
kindsWithVersion={kindsWithVersion}
form={form}
constants={{
FIELD_NAME,
}}
resource={item}
name={watchedName}
labels={watchedLabels}
fields={watchedFields}
/>
</Fragment>
))}
{watchedKinds?.map(item => {
const fields = [...(watchedFields || []), ...(watchedName ? [`metadata.name=${watchedName}`] : [])]
return (
<Fragment key={item}>
<Spacer $space={20} $samespace />
<SearchEntry
kindsWithVersion={kindsWithVersion}
form={form}
constants={{
FIELD_NAME,
}}
resource={item}
labels={watchedLabels}
fields={fields.length ? fields : undefined}
/>
</Fragment>
)
})}
</ConfigProvider>
<Spacer $space={20} $samespace />
</Styled.OverflowContainer>

View File

@@ -3,9 +3,10 @@ export const FIELD_NAME_STRING = 'name'
export const FIELD_NAME_LABELS = 'labels'
export const FIELD_NAME_FIELDS = 'fields'
export const TYPE_SELECTOR = 'TYPE_SELECTOR'
export const QUERY_KEY = 'kinds' // the query param name
export const NAME_QUERY_KEY = 'name'
export const LABELS_QUERY_KEY = 'labels'
export const FIELDS_QUERY_KEY = 'fields'
export const TYPE_SELECTOR = 'TYPE_SELECTOR'
export const TYPE_QUERY_KEY = 'type'

View File

@@ -3,6 +3,7 @@ import { useParams, useSearchParams } from 'react-router-dom'
import {
TKindWithVersion,
kindByGvr,
namespacedByGvr,
getUppercase,
hslFromString,
Spacer,
@@ -19,7 +20,6 @@ import { Styled } from './styled'
type TSearchEntryProps = {
resource: string
name?: string
labels?: string[]
fields?: string[]
form: FormInstance
@@ -29,15 +29,7 @@ type TSearchEntryProps = {
kindsWithVersion: TKindWithVersion[]
}
export const SearchEntry: FC<TSearchEntryProps> = ({
resource,
name,
labels,
fields,
form,
constants,
kindsWithVersion,
}) => {
export const SearchEntry: FC<TSearchEntryProps> = ({ resource, labels, fields, form, constants, kindsWithVersion }) => {
const { namespace, syntheticProject } = useParams()
const [searchParams] = useSearchParams()
const { token } = antdtheme.useToken()
@@ -54,6 +46,8 @@ export const SearchEntry: FC<TSearchEntryProps> = ({
const abbr = getUppercase(kindName && kindName.length ? kindName : 'Loading')
const bgColor = kindName && kindName.length ? hslFromString(abbr, theme) : ''
const isNamespaceResource = namespacedByGvr(kindsWithVersion)(resource)
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
@@ -92,11 +86,10 @@ export const SearchEntry: FC<TSearchEntryProps> = ({
{typeName && (
<TableApiBuiltin
resourceType={apiGroup.length > 0 ? 'api' : 'builtin'}
namespace={namespace}
namespace={isNamespaceResource ? namespace : undefined}
apiGroup={apiGroup.length > 0 ? apiGroup : undefined}
apiVersion={apiGroup.length > 0 ? apiVersion : undefined}
typeName={typeName}
specificName={name?.length ? name : undefined}
labels={labels?.length ? labels : undefined}
fields={fields?.length ? fields : undefined}
limit={searchParams.get('limit')}

View File

@@ -40,3 +40,20 @@ export const setStringParam = (sp: URLSearchParams, key: string, value: string |
else next.set(key, v)
return next
}
export const getTypeParam = (sp: URLSearchParams, key: string): 'name' | 'labels' | 'fields' | undefined => {
const v = sp.get(key)?.trim()
if (v === 'name' || v === 'labels' || v === 'fields') return v
return undefined
}
export const setTypeParam = (sp: URLSearchParams, key: string, value: string | undefined | null) => {
const next = new URLSearchParams(sp)
const v = (value ?? '').trim()
if (v !== 'name' && v !== 'labels' && v !== 'fields') {
next.delete(key)
} else {
next.set(key, v)
}
return next
}

View File

@@ -66,6 +66,10 @@ export const DOCS_URL = import.meta.env.DEV
? window._env_.DOCS_URL || import.meta.env.VITE_DOCS_URL
: window._env_.DOCS_URL
export const SEARCH_TABLE_CUSTOMIZATION_PREFIX = import.meta.env.DEV
? window._env_.SEARCH_TABLE_CUSTOMIZATION_PREFIX || import.meta.env.VITE_SEARCH_TABLE_CUSTOMIZATION_PREFIX
: window._env_.SEARCH_TABLE_CUSTOMIZATION_PREFIX
export const BASE_REMOVE_BACKLINK = import.meta.env.DEV
? window._env_.REMOVE_BACKLINK === 'true' || import.meta.env.VITE_REMOVE_BACKLINK?.toString().toLowerCase() === 'true'
: window._env_.REMOVE_BACKLINK === 'true'

View File

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

View File

@@ -0,0 +1,9 @@
import { useLocation, matchPath } from 'react-router-dom'
export const useIsSearchPage = (prefix: string) => {
const { pathname } = useLocation()
const base = `/${prefix}`.replace(/\/{2,}/g, '/').replace(/\/$/, '')
const pattern = `${base}/:clusterName/:namespace?/:syntheticProject?/search/*`
return Boolean(matchPath({ path: pattern }, pathname))
}

View File

@@ -16,16 +16,17 @@ export const SearchPage: FC<TSearchPageProps> = ({ forcedTheme }) => {
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace })}seach-page`
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace })}search-page`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
})}seach-page`
})}search-page`
return (
<BaseTemplate
forcedTheme={forcedTheme}
inside={false}
isSearch
sidebar={
<ManageableSidebar
instanceName={possibleInstance}

View File

@@ -24,10 +24,18 @@ type TBaseTemplateProps = {
children?: ReactNode | undefined
forcedTheme?: 'dark' | 'light'
inside?: boolean
isSearch?: boolean
sidebar?: ReactNode
}
export const BaseTemplate: FC<TBaseTemplateProps> = ({ children, withNoCluster, forcedTheme, inside, sidebar }) => {
export const BaseTemplate: FC<TBaseTemplateProps> = ({
children,
withNoCluster,
forcedTheme,
inside,
isSearch,
sidebar,
}) => {
const navigate = useNavigate()
const { clusterName } = useParams()
const { useToken } = antdtheme
@@ -106,7 +114,7 @@ export const BaseTemplate: FC<TBaseTemplateProps> = ({ children, withNoCluster,
</Col>
<FlexCol flex="auto">
<DefaultLayout.ContentPadding $isFederation={isFederation}>
<HeaderSecond inside={inside} />
<HeaderSecond inside={inside} isSearch={isSearch} />
{clusterListQuery.error && (
<Alert message={`Cluster List Error: ${clusterListQuery.error?.message} `} type="error" />
)}

View File

@@ -1,3 +1,5 @@
import { SEARCH_TABLE_CUSTOMIZATION_PREFIX } from 'constants/customizationApiGroupAndVersion'
export const getTableCustomizationIdPrefix = ({
project,
instance,
@@ -16,7 +18,7 @@ export const getTableCustomizationIdPrefix = ({
if (inside) {
result = 'inside-'
} else if (search) {
result = 'search-'
result = SEARCH_TABLE_CUSTOMIZATION_PREFIX
} else {
result = 'stock-'
}