Skip to content

Commit 56acbb0

Browse files
committed
fix: remove nested addresing imports
1 parent 8006d02 commit 56acbb0

File tree

8 files changed

+176
-171
lines changed

8 files changed

+176
-171
lines changed

dashboard/src/components/dialogs/host-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { memo, useCallback, useMemo, useState } from 'react'
2020
import { UseFormReturn } from 'react-hook-form'
2121
import { useTranslation } from 'react-i18next'
2222
import { toast } from 'sonner'
23-
import { HostFormValues } from '../hosts/hosts'
23+
import { HostFormValues } from '../hosts/hosts-list'
2424
import { LoaderButton } from '../ui/loader-button'
2525

2626
interface HostModalProps {

dashboard/src/components/groups/Groups.tsx renamed to dashboard/src/components/groups/groups-list.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ const initialDefaultValues: Partial<GroupFormValues> = {
1919
is_disabled: false,
2020
}
2121

22-
interface GroupsProps {
22+
interface GroupsListProps {
2323
isDialogOpen: boolean
2424
onOpenChange: (open: boolean) => void
2525
}
2626

27-
export default function Groups({ isDialogOpen, onOpenChange }: GroupsProps) {
27+
export default function GroupsList({ isDialogOpen, onOpenChange }: GroupsListProps) {
2828
const [editingGroup, setEditingGroup] = useState<GroupResponse | null>(null)
2929
const { t } = useTranslation()
3030
const modifyGroupMutation = useModifyGroup()

dashboard/src/components/hosts/Hosts.tsx renamed to dashboard/src/components/hosts/hosts-list.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ const initialDefaultValues: HostFormValues = {
427427
fragment_settings: undefined,
428428
}
429429

430-
export interface HostsProps {
430+
export interface HostsListProps {
431431
data: BaseHost[]
432432
isDialogOpen: boolean
433433
onDialogOpenChange: (open: boolean) => void
@@ -437,7 +437,7 @@ export interface HostsProps {
437437
setEditingHost: (host: BaseHost | null) => void
438438
}
439439

440-
export default function Hosts({ data, onAddHost, isDialogOpen, onSubmit, editingHost, setEditingHost }: HostsProps) {
440+
export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, editingHost, setEditingHost }: HostsListProps) {
441441
const [hosts, setHosts] = useState<BaseHost[] | undefined>()
442442
const [isUpdatingPriorities, setIsUpdatingPriorities] = useState(false)
443443
const { t } = useTranslation()
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { useState, useEffect } from 'react'
2+
import { useTranslation } from 'react-i18next'
3+
import Node from '@/components/nodes/node'
4+
import { useGetNodes, useModifyNode, NodeResponse, NodeConnectionType } from '@/service/api'
5+
import { toast } from 'sonner'
6+
import { queryClient } from '@/utils/query-client'
7+
import NodeModal from '@/components/dialogs/node-modal'
8+
import { useForm } from 'react-hook-form'
9+
import { zodResolver } from '@hookform/resolvers/zod'
10+
import { nodeFormSchema, NodeFormValues } from '@/components/dialogs/node-modal'
11+
import { Card, CardContent } from '@/components/ui/card'
12+
import { Skeleton } from '@/components/ui/skeleton'
13+
14+
const initialDefaultValues: Partial<NodeFormValues> = {
15+
name: '',
16+
address: '',
17+
port: 62050,
18+
usage_coefficient: 1,
19+
connection_type: NodeConnectionType.grpc,
20+
server_ca: '',
21+
keep_alive: 20000,
22+
max_logs: 100,
23+
}
24+
25+
export default function NodesList() {
26+
const { t } = useTranslation()
27+
const [isDialogOpen, setIsDialogOpen] = useState(false)
28+
const [editingNode, setEditingNode] = useState<NodeResponse | null>(null)
29+
const modifyNodeMutation = useModifyNode()
30+
31+
const { data: nodesData, isLoading } = useGetNodes(undefined, {
32+
query: {
33+
refetchInterval: 5000, // 5s
34+
staleTime: 0, // No stale time - always fetch fresh data
35+
gcTime: 0, // No garbage collection time - no caching
36+
},
37+
})
38+
39+
const form = useForm<NodeFormValues>({
40+
resolver: zodResolver(nodeFormSchema),
41+
defaultValues: initialDefaultValues,
42+
})
43+
44+
useEffect(() => {
45+
const handleOpenDialog = () => setIsDialogOpen(true)
46+
window.addEventListener('openNodeDialog', handleOpenDialog)
47+
return () => window.removeEventListener('openNodeDialog', handleOpenDialog)
48+
}, [])
49+
50+
const handleEdit = (node: NodeResponse) => {
51+
setEditingNode(node)
52+
form.reset({
53+
name: node.name,
54+
address: node.address,
55+
port: node.port || 62050,
56+
usage_coefficient: node.usage_coefficient || 1,
57+
connection_type: node.connection_type,
58+
server_ca: node.server_ca,
59+
keep_alive: node.keep_alive,
60+
max_logs: node.max_logs,
61+
})
62+
setIsDialogOpen(true)
63+
}
64+
65+
const handleToggleStatus = async (node: NodeResponse) => {
66+
try {
67+
// Determine the new status: enable if currently disabled, otherwise disable
68+
const shouldEnable = node.status === 'disabled'
69+
const newStatus = shouldEnable ? 'connected' : 'disabled'
70+
71+
await modifyNodeMutation.mutateAsync({
72+
nodeId: node.id,
73+
data: {
74+
name: node.name,
75+
address: node.address,
76+
port: node.port,
77+
usage_coefficient: node.usage_coefficient,
78+
connection_type: node.connection_type,
79+
server_ca: node.server_ca,
80+
keep_alive: node.keep_alive,
81+
max_logs: node.max_logs,
82+
status: newStatus,
83+
},
84+
})
85+
86+
toast.success(t('success', { defaultValue: 'Success' }), {
87+
description: t(shouldEnable ? 'nodes.enableSuccess' : 'nodes.disableSuccess', {
88+
name: node.name,
89+
defaultValue: `Node "{name}" has been ${shouldEnable ? 'enabled' : 'disabled'} successfully`,
90+
}),
91+
})
92+
93+
// Invalidate nodes queries
94+
queryClient.invalidateQueries({
95+
queryKey: ['/api/nodes'],
96+
})
97+
} catch (error) {
98+
toast.error(t('error', { defaultValue: 'Error' }), {
99+
description: t(node.status === 'disabled' ? 'nodes.enableFailed' : 'nodes.disableFailed', {
100+
name: node.name,
101+
defaultValue: `Failed to ${node.status === 'disabled' ? 'enable' : 'disable'} node "{name}"`,
102+
}),
103+
})
104+
}
105+
}
106+
107+
return (
108+
<div className="flex w-full flex-col items-start gap-2">
109+
<div className="w-full flex-1 space-y-4 pt-6">
110+
<div
111+
className="mb-12 grid transform-gpu animate-slide-up grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
112+
style={{ animationDuration: '500ms', animationDelay: '100ms', animationFillMode: 'both' }}
113+
>
114+
{isLoading
115+
? [...Array(6)].map((_, i) => (
116+
<Card key={i} className="p-4">
117+
<div className="space-y-3">
118+
<div className="flex items-center justify-between">
119+
<Skeleton className="h-5 w-24 sm:w-32" />
120+
<Skeleton className="h-6 w-6 shrink-0 rounded-full" />
121+
</div>
122+
<Skeleton className="h-4 w-20 sm:w-24" />
123+
<div className="flex gap-2">
124+
<Skeleton className="h-8 flex-1" />
125+
<Skeleton className="h-8 w-8 shrink-0" />
126+
</div>
127+
</div>
128+
</Card>
129+
))
130+
: nodesData?.map(node => <Node key={node.id} node={node} onEdit={handleEdit} onToggleStatus={handleToggleStatus} />)}
131+
</div>
132+
133+
{!isLoading && (!nodesData || nodesData.length === 0) && (
134+
<Card className="mb-12">
135+
<CardContent className="p-8 text-center">
136+
<div className="space-y-4">
137+
<h3 className="text-lg font-semibold">{t('nodes.noNodes')}</h3>
138+
<p className="mx-auto max-w-2xl text-muted-foreground">
139+
{t('nodes.noNodesDescription')}{' '}
140+
<a href="https://github.com/PasarGuard/node" target="_blank" rel="noopener noreferrer" className="font-medium text-primary underline-offset-4 hover:underline">
141+
PasarGuard/node
142+
</a>{' '}
143+
{t('nodes.noNodesDescription2', { defaultValue: 'and connect it to the panel.' })}
144+
</p>
145+
</div>
146+
</CardContent>
147+
</Card>
148+
)}
149+
150+
<NodeModal
151+
isDialogOpen={isDialogOpen}
152+
onOpenChange={open => {
153+
if (!open) {
154+
setEditingNode(null)
155+
form.reset(initialDefaultValues)
156+
}
157+
setIsDialogOpen(open)
158+
}}
159+
form={form}
160+
editingNode={!!editingNode}
161+
editingNodeId={editingNode?.id}
162+
/>
163+
</div>
164+
</div>
165+
)
166+
}

dashboard/src/pages/_dashboard._index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import NodeModal, { nodeFormSchema, NodeFormValues } from '@/components/dialogs/
88
import QuickActionsModal from '@/components/dialogs/shortcuts-modal'
99
import UserModal from '@/components/dialogs/user-modal'
1010
import UserTemplateModal, { userTemplateFormSchema, UserTemplatesFromValue } from '@/components/dialogs/user-template-modal'
11-
import { HostFormValues } from '@/components/hosts/hosts'
11+
import { HostFormValues } from '@/components/hosts/hosts-list'
1212
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
1313
import { Button } from '@/components/ui/button'
1414
import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '@/components/ui/command'

dashboard/src/pages/_dashboard.groups.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import PageHeader from '@/components/layout/page-header'
22
import { Separator } from '@/components/ui/separator'
33
import { Plus } from 'lucide-react'
4-
import Groups from '@/components/groups/groups'
4+
import Groups from '@/components/groups/groups-list'
55
import { useState } from 'react'
66

77
export default function GroupsPage() {

dashboard/src/pages/_dashboard.hosts.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import MainSection, { HostFormValues } from '@/components/hosts/hosts'
1+
import MainSection, { HostFormValues } from '@/components/hosts/hosts-list'
22
import PageHeader from '@/components/layout/page-header'
33
import { Card, CardContent } from '@/components/ui/card'
44
import { Separator } from '@/components/ui/separator'

0 commit comments

Comments
 (0)