From cfa180aca78eff981ee8f0f9ce31cad8eb30a798 Mon Sep 17 00:00:00 2001 From: typescreep Date: Tue, 23 Sep 2025 20:47:59 +0300 Subject: [PATCH 01/23] search filters --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7061421..99ab4bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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.121", + "@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.122", "@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.121", - "resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.121.tgz", - "integrity": "sha512-lH693F4RGC8QT5GymDkyY4NHQgDGW67ncTbbT94r/U7ZdHWoME6+z17XNThEMi65OnaMES2PpbF++hEI/Um+IQ==", + "version": "0.0.1-alpha.122", + "resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.122.tgz", + "integrity": "sha512-UsBl2jGTs3ZDCZLVm1+kdcuy6FR9W+F5Rg25Vfufud1sDMtmhCdfiDETmPCj0ClovZGm6V8gFXB8gUQtCgNQHQ==", "license": "MIT", "dependencies": { "@monaco-editor/react": "4.6.0", diff --git a/package.json b/package.json index fef1af0..b34f895 100644 --- a/package.json +++ b/package.json @@ -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.121", + "@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.122", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", From 2a603a6dbedbd9decadc526340e32a26504e97c0 Mon Sep 17 00:00:00 2001 From: typescreep Date: Thu, 25 Sep 2025 17:19:51 +0300 Subject: [PATCH 02/23] new logic --- src/components/organisms/Search/Search.tsx | 220 +++++++++++++++++-- src/components/organisms/Search/constants.ts | 11 + src/components/organisms/Search/utils.ts | 42 ++++ 3 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 src/components/organisms/Search/constants.ts create mode 100644 src/components/organisms/Search/utils.ts diff --git a/src/components/organisms/Search/Search.tsx b/src/components/organisms/Search/Search.tsx index d58114b..b99d973 100644 --- a/src/components/organisms/Search/Search.tsx +++ b/src/components/organisms/Search/Search.tsx @@ -1,31 +1,217 @@ /* eslint-disable max-lines-per-function */ -import React, { FC, Fragment, useState } from 'react' -import { Search as PackageSearch, Spacer } from '@prorobotech/openapi-k8s-toolkit' +import React, { FC, Fragment, useState, useEffect } from 'react' +import { useLocation, useSearchParams } from 'react-router-dom' +import { + Search as PackageSearch, + Spacer, + TRequestError, + TKindIndex, + TKindWithVersion, + getKinds, + getSortedKinds, + // kindByGvr, +} from '@prorobotech/openapi-k8s-toolkit' +import { Form, Spin, Alert } from 'antd' import { useSelector } from 'react-redux' import { RootState } from 'store/store' +import { + FIELD_NAME, + FIELD_NAME_STRING, + FIELD_NAME_LABELS, + FIELD_NAME_FIELDS, + TYPE_SELECTOR, + QUERY_KEY, + NAME_QUERY_KEY, + LABELS_QUERY_KEY, + FIELDS_QUERY_KEY, +} from './constants' +import { useDebouncedCallback, getArrayParam, setArrayParam, getStringParam, setStringParam } from './utils' import { SearchEntry } from './molecules' export const Search: FC = () => { - const cluster = useSelector((state: RootState) => state.cluster.cluster) + const [searchParams, setSearchParams] = useSearchParams() + const location = useLocation() - const [currentSearch, setCurrentSearch] = useState<{ - resources?: string[] - name?: string - labels?: string[] - fields?: string[] - }>() + const cluster = useSelector((state: RootState) => state.cluster.cluster) + const theme = useSelector((state: RootState) => state.openapiTheme.theme) + + const [form] = Form.useForm() + + const [error, setError] = useState() + const [isLoading, setIsLoading] = useState(false) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [kindIndex, setKindIndex] = useState() + const [kindsWithVersion, setKindWithVersion] = useState() + + useEffect(() => { + setIsLoading(true) + setError(undefined) + getKinds({ + clusterName: cluster, + }) + .then(data => { + setKindIndex(data) + setKindWithVersion(getSortedKinds(data)) + setIsLoading(false) + setError(undefined) + }) + .catch(error => { + setIsLoading(false) + setError(error) + }) + }, [cluster]) + + const watchedKinds = Form.useWatch(FIELD_NAME, form) + const watchedName = Form.useWatch(FIELD_NAME_STRING, form) + const watchedLabels = Form.useWatch(FIELD_NAME_LABELS, form) + const watchedFields = Form.useWatch(FIELD_NAME_FIELDS, form) + const watchedTypedSelector = Form.useWatch(TYPE_SELECTOR, form) + + // Apply current values from search params on mount / when URL changes + useEffect(() => { + 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 nameDiffer = (fromName || '') !== (currentName || '') + + // labels + const fromLabels = getArrayParam(searchParams, LABELS_QUERY_KEY) + const currentLabels = form.getFieldValue(FIELD_NAME_LABELS) as string[] | undefined + 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 fieldsDiffer = + (fromFields.length || 0) !== (currentFields?.length || 0) || fromFields.some((v, i) => v !== currentFields?.[i]) + + // decide type from params + 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 + + // Only update the form if URL differs from form (prevents loops) + if (kindsDiffer || nameDiffer || labelsDiffer || fieldsDiffer) { + 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, + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location.search]) // react to back/forward, external URL edits + + // Watch field changes to push to URL (debounced) + const debouncedPush = useDebouncedCallback((values: string[]) => { + const next = setArrayParam(searchParams, QUERY_KEY, values) + setSearchParams(next, { replace: true }) // replace to keep history cleaner + }, 250) + + const debouncedPushName = useDebouncedCallback((value: string) => { + const next = setStringParam(searchParams, NAME_QUERY_KEY, value) + setSearchParams(next, { replace: true }) + }, 250) + + const debouncedPushLabels = useDebouncedCallback((values: string[]) => { + const next = setArrayParam(searchParams, LABELS_QUERY_KEY, values) + setSearchParams(next, { replace: true }) + }, 250) + + const debouncedPushFields = useDebouncedCallback((values: string[]) => { + const next = setArrayParam(searchParams, FIELDS_QUERY_KEY, values) + setSearchParams(next, { replace: true }) + }, 250) + + useEffect(() => { + debouncedPush(watchedKinds || []) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watchedKinds]) + + useEffect(() => { + debouncedPushName((watchedName || '').trim()) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watchedName]) + + useEffect(() => { + debouncedPushLabels(watchedLabels || []) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watchedLabels]) + + useEffect(() => { + 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]: [] }) + // } + } + // Optional: if undefined (e.g., initial), choose a default behavior: + // else { form.setFieldsValue({ [FIELD_NAME_STRING]: '', [FIELD_NAME_MULTIPLE]: [] }) } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watchedTypedSelector]) + + if (error) { + return + } + + if (isLoading || !kindsWithVersion) { + return + } + + if (!kindsWithVersion) { + return + } return ( <> - setCurrentSearch(value)} /> - {currentSearch?.resources?.map(item => ( + + {watchedKinds?.map(item => ( - + ))} diff --git a/src/components/organisms/Search/constants.ts b/src/components/organisms/Search/constants.ts new file mode 100644 index 0000000..021c83e --- /dev/null +++ b/src/components/organisms/Search/constants.ts @@ -0,0 +1,11 @@ +export const FIELD_NAME = 'kinds' +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' diff --git a/src/components/organisms/Search/utils.ts b/src/components/organisms/Search/utils.ts new file mode 100644 index 0000000..bcc977f --- /dev/null +++ b/src/components/organisms/Search/utils.ts @@ -0,0 +1,42 @@ +import { useRef } from 'react' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const useDebouncedCallback = void>(fn: T, delay = 300) => { + const timer = useRef(undefined) + return (...args: Parameters) => { + if (timer.current) window.clearTimeout(timer.current) + timer.current = window.setTimeout(() => fn(...args), delay) + } +} + +// Convert between array and a single comma-separated query param. +export const getArrayParam = (sp: URLSearchParams, key: string): string[] => { + const raw = sp.get(key) + if (!raw) return [] + return raw + .split(',') + .map(s => s.trim()) + .filter(Boolean) +} + +export const setArrayParam = (sp: URLSearchParams, key: string, values: string[] | undefined | null) => { + const next = new URLSearchParams(sp) // preserve other params + if (!values || values.length === 0) { + next.delete(key) + } else { + next.set(key, values.join(',')) + } + return next +} + +export const getStringParam = (sp: URLSearchParams, key: string): string => { + return sp.get(key) ?? '' +} + +export const setStringParam = (sp: URLSearchParams, key: string, value: string | undefined | null) => { + const next = new URLSearchParams(sp) // preserve other params + const v = (value ?? '').trim() + if (!v) next.delete(key) + else next.set(key, v) + return next +} From 371e9ee80b5a27b4d22a431cb91ff3c578b47c6e Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 15:53:33 +0300 Subject: [PATCH 03/23] search design --- package-lock.json | 97 ++++++++------- package.json | 2 +- .../TableApiBuiltin/TableApiBuiltin.tsx | 88 +++++++------- .../molecules/TableApiBuiltin/utils.ts | 40 ++++++- src/components/organisms/Search/Search.tsx | 88 ++++++++++---- .../molecules/SearchEntry/SearchEntry.tsx | 110 +++++++++++++----- .../Search/molecules/SearchEntry/styled.ts | 39 +++++++ src/components/organisms/Search/styled.ts | 19 +++ 8 files changed, 342 insertions(+), 141 deletions(-) create mode 100644 src/components/organisms/Search/molecules/SearchEntry/styled.ts create mode 100644 src/components/organisms/Search/styled.ts diff --git a/package-lock.json b/package-lock.json index 99ab4bf..e6c683a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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.122", + "@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.123", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", @@ -146,6 +146,7 @@ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.0.tgz", "integrity": "sha512-Mb6QkQmPLZsmIHJ6oBsoyKrrT8/kAUdQ6+8q38e2bQSclROi69SiDlI4zZroaIPseae1w110RJH0zGrphAvlSQ==", "license": "MIT", + "peer": true, "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", @@ -257,6 +258,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1857,7 +1859,6 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.1.90" } @@ -1878,6 +1879,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1901,6 +1903,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1957,7 +1960,6 @@ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", "license": "MIT", - "peer": true, "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -2802,9 +2804,9 @@ } }, "node_modules/@prorobotech/openapi-k8s-toolkit": { - "version": "0.0.1-alpha.122", - "resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.122.tgz", - "integrity": "sha512-UsBl2jGTs3ZDCZLVm1+kdcuy6FR9W+F5Rg25Vfufud1sDMtmhCdfiDETmPCj0ClovZGm6V8gFXB8gUQtCgNQHQ==", + "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==", "license": "MIT", "dependencies": { "@monaco-editor/react": "4.6.0", @@ -3709,6 +3711,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.2.tgz", "integrity": "sha512-fkTpKKfwTJtVPKVR+ag7YqFgG/7TRVVPzduPAUF9zRCiiA8Wu305u+KJl8rCrh98Qce77vzIakvtUyzWLtaPGA==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.62.2" }, @@ -3725,6 +3728,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.62.2.tgz", "integrity": "sha512-s4+88OZ6ygD4ziNfUgh9y1XxsGqpscI77c8EaLP7KwEfa5WqnlB9MT/uslFkFq3vwb8JhMjB7Osv2MYrSMry6w==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-devtools": "5.61.4" }, @@ -3859,6 +3863,7 @@ "integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3948,8 +3953,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", @@ -3997,7 +4001,6 @@ "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", @@ -4023,7 +4026,6 @@ "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" @@ -4042,7 +4044,6 @@ "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" @@ -4061,7 +4062,6 @@ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4188,7 +4188,6 @@ "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4203,7 +4202,6 @@ "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", @@ -4231,7 +4229,6 @@ "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" @@ -4250,7 +4247,6 @@ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4264,7 +4260,6 @@ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -4278,7 +4273,6 @@ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18.12" }, @@ -4439,7 +4433,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/accepts": { "version": "1.3.8", @@ -4460,6 +4455,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4482,6 +4478,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4538,6 +4535,7 @@ "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.4.tgz", "integrity": "sha512-e1EnOvEkvvqcQ18dxfzChBJyJACyih13WpNf2OtnP9z2POh/SF0fXL+ynUemT1zfr+p+P1po/tmHXaMc5PMghg==", "license": "MIT", + "peer": true, "dependencies": { "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^1.23.0", @@ -4855,8 +4853,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/async-function": { "version": "1.0.0", @@ -5259,6 +5256,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -5508,7 +5506,6 @@ "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" @@ -5538,7 +5535,6 @@ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "license": "MIT", - "peer": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -5549,7 +5545,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "license": "MIT", - "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -5558,8 +5553,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/colord": { "version": "2.9.3", @@ -5572,7 +5566,6 @@ "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", "license": "MIT", - "peer": true, "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" @@ -5970,7 +5963,8 @@ "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "4.4.1", @@ -6200,8 +6194,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -6570,6 +6563,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6731,6 +6725,7 @@ "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.findlastindex": "^1.2.2", @@ -6810,6 +6805,7 @@ "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.7", "aria-query": "^5.1.3", @@ -6895,6 +6891,7 @@ "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", @@ -6926,6 +6923,7 @@ "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -7503,8 +7501,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "6.0.1", @@ -7607,8 +7604,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.9", @@ -8744,7 +8740,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -9035,8 +9030,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/language-subtag-registry": { "version": "0.3.23", @@ -9132,7 +9126,6 @@ "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", @@ -9678,7 +9671,6 @@ "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", - "peer": true, "dependencies": { "fn.name": "1.x.x" } @@ -9687,7 +9679,8 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/optionator": { "version": "0.9.4", @@ -9973,6 +9966,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -10022,6 +10016,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -10093,6 +10088,7 @@ "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10138,6 +10134,7 @@ "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" @@ -10914,6 +10911,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10926,6 +10924,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -10984,6 +10983,7 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", "license": "MIT", + "peer": true, "dependencies": { "@remix-run/router": "1.18.0", "react-router": "6.25.1" @@ -11027,7 +11027,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -11259,6 +11260,7 @@ "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -11397,7 +11399,6 @@ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -11682,7 +11683,6 @@ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "license": "MIT", - "peer": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -11691,8 +11691,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/slash": { "version": "2.0.0", @@ -11758,7 +11757,6 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -12283,8 +12281,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/text-table": { "version": "0.2.0", @@ -12347,7 +12344,6 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 14.0.0" } @@ -12977,6 +12973,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13213,6 +13210,7 @@ "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -13464,7 +13462,6 @@ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", - "peer": true, "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", diff --git a/package.json b/package.json index b34f895..495858c 100644 --- a/package.json +++ b/package.json @@ -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.122", + "@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.123", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", diff --git a/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx b/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx index fd232a3..987fb40 100644 --- a/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx +++ b/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx @@ -14,6 +14,7 @@ import { checkIfApiInstanceNamespaceScoped, useBuiltinResources, useApiResources, + Spacer, } from '@prorobotech/openapi-k8s-toolkit' import { FlexGrow, PaddingContainer } from 'components' import { TABLE_PROPS } from 'constants/tableProps' @@ -41,6 +42,7 @@ type TTableApiBuiltinProps = { inside?: boolean customizationIdPrefix: string searchMount?: boolean + kindName?: string } export const TableApiBuiltin: FC = ({ @@ -56,6 +58,7 @@ export const TableApiBuiltin: FC = ({ inside, customizationIdPrefix, searchMount, + kindName, }) => { const location = useLocation() const navigate = useNavigate() @@ -196,6 +199,8 @@ export const TableApiBuiltin: FC = ({ return acc }, {}) + const fullPath = `${location.pathname}${location.search}` + return ( <> {((resourceType === 'builtin' && isPendingBuiltin) || (resourceType === 'api' && isPendingApi)) && } @@ -251,6 +256,8 @@ export const TableApiBuiltin: FC = ({ apiVersion, typeName, inside, + fullPath, + searchMount, }), deletePathPrefix: resourceType === 'builtin' ? `/api/clusters/${cluster}/k8s/api` : `/api/clusters/${cluster}/k8s/apis`, @@ -286,49 +293,48 @@ export const TableApiBuiltin: FC = ({ )} */} - {!searchMount && ( - <> - - - - + {selectedRowKeys.length > 0 && ( + + + - {selectedRowKeys.length > 0 && ( - - - - - )} - - - )} + )} + + {isDeleteModalOpen && ( { + if (searchMount) { + return fullPath + } const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}` const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}` @@ -53,6 +60,8 @@ export const getBackLinkToApiTable = ({ apiVersion, typeName, inside, + fullPath, + searchMount, }: { cluster: string baseprefix?: string @@ -62,7 +71,13 @@ export const getBackLinkToApiTable = ({ apiVersion?: string // api typeName: string inside?: boolean + fullPath: string + searchMount?: boolean }): string => { + if (searchMount) { + return fullPath + } + const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}` const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}` @@ -82,6 +97,8 @@ export const getBackLinkToTable = ({ apiVersion?: string // api typeName: string inside?: boolean + fullPath: string + searchMount?: boolean }): string => { return resourceType === 'builtin' ? getBackLinkToBuiltinTable({ ...rest }) : getBackLinkToApiTable({ ...rest }) } @@ -93,6 +110,8 @@ export const getLinkToBuiltinForm = ({ syntheticProject, typeName, inside, + fullPath, + searchMount, }: { cluster: string baseprefix?: string @@ -100,10 +119,21 @@ export const getLinkToBuiltinForm = ({ syntheticProject?: string typeName: string inside?: boolean + fullPath: string + searchMount?: boolean }): string => { const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}` const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}` - const backlink = getBackLinkToBuiltinTable({ cluster, baseprefix, namespace, syntheticProject, typeName, inside }) + const backlink = getBackLinkToBuiltinTable({ + cluster, + baseprefix, + namespace, + syntheticProject, + typeName, + inside, + fullPath, + searchMount, + }) return `${mainRoute}/forms/builtin/v1/${typeName}?backlink=${backlink}` } @@ -117,6 +147,8 @@ export const getLinkToApiForm = ({ apiVersion, typeName, inside, + fullPath, + searchMount, }: { cluster: string baseprefix?: string @@ -126,6 +158,8 @@ export const getLinkToApiForm = ({ apiVersion?: string // api typeName: string inside?: boolean + fullPath: string + searchMount?: boolean }): string => { const root = `${baseprefix}${inside ? '/inside' : ''}/${cluster}` const mainRoute = `${root}${namespace ? `/${namespace}` : ''}${syntheticProject ? `/${syntheticProject}` : ''}` @@ -138,6 +172,8 @@ export const getLinkToApiForm = ({ apiVersion, typeName, inside, + fullPath, + searchMount, }) return `${mainRoute}/forms/apis/${apiGroup}/${apiVersion}/${typeName}?backlink=${backlink}` @@ -156,6 +192,8 @@ export const getLinkToForm = ({ apiVersion?: string // api typeName: string inside?: boolean + fullPath: string + searchMount?: boolean }): string => { return resourceType === 'builtin' ? getLinkToBuiltinForm({ ...rest }) : getLinkToApiForm({ ...rest }) } diff --git a/src/components/organisms/Search/Search.tsx b/src/components/organisms/Search/Search.tsx index b99d973..1527e5e 100644 --- a/src/components/organisms/Search/Search.tsx +++ b/src/components/organisms/Search/Search.tsx @@ -11,9 +11,10 @@ import { getSortedKinds, // kindByGvr, } from '@prorobotech/openapi-k8s-toolkit' -import { Form, Spin, Alert } from 'antd' +import { ConfigProvider, theme as antdtheme, Form, Spin, Alert } from 'antd' import { useSelector } from 'react-redux' import { RootState } from 'store/store' +import { HEAD_FIRST_ROW, HEAD_SECOND_ROW, FOOTER_HEIGHT, NAV_HEIGHT, CONTENT_CARD_PADDING } from 'constants/blocksSizes' import { FIELD_NAME, FIELD_NAME_STRING, @@ -27,10 +28,12 @@ import { } from './constants' import { useDebouncedCallback, getArrayParam, setArrayParam, getStringParam, setStringParam } from './utils' import { SearchEntry } from './molecules' +import { Styled } from './styled' export const Search: FC = () => { const [searchParams, setSearchParams] = useSearchParams() const location = useLocation() + const { token } = antdtheme.useToken() const cluster = useSelector((state: RootState) => state.cluster.cluster) const theme = useSelector((state: RootState) => state.openapiTheme.theme) @@ -43,6 +46,24 @@ export const Search: FC = () => { const [kindIndex, setKindIndex] = useState() const [kindsWithVersion, setKindWithVersion] = useState() + const [height, setHeight] = useState(0) + + useEffect(() => { + const height = + window.innerHeight - HEAD_FIRST_ROW - HEAD_SECOND_ROW - NAV_HEIGHT - CONTENT_CARD_PADDING * 2 - FOOTER_HEIGHT - 1 + setHeight(height) + + const handleResize = () => { + setHeight(height) + } + + window.addEventListener('resize', handleResize) + + return () => { + window.removeEventListener('resize', handleResize) + } + }, []) + useEffect(() => { setIsLoading(true) setError(undefined) @@ -195,26 +216,49 @@ export const Search: FC = () => { } return ( - <> - - {watchedKinds?.map(item => ( - - - - - ))} - + + + + + {watchedKinds?.map(item => ( + + + + + ))} + + + + ) } diff --git a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx index d5b260d..3312adc 100644 --- a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx +++ b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx @@ -1,23 +1,59 @@ -import React, { FC } from 'react' +import React, { FC, useState } from 'react' import { useParams, useSearchParams } from 'react-router-dom' -import { Typography } from 'antd' +import { + TKindWithVersion, + kindByGvr, + getUppercase, + hslFromString, + Spacer, + UpIcon, + DownIcon, +} from '@prorobotech/openapi-k8s-toolkit' +import { theme as antdtheme, Flex, FormInstance, Button } from 'antd' +import { useSelector } from 'react-redux' +import { RootState } from 'store/store' import { TableApiBuiltin } from 'components' import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix' import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion' +import { Styled } from './styled' type TSearchEntryProps = { resource: string name?: string labels?: string[] fields?: string[] + form: FormInstance + constants: { + FIELD_NAME: string + } + kindsWithVersion: TKindWithVersion[] } -export const SearchEntry: FC = ({ resource, name, labels, fields }) => { +export const SearchEntry: FC = ({ + resource, + name, + labels, + fields, + form, + constants, + kindsWithVersion, +}) => { const { namespace, syntheticProject } = useParams() const [searchParams] = useSearchParams() + const { token } = antdtheme.useToken() + + const [isOpen, setIsOpen] = useState(true) + + const theme = useSelector((state: RootState) => state.openapiTheme.theme) + + const { FIELD_NAME } = constants const [apiGroup, apiVersion, typeName] = resource.split('~') + const kindName = kindByGvr(kindsWithVersion)(resource) + const abbr = getUppercase(kindName && kindName.length ? kindName : 'Loading') + const bgColor = kindName && kindName.length ? hslFromString(abbr, theme) : '' + const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({ instance: !!syntheticProject, project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace, @@ -25,30 +61,52 @@ export const SearchEntry: FC = ({ resource, name, labels, fie search: true, }) + const removeKind = (value: string) => { + const cur: string[] = form.getFieldValue(FIELD_NAME) || [] + form.setFieldsValue({ [FIELD_NAME]: cur.filter(v => v !== value) }) + } + return ( -
- - {apiGroup.length > 0 ? `${apiGroup}/${apiVersion}/` : 'v1/'} - {typeName} - {name ? ` & name=${name}` : ''} - {labels && labels.length ? ` & labels=${labels.join('+')}` : ''} - {fields && fields.length ? ` & labels=${fields.join('+')}` : ''} - - {typeName && ( - 0 ? 'api' : 'builtin'} - namespace={namespace} - 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')} - customizationIdPrefix={tableCustomizationIdPrefix} - searchMount - /> + + + { + e.preventDefault() + removeKind(resource) + }} + closable + > + {kindName && kindName.length && bgColor.length && {abbr}} + {kindName} + +
+ +
+
+ {isOpen && ( + <> + + {typeName && ( + 0 ? 'api' : 'builtin'} + namespace={namespace} + 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')} + customizationIdPrefix={tableCustomizationIdPrefix} + searchMount + kindName={kindName} + /> + )} + )} -
+ ) } diff --git a/src/components/organisms/Search/molecules/SearchEntry/styled.ts b/src/components/organisms/Search/molecules/SearchEntry/styled.ts new file mode 100644 index 0000000..f4a3fa5 --- /dev/null +++ b/src/components/organisms/Search/molecules/SearchEntry/styled.ts @@ -0,0 +1,39 @@ +import styled from 'styled-components' +import { Tag } from 'antd' + +type TContainerProps = { + $colorBorder: string + $colorText: string +} + +const Container = styled.div` + border-radius: 0 6px 6px 0; + border: 1px solid ${({ $colorBorder }) => $colorBorder}; + padding: 12px; + margin-left: 20px; + border-left-color: ${({ $colorText }) => $colorText}; + border-left-width: 3px; +` + +const CustomTag = styled(Tag)` + font-size: 14px; + height: 22px; + margin-inline-end: 0 !important; +` + +type TAbbrProps = { + $bgColor: string +} + +const Abbr = styled.span` + background-color: ${({ $bgColor }) => $bgColor}; + border-radius: 13px; + padding: 2px 5px; + height: min-content; +` + +export const Styled = { + Container, + CustomTag, + Abbr, +} diff --git a/src/components/organisms/Search/styled.ts b/src/components/organisms/Search/styled.ts new file mode 100644 index 0000000..b6089f0 --- /dev/null +++ b/src/components/organisms/Search/styled.ts @@ -0,0 +1,19 @@ +import styled from 'styled-components' + +type TContainerProps = { + $height?: number +} + +const Container = styled.div` + height: ${({ $height }) => ($height ? `${$height}px` : '75vh')}; +` + +const OverflowContainer = styled.div` + overflow-x: auto; + scrollbar-width: thin; +` + +export const Styled = { + Container, + OverflowContainer, +} From acef13b96698cf9bd9364a79cadc1f8aca578429 Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 15:55:18 +0300 Subject: [PATCH 04/23] search design --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 495858c..e099a4d 100644 --- a/package.json +++ b/package.json @@ -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.123", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", From 709e21b284049572577c08b368ee5f86922c4fe1 Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 15:55:35 +0300 Subject: [PATCH 05/23] linters --- src/components/organisms/Search/molecules/SearchEntry/styled.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/organisms/Search/molecules/SearchEntry/styled.ts b/src/components/organisms/Search/molecules/SearchEntry/styled.ts index f4a3fa5..847bf38 100644 --- a/src/components/organisms/Search/molecules/SearchEntry/styled.ts +++ b/src/components/organisms/Search/molecules/SearchEntry/styled.ts @@ -18,6 +18,7 @@ const Container = styled.div` const CustomTag = styled(Tag)` font-size: 14px; height: 22px; + /* stylelint-disable declaration-no-important */ margin-inline-end: 0 !important; ` From 85228a321e5cdeb1a171965edbba029a67014e2b Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 15:57:49 +0300 Subject: [PATCH 06/23] new gh wf --- .github/workflows/docker-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 48d2f7c..3bea2da 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -2,7 +2,9 @@ name: release on: push: branches: - - '*' + - main + - develop + - release/* tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' From 7ad8969b352a7cb89d9ab9852762c7ff05500069 Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 16:00:37 +0300 Subject: [PATCH 07/23] new gh wf --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 3bea2da..cb2e887 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -37,6 +37,6 @@ jobs: file: ./Dockerfile.build builder: default push: ${{ github.event_name != 'pull_request' }} - tags: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }}/openapi-ui:${{ github.head_ref || github.ref_name }}-${{ steps.short-sha.outputs.sha }} + tags: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }}/openapi-ui:${{ replace(github.head_ref || github.ref_name, '/', '-') }}-${{ steps.short-sha.outputs.sha }} build-args: | NODE_VERSION=${{ env.NODE_VERSION }} From 5139a0219fa42729235c0cd4df4740ec4eff37c3 Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 16:01:59 +0300 Subject: [PATCH 08/23] new gh wf --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index cb2e887..e81772f 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -37,6 +37,6 @@ jobs: file: ./Dockerfile.build builder: default push: ${{ github.event_name != 'pull_request' }} - tags: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }}/openapi-ui:${{ replace(github.head_ref || github.ref_name, '/', '-') }}-${{ steps.short-sha.outputs.sha }} + tags: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }}/openapi-ui:${{ (github.head_ref || github.ref_name) | gsub('/','-') }}-${{ steps.short-sha.outputs.sha }} build-args: | NODE_VERSION=${{ env.NODE_VERSION }} From adae7e6767e4fc18ac902ee5ce2223c3abe5487d Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 27 Sep 2025 16:03:31 +0300 Subject: [PATCH 09/23] new gh wf --- .github/workflows/docker-build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index e81772f..47f639b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -30,6 +30,10 @@ jobs: username: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }} password: ${{ secrets.CUSTOM_DOCKERHUB_TOKEN }} + - name: Sanitize branch name + id: sanitize + run: echo "branch=$(echo '${{ github.head_ref || github.ref_name }}' | tr '/' '-')" >> $GITHUB_OUTPUT + - name: Build and push Docker image uses: docker/build-push-action@v4 with: @@ -37,6 +41,6 @@ jobs: file: ./Dockerfile.build builder: default push: ${{ github.event_name != 'pull_request' }} - tags: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }}/openapi-ui:${{ (github.head_ref || github.ref_name) | gsub('/','-') }}-${{ steps.short-sha.outputs.sha }} + tags: ${{ secrets.CUSTOM_DOCKERHUB_USERNAME }}/openapi-ui:${{ steps.sanitize.outputs.branch }}-${{ steps.short-sha.outputs.sha }} build-args: | NODE_VERSION=${{ env.NODE_VERSION }} From c66ff4a398dbe7960305798d063e631baf75bf64 Mon Sep 17 00:00:00 2001 From: typescreep Date: Sun, 28 Sep 2025 19:29:43 +0300 Subject: [PATCH 10/23] search by name fix | new colors from hsl | only kinds with list verb in search --- .../TableApiBuiltin/TableApiBuiltin.tsx | 6 +--- .../molecules/TableApiBuiltin/utils.ts | 11 ------ src/components/organisms/Search/Search.tsx | 34 ++++++++++--------- .../molecules/SearchEntry/SearchEntry.tsx | 12 +------ src/pages/SearchPage/SearchPage.tsx | 4 +-- 5 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx b/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx index 987fb40..00e3bb6 100644 --- a/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx +++ b/src/components/molecules/TableApiBuiltin/TableApiBuiltin.tsx @@ -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 = ({ apiGroup, apiVersion, typeName, - specificName, labels, fields, limit, @@ -159,7 +157,6 @@ export const TableApiBuiltin: FC = ({ clusterName: cluster, namespace, typeName, - specificName, labels, fields, limit, @@ -176,7 +173,6 @@ export const TableApiBuiltin: FC = ({ apiGroup: apiGroup || '', apiVersion: apiVersion || '', typeName, - specificName, labels, fields, limit, @@ -239,7 +235,7 @@ export const TableApiBuiltin: FC = ({ 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, diff --git a/src/components/molecules/TableApiBuiltin/utils.ts b/src/components/molecules/TableApiBuiltin/utils.ts index a523a9c..77a18f3 100644 --- a/src/components/molecules/TableApiBuiltin/utils.ts +++ b/src/components/molecules/TableApiBuiltin/utils.ts @@ -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 || [] } diff --git a/src/components/organisms/Search/Search.tsx b/src/components/organisms/Search/Search.tsx index 1527e5e..23fd9f7 100644 --- a/src/components/organisms/Search/Search.tsx +++ b/src/components/organisms/Search/Search.tsx @@ -240,22 +240,24 @@ export const Search: FC = () => { }, }} > - {watchedKinds?.map(item => ( - - - - - ))} + {watchedKinds?.map(item => { + const fields = [...(watchedFields || []), ...(watchedName ? [`metadata.name=${watchedName}`] : [])] + return ( + + + + + ) + })} diff --git a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx index 3312adc..6a90d20 100644 --- a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx +++ b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx @@ -19,7 +19,6 @@ import { Styled } from './styled' type TSearchEntryProps = { resource: string - name?: string labels?: string[] fields?: string[] form: FormInstance @@ -29,15 +28,7 @@ type TSearchEntryProps = { kindsWithVersion: TKindWithVersion[] } -export const SearchEntry: FC = ({ - resource, - name, - labels, - fields, - form, - constants, - kindsWithVersion, -}) => { +export const SearchEntry: FC = ({ resource, labels, fields, form, constants, kindsWithVersion }) => { const { namespace, syntheticProject } = useParams() const [searchParams] = useSearchParams() const { token } = antdtheme.useToken() @@ -96,7 +87,6 @@ export const SearchEntry: FC = ({ 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')} diff --git a/src/pages/SearchPage/SearchPage.tsx b/src/pages/SearchPage/SearchPage.tsx index 922f312..b2ed41e 100644 --- a/src/pages/SearchPage/SearchPage.tsx +++ b/src/pages/SearchPage/SearchPage.tsx @@ -16,11 +16,11 @@ export const SearchPage: FC = ({ 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 ( Date: Mon, 29 Sep 2025 14:04:23 +0300 Subject: [PATCH 11/23] preserver all fields --- src/components/organisms/Search/Search.tsx | 128 +++++++++++-------- src/components/organisms/Search/constants.ts | 5 +- src/components/organisms/Search/utils.ts | 17 +++ 3 files changed, 95 insertions(+), 55 deletions(-) diff --git a/src/components/organisms/Search/Search.tsx b/src/components/organisms/Search/Search.tsx index 23fd9f7..863dbd3 100644 --- a/src/components/organisms/Search/Search.tsx +++ b/src/components/organisms/Search/Search.tsx @@ -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(FIELD_NAME_FIELDS, form) const watchedTypedSelector = Form.useWatch(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 aren’t 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]) diff --git a/src/components/organisms/Search/constants.ts b/src/components/organisms/Search/constants.ts index 021c83e..af6cc45 100644 --- a/src/components/organisms/Search/constants.ts +++ b/src/components/organisms/Search/constants.ts @@ -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' diff --git a/src/components/organisms/Search/utils.ts b/src/components/organisms/Search/utils.ts index bcc977f..01d2034 100644 --- a/src/components/organisms/Search/utils.ts +++ b/src/components/organisms/Search/utils.ts @@ -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 +} From 54c279b5f1dfa52f9153a2fb936ca5f01ac6516d Mon Sep 17 00:00:00 2001 From: typescreep Date: Mon, 29 Sep 2025 14:29:05 +0300 Subject: [PATCH 12/23] + SEARCH_TABLE_CUSTOMIZATION_PREFIX --- .env | 2 ++ .env.options.dist | 2 ++ README.md | 1 + server/index.ts | 6 ++++++ src/constants/customizationApiGroupAndVersion.ts | 4 ++++ src/utils/getTableCustomizationIdPrefix.ts | 4 +++- 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.env b/.env index 295e21a..1844f4b 100644 --- a/.env +++ b/.env @@ -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- diff --git a/.env.options.dist b/.env.options.dist index 5d586f8..a04925e 100644 --- a/.env.options.dist +++ b/.env.options.dist @@ -33,3 +33,5 @@ REMOVE_BACKLINK= REMOVE_BACKLINK_TEXT= DOCS_URL= + +SEARCH_TABLE_CUSTOMIZATION_PREFIX= diff --git a/README.md b/README.md index 48452ce..e9e09cd 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/server/index.ts b/server/index.ts index d54d03c..f95b1ec 100644 --- a/server/index.ts +++ b/server/index.ts @@ -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"'} } diff --git a/src/constants/customizationApiGroupAndVersion.ts b/src/constants/customizationApiGroupAndVersion.ts index 2cf6fc3..b261e26 100644 --- a/src/constants/customizationApiGroupAndVersion.ts +++ b/src/constants/customizationApiGroupAndVersion.ts @@ -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' diff --git a/src/utils/getTableCustomizationIdPrefix.ts b/src/utils/getTableCustomizationIdPrefix.ts index 4dc6106..336c0d9 100644 --- a/src/utils/getTableCustomizationIdPrefix.ts +++ b/src/utils/getTableCustomizationIdPrefix.ts @@ -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-' } From 96f85c95813bf8eaa74497937dcd93d28a239111 Mon Sep 17 00:00:00 2001 From: typescreep Date: Tue, 30 Sep 2025 14:56:08 +0300 Subject: [PATCH 13/23] custom navigation for search --- .../organisms/HeaderSecond/HeaderSecond.tsx | 7 +-- .../SelectorNamespace/SelectorNamespace.tsx | 46 ++++++++++++++++++- src/hooks/useIsSearchPage/index.ts | 1 + src/hooks/useIsSearchPage/useIsSearchPage.ts | 9 ++++ src/pages/SearchPage/SearchPage.tsx | 1 + src/templates/BaseTemplate/BaseTemplate.tsx | 12 ++++- 6 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/hooks/useIsSearchPage/index.ts create mode 100644 src/hooks/useIsSearchPage/useIsSearchPage.ts diff --git a/src/components/organisms/HeaderSecond/HeaderSecond.tsx b/src/components/organisms/HeaderSecond/HeaderSecond.tsx index 1d0d561..10b588f 100644 --- a/src/components/organisms/HeaderSecond/HeaderSecond.tsx +++ b/src/components/organisms/HeaderSecond/HeaderSecond.tsx @@ -8,9 +8,10 @@ import { Styled } from './styled' type THeaderProps = { inside?: boolean + isSearch?: boolean } -export const HeaderSecond: FC = ({ inside }) => { +export const HeaderSecond: FC = ({ 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 = ({ inside }) => { {inside ? : } {inside && } - {!inside && BASE_USE_NAMESPACE_NAV !== 'true' && ( + {!inside && !isSearch && BASE_USE_NAMESPACE_NAV !== 'true' && ( )} - {!inside && BASE_USE_NAMESPACE_NAV === 'true' && ( + {!inside && (isSearch || BASE_USE_NAMESPACE_NAV === 'true') && ( )} diff --git a/src/components/organisms/HeaderSecond/organisms/SelectorNamespace/SelectorNamespace.tsx b/src/components/organisms/HeaderSecond/organisms/SelectorNamespace/SelectorNamespace.tsx index 41e2a40..c3bf2a1 100644 --- a/src/components/organisms/HeaderSecond/organisms/SelectorNamespace/SelectorNamespace.tsx +++ b/src/components/organisms/HeaderSecond/organisms/SelectorNamespace/SelectorNamespace.tsx @@ -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 = ({ 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 = ({ 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 = diff --git a/src/hooks/useIsSearchPage/index.ts b/src/hooks/useIsSearchPage/index.ts new file mode 100644 index 0000000..6b84b05 --- /dev/null +++ b/src/hooks/useIsSearchPage/index.ts @@ -0,0 +1 @@ +export * from './useIsSearchPage' diff --git a/src/hooks/useIsSearchPage/useIsSearchPage.ts b/src/hooks/useIsSearchPage/useIsSearchPage.ts new file mode 100644 index 0000000..a7c3ad9 --- /dev/null +++ b/src/hooks/useIsSearchPage/useIsSearchPage.ts @@ -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)) +} diff --git a/src/pages/SearchPage/SearchPage.tsx b/src/pages/SearchPage/SearchPage.tsx index b2ed41e..57d96d4 100644 --- a/src/pages/SearchPage/SearchPage.tsx +++ b/src/pages/SearchPage/SearchPage.tsx @@ -26,6 +26,7 @@ export const SearchPage: FC = ({ forcedTheme }) => { = ({ children, withNoCluster, forcedTheme, inside, sidebar }) => { +export const BaseTemplate: FC = ({ + children, + withNoCluster, + forcedTheme, + inside, + isSearch, + sidebar, +}) => { const navigate = useNavigate() const { clusterName } = useParams() const { useToken } = antdtheme @@ -106,7 +114,7 @@ export const BaseTemplate: FC = ({ children, withNoCluster, - + {clusterListQuery.error && ( )} From 50a65393aca9d37fcddc166024eab1d47c5f110f Mon Sep 17 00:00:00 2001 From: typescreep Date: Tue, 30 Sep 2025 15:06:25 +0300 Subject: [PATCH 14/23] search uses namespaces from nav --- .../organisms/Search/molecules/SearchEntry/SearchEntry.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx index 6a90d20..c4e766d 100644 --- a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx +++ b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx @@ -3,6 +3,7 @@ import { useParams, useSearchParams } from 'react-router-dom' import { TKindWithVersion, kindByGvr, + namespacedByGvr, getUppercase, hslFromString, Spacer, @@ -45,6 +46,8 @@ export const SearchEntry: FC = ({ resource, labels, fields, f 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, @@ -83,7 +86,7 @@ export const SearchEntry: FC = ({ resource, labels, fields, f {typeName && ( 0 ? 'api' : 'builtin'} - namespace={namespace} + namespace={isNamespaceResource ? namespace : undefined} apiGroup={apiGroup.length > 0 ? apiGroup : undefined} apiVersion={apiGroup.length > 0 ? apiVersion : undefined} typeName={typeName} From 09aa1a7edc7fd68d3edae4434c9688ee572e9b71 Mon Sep 17 00:00:00 2001 From: typescreep Date: Tue, 30 Sep 2025 15:21:15 +0300 Subject: [PATCH 15/23] package upgrade --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6c683a..a105c68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index e099a4d..07d1a9f 100644 --- a/package.json +++ b/package.json @@ -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", From 6e3f5c11863f373306a0114d3503c5824342ccc7 Mon Sep 17 00:00:00 2001 From: typescreep Date: Wed, 1 Oct 2025 15:24:03 +0300 Subject: [PATCH 16/23] search: design fixes --- src/components/organisms/Search/Search.tsx | 38 +++++++++++++++++++++- src/components/organisms/Search/styled.ts | 21 ++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/Search/Search.tsx b/src/components/organisms/Search/Search.tsx index 863dbd3..020d809 100644 --- a/src/components/organisms/Search/Search.tsx +++ b/src/components/organisms/Search/Search.tsx @@ -9,6 +9,7 @@ import { TKindWithVersion, getKinds, getSortedKinds, + LookingGlassIcon, } from '@prorobotech/openapi-k8s-toolkit' import { ConfigProvider, theme as antdtheme, Form, Spin, Alert } from 'antd' import { useSelector } from 'react-redux' @@ -55,6 +56,7 @@ export const Search: FC = () => { const [kindsWithVersion, setKindWithVersion] = useState() const [height, setHeight] = useState(0) + const [emptyHeight, setEmptyHeight] = useState(0) useEffect(() => { const height = @@ -72,6 +74,29 @@ export const Search: FC = () => { } }, []) + useEffect(() => { + const emptyHeight = + window.innerHeight - + HEAD_FIRST_ROW - + HEAD_SECOND_ROW - + NAV_HEIGHT - + CONTENT_CARD_PADDING * 2 - + FOOTER_HEIGHT - + 1 - + 50 // packagesearch emptyy height + setEmptyHeight(emptyHeight) + + const handleResize = () => { + setEmptyHeight(emptyHeight) + } + + window.addEventListener('resize', handleResize) + + return () => { + window.removeEventListener('resize', handleResize) + } + }, []) + useEffect(() => { setIsLoading(true) setError(undefined) @@ -281,7 +306,18 @@ export const Search: FC = () => { ) })} - + + {(watchedKinds && watchedKinds.length) || + (watchedName && watchedName.length) || + (watchedLabels && watchedLabels.length) || + (watchedFields && watchedFields.length) ? ( + + ) : ( + + + Select search options + + )} ) diff --git a/src/components/organisms/Search/styled.ts b/src/components/organisms/Search/styled.ts index b6089f0..04f3b93 100644 --- a/src/components/organisms/Search/styled.ts +++ b/src/components/organisms/Search/styled.ts @@ -13,7 +13,28 @@ const OverflowContainer = styled.div` scrollbar-width: thin; ` +type TEmptyContainerProps = { + $height?: number +} + +const EmptyContainer = styled.div` + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + height: ${({ $height }) => ($height ? `${$height}px` : '25vh')}; +` + +const EmptyText = styled.div` + margin-top: 12px; + font-size: 16px; + font-weight: 400; + line-height: 24px; /* 150% */ +` + export const Styled = { Container, OverflowContainer, + EmptyContainer, + EmptyText, } From d0a0cf1cdb0f8770fe02853dc61b759c6e21f82d Mon Sep 17 00:00:00 2001 From: typescreep Date: Wed, 1 Oct 2025 16:10:04 +0300 Subject: [PATCH 17/23] icon designs --- package-lock.json | 8 ++--- package.json | 2 +- .../molecules/SearchEntry/SearchEntry.tsx | 30 +++++++++++-------- .../Search/molecules/SearchEntry/styled.ts | 20 +++++++++++-- src/pages/SearchPage/SearchPage.tsx | 10 +++++-- 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index a105c68..95a740d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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.124", + "@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.126", "@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.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==", + "version": "0.0.1-alpha.126", + "resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.126.tgz", + "integrity": "sha512-sTEz+LeMhddSOnhOEtFmH1KNHwANVAkuaoF++umrnIQ1msMKLRCbCNHEeH7glQYr10D7HJQCwlgyplRCtUmvAg==", "license": "MIT", "dependencies": { "@monaco-editor/react": "4.6.0", diff --git a/package.json b/package.json index 07d1a9f..076b7da 100644 --- a/package.json +++ b/package.json @@ -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.124", + "@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.126", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", diff --git a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx index c4e766d..5f3cf6e 100644 --- a/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx +++ b/src/components/organisms/Search/molecules/SearchEntry/SearchEntry.tsx @@ -44,7 +44,7 @@ export const SearchEntry: FC = ({ resource, labels, fields, f const kindName = kindByGvr(kindsWithVersion)(resource) const abbr = getUppercase(kindName && kindName.length ? kindName : 'Loading') - const bgColor = kindName && kindName.length ? hslFromString(abbr, theme) : '' + const bgColor = kindName && kindName.length ? hslFromString(kindName, theme) : '' const isNamespaceResource = namespacedByGvr(kindsWithVersion)(resource) @@ -63,17 +63,23 @@ export const SearchEntry: FC = ({ resource, labels, fields, f return ( - { - e.preventDefault() - removeKind(resource) - }} - closable - > - {kindName && kindName.length && bgColor.length && {abbr}} - {kindName} - + + { + e.preventDefault() + removeKind(resource) + }} + closable + > + {kindName && kindName.length && bgColor.length && {abbr}} + {kindName} + + + {apiGroup && apiGroup.length > 0 ? `${apiGroup}/` : ''} + {apiVersion} + +