feat(services): filter routes/stream_routes by `service_id` (#3111)

* feat(services/routes): filter by `service_id`

* feat(services): filter stream_routes by service_id

* fix: type
diff --git a/src/apis/hooks.ts b/src/apis/hooks.ts
index 1e427c4..4825f33 100644
--- a/src/apis/hooks.ts
+++ b/src/apis/hooks.ts
@@ -82,14 +82,16 @@
   routeKey: T,
   listQueryOptions: ReturnType<typeof genListQueryOptions<P, R>>
 ) => {
-  return (replaceKey?: U) => {
+  return (replaceKey?: U, defaultParams?: Partial<P>) => {
     const key = replaceKey || routeKey;
     const { params, setParams } = useSearchParams<T | U, P>(key);
-    const listQuery = useSuspenseQuery(listQueryOptions(params));
+    const listQuery = useSuspenseQuery(
+      listQueryOptions({ ...defaultParams, ...params })
+    );
     const { data, isLoading, refetch } = listQuery;
     const opts = { data, setParams, params };
     const pagination = useTablePagination(opts);
-    return { data, isLoading, refetch, pagination };
+    return { data, isLoading, refetch, pagination, setParams };
   };
 };
 
diff --git a/src/apis/routes.ts b/src/apis/routes.ts
index fc5af37..cb99f71 100644
--- a/src/apis/routes.ts
+++ b/src/apis/routes.ts
@@ -21,7 +21,13 @@
 import type { APISIXType } from '@/types/schema/apisix';
 import type { PageSearchType } from '@/types/schema/pageSearch';
 
-export const getRouteListReq = (req: AxiosInstance, params: PageSearchType) =>
+export type WithServiceIdFilter = PageSearchType & {
+  filter?: {
+    service_id?: string;
+  };
+};
+
+export const getRouteListReq = (req: AxiosInstance, params: WithServiceIdFilter) =>
   req
     .get<undefined, APISIXType['RespRouteList']>(API_ROUTES, { params })
     .then((v) => v.data);
diff --git a/src/apis/stream_routes.ts b/src/apis/stream_routes.ts
index 0e26ae2..0dbf09d 100644
--- a/src/apis/stream_routes.ts
+++ b/src/apis/stream_routes.ts
@@ -20,9 +20,13 @@
 import type { StreamRoutePostType } from '@/components/form-slice/FormPartStreamRoute/schema';
 import { API_STREAM_ROUTES } from '@/config/constant';
 import type { APISIXType } from '@/types/schema/apisix';
-import type { PageSearchType } from '@/types/schema/pageSearch';
 
-export const getStreamRouteListReq = (req: AxiosInstance, params: PageSearchType) =>
+import type { WithServiceIdFilter } from './routes';
+
+export const getStreamRouteListReq = (
+  req: AxiosInstance,
+  params: WithServiceIdFilter
+) =>
   req
     .get<unknown, APISIXType['RespStreamRouteList']>(API_STREAM_ROUTES, {
       params,
diff --git a/src/config/req.ts b/src/config/req.ts
index 232a9b0..283338f 100644
--- a/src/config/req.ts
+++ b/src/config/req.ts
@@ -29,10 +29,16 @@
 export const req = axios.create();
 
 req.interceptors.request.use((conf) => {
-  conf.paramsSerializer = (p) =>
-    stringify(p, {
+  conf.paramsSerializer = (p) => {
+    // from { filter: { service_id: 1 } }
+    // to `filter=service_id%3D1`
+    if (p.filter) {
+      p.filter = stringify(p.filter);
+    }
+    return stringify(p, {
       arrayFormat: 'repeat',
     });
+  };
   conf.baseURL = API_PREFIX;
   conf.headers.set(API_HEADER_KEY, globalStore.settings.adminKey);
   return conf;
diff --git a/src/routes/routes/index.tsx b/src/routes/routes/index.tsx
index 7374b48..6b44fbd 100644
--- a/src/routes/routes/index.tsx
+++ b/src/routes/routes/index.tsx
@@ -21,6 +21,7 @@
 import { useTranslation } from 'react-i18next';
 
 import { getRouteListQueryOptions, useRouteList } from '@/apis/hooks';
+import type { WithServiceIdFilter } from '@/apis/routes';
 import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
 import PageHeader from '@/components/page/PageHeader';
 import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
@@ -33,14 +34,18 @@
 
 export type RouteListProps = {
   routeKey: Extract<ListPageKeys, '/routes/' | '/services/detail/$id/routes/'>;
+  defaultParams?: Partial<WithServiceIdFilter>;
   ToDetailBtn: (props: {
     record: APISIXType['RespRouteItem'];
   }) => React.ReactNode;
 };
 
 export const RouteList = (props: RouteListProps) => {
-  const { routeKey, ToDetailBtn } = props;
-  const { data, isLoading, refetch, pagination } = useRouteList(routeKey);
+  const { routeKey, ToDetailBtn, defaultParams } = props;
+  const { data, isLoading, refetch, pagination } = useRouteList(
+    routeKey,
+    defaultParams
+  );
   const { t } = useTranslation();
 
   const columns = useMemo<ProColumns<APISIXType['RespRouteItem']>[]>(() => {
diff --git a/src/routes/services/detail.$id/routes/index.tsx b/src/routes/services/detail.$id/routes/index.tsx
index 4a04170..97e092c 100644
--- a/src/routes/services/detail.$id/routes/index.tsx
+++ b/src/routes/services/detail.$id/routes/index.tsx
@@ -32,6 +32,11 @@
       <PageHeader title={t('sources.routes')} />
       <RouteList
         routeKey="/services/detail/$id/routes/"
+        defaultParams={{
+          filter: {
+            service_id: id,
+          },
+        }}
         ToDetailBtn={({ record }) => (
           <ToDetailPageBtn
             key="detail"
diff --git a/src/routes/services/detail.$id/stream_routes/index.tsx b/src/routes/services/detail.$id/stream_routes/index.tsx
index 8c3f6d1..f350327 100644
--- a/src/routes/services/detail.$id/stream_routes/index.tsx
+++ b/src/routes/services/detail.$id/stream_routes/index.tsx
@@ -39,6 +39,11 @@
             params={{ id, routeId: record.value.id }}
           />
         )}
+        defaultParams={{
+          filter: {
+            service_id: id,
+          },
+        }}
       />
     </>
   );
diff --git a/src/routes/stream_routes/index.tsx b/src/routes/stream_routes/index.tsx
index 1909744..3fb5247 100644
--- a/src/routes/stream_routes/index.tsx
+++ b/src/routes/stream_routes/index.tsx
@@ -21,6 +21,7 @@
 import { useTranslation } from 'react-i18next';
 
 import { getStreamRouteListQueryOptions, useStreamRouteList } from '@/apis/hooks';
+import type { WithServiceIdFilter } from '@/apis/routes';
 import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
 import PageHeader from '@/components/page/PageHeader';
 import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
@@ -39,11 +40,15 @@
   ToDetailBtn: (props: {
     record: APISIXType['RespStreamRouteItem'];
   }) => React.ReactNode;
+  defaultParams?: Partial<WithServiceIdFilter>;
 };
 
 export const StreamRouteList = (props: StreamRouteListProps) => {
-  const { routeKey, ToDetailBtn } = props;
-  const { data, isLoading, refetch, pagination } = useStreamRouteList(routeKey);
+  const { routeKey, ToDetailBtn, defaultParams } = props;
+  const { data, isLoading, refetch, pagination } = useStreamRouteList(
+    routeKey,
+    defaultParams
+  );
   const { t } = useTranslation();
 
   const columns = useMemo<