mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-09-26 19:31:18 +08:00
chore: frontend form style
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
import { Check } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { CaretSortIcon } from "@radix-ui/react-icons"
|
||||
|
||||
export interface ComboboxProps {
|
||||
value?: string
|
||||
@@ -67,17 +68,17 @@ export function Combobox({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn("w-full justify-between font-normal", className,
|
||||
className={cn("w-full justify-between font-normal px-3", className,
|
||||
!value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{value
|
||||
? (dataList.find((item) => item.value === value)?.label || value)
|
||||
: (placeholder || defaultPlaceholder)}
|
||||
<ChevronsUpDown className="opacity-50 h-[12px] w-[12px]" />
|
||||
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0" align="start">
|
||||
<PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
value={keyword}
|
||||
|
@@ -19,6 +19,7 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { IsIDValid } from '@/lib/consts'
|
||||
|
||||
export const CreateClientDialog = ({refetchTrigger}: {refetchTrigger?: (randStr: string) => void}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -60,9 +61,11 @@ export const CreateClientDialog = ({refetchTrigger}: {refetchTrigger?: (randStr:
|
||||
</DialogHeader>
|
||||
|
||||
<Label>{t('client.create.id')}</Label>
|
||||
<Input className="mt-2" value={clientID} onChange={(e) => setClientID(e.target.value)} />
|
||||
<Input value={clientID} onChange={(e) => setClientID(e.target.value)} />
|
||||
<DialogFooter>
|
||||
<Button onClick={handleNewClient}>{t('client.create.submit')}</Button>
|
||||
<Button onClick={handleNewClient}
|
||||
disabled={!IsIDValid(clientID)}
|
||||
className='w-full'>{t('client.create.submit')}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@@ -18,8 +18,9 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { IsIDValid } from '@/lib/consts'
|
||||
|
||||
export const CreateServerDialog = ({refetchTrigger}: {refetchTrigger?: (randStr: string) => void}) => {
|
||||
export const CreateServerDialog = ({ refetchTrigger }: { refetchTrigger?: (randStr: string) => void }) => {
|
||||
const { t } = useTranslation()
|
||||
const [serverID, setServerID] = useState<string | undefined>()
|
||||
const [serverIP, setServerIP] = useState<string | undefined>()
|
||||
@@ -60,11 +61,13 @@ export const CreateServerDialog = ({refetchTrigger}: {refetchTrigger?: (randStr:
|
||||
</DialogHeader>
|
||||
|
||||
<Label>{t('server.create.id')}</Label>
|
||||
<Input className="mt-2" value={serverID} onChange={(e) => setServerID(e.target.value)} />
|
||||
<Input value={serverID} onChange={(e) => setServerID(e.target.value)} />
|
||||
<Label>{t('server.create.ip')}</Label>
|
||||
<Input className="mt-2" value={serverIP} onChange={(e) => setServerIP(e.target.value)} />
|
||||
<Input value={serverIP} onChange={(e) => setServerIP(e.target.value)} />
|
||||
<DialogFooter>
|
||||
<Button onClick={handleNewServer}>{t('server.create.submit')}</Button>
|
||||
<Button onClick={handleNewServer}
|
||||
disabled={!IsIDValid(serverID) || !serverIP}
|
||||
className='w-full'>{t('server.create.submit')}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@@ -9,7 +9,6 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
@@ -47,6 +46,12 @@ export const ProxyConfigMutateDialog = ({ ...props }: ProxyConfigMutateDialogPro
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className='max-h-screen overflow-auto'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('proxy.config.create_proxy')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('proxy.config.create_proxy_description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ProxyConfigMutateForm {...props} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -113,14 +118,14 @@ export const ProxyConfigMutateForm = ({ overwrite, defaultProxyConfig, defaultOr
|
||||
value={proxyType}
|
||||
setValue={(value) => { setProxyType(value as ProxyType) }}
|
||||
/>
|
||||
<div className='flex flex-row w-full overflow-auto'>
|
||||
{proxyConfigs && selectedServer && proxyConfigs.length > 0 &&
|
||||
proxyConfigs[0] && TypedProxyConfigValid(proxyConfigs[0]) &&
|
||||
{proxyConfigs && selectedServer && proxyConfigs.length > 0 &&
|
||||
proxyConfigs[0] && TypedProxyConfigValid(proxyConfigs[0]) &&
|
||||
<div className='flex flex-row w-full overflow-auto'>
|
||||
<div className='flex flex-col'>
|
||||
<VisitPreview server={selectedServer} typedProxyConfig={proxyConfigs[0]} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<Label>{t('proxy.config.proxy_name')} </Label>
|
||||
<Input className='text-sm' defaultValue={proxyName} onChange={(e) => setProxyName(e.target.value)} disabled={disableChangeProxyName} />
|
||||
{proxyName && newClientID && newServerID && <TypedProxyForm
|
||||
|
@@ -353,6 +353,9 @@
|
||||
},
|
||||
"config": {
|
||||
"create": "Create",
|
||||
"create_proxy": "Create Tunnel",
|
||||
"create_proxy_description": "Expose the client's services to the server. The same client tunnel name must be unique",
|
||||
"close": "Close",
|
||||
"create_success": "Create successful",
|
||||
"create_failed": "Create failed",
|
||||
"select_server": "Select Server",
|
||||
|
@@ -353,6 +353,9 @@
|
||||
},
|
||||
"config": {
|
||||
"create": "创建",
|
||||
"create_proxy": "创建隧道",
|
||||
"create_proxy_description": "将客户端的服务暴露在服务端,同一个客户端隧道名称必须唯一",
|
||||
"close": "关闭",
|
||||
"create_success": "创建成功",
|
||||
"create_failed": "创建失败",
|
||||
"select_server": "选择服务器",
|
||||
|
@@ -24,6 +24,14 @@ export const TypedProxyConfigValid = (typedProxyCfg: TypedProxyConfig | undefine
|
||||
return (typedProxyCfg?.localPort && typedProxyCfg.localIP && typedProxyCfg.name && typedProxyCfg.type) ? true : false
|
||||
}
|
||||
|
||||
export const IsIDValid = (clientID: string|undefined): boolean => {
|
||||
if (clientID == undefined) {
|
||||
return false
|
||||
}
|
||||
const regex = /^[a-zA-Z0-9-_]+$/;
|
||||
return clientID.length > 0 && regex.test(clientID);
|
||||
}
|
||||
|
||||
export const ClientConfigured = (client: Client | undefined): boolean => {
|
||||
if (client == undefined) {
|
||||
return false
|
||||
|
@@ -104,3 +104,10 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.popover-content-width-full {
|
||||
width: var(--radix-popover-trigger-width);
|
||||
max-height: var(--radix-popover-content-available-height);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user