Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
492 changes: 167 additions & 325 deletions dashboard/bun.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions dashboard/orval.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ export default defineConfig({
target: './src/service/api/index.ts',
mode: 'single',
clean: false,
prettier: true,
tslint: true,
headers: false,
override: {
fetch: {
includeHttpResponseReturnType: false,
},
mutator: {
path: './src/service/http.ts',
name: 'orvalFetcher',
Expand Down
4 changes: 2 additions & 2 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.26",
"orval": "7.4.1",
"orval": "8.10.0",
"postcss": "^8.5.13",
"prettier": "^3.8.3",
"prettier-plugin-tailwindcss": "^0.8.0",
Expand All @@ -113,5 +113,5 @@
"vite-plugin-svgr": "^4.5.0",
"wait-port": "^1.1.0"
},
"packageManager": "bun@1.0.0"
"packageManager": "bun@1.3.14"
}
19 changes: 13 additions & 6 deletions dashboard/src/app/router.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Suspense } from 'react'
import { useAdmin } from '@/hooks/use-admin'
import { getCurrentAdmin } from '@/service/api'
import { createHashRouter, Navigate, RouteObject } from 'react-router'
import { LoadingSpinner } from '@/components/common/loading-spinner'
import { TabbedRouteSuspenseFallback } from '@/components/layout/tabbed-route-suspense-fallback'
import { useAdmin } from '@/hooks/use-admin'
import { AdminDetails, getCurrentAdmin } from '@/service/api'
import { clearAuthSession } from '@/utils/authSession'
import { getAuthToken } from '@/utils/authStorage'
import { lazyWithChunkRecovery } from '@/utils/chunk-recovery'
import { Suspense } from 'react'
import { createHashRouter, Navigate, RouteObject } from 'react-router'
// Replace direct imports with lazy imports for route-level components
const CoresLayout = lazyWithChunkRecovery(() => import('@/pages/_dashboard.nodes.cores'))
const CoresIndex = lazyWithChunkRecovery(() => import('@/pages/_dashboard.nodes.cores._index'))
Expand Down Expand Up @@ -52,11 +54,16 @@ function SettingsIndex() {
return <Navigate to={defaultPath} replace />
}

const fetchAdminLoader = async (): Promise<any> => {
const fetchAdminLoader = async (): Promise<AdminDetails> => {
if (!getAuthToken()) {
throw Response.redirect('/login')
}

try {
const response = await getCurrentAdmin()
return response
} catch (error) {
} catch {
await clearAuthSession()
throw Response.redirect('/login')
}
}
Expand Down
40 changes: 16 additions & 24 deletions dashboard/src/components/layout/nav-user.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
'use client'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'
import { Language } from '@/components/common/language'
import { ThemeToggle } from '@/components/common/theme-toggle'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { useSidebar } from '@/components/ui/sidebar'
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar'
import { type AdminDetails } from '@/service/api'
import { ChevronsUpDown, LogOut, UserRoundKey, UsersIcon, UserCircle, ChartPie, ChartNoAxesColumn, UserRound } from 'lucide-react'
import { clearAuthSession } from '@/utils/authSession'
import { formatBytes } from '@/utils/formatByte'
import { ChartNoAxesColumn, ChartPie, ChevronsUpDown, LogOut, UserCircle, UserRound, UserRoundKey, UsersIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import { formatBytes } from '@/utils/formatByte'
import { Badge } from '@/components/ui/badge'
import { removeAuthToken } from '@/utils/authStorage'
import { queryClient } from '@/utils/query-client'
import { ThemeToggle } from '@/components/common/theme-toggle'
import { Language } from '@/components/common/language'

export function NavUser({
username,
Expand All @@ -28,15 +26,9 @@ export function NavUser({
const { state, isMobile } = useSidebar()
const navigate = useNavigate()

const handleLogout = (e: React.MouseEvent) => {
const handleLogout = async (e: React.MouseEvent) => {
e.preventDefault()
// Cancel all ongoing queries
queryClient.cancelQueries()
// Remove auth token
removeAuthToken()
// Clear React Query cache
queryClient.clear()
// Navigate to login
await clearAuthSession()
navigate('/login', { replace: true })
}

Expand All @@ -49,13 +41,13 @@ export function NavUser({
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-md">
<UserCircle className="h-4 w-4 text-sidebar-foreground" />
<UserCircle className="text-sidebar-foreground h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-64 p-3" side="right" align="start">
<div className="space-y-2">
<div className="flex items-center gap-2">
<UserCircle className="h-4 w-4 text-primary" />
<UserCircle className="text-primary h-4 w-4" />
<div className="flex items-center gap-2">
<span className="text-sm font-semibold">{username.name}</span>
{admin && (
Expand Down Expand Up @@ -125,7 +117,7 @@ export function NavUser({
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg" className="pl-3 data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground pl-3">
<div className="grid flex-1 text-left text-sm leading-tight">
<div className="flex items-center gap-2">
<span className="truncate font-semibold">{username.name}</span>
Expand All @@ -146,7 +138,7 @@ export function NavUser({
)}
</div>
{admin && (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<div className="text-muted-foreground flex items-center gap-2 text-xs">
<ChartPie className="size-3" />
<span dir="ltr" style={{ unicodeBidi: 'isolate' }}>
{formatBytes(admin?.used_traffic || 0)}
Expand Down Expand Up @@ -181,7 +173,7 @@ export function NavUser({
</div>
</div>
{admin && (
<div className="flex flex-col gap-1 text-xs text-muted-foreground">
<div className="text-muted-foreground flex flex-col gap-1 text-xs">
<div className="flex items-center gap-2">
<ChartPie className="size-3" />
<span>
Expand Down Expand Up @@ -211,7 +203,7 @@ export function NavUser({
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout} className="cursor-pointer text-destructive focus:text-destructive">
<DropdownMenuItem onClick={handleLogout} className="text-destructive focus:text-destructive cursor-pointer">
<LogOut className="mr-2 size-4" />
{t('header.logout')}
</DropdownMenuItem>
Expand Down
90 changes: 32 additions & 58 deletions dashboard/src/features/bulk/components/bulk-flow.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,42 @@
'use client'

import { useState, useEffect } from 'react'
import {
useGetGroupsSimple,
useGetUsersSimple,
useGetAdminsSimple,
useBulkModifyUsersProxySettings,
useBulkModifyUsersDatalimit,
useBulkModifyUsersExpire,
useBulkAddGroupsToUsers,
useBulkRemoveUsersFromGroups,
useBulkReallocateWireguardPeerIps,
XTLSFlows,
ShadowsocksMethods,
UserStatus,
} from '@/service/api'
import { Button } from '@/components/ui/button'
import { LoaderButton } from '@/components/ui/loader-button'
import { DecimalInput } from '@/components/common/decimal-input'
import { TIME_UNIT_SECONDS, TimeUnitSelect, type TimeUnit } from '@/components/common/time-unit-select'
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Checkbox } from '@/components/ui/checkbox'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command'
import { Label } from '@/components/ui/label'
import { LoaderButton } from '@/components/ui/loader-button'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Settings, Group, User, Shield, CheckCircle, AlertTriangle, Plus, Minus, X, HardDrive, Calendar, Network, CheckCircle2, ChevronLeft, ChevronRight, Eye, Loader2 } from 'lucide-react'
import { BulkExpiredDateFilters } from '@/features/bulk/components/bulk-expired-date-filters'
import { DecimalInput } from '@/components/common/decimal-input'
import { SelectorPanel } from '@/features/bulk/components/selector-panel'
import { TimeUnitSelect, TIME_UNIT_SECONDS, type TimeUnit } from '@/components/common/time-unit-select'
import { formatDateByLocale } from '@/utils/datePickerUtils'
import { formatBytes, gbToBytes } from '@/utils/formatByte'
import { useDebouncedSearch } from '@/hooks/use-debounced-search'
import { cn } from '@/lib/utils'
import useDirDetection from '@/hooks/use-dir-detection'
import { cn } from '@/lib/utils'
import {
ShadowsocksMethods,
useBulkAddGroupsToUsers,
useBulkModifyUsersDatalimit,
useBulkModifyUsersExpire,
useBulkModifyUsersProxySettings,
useBulkReallocateWireguardPeerIps,
useBulkRemoveUsersFromGroups,
useGetAdminsSimple,
useGetGroupsSimple,
useGetUsersSimple,
UserStatus,
} from '@/service/api'
import { formatDateByLocale } from '@/utils/datePickerUtils'
import { formatBytes, gbToBytes } from '@/utils/formatByte'
import { endOfDay, startOfDay } from 'date-fns'
import { AlertTriangle, Calendar, CheckCircle, CheckCircle2, ChevronLeft, ChevronRight, Eye, Group, HardDrive, Loader2, Minus, Plus, Settings, Shield, User, X } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'

const PAGE_SIZE = 50

Expand All @@ -57,7 +56,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {

const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1)

const [selectedFlow, setSelectedFlow] = useState<XTLSFlows | 'none' | undefined>(undefined)
const [selectedMethod, setSelectedMethod] = useState<ShadowsocksMethods | undefined>(undefined)

const [dataLimit, setDataLimit] = useState<number | undefined>(undefined)
Expand Down Expand Up @@ -157,7 +155,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
return true
}
if (operationType === 'proxy') {
return selectedFlow || selectedMethod
return selectedMethod
}
if (operationType === 'groups') {
return selectedGroups.length > 0
Expand All @@ -172,7 +170,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
case 2:
switch (operationType) {
case 'proxy':
return selectedFlow || selectedMethod
return selectedMethod
case 'data':
return dataLimit !== undefined
case 'expire':
Expand Down Expand Up @@ -246,7 +244,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
case 'proxy':
return {
...basePayload,
flow: selectedFlow === 'none' ? ('' as XTLSFlows) : selectedFlow,
method: selectedMethod,
dry_run: false,
}
Expand Down Expand Up @@ -348,7 +345,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
toast.success(t('operationSuccess', { defaultValue: 'Operation successful!' }), { description })

setCurrentStep(1)
setSelectedFlow(undefined)
setSelectedMethod(undefined)
setDataLimit(undefined)
setExpireSeconds(undefined)
Expand Down Expand Up @@ -386,7 +382,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
case 'proxy':
return {
...basePayload,
flow: selectedFlow === 'none' ? ('' as XTLSFlows) : selectedFlow,
method: selectedMethod,
dry_run: true,
}
Expand Down Expand Up @@ -541,28 +536,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
<div className="space-y-3 sm:space-y-4">
{operationType === 'proxy' && (
<div className="space-y-3 sm:space-y-4">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4">
<div className="space-y-2">
<Label htmlFor="flow" className="flex items-center gap-1.5 text-sm font-medium">
<Network className="text-muted-foreground h-3.5 w-3.5" />
{t('bulk.flowLabel', { defaultValue: 'Flow' })}
</Label>
<Select value={selectedFlow || ''} onValueChange={value => setSelectedFlow(value as XTLSFlows | 'none')}>
<SelectTrigger>
<SelectValue placeholder={t('bulk.selectFlowPlaceholder', { defaultValue: 'Select flow' })} />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">{t('none', { defaultValue: 'None' })}</SelectItem>
{Object.values(XTLSFlows)
.filter(flow => flow !== '')
.map(flow => (
<SelectItem key={flow} value={flow}>
{flow}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-1 gap-3 sm:gap-4">
<div className="space-y-2">
<Label htmlFor="method" className="flex items-center gap-1.5 text-sm font-medium">
<Settings className="text-muted-foreground h-3.5 w-3.5" />
Expand Down Expand Up @@ -1018,8 +992,8 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {

{operationType === 'proxy' && (
<div className="flex items-center justify-between">
<span className="text-muted-foreground">{t('bulk.settings', { defaultValue: 'Settings' })}:</span>
<span>{t('bulk.flowMethod', { flow: selectedFlow === 'none' || !selectedFlow ? t('none') : selectedFlow, method: selectedMethod || t('none') })}</span>
<span className="text-muted-foreground">{t('bulk.methodLabel', { defaultValue: 'Method' })}:</span>
<span>{selectedMethod || t('none')}</span>
</div>
)}

Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/features/core-editor/kit/core-kind.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { CoreType } from '@/service/api'
import type { CoreKind } from '@pasarguard/core-kit'
import type { CoreResponseType } from '@/service/api'

export function apiCoreTypeToKind(type: CoreResponseType | undefined): CoreKind {
export function apiCoreTypeToKind(type: CoreType | null | undefined): CoreKind {
if (type === 'wg') return 'wg'
return 'xray'
}

export function isSupportedCoreEditorKind(type: CoreResponseType | undefined): boolean {
export function isSupportedCoreEditorKind(type: CoreType | null | undefined): boolean {
return type === 'wg' || type === 'xray' || type == null || type === undefined
}
Loading
Loading