diff --git a/src/apis/plugins.ts b/src/apis/plugins.ts index 8e14b343c7..22b3a0c977 100644 --- a/src/apis/plugins.ts +++ b/src/apis/plugins.ts @@ -26,6 +26,37 @@ import { import { req } from '@/config/req'; import type { APISIXType } from '@/types/schema/apisix'; +const normalizePluginMap = ( + payload: unknown +): Record> => { + if (!payload || typeof payload !== 'object') { + return {}; + } + + const obj = payload as Record; + + // Some APISIX variants may wrap plugin definitions under `plugins`. + if (obj.plugins && typeof obj.plugins === 'object' && !Array.isArray(obj.plugins)) { + return obj.plugins as Record>; + } + + // Some variants may return an array of one-key objects. + if (Array.isArray(payload)) { + const merged: Record> = {}; + for (const item of payload) { + if (!item || typeof item !== 'object' || Array.isArray(item)) continue; + for (const [name, schemaObj] of Object.entries(item)) { + if (schemaObj && typeof schemaObj === 'object') { + merged[name] = schemaObj as Record; + } + } + } + return merged; + } + + return obj as Record>; +}; + export type NeedPluginSchema = { schema: APISIXType['PluginSchemaKeys']; @@ -53,14 +84,15 @@ export const getPluginsListWithSchemaQueryOptions = ( params: { subsystem, all: true }, }) .then((v) => { - const data = Object.entries(v.data); - const names = []; + const originObj = normalizePluginMap(v.data); + const data = Object.entries(originObj); + const names: string[] = []; for (const [name, config] of data) { if (config[schema]) { names.push(name); } } - return { names, originObj: v.data }; + return { names, originObj }; }), }); }; @@ -73,11 +105,11 @@ export const getPluginSchemaQueryOptions = ( queryKey: ['plugin-schema', name], queryFn: name ? () => - req - .get( - `${API_PLUGINS}/${name}` - ) - .then((v) => v.data) + req + .get( + `${API_PLUGINS}/${name}` + ) + .then((v) => v.data) : skipToken, enabled, }); diff --git a/src/components/Header/LanguageMenu.tsx b/src/components/Header/LanguageMenu.tsx index 48fcd8f0ef..46b6153d76 100644 --- a/src/components/Header/LanguageMenu.tsx +++ b/src/components/Header/LanguageMenu.tsx @@ -30,7 +30,8 @@ const LangMap: Record = { }; const TranslationProgress = ({ lang }: { lang: string }) => { - const percent = i18nProgress[lang].percent; + const progressData = i18nProgress[lang as keyof typeof i18nProgress]; + const percent = progressData?.percent ?? 0; if (typeof percent === 'number' && percent < 100) { return ( { const { name, desc, mode, onAdd, onEdit, onView, onDelete } = props; const { t } = useTranslation(); + + const handleDelete = useCallbackRef(() => + modals.openConfirmModal({ + centered: true, + confirmProps: { color: 'red' }, + title: t('info.delete.title', { name: name }), + children: ( + + {t('info.delete.content', { name: name })} + + {name} + + {t('mark.question')} + + ), + labels: { confirm: t('form.btn.delete'), cancel: t('form.btn.cancel') }, + onConfirm: () => onDelete?.(name), + }) + ); + return ( @@ -78,7 +105,7 @@ export const PluginCard = (props: PluginCardProps) => { size="compact-xs" variant="light" color="red" - onClick={() => onDelete?.(name)} + onClick={handleDelete} > {t('form.btn.delete')} diff --git a/src/components/form-slice/FormItemPlugins/PluginCardList.tsx b/src/components/form-slice/FormItemPlugins/PluginCardList.tsx index 53de9090ad..311dc74e6a 100644 --- a/src/components/form-slice/FormItemPlugins/PluginCardList.tsx +++ b/src/components/form-slice/FormItemPlugins/PluginCardList.tsx @@ -31,7 +31,7 @@ import { PluginCard, type PluginCardProps } from './PluginCard'; type PluginCardListSearchProps = Pick & { search: string; - setSearch: (search: string) => void; + setSearch: (search: string | string[] | null | undefined) => void; }; export const PluginCardListSearch = (props: PluginCardListSearchProps) => { const { placeholder, search, setSearch } = props; @@ -98,10 +98,19 @@ export type PluginCardListProps = Omit & cols?: number; h?: number | string; mah?: number | string; - search: string; + search: string | string[] | null | undefined; plugins: string[]; }; +const normalizeSearch = ( + search: string | string[] | null | undefined +): string => { + if (Array.isArray(search)) { + return search.filter((v): v is string => typeof v === 'string').join(' '); + } + return typeof search === 'string' ? search : ''; +}; + export const PluginCardList = (props: PluginCardListProps) => { const { search = '', cols = 3, h, mah, plugins } = props; const { mode, onAdd, onEdit, onDelete, onView } = props; @@ -111,8 +120,8 @@ export const PluginCardList = (props: PluginCardListProps) => { search: '', plugins: [] as string[], mode: 'add' as OptionProps['mode'], - setSearch(search: string) { - this.search = search.toLowerCase().trim(); + setSearch(search: string | string[] | null | undefined) { + this.search = normalizeSearch(search).toLowerCase().trim(); }, setPlugins(plugins: string[]) { this.plugins = plugins; diff --git a/src/routes/plugin_configs/index.tsx b/src/routes/plugin_configs/index.tsx index bccb89848f..cbfcc96370 100644 --- a/src/routes/plugin_configs/index.tsx +++ b/src/routes/plugin_configs/index.tsx @@ -102,23 +102,15 @@ function PluginConfigsList() { pagination={pagination} cardProps={{ bodyStyle: { padding: 0 } }} toolbar={{ - menu: { - type: 'inline', - items: [ - { - key: 'add', - label: ( - - ), - }, - ], - }, + actions: [ + , + ], }} /> diff --git a/src/utils/zod.ts b/src/utils/zod.ts index 82435f9aae..ee750cf16b 100644 --- a/src/utils/zod.ts +++ b/src/utils/zod.ts @@ -14,37 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { xor } from 'rambdax'; -import { type RefinementCtx, z } from 'zod'; import { init } from 'zod-empty'; -// ref: https://github.com/colinhacks/zod/issues/61#issuecomment-1741983149 -export const zOneOf = - < - A, - K1 extends Extract, - K2 extends Extract, - R extends A & - ( - | (Required> & { [P in K2]: undefined }) - | (Required> & { [P in K1]: undefined }) - ) - >( - key1: K1, - key2: K2 - ): ((arg: A, ctx: RefinementCtx) => arg is R) => - (arg, ctx): arg is R => { - if (xor(arg[key1] as boolean, arg[key2] as boolean)) { - [key1, key2].forEach((key) => { - ctx.addIssue({ - path: [key], - code: z.ZodIssueCode.custom, - message: `Either '${key1}' or '${key2}' must be filled, but not both`, - }); - }); - return false; - } - return true; - }; - export const zGetDefault = init; diff --git a/vite.config.ts b/vite.config.ts index 799fdf323d..09887546c7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -91,7 +91,7 @@ export default defineConfig({ semicolons: false, }), i18nProgress({ - langs: ['en', 'es', 'de', 'zh'], + langs: ['en', 'es', 'de', 'zh', 'tr'], baseLang: 'en', getTranslationDir: (lang) => `./src/locales/${lang}`, }),