✨ feat: Add queryNodeTag function and integrate tag retrieval in NodeForm and SubscribeForm components
This commit is contained in:
parent
9e01f4f590
commit
4563c570ac
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { filterServerList } from '@/services/admin/server';
|
import { filterServerList, queryNodeTag } from '@/services/admin/server';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
@ -98,6 +98,15 @@ export default function NodeForm(props: {
|
|||||||
});
|
});
|
||||||
const servers: ServerRow[] = data as ServerRow[];
|
const servers: ServerRow[] = data as ServerRow[];
|
||||||
|
|
||||||
|
const { data: tagsData } = useQuery({
|
||||||
|
queryKey: ['queryNodeTag'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await queryNodeTag();
|
||||||
|
return data?.data?.tags || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const existingTags: string[] = tagsData as string[];
|
||||||
|
|
||||||
const currentServer = useMemo(() => servers?.find((s) => s.id === serverId), [servers, serverId]);
|
const currentServer = useMemo(() => servers?.find((s) => s.id === serverId), [servers, serverId]);
|
||||||
|
|
||||||
const availableProtocols = useMemo(() => {
|
const availableProtocols = useMemo(() => {
|
||||||
@ -298,6 +307,7 @@ export default function NodeForm(props: {
|
|||||||
placeholder={t('tags_placeholder')}
|
placeholder={t('tags_placeholder')}
|
||||||
value={field.value || []}
|
value={field.value || []}
|
||||||
onChange={(v) => form.setValue(field.name, v)}
|
onChange={(v) => form.setValue(field.name, v)}
|
||||||
|
options={existingTags}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>{t('tags_description')}</FormDescription>
|
<FormDescription>{t('tags_description')}</FormDescription>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { filterNodeList } from '@/services/admin/server';
|
import { filterNodeList, queryNodeTag } from '@/services/admin/server';
|
||||||
import { getSubscribeGroupList } from '@/services/admin/subscribe';
|
import { getSubscribeGroupList } from '@/services/admin/subscribe';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
@ -245,7 +245,16 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
return (data.data?.list || []) as API.Node[];
|
return (data.data?.list || []) as API.Node[];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const tagGroups = Array.from(
|
|
||||||
|
const { data: allTagsData } = useQuery({
|
||||||
|
queryKey: ['queryNodeTag'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await queryNodeTag();
|
||||||
|
return data?.data?.tags || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeExtractedTags = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
((nodes as API.Node[]) || [])
|
((nodes as API.Node[]) || [])
|
||||||
.flatMap((n) => (Array.isArray(n.tags) ? n.tags : []))
|
.flatMap((n) => (Array.isArray(n.tags) ? n.tags : []))
|
||||||
@ -253,6 +262,12 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
),
|
),
|
||||||
) as string[];
|
) as string[];
|
||||||
|
|
||||||
|
const allAvailableTags = (allTagsData as string[]) || [];
|
||||||
|
|
||||||
|
const tagGroups = Array.from(new Set([...allAvailableTags, ...nodeExtractedTags])).filter(
|
||||||
|
Boolean,
|
||||||
|
);
|
||||||
|
|
||||||
const unit_time = form.watch('unit_time');
|
const unit_time = form.watch('unit_time');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -271,7 +286,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>{title}</SheetTitle>
|
<SheetTitle>{title}</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<ScrollArea className='h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))]'>
|
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))] px-6'>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(handleSubmit)} className='pt-4'>
|
<form onSubmit={form.handleSubmit(handleSubmit)} className='pt-4'>
|
||||||
<Tabs defaultValue='basic' className='w-full'>
|
<Tabs defaultValue='basic' className='w-full'>
|
||||||
@ -801,8 +816,12 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<Accordion type='single' collapsible className='w-full'>
|
<Accordion type='single' collapsible className='w-full'>
|
||||||
{tagGroups.map((tag) => {
|
{tagGroups.map((tag) => {
|
||||||
const value = field.value || [];
|
const value = field.value || [];
|
||||||
// Use a synthetic ID for tag grouping selection by name
|
|
||||||
const tagId = tag;
|
const tagId = tag;
|
||||||
|
const nodesWithTag =
|
||||||
|
(nodes as API.Node[])?.filter((n) =>
|
||||||
|
(n.tags || []).includes(tag),
|
||||||
|
) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem key={tag} value={String(tag)}>
|
<AccordionItem key={tag} value={String(tag)}>
|
||||||
<AccordionTrigger>
|
<AccordionTrigger>
|
||||||
@ -818,7 +837,12 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label>{tag}</Label>
|
<Label>
|
||||||
|
{tag}
|
||||||
|
<span className='text-muted-foreground ml-2 text-xs'>
|
||||||
|
({nodesWithTag.length})
|
||||||
|
</span>
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
@ -830,11 +854,13 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
key={node.id}
|
key={node.id}
|
||||||
className='flex items-center justify-between gap-3'
|
className='flex items-center justify-between gap-3'
|
||||||
>
|
>
|
||||||
<span>{node.name}</span>
|
<span className='flex-1'>{node.name}</span>
|
||||||
<span>
|
<span className='flex-1'>
|
||||||
{node.address}:{node.port}
|
{node.address}:{node.port}
|
||||||
</span>
|
</span>
|
||||||
<span>{node.protocol}</span>
|
<span className='flex-1 text-right'>
|
||||||
|
{node.protocol}
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@ -876,11 +902,11 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label className='flex w-full items-center justify-between gap-3'>
|
<Label className='flex w-full items-center justify-between gap-3'>
|
||||||
<span>{item.name}</span>
|
<span className='flex-1'>{item.name}</span>
|
||||||
<span>
|
<span className='flex-1'>
|
||||||
{item.address}:{item.port}
|
{item.address}:{item.port}
|
||||||
</span>
|
</span>
|
||||||
<span>{item.protocol}</span>
|
<span className='flex-1 text-right'>{item.protocol}</span>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -141,6 +141,14 @@ export async function toggleNodeStatus(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Query all node tags GET /v1/admin/server/node/tags */
|
||||||
|
export async function queryNodeTag(options?: { [key: string]: any }) {
|
||||||
|
return request<API.Response & { data?: API.QueryNodeTagResponse }>('/v1/admin/server/node/tags', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Update Node POST /v1/admin/server/node/update */
|
/** Update Node POST /v1/admin/server/node/update */
|
||||||
export async function updateNode(body: API.UpdateNodeRequest, options?: { [key: string]: any }) {
|
export async function updateNode(body: API.UpdateNodeRequest, options?: { [key: string]: any }) {
|
||||||
return request<API.Response & { data?: any }>('/v1/admin/server/node/update', {
|
return request<API.Response & { data?: any }>('/v1/admin/server/node/update', {
|
||||||
|
|||||||
4
apps/admin/services/admin/typings.d.ts
vendored
4
apps/admin/services/admin/typings.d.ts
vendored
@ -1551,6 +1551,10 @@ declare namespace API {
|
|||||||
list: Document[];
|
list: Document[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type QueryNodeTagResponse = {
|
||||||
|
tags: string[];
|
||||||
|
};
|
||||||
|
|
||||||
type QueryOrderDetailRequest = {
|
type QueryOrderDetailRequest = {
|
||||||
order_no: string;
|
order_no: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user