diff --git a/e2e/tests/consumer_groups.list.spec.ts b/e2e/tests/consumer_groups.list.spec.ts index a9dd217b83..b2ff734453 100644 --- a/e2e/tests/consumer_groups.list.spec.ts +++ b/e2e/tests/consumer_groups.list.spec.ts @@ -47,6 +47,7 @@ const consumerGroups: APISIXType['ConsumerGroupPut'][] = Array.from( test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllConsumerGroups(e2eReq); await Promise.all( diff --git a/e2e/tests/consumers.list.spec.ts b/e2e/tests/consumers.list.spec.ts index 43ff5ce9b5..685a770861 100644 --- a/e2e/tests/consumers.list.spec.ts +++ b/e2e/tests/consumers.list.spec.ts @@ -49,6 +49,7 @@ const consumers: APISIXType['ConsumerPut'][] = Array.from({ length: 11 }, (_, i) test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllConsumers(e2eReq); await Promise.all(consumers.map((d) => putConsumerReq(e2eReq, d))); diff --git a/e2e/tests/hot-path.upstream-service-route.spec.ts b/e2e/tests/hot-path.upstream-service-route.spec.ts index 60521f3bcd..811c870cf4 100644 --- a/e2e/tests/hot-path.upstream-service-route.spec.ts +++ b/e2e/tests/hot-path.upstream-service-route.spec.ts @@ -61,6 +61,7 @@ test('can create upstream -> service -> route', async ({ page }) => { scheme: 'https', nodes: [{ host: 'httpbin.org', port: 443 }], }; + await test.step('create upstream', async () => { // Navigate to the upstream list page await upstreamsPom.toIndex(page); @@ -158,6 +159,7 @@ test('can create upstream -> service -> route', async ({ page }) => { }, }, } satisfies Partial; + await test.step('create service', async () => { // upstream id should be set expect(service.upstream_id).not.toBeUndefined(); @@ -275,6 +277,7 @@ test('can create upstream -> service -> route', async ({ page }) => { }, }, }; + await test.step('create route', async () => { // service id should be set expect(route.service_id).not.toBeUndefined(); diff --git a/e2e/tests/plugin_configs.list.spec.ts b/e2e/tests/plugin_configs.list.spec.ts index f4a67a396b..2806b042d4 100644 --- a/e2e/tests/plugin_configs.list.spec.ts +++ b/e2e/tests/plugin_configs.list.spec.ts @@ -67,6 +67,7 @@ const pluginConfigs: APISIXType['PluginConfigPut'][] = Array.from( test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllPluginConfigs(e2eReq); await Promise.all(pluginConfigs.map((d) => putPluginConfigReq(e2eReq, d))); diff --git a/e2e/tests/protos.list.spec.ts b/e2e/tests/protos.list.spec.ts index 2f17897e08..cf9222e34b 100644 --- a/e2e/tests/protos.list.spec.ts +++ b/e2e/tests/protos.list.spec.ts @@ -55,6 +55,7 @@ message TestMessage${i + 1} { test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { // Delete all existing protos const existingProtos = await e2eReq diff --git a/e2e/tests/routes.list.spec.ts b/e2e/tests/routes.list.spec.ts index b6f171d920..7a999cd12d 100644 --- a/e2e/tests/routes.list.spec.ts +++ b/e2e/tests/routes.list.spec.ts @@ -63,6 +63,7 @@ const routes: APISIXType['Route'][] = Array.from({ length: 11 }, (_, i) => ({ test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllRoutes(e2eReq); await Promise.all(routes.map((d) => putRouteReq(e2eReq, d))); diff --git a/e2e/tests/services.crud-required-fields.spec.ts b/e2e/tests/services.crud-required-fields.spec.ts index f0f17b3eec..ede2ac2c6b 100644 --- a/e2e/tests/services.crud-required-fields.spec.ts +++ b/e2e/tests/services.crud-required-fields.spec.ts @@ -41,6 +41,7 @@ test('should CRUD service with required fields', async ({ page }) => { await servicesPom.getAddServiceBtn(page).click(); await servicesPom.isAddPage(page); + await test.step('submit with required fields', async () => { await uiFillServiceRequiredFields(page, { name: serviceName, diff --git a/e2e/tests/services.list.spec.ts b/e2e/tests/services.list.spec.ts index ceb3972951..ef45cba464 100644 --- a/e2e/tests/services.list.spec.ts +++ b/e2e/tests/services.list.spec.ts @@ -55,6 +55,7 @@ const services: APISIXType['Service'][] = Array.from({ length: 11 }, (_, i) => ( test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllServices(e2eReq); await Promise.all( diff --git a/e2e/tests/stream_routes.list.spec.ts b/e2e/tests/stream_routes.list.spec.ts index 5363b6fe53..28f9c5cc28 100644 --- a/e2e/tests/stream_routes.list.spec.ts +++ b/e2e/tests/stream_routes.list.spec.ts @@ -58,6 +58,7 @@ const streamRoutes: APISIXType['StreamRoute'][] = Array.from( test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllStreamRoutes(e2eReq); await Promise.all( diff --git a/e2e/tests/upstreams.list.spec.ts b/e2e/tests/upstreams.list.spec.ts index 4119da66d1..99a252ef0c 100644 --- a/e2e/tests/upstreams.list.spec.ts +++ b/e2e/tests/upstreams.list.spec.ts @@ -60,6 +60,7 @@ const upstreams: APISIXType['Upstream'][] = Array.from( test.describe('page and page_size should work correctly', () => { test.describe.configure({ mode: 'serial' }); + test.beforeAll(async () => { await deleteAllUpstreams(e2eReq); await Promise.all(upstreams.map((d) => putUpstreamReq(e2eReq, d))); diff --git a/package.json b/package.json index ff6720917a..21451942d5 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mantine/hooks": "^8.0.0", "@mantine/modals": "^8.0.0", "@mantine/notifications": "^8.0.0", + "@mantine/spotlight": "^8.3.15", "@monaco-editor/react": "^4.7.0", "@tanstack/react-query": "^5.74.4", "@tanstack/react-router": "^1.116.0", @@ -102,11 +103,12 @@ "overrides": { "lodash": ">=4.17.21", "minimatch": ">=3.0.5", - "@swc/core": "1.10.0" + "@swc/core": "1.10.0", + "@iconify/utils": "3.0.1" }, "onlyBuiltDependencies": [ "@swc/core", "esbuild" ] } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 518d990b5b..85ee9912a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: '@mantine/notifications': specifier: ^8.0.0 version: 8.3.12(@mantine/core@8.3.12(@mantine/hooks@8.3.12(react@19.1.0))(@types/react@19.2.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.3.12(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/spotlight': + specifier: ^8.3.15 + version: 8.3.15(@mantine/core@8.3.12(@mantine/hooks@8.3.12(react@19.1.0))(@types/react@19.2.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.3.12(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@monaco-editor/react': specifier: ^4.7.0 version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1042,11 +1045,24 @@ packages: react: ^18.x || ^19.x react-dom: ^18.x || ^19.x + '@mantine/spotlight@8.3.12': + resolution: {integrity: sha512-zKssw/6eBmkY+1sGAgD8Vpy7dU5MXcY/cpvfr65SfIknRljKM9D4Z9TflzgIpxEdhvozls06MPcxj/pZkGpELQ==} + peerDependencies: + '@mantine/core': 8.3.12 + '@mantine/hooks': 8.3.12 + react: ^18.x || ^19.x + react-dom: ^18.x || ^19.x + '@mantine/store@8.3.12': resolution: {integrity: sha512-EC4eIKpm5s7neMbBrWsP6jGKLqrzHf63Ao3penYr7fn25dFXdbXZYw+IG8GYzxOC4yG61b2zTS+bpy5+vwzXpw==} peerDependencies: react: ^18.x || ^19.x + '@mantine/store@8.3.15': + resolution: {integrity: sha512-wdx91a73dM2G02YPIZ9i5UXPWfvjdf3qPAwSGnSsBFQg5uM/5CcPAOOQwlYIkvX1edUA5BFOk/4IjpEXSYUDeQ==} + peerDependencies: + react: ^18.x || ^19.x + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -5041,10 +5057,22 @@ snapshots: react-dom: 19.1.0(react@19.1.0) react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/spotlight@8.3.15(@mantine/core@8.3.12(@mantine/hooks@8.3.12(react@19.1.0))(@types/react@19.2.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.3.12(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@mantine/core': 8.3.12(@mantine/hooks@8.3.12(react@19.1.0))(@types/react@19.2.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/hooks': 8.3.12(react@19.1.0) + '@mantine/store': 8.3.15(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + '@mantine/store@8.3.12(react@19.1.0)': dependencies: react: 19.1.0 + '@mantine/store@8.3.15(react@19.1.0)': + dependencies: + react: 19.1.0 + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 diff --git a/src/components/GlobalSpotlight.tsx b/src/components/GlobalSpotlight.tsx new file mode 100644 index 0000000000..07431562f3 --- /dev/null +++ b/src/components/GlobalSpotlight.tsx @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionIcon } from '@mantine/core'; +import { Spotlight, spotlight } from '@mantine/spotlight'; +import { useNavigate } from '@tanstack/react-router'; +import { useTranslation } from 'react-i18next'; + +import { navRoutes } from '@/config/navRoutes'; +import IconClose from '~icons/material-symbols/close'; +import IconDashboard from '~icons/material-symbols/dashboard-outline'; +import IconSearch from '~icons/material-symbols/search'; + +export const GlobalSpotlight = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const actions = navRoutes.map((route) => ({ + id: route.to, + label: t(`sources.${route.label}`), + description: t('spotlight.jumpToDashboard', { resource: t(`sources.${route.label}`) }), + leftSection: , + onClick: () => { + navigate({ to: route.to }); + }, + })); + + return ( + , + placeholder: 'Search resources... (Ctrl + K)', + rightSectionPointerEvents: 'all', + rightSection: ( + { + e.preventDefault(); + spotlight.close(); + }} + > + + + ), + }} + /> + ); +}; diff --git a/src/main.tsx b/src/main.tsx index 7dacffa7e6..c1c5033996 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -16,6 +16,7 @@ */ import '@mantine/core/styles.css'; import '@mantine/notifications/styles.css'; +import '@mantine/spotlight/styles.css'; import './styles/global.css'; import { createTheme, MantineProvider } from '@mantine/core'; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index bcce5948eb..1b2ee49ab3 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -21,6 +21,7 @@ import { createRootRoute, HeadContent, Outlet } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; import { I18nextProvider } from 'react-i18next'; +import { GlobalSpotlight } from '@/components/GlobalSpotlight'; import { Header } from '@/components/Header'; import { Navbar } from '@/components/Navbar'; import { SettingsModal } from '@/components/page/SettingsModal'; @@ -55,6 +56,7 @@ const Root = () => { + ); };