mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-10-26 08:50:37 +08:00
chore: prettify frontend code
This commit is contained in:
2
www/.prettierignore
Normal file
2
www/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
lib/pb
|
||||||
|
out
|
||||||
41
www/.prettierrc.js
Normal file
41
www/.prettierrc.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
module.exports = {
|
||||||
|
// max 120 characters per line
|
||||||
|
printWidth: 120,
|
||||||
|
// use 2 spaces for indentation
|
||||||
|
tabWidth: 2,
|
||||||
|
// use spaces instead of indentations
|
||||||
|
useTabs: false,
|
||||||
|
// semicolon at the end of the line
|
||||||
|
semi: false,
|
||||||
|
// use single quotes
|
||||||
|
singleQuote: true,
|
||||||
|
// object's key is quoted only when necessary
|
||||||
|
quoteProps: 'as-needed',
|
||||||
|
// use double quotes instead of single quotes in jsx
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
// no comma at the end
|
||||||
|
trailingComma: 'all',
|
||||||
|
// spaces are required at the beginning and end of the braces
|
||||||
|
bracketSpacing: true,
|
||||||
|
// end tag of jsx need to wrap
|
||||||
|
bracketSameLine: false,
|
||||||
|
// brackets are required for arrow function parameter, even when there is only one parameter
|
||||||
|
arrowParens: 'always',
|
||||||
|
// format the entire contents of the file
|
||||||
|
rangeStart: 0,
|
||||||
|
rangeEnd: Infinity,
|
||||||
|
// no need to write the beginning @prettier of the file
|
||||||
|
requirePragma: false,
|
||||||
|
// No need to automatically insert @prettier at the beginning of the file
|
||||||
|
insertPragma: false,
|
||||||
|
// use default break criteria
|
||||||
|
proseWrap: 'preserve',
|
||||||
|
// decide whether to break the html according to the display style
|
||||||
|
htmlWhitespaceSensitivity: 'css',
|
||||||
|
// vue files script and style tags indentation
|
||||||
|
vueIndentScriptAndStyle: false,
|
||||||
|
// lf for newline
|
||||||
|
endOfLine: 'lf',
|
||||||
|
// formats quoted code embedded
|
||||||
|
embeddedLanguageFormatting: 'auto',
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import http from '@/api/http'
|
import http from '@/api/http'
|
||||||
import { API_PATH } from '@/lib/consts'
|
import { API_PATH } from '@/lib/consts'
|
||||||
import {
|
import { LoginRequest, LoginResponse, RegisterRequest, RegisterResponse } from '@/lib/pb/api_auth'
|
||||||
LoginRequest, LoginResponse,
|
|
||||||
RegisterRequest, RegisterResponse
|
|
||||||
} from '@/lib/pb/api_auth'
|
|
||||||
import { CommonResponse } from '@/lib/pb/common'
|
import { CommonResponse } from '@/lib/pb/common'
|
||||||
import { BaseResponse } from '@/types/api'
|
import { BaseResponse } from '@/types/api'
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import http from '@/api/http'
|
import http from '@/api/http'
|
||||||
import { API_PATH } from "@/lib/consts";
|
import { API_PATH } from '@/lib/consts'
|
||||||
import {
|
import {
|
||||||
DeleteClientRequest, DeleteClientResponse,
|
DeleteClientRequest,
|
||||||
GetClientRequest, GetClientResponse,
|
DeleteClientResponse,
|
||||||
InitClientRequest, InitClientResponse,
|
GetClientRequest,
|
||||||
ListClientsRequest, ListClientsResponse
|
GetClientResponse,
|
||||||
} from '@/lib/pb/api_client';
|
InitClientRequest,
|
||||||
import { BaseResponse } from "@/types/api";
|
InitClientResponse,
|
||||||
|
ListClientsRequest,
|
||||||
|
ListClientsResponse,
|
||||||
|
} from '@/lib/pb/api_client'
|
||||||
|
import { BaseResponse } from '@/types/api'
|
||||||
|
|
||||||
export const getClient = async (req: GetClientRequest) => {
|
export const getClient = async (req: GetClientRequest) => {
|
||||||
const res = await http.post(API_PATH + '/client/get', GetClientRequest.toJson(req))
|
const res = await http.post(API_PATH + '/client/get', GetClientRequest.toJson(req))
|
||||||
@@ -24,7 +28,7 @@ export const deleteClient = async (req: DeleteClientRequest) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const initClient = async (req: InitClientRequest) => {
|
export const initClient = async (req: InitClientRequest) => {
|
||||||
console.log("attempting init client:", InitClientRequest.toJsonString(req))
|
console.log('attempting init client:', InitClientRequest.toJsonString(req))
|
||||||
const res = await http.post(API_PATH + '/client/init', InitClientRequest.toJson(req))
|
const res = await http.post(API_PATH + '/client/init', InitClientRequest.toJson(req))
|
||||||
return InitClientResponse.fromJson((res.data as BaseResponse).body)
|
return InitClientResponse.fromJson((res.data as BaseResponse).body)
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
import http from '@/api/http'
|
import http from '@/api/http'
|
||||||
import { API_PATH } from "@/lib/consts";
|
import { API_PATH } from '@/lib/consts'
|
||||||
import {
|
import { RemoveFRPCRequest, RemoveFRPCResponse, UpdateFRPCRequest, UpdateFRPCResponse } from '@/lib/pb/api_client'
|
||||||
RemoveFRPCRequest,
|
import { RemoveFRPSRequest, RemoveFRPSResponse, UpdateFRPSRequest, UpdateFRPSResponse } from '@/lib/pb/api_server'
|
||||||
RemoveFRPCResponse,
|
import { BaseResponse } from '@/types/api'
|
||||||
UpdateFRPCRequest,
|
|
||||||
UpdateFRPCResponse
|
|
||||||
} from '@/lib/pb/api_client';
|
|
||||||
import {
|
|
||||||
RemoveFRPSRequest,
|
|
||||||
RemoveFRPSResponse,
|
|
||||||
UpdateFRPSRequest,
|
|
||||||
UpdateFRPSResponse
|
|
||||||
} from '@/lib/pb/api_server';
|
|
||||||
import { BaseResponse } from "@/types/api";
|
|
||||||
|
|
||||||
export const updateFRPS = async (req: UpdateFRPSRequest) => {
|
export const updateFRPS = async (req: UpdateFRPSRequest) => {
|
||||||
const res = await http.post(API_PATH + '/frps/update', UpdateFRPSRequest.toJson(req))
|
const res = await http.post(API_PATH + '/frps/update', UpdateFRPSRequest.toJson(req))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { LOCAL_STORAGE_TOKEN_KEY, SET_TOKEN_HEADER, X_CLIENT_REQUEST_ID } from '@/lib/consts';
|
import { LOCAL_STORAGE_TOKEN_KEY, SET_TOKEN_HEADER, X_CLIENT_REQUEST_ID } from '@/lib/consts'
|
||||||
import { $token } from '@/store/user';
|
import { $token } from '@/store/user'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
const instance = axios.create({})
|
const instance = axios.create({})
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ instance.interceptors.request.use((request) => {
|
|||||||
request.headers.Authorization = token
|
request.headers.Authorization = token
|
||||||
$token.set(token)
|
$token.set(token)
|
||||||
}
|
}
|
||||||
request.headers[X_CLIENT_REQUEST_ID] = uuidv4();
|
request.headers[X_CLIENT_REQUEST_ID] = uuidv4()
|
||||||
return request
|
return request
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import http from '@/api/http'
|
import http from '@/api/http'
|
||||||
import { API_PATH } from '@/lib/consts'
|
import { API_PATH } from '@/lib/consts'
|
||||||
import {
|
import { GetClientsStatusRequest, GetClientsStatusResponse } from '@/lib/pb/api_master'
|
||||||
GetClientsStatusRequest,
|
import { GetPlatformInfoResponse } from '@/lib/pb/api_user'
|
||||||
GetClientsStatusResponse
|
|
||||||
} from '@/lib/pb/api_master'
|
|
||||||
import {
|
|
||||||
GetPlatformInfoResponse,
|
|
||||||
} from '@/lib/pb/api_user'
|
|
||||||
import { BaseResponse } from '@/types/api'
|
import { BaseResponse } from '@/types/api'
|
||||||
|
|
||||||
export const getPlatformInfo = async () => {
|
export const getPlatformInfo = async () => {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import http from '@/api/http'
|
import http from '@/api/http'
|
||||||
import { API_PATH } from "@/lib/consts";
|
import { API_PATH } from '@/lib/consts'
|
||||||
import {
|
import {
|
||||||
DeleteServerRequest,
|
DeleteServerRequest,
|
||||||
DeleteServerResponse,
|
DeleteServerResponse,
|
||||||
GetServerRequest, GetServerResponse,
|
GetServerRequest,
|
||||||
|
GetServerResponse,
|
||||||
InitServerRequest,
|
InitServerRequest,
|
||||||
InitServerResponse,
|
InitServerResponse,
|
||||||
ListServersRequest, ListServersResponse
|
ListServersRequest,
|
||||||
} from "@/lib/pb/api_server";
|
ListServersResponse,
|
||||||
import { BaseResponse } from "@/types/api";
|
} from '@/lib/pb/api_server'
|
||||||
|
import { BaseResponse } from '@/types/api'
|
||||||
|
|
||||||
export const getServer = async (req: GetServerRequest) => {
|
export const getServer = async (req: GetServerRequest) => {
|
||||||
const res = await http.post(API_PATH + '/server/get', GetServerRequest.toJson(req))
|
const res = await http.post(API_PATH + '/server/get', GetServerRequest.toJson(req))
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import http from '@/api/http'
|
import http from '@/api/http'
|
||||||
import { API_PATH } from '@/lib/consts'
|
import { API_PATH } from '@/lib/consts'
|
||||||
import {
|
import {
|
||||||
GetUserInfoRequest, GetUserInfoResponse,
|
GetUserInfoRequest,
|
||||||
UpdateUserInfoRequest, UpdateUserInfoResponse
|
GetUserInfoResponse,
|
||||||
|
UpdateUserInfoRequest,
|
||||||
|
UpdateUserInfoResponse,
|
||||||
} from '@/lib/pb/api_user'
|
} from '@/lib/pb/api_user'
|
||||||
import { $userInfo } from '@/store/user'
|
import { $userInfo } from '@/store/user'
|
||||||
import { BaseResponse } from '@/types/api'
|
import { BaseResponse } from '@/types/api'
|
||||||
|
|||||||
@@ -1,123 +1,204 @@
|
|||||||
import { login, register } from "@/api/auth"
|
import { login, register } from '@/api/auth'
|
||||||
import { Button } from "./ui/button"
|
import { Button } from './ui/button'
|
||||||
import { deleteClient, getClient, initClient, listClient } from "@/api/client"
|
import { deleteClient, getClient, initClient, listClient } from '@/api/client'
|
||||||
import { deleteServer, getServer, initServer, listServer } from "@/api/server"
|
import { deleteServer, getServer, initServer, listServer } from '@/api/server'
|
||||||
import { updateFRPC, updateFRPS } from "@/api/frp"
|
import { updateFRPC, updateFRPS } from '@/api/frp'
|
||||||
import { ClientConfig } from "@/types/client"
|
import { ClientConfig } from '@/types/client'
|
||||||
import { ServerConfig } from "@/types/server"
|
import { ServerConfig } from '@/types/server'
|
||||||
import { getUserInfo, updateUserInfo } from "@/api/user"
|
import { getUserInfo, updateUserInfo } from '@/api/user'
|
||||||
import { Separator } from "./ui/separator"
|
import { Separator } from './ui/separator'
|
||||||
import { useState } from "react"
|
import { useState } from 'react'
|
||||||
import { Input } from "./ui/input"
|
import { Input } from './ui/input'
|
||||||
import { Label } from "@radix-ui/react-label"
|
import { Label } from '@radix-ui/react-label'
|
||||||
|
|
||||||
export const APITest = () => {
|
export const APITest = () => {
|
||||||
const [serverID, setServerID] = useState<string>("admin.server")
|
const [serverID, setServerID] = useState<string>('admin.server')
|
||||||
const [clientID, setClientID] = useState<string>("admin.client")
|
const [clientID, setClientID] = useState<string>('admin.client')
|
||||||
const [username, setUsername] = useState<string>("admin")
|
const [username, setUsername] = useState<string>('admin')
|
||||||
const [password, setPassword] = useState<string>("admin")
|
const [password, setPassword] = useState<string>('admin')
|
||||||
const [email, setEmail] = useState<string>("admin@localhost")
|
const [email, setEmail] = useState<string>('admin@localhost')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col w-full p-10 lg:w-1/2'>
|
<div className="flex flex-col w-full p-10 lg:w-1/2">
|
||||||
<div className='grid grid-cols-2 sm:grid-cols-5 gap-4 my-4'>
|
<div className="grid grid-cols-2 sm:grid-cols-5 gap-4 my-4">
|
||||||
<div>
|
<div>
|
||||||
<Label>username</Label>
|
<Label>username</Label>
|
||||||
<Input value={username} onChange={e => setUsername(e.target.value)} />
|
<Input value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>password</Label>
|
<Label>password</Label>
|
||||||
<Input value={password} onChange={e => setPassword(e.target.value)} />
|
<Input value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>email</Label>
|
<Label>email</Label>
|
||||||
<Input value={email} onChange={e => setEmail(e.target.value)} />
|
<Input value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>clientID</Label>
|
<Label>clientID</Label>
|
||||||
<Input value={clientID} onChange={e => setClientID(e.target.value)} />
|
<Input value={clientID} onChange={(e) => setClientID(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>serverID</Label>
|
<Label>serverID</Label>
|
||||||
<Input value={serverID} onChange={e => setServerID(e.target.value)} />
|
<Input value={serverID} onChange={(e) => setServerID(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='grid grid-cols-2 sm:grid-cols-4 gap-4 my-4'>
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
||||||
<Button onClick={async () => {
|
<Button
|
||||||
console.log("attempting login:", await login({ username: username, password: password }))
|
onClick={async () => {
|
||||||
}}>login</Button>
|
console.log('attempting login:', await login({ username: username, password: password }))
|
||||||
<Button onClick={async () => {
|
}}
|
||||||
console.log("attempting register:", await register({ username: username, password: password, email: email }))
|
>
|
||||||
}}>register</Button>
|
login
|
||||||
<Button onClick={async () => {
|
</Button>
|
||||||
console.log("attempting update user:", await updateUserInfo({
|
<Button
|
||||||
userInfo: { token: "123123" },
|
onClick={async () => {
|
||||||
}))
|
console.log(
|
||||||
}}>update user</Button>
|
'attempting register:',
|
||||||
<Button onClick={async () => {
|
await register({ username: username, password: password, email: email }),
|
||||||
console.log("attempting get user:", await getUserInfo({}))
|
)
|
||||||
}}>get user</Button>
|
}}
|
||||||
|
>
|
||||||
|
register
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log(
|
||||||
|
'attempting update user:',
|
||||||
|
await updateUserInfo({
|
||||||
|
userInfo: { token: '123123' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
update user
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting get user:', await getUserInfo({}))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
get user
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className='grid grid-cols-2 sm:grid-cols-4 gap-4 my-4'>
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
||||||
<Button onClick={async () => {
|
<Button
|
||||||
console.log("attempting init server:", await initServer({ serverId: serverID.replace(username + ".", ""), serverIp: "127.0.0.1" }))
|
onClick={async () => {
|
||||||
}}>init server</Button>
|
console.log(
|
||||||
<Button onClick={async () => {
|
'attempting init server:',
|
||||||
console.log("attempting delete server:", await deleteServer({ serverId: serverID }))
|
await initServer({ serverId: serverID.replace(username + '.', ''), serverIp: '127.0.0.1' }),
|
||||||
}}>delete server</Button>
|
)
|
||||||
<Button onClick={async () => {
|
}}
|
||||||
console.log("attempting list servers:", await listServer({ page: 1, pageSize: 10 }))
|
>
|
||||||
}}>list servers</Button>
|
init server
|
||||||
<Button onClick={async () => {
|
</Button>
|
||||||
console.log("attempting get server:", await getServer({ serverId: serverID }))
|
<Button
|
||||||
}}>get server</Button>
|
onClick={async () => {
|
||||||
|
console.log('attempting delete server:', await deleteServer({ serverId: serverID }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
delete server
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting list servers:', await listServer({ page: 1, pageSize: 10 }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
list servers
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting get server:', await getServer({ serverId: serverID }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
get server
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className='grid grid-cols-2 sm:grid-cols-4 gap-4 my-4'>
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
||||||
<Button onClick={async () => {
|
<Button
|
||||||
console.log("attempting update frps:", await updateFRPS({
|
onClick={async () => {
|
||||||
|
console.log(
|
||||||
|
'attempting update frps:',
|
||||||
|
await updateFRPS({
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
config: Buffer.from(JSON.stringify({
|
config: Buffer.from(
|
||||||
|
JSON.stringify({
|
||||||
bindPort: 1122,
|
bindPort: 1122,
|
||||||
} as ServerConfig))
|
} as ServerConfig),
|
||||||
}))
|
),
|
||||||
}}>update frps</Button>
|
}),
|
||||||
<Button onClick={async () => {
|
)
|
||||||
console.log("attempting delete frps:", await deleteServer({ serverId: serverID }))
|
}}
|
||||||
}}>delete frps</Button>
|
>
|
||||||
|
update frps
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting delete frps:', await deleteServer({ serverId: serverID }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
delete frps
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className='grid grid-cols-2 sm:grid-cols-4 gap-4 my-4'>
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
||||||
<Button onClick={async () => {
|
<Button
|
||||||
console.log("attempting init client:", await initClient({ clientId: clientID.replace(username + ".", "") }))
|
onClick={async () => {
|
||||||
}}>init client</Button>
|
console.log('attempting init client:', await initClient({ clientId: clientID.replace(username + '.', '') }))
|
||||||
<Button onClick={async () => {
|
}}
|
||||||
console.log("attempting delete client:", await deleteClient({ clientId: clientID }))
|
>
|
||||||
}}>delete client</Button>
|
init client
|
||||||
<Button onClick={async () => {
|
</Button>
|
||||||
console.log("attempting list clients:", await listClient({ page: 1, pageSize: 10 }))
|
<Button
|
||||||
}}>list clients</Button>
|
onClick={async () => {
|
||||||
<Button onClick={async () => {
|
console.log('attempting delete client:', await deleteClient({ clientId: clientID }))
|
||||||
console.log("attempting get client:", await getClient({ clientId: clientID }))
|
}}
|
||||||
}}>get client</Button>
|
>
|
||||||
|
delete client
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting list clients:', await listClient({ page: 1, pageSize: 10 }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
list clients
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting get client:', await getClient({ clientId: clientID }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
get client
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className='grid grid-cols-2 sm:grid-cols-4 gap-4 my-4'>
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 my-4">
|
||||||
<Button onClick={async () => {
|
<Button
|
||||||
console.log("attempting update frpc:", await updateFRPC({
|
onClick={async () => {
|
||||||
|
console.log(
|
||||||
|
'attempting update frpc:',
|
||||||
|
await updateFRPC({
|
||||||
clientId: clientID,
|
clientId: clientID,
|
||||||
serverId: serverID,
|
serverId: serverID,
|
||||||
config: Buffer.from(JSON.stringify({
|
config: Buffer.from(
|
||||||
proxies: [
|
JSON.stringify({
|
||||||
{ name: "test", type: "tcp", localIP: "127.0.0.1", localPort: 1234, remotePort: 4321 }
|
proxies: [{ name: 'test', type: 'tcp', localIP: '127.0.0.1', localPort: 1234, remotePort: 4321 }],
|
||||||
]
|
} as ClientConfig),
|
||||||
} as ClientConfig))
|
),
|
||||||
}))
|
}),
|
||||||
}}>update frpc</Button>
|
)
|
||||||
<Button onClick={async () => {
|
}}
|
||||||
console.log("attempting delete frpc:", await deleteClient({ clientId: clientID }))
|
>
|
||||||
}}>delete frpc</Button>
|
update frpc
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('attempting delete frpc:', await deleteClient({ clientId: clientID }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
delete frpc
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useState } from "react"
|
import { useState } from 'react'
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { initClient, listClient } from "@/api/client"
|
import { initClient, listClient } from '@/api/client'
|
||||||
import { Label } from "./ui/label"
|
import { Label } from './ui/label'
|
||||||
import { Input } from "./ui/input"
|
import { Input } from './ui/input'
|
||||||
import { Button } from "./ui/button"
|
import { Button } from './ui/button'
|
||||||
import { useToast } from "./ui/use-toast"
|
import { useToast } from './ui/use-toast'
|
||||||
import { RespCode } from "@/lib/pb/common"
|
import { RespCode } from '@/lib/pb/common'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from '@/components/ui/dialog'
|
||||||
|
|
||||||
export const CreateClientDialog = () => {
|
export const CreateClientDialog = () => {
|
||||||
const [clientID, setClientID] = useState<string | undefined>()
|
const [clientID, setClientID] = useState<string | undefined>()
|
||||||
@@ -22,32 +22,34 @@ export const CreateClientDialog = () => {
|
|||||||
mutationFn: initClient,
|
mutationFn: initClient,
|
||||||
})
|
})
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ["listClient", { pageIndex: 0, pageSize: 10 }],
|
queryKey: ['listClient', { pageIndex: 0, pageSize: 10 }],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await listClient({ page: 1, pageSize: 10 })
|
return await listClient({ page: 1, pageSize: 10 })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
const handleNewClient = async () => {
|
const handleNewClient = async () => {
|
||||||
toast({ title: "已提交创建请求" })
|
toast({ title: '已提交创建请求' })
|
||||||
try {
|
try {
|
||||||
let resp = await newClient.mutateAsync({ clientId: clientID })
|
let resp = await newClient.mutateAsync({ clientId: clientID })
|
||||||
if (resp.status?.code !== RespCode.SUCCESS) {
|
if (resp.status?.code !== RespCode.SUCCESS) {
|
||||||
toast({ title: "创建客户端失败" })
|
toast({ title: '创建客户端失败' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toast({ title: "创建客户端成功" })
|
toast({ title: '创建客户端成功' })
|
||||||
dataQuery.refetch()
|
dataQuery.refetch()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ title: "创建客户端失败" })
|
toast({ title: '创建客户端失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" size={"sm"}>新建</Button>
|
<Button variant="outline" size={'sm'}>
|
||||||
|
新建
|
||||||
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ColumnDef, Table } from "@tanstack/react-table"
|
import { ColumnDef, Table } from '@tanstack/react-table'
|
||||||
import { MoreHorizontal } from "lucide-react"
|
import { MoreHorizontal } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from '@/components/ui/dialog'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -19,23 +19,23 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { useToast } from "./ui/use-toast"
|
import { useToast } from './ui/use-toast'
|
||||||
import React, { useState } from "react"
|
import React, { useState } from 'react'
|
||||||
import { ExecCommandStr, LinuxInstallCommand, WindowsInstallCommand } from "@/lib/consts"
|
import { ExecCommandStr, LinuxInstallCommand, WindowsInstallCommand } from '@/lib/consts'
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { deleteClient, listClient } from "@/api/client"
|
import { deleteClient, listClient } from '@/api/client'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from '@nanostores/react'
|
||||||
import { $platformInfo } from "@/store/user"
|
import { $platformInfo } from '@/store/user'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'
|
||||||
import { getClientsStatus } from "@/api/platform"
|
import { getClientsStatus } from '@/api/platform'
|
||||||
import { ClientType } from "@/lib/pb/common"
|
import { ClientType } from '@/lib/pb/common'
|
||||||
import { ClientStatus, ClientStatus_Status } from "@/lib/pb/api_master"
|
import { ClientStatus, ClientStatus_Status } from '@/lib/pb/api_master'
|
||||||
|
|
||||||
export type ClientTableSchema = {
|
export type ClientTableSchema = {
|
||||||
id: string,
|
id: string
|
||||||
status: "invalid" | "valid"
|
status: 'invalid' | 'valid'
|
||||||
secret: string
|
secret: string
|
||||||
info?: string
|
info?: string
|
||||||
config?: string
|
config?: string
|
||||||
@@ -43,58 +43,65 @@ export type ClientTableSchema = {
|
|||||||
|
|
||||||
export const columns: ColumnDef<ClientTableSchema>[] = [
|
export const columns: ColumnDef<ClientTableSchema>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "id",
|
accessorKey: 'id',
|
||||||
header: "ID",
|
header: 'ID',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <ClientID client={row.original} />
|
return <ClientID client={row.original} />
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "status",
|
accessorKey: 'status',
|
||||||
header: "是否配置",
|
header: '是否配置',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const client = row.original
|
const client = row.original
|
||||||
return <div className={`font-medium ${client.status === "valid" ? "text-green-500" : "text-red-500"} min-w-12`}>{
|
return (
|
||||||
|
<div className={`font-medium ${client.status === 'valid' ? 'text-green-500' : 'text-red-500'} min-w-12`}>
|
||||||
{
|
{
|
||||||
valid: "已配置",
|
{
|
||||||
invalid: "未配置",
|
valid: '已配置',
|
||||||
|
invalid: '未配置',
|
||||||
}[client.status]
|
}[client.status]
|
||||||
}</div>
|
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "info",
|
accessorKey: 'info',
|
||||||
header: "运行信息",
|
header: '运行信息',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const client = row.original
|
const client = row.original
|
||||||
return <ClientInfo client={client} />
|
return <ClientInfo client={client} />
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "secret",
|
accessorKey: 'secret',
|
||||||
header: "连接密钥",
|
header: '连接密钥',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const client = row.original
|
const client = row.original
|
||||||
return <ClientSecret client={client} />
|
return <ClientSecret client={client} />
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "action",
|
id: 'action',
|
||||||
cell: ({ row, table }) => {
|
cell: ({ row, table }) => {
|
||||||
const client = row.original
|
const client = row.original
|
||||||
return (<ClientActions client={client} table={table} />)
|
return <ClientActions client={client} table={table} />
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
return <Popover >
|
return (
|
||||||
<PopoverTrigger asChild><div className="font-mono">{client.id}</div></PopoverTrigger>
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div className="font-mono">{client.id}</div>
|
||||||
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-fit overflow-auto max-w-64">
|
<PopoverContent className="w-fit overflow-auto max-w-64">
|
||||||
<div>Linux安装到systemd</div>
|
<div>Linux安装到systemd</div>
|
||||||
<div className="p-2 border rounded font-mono w-fit">
|
<div className="p-2 border rounded font-mono w-fit">
|
||||||
{platformInfo === undefined ? "获取平台信息失败" : LinuxInstallCommand("client", client, platformInfo)}
|
{platformInfo === undefined ? '获取平台信息失败' : LinuxInstallCommand('client', client, platformInfo)}
|
||||||
</div>
|
</div>
|
||||||
{/* <div>Windows</div>
|
{/* <div>Windows</div>
|
||||||
<div className="p-2 border rounded font-mono w-fit">
|
<div className="p-2 border rounded font-mono w-fit">
|
||||||
@@ -102,68 +109,78 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
</div> */}
|
</div> */}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientInfo = ({ client }: { client: ClientTableSchema }) => {
|
export const ClientInfo = ({ client }: { client: ClientTableSchema }) => {
|
||||||
const clientsInfo = useQuery({
|
const clientsInfo = useQuery({
|
||||||
queryKey: ["getClientsStatus", [client.id]],
|
queryKey: ['getClientsStatus', [client.id]],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await getClientsStatus({
|
return await getClientsStatus({
|
||||||
clientIds: [client.id],
|
clientIds: [client.id],
|
||||||
clientType: ClientType.FRPC,
|
clientType: ClientType.FRPC,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const trans = (info: ClientStatus | undefined) => {
|
const trans = (info: ClientStatus | undefined) => {
|
||||||
let statusText: "在线" | "离线" | "错误" | "未知" = "未知";
|
let statusText: '在线' | '离线' | '错误' | '未知' = '未知'
|
||||||
if (info === undefined) {
|
if (info === undefined) {
|
||||||
return statusText;
|
return statusText
|
||||||
}
|
}
|
||||||
if (info.status === ClientStatus_Status.ONLINE) {
|
if (info.status === ClientStatus_Status.ONLINE) {
|
||||||
statusText = "在线";
|
statusText = '在线'
|
||||||
} else if (info.status === ClientStatus_Status.OFFLINE) {
|
} else if (info.status === ClientStatus_Status.OFFLINE) {
|
||||||
statusText = "离线";
|
statusText = '离线'
|
||||||
} else if (info.status === ClientStatus_Status.ERROR) {
|
} else if (info.status === ClientStatus_Status.ERROR) {
|
||||||
statusText = "错误";
|
statusText = '错误'
|
||||||
} return statusText;
|
}
|
||||||
|
return statusText
|
||||||
}
|
}
|
||||||
|
|
||||||
const infoColor = clientsInfo.data?.clients[client.id]?.status === ClientStatus_Status.ONLINE ? "text-green-500" : "text-red-500"
|
const infoColor =
|
||||||
|
clientsInfo.data?.clients[client.id]?.status === ClientStatus_Status.ONLINE ? 'text-green-500' : 'text-red-500'
|
||||||
|
|
||||||
return <div className={`p-2 border rounded font-mono w-fit ${infoColor}`}>
|
return (
|
||||||
|
<div className={`p-2 border rounded font-mono w-fit ${infoColor}`}>
|
||||||
{`${clientsInfo.data?.clients[client.id].ping}ms, ${trans(clientsInfo.data?.clients[client.id])}`}
|
{`${clientsInfo.data?.clients[client.id].ping}ms, ${trans(clientsInfo.data?.clients[client.id])}`}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientSecret = ({ client }: { client: ClientTableSchema }) => {
|
export const ClientSecret = ({ client }: { client: ClientTableSchema }) => {
|
||||||
const [showSecrect, setShowSecrect] = useState<boolean>(false)
|
const [showSecrect, setShowSecrect] = useState<boolean>(false)
|
||||||
const fakeSecret = Array.from({ length: client.secret.length }).map(() => '*').join('')
|
const fakeSecret = Array.from({ length: client.secret.length })
|
||||||
|
.map(() => '*')
|
||||||
|
.join('')
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
return <Popover >
|
return (
|
||||||
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div
|
<div
|
||||||
onMouseEnter={() => setShowSecrect(true)}
|
onMouseEnter={() => setShowSecrect(true)}
|
||||||
onMouseLeave={() => setShowSecrect(false)}
|
onMouseLeave={() => setShowSecrect(false)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (platformInfo) {
|
if (platformInfo) {
|
||||||
navigator.clipboard.writeText(ExecCommandStr("client", client, platformInfo));
|
navigator.clipboard.writeText(ExecCommandStr('client', client, platformInfo))
|
||||||
toast({ description: "复制成功", });
|
toast({ description: '复制成功' })
|
||||||
} else {
|
} else {
|
||||||
toast({ description: "获取平台信息失败", });
|
toast({ description: '获取平台信息失败' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="font-medium hover:rounded hover:bg-slate-100 p-2 font-mono whitespace-nowrap">{
|
className="font-medium hover:rounded hover:bg-slate-100 p-2 font-mono whitespace-nowrap"
|
||||||
showSecrect ? client.secret : fakeSecret
|
>
|
||||||
}</div>
|
{showSecrect ? client.secret : fakeSecret}
|
||||||
|
</div>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-fit overflow-auto max-w-48">
|
<PopoverContent className="w-fit overflow-auto max-w-48">
|
||||||
<div className="p-2 border rounded font-mono w-fit">
|
<div className="p-2 border rounded font-mono w-fit">
|
||||||
{platformInfo === undefined ? "获取平台信息失败" : ExecCommandStr("client", client, platformInfo)}
|
{platformInfo === undefined ? '获取平台信息失败' : ExecCommandStr('client', client, platformInfo)}
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientItemProps {
|
export interface ClientItemProps {
|
||||||
@@ -173,7 +190,7 @@ export interface ClientItemProps {
|
|||||||
|
|
||||||
export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
const fetchDataOptions = {
|
const fetchDataOptions = {
|
||||||
pageIndex: table.getState().pagination.pageIndex,
|
pageIndex: table.getState().pagination.pageIndex,
|
||||||
@@ -181,27 +198,28 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ["listClient", fetchDataOptions],
|
queryKey: ['listClient', fetchDataOptions],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await listClient({
|
return await listClient({
|
||||||
page: fetchDataOptions.pageIndex + 1,
|
page: fetchDataOptions.pageIndex + 1,
|
||||||
pageSize: fetchDataOptions.pageSize
|
pageSize: fetchDataOptions.pageSize,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeClient = useMutation({
|
const removeClient = useMutation({
|
||||||
mutationFn: deleteClient,
|
mutationFn: deleteClient,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast({ description: "删除成功" })
|
toast({ description: '删除成功' })
|
||||||
dataQuery.refetch()
|
dataQuery.refetch()
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({ description: "删除失败" })
|
toast({ description: '删除失败' })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return <Dialog>
|
return (
|
||||||
|
<Dialog>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
@@ -214,19 +232,23 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (platformInfo) {
|
if (platformInfo) {
|
||||||
navigator.clipboard.writeText(ExecCommandStr("client", client, platformInfo));
|
navigator.clipboard.writeText(ExecCommandStr('client', client, platformInfo))
|
||||||
toast({ description: "复制成功,如果复制不成功,请点击ID字段手动复制", });
|
toast({ description: '复制成功,如果复制不成功,请点击ID字段手动复制' })
|
||||||
} else {
|
} else {
|
||||||
toast({ description: "获取平台信息失败,如果复制不成功,请点击ID字段手动复制", });
|
toast({ description: '获取平台信息失败,如果复制不成功,请点击ID字段手动复制' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
复制启动命令
|
复制启动命令
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={() => {
|
<DropdownMenuItem
|
||||||
router.push({ pathname: "/clientedit", query: { clientID: client.id } })
|
onClick={() => {
|
||||||
}}>修改</DropdownMenuItem>
|
router.push({ pathname: '/clientedit', query: { clientID: client.id } })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</DropdownMenuItem>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<DropdownMenuItem className="text-destructive">删除</DropdownMenuItem>
|
<DropdownMenuItem className="text-destructive">删除</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
@@ -236,17 +258,20 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>确定删除该客户端?</DialogTitle>
|
<DialogTitle>确定删除该客户端?</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<p className="text-destructive">
|
<p className="text-destructive">此操作无法撤消。您确定要永久从我们的服务器中删除该客户端?</p>
|
||||||
此操作无法撤消。您确定要永久从我们的服务器中删除该客户端?
|
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">
|
||||||
|
删除后运行中的客户端将无法通过现有参数再次连接,如果您需要删除客户端对外的连接,可以选择清空配置
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">删除后运行中的客户端将无法通过现有参数再次连接,如果您需要删除客户端对外的连接,可以选择清空配置</p>
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type="submit" onClick={() => removeClient.mutate({ clientId: client.id })}>确定</Button>
|
<Button type="submit" onClick={() => removeClient.mutate({ clientId: client.id })}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Client } from "@/lib/pb/common";
|
import { Client } from '@/lib/pb/common'
|
||||||
import { ClientTableSchema, columns as clientColumnsDef } from "./client_item";
|
import { ClientTableSchema, columns as clientColumnsDef } from './client_item'
|
||||||
import { DataTable } from "./data_table";
|
import { DataTable } from './data_table'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
@@ -11,11 +11,11 @@ import {
|
|||||||
getPaginationRowModel,
|
getPaginationRowModel,
|
||||||
SortingState,
|
SortingState,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
} from "@tanstack/react-table"
|
} from '@tanstack/react-table'
|
||||||
|
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { listClient } from "@/api/client";
|
import { listClient } from '@/api/client'
|
||||||
|
|
||||||
export interface ClientListProps {
|
export interface ClientListProps {
|
||||||
Clients: Client[]
|
Clients: Client[]
|
||||||
@@ -23,19 +23,18 @@ export interface ClientListProps {
|
|||||||
|
|
||||||
export const ClientList: React.FC<ClientListProps> = ({ Clients }) => {
|
export const ClientList: React.FC<ClientListProps> = ({ Clients }) => {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
||||||
[]
|
const data = Clients.map(
|
||||||
)
|
(client) =>
|
||||||
const data = Clients.map((client) =>
|
|
||||||
({
|
({
|
||||||
id: client.id == undefined ? "" : client.id,
|
id: client.id == undefined ? '' : client.id,
|
||||||
status: client.config == undefined || client.config == "" ? "invalid" : "valid",
|
status: client.config == undefined || client.config == '' ? 'invalid' : 'valid',
|
||||||
secret: client.secret == undefined ? "" : client.secret,
|
secret: client.secret == undefined ? '' : client.secret,
|
||||||
config: client.config
|
config: client.config,
|
||||||
} as ClientTableSchema))
|
}) as ClientTableSchema,
|
||||||
|
)
|
||||||
|
|
||||||
const [{ pageIndex, pageSize }, setPagination] =
|
const [{ pageIndex, pageSize }, setPagination] = React.useState<PaginationState>({
|
||||||
React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
})
|
})
|
||||||
@@ -49,26 +48,29 @@ export const ClientList: React.FC<ClientListProps> = ({ Clients }) => {
|
|||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
}),
|
}),
|
||||||
[pageIndex, pageSize]
|
[pageIndex, pageSize],
|
||||||
)
|
)
|
||||||
|
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ["listClient", fetchDataOptions],
|
queryKey: ['listClient', fetchDataOptions],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await listClient({ page: fetchDataOptions.pageIndex + 1, pageSize: fetchDataOptions.pageSize })
|
return await listClient({ page: fetchDataOptions.pageIndex + 1, pageSize: fetchDataOptions.pageSize })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataQuery.data?.clients.map((client) => {
|
data:
|
||||||
|
dataQuery.data?.clients.map((client) => {
|
||||||
return {
|
return {
|
||||||
id: client.id == undefined ? "" : client.id,
|
id: client.id == undefined ? '' : client.id,
|
||||||
status: client.config == undefined || client.config == "" ? "invalid" : "valid",
|
status: client.config == undefined || client.config == '' ? 'invalid' : 'valid',
|
||||||
secret: client.secret == undefined ? "" : client.secret,
|
secret: client.secret == undefined ? '' : client.secret,
|
||||||
config: client.config
|
config: client.config,
|
||||||
} as ClientTableSchema
|
} as ClientTableSchema
|
||||||
}) ?? data,
|
}) ?? data,
|
||||||
pageCount: Math.ceil((dataQuery.data?.total == undefined ? 0 : dataQuery.data?.total) / fetchDataOptions.pageSize ?? 0),
|
pageCount: Math.ceil(
|
||||||
|
(dataQuery.data?.total == undefined ? 0 : dataQuery.data?.total) / fetchDataOptions.pageSize ?? 0,
|
||||||
|
),
|
||||||
columns: clientColumnsDef,
|
columns: clientColumnsDef,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
@@ -85,4 +87,4 @@ export const ClientList: React.FC<ClientListProps> = ({ Clients }) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
return <DataTable table={table} columns={clientColumnsDef} />
|
return <DataTable table={table} columns={clientColumnsDef} />
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
import {
|
import { ArrowDownIcon, ArrowUpIcon, CaretSortIcon, EyeNoneIcon } from '@radix-ui/react-icons'
|
||||||
ArrowDownIcon,
|
import { Column } from '@tanstack/react-table'
|
||||||
ArrowUpIcon,
|
|
||||||
CaretSortIcon,
|
|
||||||
EyeNoneIcon,
|
|
||||||
} from "@radix-ui/react-icons"
|
|
||||||
import { Column } from "@tanstack/react-table"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
|
||||||
interface DataTableColumnHeaderProps<TData, TValue>
|
interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
extends React.HTMLAttributes<HTMLDivElement> {
|
|
||||||
column: Column<TData, TValue>
|
column: Column<TData, TValue>
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
@@ -32,18 +26,14 @@ export function DataTableColumnHeader<TData, TValue>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center space-x-2", className)}>
|
<div className={cn('flex items-center space-x-2', className)}>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button variant="ghost" size="sm" className="-ml-3 h-8 data-[state=open]:bg-accent">
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
|
||||||
>
|
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
{column.getIsSorted() === "desc" ? (
|
{column.getIsSorted() === 'desc' ? (
|
||||||
<ArrowDownIcon className="ml-2 h-4 w-4" />
|
<ArrowDownIcon className="ml-2 h-4 w-4" />
|
||||||
) : column.getIsSorted() === "asc" ? (
|
) : column.getIsSorted() === 'asc' ? (
|
||||||
<ArrowUpIcon className="ml-2 h-4 w-4" />
|
<ArrowUpIcon className="ml-2 h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
<CaretSortIcon className="ml-2 h-4 w-4" />
|
<CaretSortIcon className="ml-2 h-4 w-4" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
'use client'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
@@ -11,20 +11,13 @@ import {
|
|||||||
getPaginationRowModel,
|
getPaginationRowModel,
|
||||||
SortingState,
|
SortingState,
|
||||||
Table as TableType,
|
Table as TableType,
|
||||||
} from "@tanstack/react-table"
|
} from '@tanstack/react-table'
|
||||||
|
|
||||||
import {
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table"
|
|
||||||
|
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from '@/components/ui/input'
|
||||||
import { DataTablePagination } from "./data_table_pagination"
|
import { DataTablePagination } from './data_table_pagination'
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
interface DataTableProps<TData, TValue> {
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
@@ -33,24 +26,19 @@ interface DataTableProps<TData, TValue> {
|
|||||||
table: TableType<TData>
|
table: TableType<TData>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<TData, TValue>({
|
export function DataTable<TData, TValue>({ columns, filterColumnName, table }: DataTableProps<TData, TValue>) {
|
||||||
columns,
|
|
||||||
filterColumnName,
|
|
||||||
table,
|
|
||||||
}: DataTableProps<TData, TValue>) {
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{filterColumnName && <div className="flex flex-1 items-center py-4">
|
{filterColumnName && (
|
||||||
|
<div className="flex flex-1 items-center py-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder={`根据 ${filterColumnName} 筛选`}
|
placeholder={`根据 ${filterColumnName} 筛选`}
|
||||||
value={(table.getColumn(filterColumnName)?.getFilterValue() as string) ?? ""}
|
value={(table.getColumn(filterColumnName)?.getFilterValue() as string) ?? ''}
|
||||||
onChange={(event) =>
|
onChange={(event) => table.getColumn(filterColumnName)?.setFilterValue(event.target.value)}
|
||||||
table.getColumn(filterColumnName)?.setFilterValue(event.target.value)
|
|
||||||
}
|
|
||||||
className="max-w-sm"
|
className="max-w-sm"
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -59,12 +47,7 @@ export function DataTable<TData, TValue>({
|
|||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<TableHead key={header.id}>
|
<TableHead key={header.id}>
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -74,14 +57,9 @@ export function DataTable<TData, TValue>({
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{table.getRowModel().rows?.length ? (
|
{table.getRowModel().rows?.length ? (
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<TableRow
|
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell key={cell.id}>
|
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
import {
|
import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons'
|
||||||
ChevronLeftIcon,
|
import { Table } from '@tanstack/react-table'
|
||||||
ChevronRightIcon,
|
|
||||||
DoubleArrowLeftIcon,
|
|
||||||
DoubleArrowRightIcon,
|
|
||||||
} from "@radix-ui/react-icons"
|
|
||||||
import { Table } from "@tanstack/react-table"
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select"
|
|
||||||
|
|
||||||
interface DataTablePaginationProps<TData> {
|
interface DataTablePaginationProps<TData> {
|
||||||
table: Table<TData>
|
table: Table<TData>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTablePagination<TData>({
|
export function DataTablePagination<TData>({ table }: DataTablePaginationProps<TData>) {
|
||||||
table,
|
|
||||||
}: DataTablePaginationProps<TData>) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between px-2">
|
<div className="flex items-center justify-between px-2">
|
||||||
{/* <div className="flex-1 text-sm text-muted-foreground">
|
{/* <div className="flex-1 text-sm text-muted-foreground">
|
||||||
@@ -50,8 +37,7 @@ export function DataTablePagination<TData>({
|
|||||||
<p className="text-sm font-medium">行 每页</p>
|
<p className="text-sm font-medium">行 每页</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-[120px] items-center justify-center text-sm font-medium">
|
<div className="flex w-[120px] items-center justify-center text-sm font-medium">
|
||||||
第 {table.getState().pagination.pageIndex + 1} 页, 共{" "}
|
第 {table.getState().pagination.pageIndex + 1} 页, 共 {table.getPageCount()}页
|
||||||
{table.getPageCount()}页
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,33 +1,24 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from 'react'
|
||||||
import { useState } from "react"
|
import { useState } from 'react'
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
Select,
|
import { Label } from '@radix-ui/react-label'
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select"
|
|
||||||
import { Label } from "@radix-ui/react-label"
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { listServer } from "@/api/server"
|
import { listServer } from '@/api/server'
|
||||||
import { getClient, listClient } from "@/api/client"
|
import { getClient, listClient } from '@/api/client'
|
||||||
import {
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
Card,
|
import { Switch } from './ui/switch'
|
||||||
CardContent,
|
import { FRPCEditor } from './frpc_editor'
|
||||||
CardDescription,
|
import { FRPCForm } from './frpc_form'
|
||||||
CardHeader,
|
import { useSearchParams } from 'next/navigation'
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card"
|
|
||||||
import { Switch } from "./ui/switch"
|
|
||||||
import { FRPCEditor } from "./frpc_editor"
|
|
||||||
import { FRPCForm } from "./frpc_form"
|
|
||||||
import { useSearchParams } from "next/navigation"
|
|
||||||
|
|
||||||
export interface FRPCFormCardProps {
|
export interface FRPCFormCardProps {
|
||||||
clientID?: string
|
clientID?: string
|
||||||
serverID?: string
|
serverID?: string
|
||||||
}
|
}
|
||||||
export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({ clientID: defaultClientID, serverID: defaultServerID }: FRPCFormCardProps) => {
|
export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({
|
||||||
|
clientID: defaultClientID,
|
||||||
|
serverID: defaultServerID,
|
||||||
|
}: FRPCFormCardProps) => {
|
||||||
const [advanceMode, setAdvanceMode] = useState<boolean>(false)
|
const [advanceMode, setAdvanceMode] = useState<boolean>(false)
|
||||||
const [clientID, setClientID] = useState<string | undefined>()
|
const [clientID, setClientID] = useState<string | undefined>()
|
||||||
const [serverID, setServerID] = useState<string | undefined>()
|
const [serverID, setServerID] = useState<string | undefined>()
|
||||||
@@ -51,21 +42,24 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({ clientID: defaultCli
|
|||||||
}, [defaultClientID, defaultServerID])
|
}, [defaultClientID, defaultServerID])
|
||||||
|
|
||||||
const { data: serverList, refetch: refetchServers } = useQuery({
|
const { data: serverList, refetch: refetchServers } = useQuery({
|
||||||
queryKey: ["listServer"], queryFn: () => {
|
queryKey: ['listServer'],
|
||||||
|
queryFn: () => {
|
||||||
return listServer({ page: 1, pageSize: 500 })
|
return listServer({ page: 1, pageSize: 500 })
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const { data: clientList, refetch: refetchClients } = useQuery({
|
const { data: clientList, refetch: refetchClients } = useQuery({
|
||||||
queryKey: ["listClient"], queryFn: () => {
|
queryKey: ['listClient'],
|
||||||
|
queryFn: () => {
|
||||||
return listClient({ page: 1, pageSize: 500 })
|
return listClient({ page: 1, pageSize: 500 })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: client, refetch: refetchClient } = useQuery({
|
const { data: client, refetch: refetchClient } = useQuery({
|
||||||
queryKey: ["getClient", clientID], queryFn: () => {
|
queryKey: ['getClient', clientID],
|
||||||
|
queryFn: () => {
|
||||||
return getClient({ clientId: clientID })
|
return getClient({ clientId: clientID })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -84,19 +78,15 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({ clientID: defaultCli
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className=" flex items-center space-x-4 rounded-md border p-4">
|
<div className=" flex items-center space-x-4 rounded-md border p-4">
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<p className="text-sm font-medium leading-none">
|
<p className="text-sm font-medium leading-none">高级模式</p>
|
||||||
高级模式
|
<p className="text-sm text-muted-foreground">编辑客户端原始配置文件</p>
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
编辑客户端原始配置文件
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Switch onCheckedChange={setAdvanceMode} />
|
<Switch onCheckedChange={setAdvanceMode} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full pt-2">
|
<div className="flex flex-col w-full pt-2">
|
||||||
|
|
||||||
<Label className="text-sm font-medium">服务端</Label>
|
<Label className="text-sm font-medium">服务端</Label>
|
||||||
<Select onValueChange={handleServerChange}
|
<Select
|
||||||
|
onValueChange={handleServerChange}
|
||||||
value={serverID}
|
value={serverID}
|
||||||
onOpenChange={() => {
|
onOpenChange={() => {
|
||||||
refetchServers()
|
refetchServers()
|
||||||
@@ -108,12 +98,18 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({ clientID: defaultCli
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{serverList?.servers.map(
|
{serverList?.servers.map(
|
||||||
server => server.id && <SelectItem key={server.id} value={server.id}>{server.id}
|
(server) =>
|
||||||
</SelectItem>)}
|
server.id && (
|
||||||
|
<SelectItem key={server.id} value={server.id}>
|
||||||
|
{server.id}
|
||||||
|
</SelectItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Label className="text-sm font-medium">客户端</Label>
|
<Label className="text-sm font-medium">客户端</Label>
|
||||||
<Select onValueChange={handleClientChange}
|
<Select
|
||||||
|
onValueChange={handleClientChange}
|
||||||
value={clientID}
|
value={clientID}
|
||||||
onOpenChange={() => {
|
onOpenChange={() => {
|
||||||
refetchServers()
|
refetchServers()
|
||||||
@@ -125,8 +121,13 @@ export const FRPCFormCard: React.FC<FRPCFormCardProps> = ({ clientID: defaultCli
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{clientList?.clients.map(
|
{clientList?.clients.map(
|
||||||
client => client.id && <SelectItem key={client.id} value={client.id}>{client.id}
|
(client) =>
|
||||||
</SelectItem>)}
|
client.id && (
|
||||||
|
<SelectItem key={client.id} value={client.id}>
|
||||||
|
{client.id}
|
||||||
|
</SelectItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,70 +1,94 @@
|
|||||||
import { Label } from "@radix-ui/react-label"
|
import { Label } from '@radix-ui/react-label'
|
||||||
import { Textarea } from "./ui/textarea"
|
import { Textarea } from './ui/textarea'
|
||||||
import { FRPCFormProps } from "./frpc_form"
|
import { FRPCFormProps } from './frpc_form'
|
||||||
import { getClient } from "@/api/client";
|
import { getClient } from '@/api/client'
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from 'react'
|
||||||
import { Button } from "./ui/button";
|
import { Button } from './ui/button'
|
||||||
import { updateFRPC } from "@/api/frp";
|
import { updateFRPC } from '@/api/frp'
|
||||||
import { useToast } from "./ui/use-toast";
|
import { useToast } from './ui/use-toast'
|
||||||
import { RespCode } from "@/lib/pb/common";
|
import { RespCode } from '@/lib/pb/common'
|
||||||
|
|
||||||
export const FRPCEditor: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
export const FRPCEditor: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const { data: client, refetch: refetchClient } = useQuery({
|
const { data: client, refetch: refetchClient } = useQuery({
|
||||||
queryKey: ["getClient", clientID], queryFn: () => {
|
queryKey: ['getClient', clientID],
|
||||||
|
queryFn: () => {
|
||||||
return getClient({ clientId: clientID })
|
return getClient({ clientId: clientID })
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const [configContent, setConfigContent] = useState<string>("{}")
|
const [configContent, setConfigContent] = useState<string>('{}')
|
||||||
const updateFrpc = useMutation({ mutationFn: updateFRPC, })
|
const updateFrpc = useMutation({ mutationFn: updateFRPC })
|
||||||
const [editorValue, setEditorValue] = useState<string>("")
|
const [editorValue, setEditorValue] = useState<string>('')
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await updateFrpc.mutateAsync({
|
let res = await updateFrpc.mutateAsync({
|
||||||
clientId: clientID, config: Buffer.from(editorValue), serverId: serverID
|
clientId: clientID,
|
||||||
|
config: Buffer.from(editorValue),
|
||||||
|
serverId: serverID,
|
||||||
})
|
})
|
||||||
if (res.status?.code !== RespCode.SUCCESS) {
|
if (res.status?.code !== RespCode.SUCCESS) {
|
||||||
toast({ title: "更新失败" })
|
toast({ title: '更新失败' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toast({ title: "更新成功" })
|
toast({ title: '更新成功' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ title: "更新失败" })
|
toast({ title: '更新失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refetchClient()
|
refetchClient()
|
||||||
try {
|
try {
|
||||||
setConfigContent(JSON.stringify(JSON.parse(client?.client?.config == undefined ? "{}" ||
|
setConfigContent(
|
||||||
client?.client?.config == "" : client?.client?.config), null, 2))
|
JSON.stringify(
|
||||||
setEditorValue(JSON.stringify(JSON.parse(client?.client?.config == undefined ||
|
JSON.parse(
|
||||||
client?.client?.config == "" ? "{}" : client?.client?.config), null, 2))
|
client?.client?.config == undefined ? '{}' || client?.client?.config == '' : client?.client?.config,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setEditorValue(
|
||||||
|
JSON.stringify(
|
||||||
|
JSON.parse(
|
||||||
|
client?.client?.config == undefined || client?.client?.config == '' ? '{}' : client?.client?.config,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setConfigContent("{}")
|
setConfigContent('{}')
|
||||||
setEditorValue("{}")
|
setEditorValue('{}')
|
||||||
}
|
}
|
||||||
}, [client, refetchClient])
|
}, [client, refetchClient])
|
||||||
|
|
||||||
return (<div className="grid w-full gap-1.5">
|
return (
|
||||||
|
<div className="grid w-full gap-1.5">
|
||||||
<Label className="text-sm font-medium">客户端 {clientID} 配置文件`frpc.json`内容</Label>
|
<Label className="text-sm font-medium">客户端 {clientID} 配置文件`frpc.json`内容</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
只需要配置proxies和visitors字段,认证信息和服务器连接信息会由系统补全
|
只需要配置proxies和visitors字段,认证信息和服务器连接信息会由系统补全
|
||||||
</p>
|
</p>
|
||||||
<Textarea key={configContent} placeholder="配置文件内容" id="message"
|
<Textarea
|
||||||
|
key={configContent}
|
||||||
|
placeholder="配置文件内容"
|
||||||
|
id="message"
|
||||||
defaultValue={configContent}
|
defaultValue={configContent}
|
||||||
onChange={(e) => setEditorValue(e.target.value)}
|
onChange={(e) => setEditorValue(e.target.value)}
|
||||||
className="h-72"
|
className="h-72"
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-2 gap-2 mt-1">
|
<div className="grid grid-cols-2 gap-2 mt-1">
|
||||||
<Button size="sm" onClick={handleSubmit} >提交</Button>
|
<Button size="sm" onClick={handleSubmit}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
{/* <Button variant="outline" size="sm" onClick={async () => {
|
{/* <Button variant="outline" size="sm" onClick={async () => {
|
||||||
await refetchClient()
|
await refetchClient()
|
||||||
setConfigContent(client?.client?.config == undefined ? "{}" : client?.client?.config)
|
setConfigContent(client?.client?.config == undefined ? "{}" : client?.client?.config)
|
||||||
}}>加载服务端配置</Button> */}
|
}}>加载服务端配置</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,24 @@
|
|||||||
import { ProxyType, TypedProxyConfig } from "@/types/proxy"
|
import { ProxyType, TypedProxyConfig } from '@/types/proxy'
|
||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from 'react'
|
||||||
import { useState } from "react"
|
import { useState } from 'react'
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
Select,
|
import { Label } from '@radix-ui/react-label'
|
||||||
SelectContent,
|
import { HTTPProxyForm, TCPProxyForm, UDPProxyForm } from './proxy_form'
|
||||||
SelectItem,
|
import { useQuery } from '@tanstack/react-query'
|
||||||
SelectTrigger,
|
import { getClient } from '@/api/client'
|
||||||
SelectValue,
|
import { useStore } from '@nanostores/react'
|
||||||
} from "@/components/ui/select"
|
import { $clientProxyConfigs } from '@/store/proxy'
|
||||||
import { Label } from "@radix-ui/react-label"
|
import { Button } from './ui/button'
|
||||||
import { HTTPProxyForm, TCPProxyForm, UDPProxyForm } from "./proxy_form"
|
import { RespCode } from '@/lib/pb/common'
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { ClientConfig } from '@/types/client'
|
||||||
import { getClient } from "@/api/client"
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
import { useStore } from "@nanostores/react"
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'
|
||||||
import { $clientProxyConfigs } from "@/store/proxy"
|
import { Input } from './ui/input'
|
||||||
import { Button } from "./ui/button"
|
import { AccordionHeader } from '@radix-ui/react-accordion'
|
||||||
import { RespCode } from "@/lib/pb/common"
|
import { useToast } from './ui/use-toast'
|
||||||
import { ClientConfig } from "@/types/client"
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover"
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion"
|
|
||||||
import { Input } from "./ui/input"
|
|
||||||
import { AccordionHeader } from "@radix-ui/react-accordion"
|
|
||||||
import { useToast } from "./ui/use-toast"
|
|
||||||
import { useMutation } from '@tanstack/react-query'
|
import { useMutation } from '@tanstack/react-query'
|
||||||
import { updateFRPC } from "@/api/frp"
|
import { updateFRPC } from '@/api/frp'
|
||||||
import { Card, CardContent } from "./ui/card"
|
import { Card, CardContent } from './ui/card'
|
||||||
|
|
||||||
|
|
||||||
export interface FRPCFormProps {
|
export interface FRPCFormProps {
|
||||||
clientID: string
|
clientID: string
|
||||||
@@ -50,10 +34,11 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data: client, refetch: refetchClient } = useQuery({
|
const { data: client, refetch: refetchClient } = useQuery({
|
||||||
queryKey: ["getClient", clientID], queryFn: () => {
|
queryKey: ['getClient', clientID],
|
||||||
|
queryFn: () => {
|
||||||
return getClient({ clientId: clientID })
|
return getClient({ clientId: clientID })
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const clientProxyConfigs = useStore($clientProxyConfigs)
|
const clientProxyConfigs = useStore($clientProxyConfigs)
|
||||||
|
|
||||||
@@ -66,11 +51,11 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
|||||||
}, [clientID, serverID, client])
|
}, [clientID, serverID, client])
|
||||||
|
|
||||||
const handleAddProxy = () => {
|
const handleAddProxy = () => {
|
||||||
console.log("add proxy", proxyName, proxyType)
|
console.log('add proxy', proxyName, proxyType)
|
||||||
if (!proxyName) return
|
if (!proxyName) return
|
||||||
if (!proxyType) return
|
if (!proxyType) return
|
||||||
if (clientProxyConfigs.findIndex(proxy => proxy.name === proxyName) !== -1) {
|
if (clientProxyConfigs.findIndex((proxy) => proxy.name === proxyName) !== -1) {
|
||||||
toast({ title: "创建隧道状态", description: "名称重复" })
|
toast({ title: '创建隧道状态', description: '名称重复' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const newProxy = {
|
const newProxy = {
|
||||||
@@ -81,23 +66,27 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteProxy = (proxyName: string) => {
|
const handleDeleteProxy = (proxyName: string) => {
|
||||||
const newProxies = clientProxyConfigs.filter(proxy => proxy.name !== proxyName)
|
const newProxies = clientProxyConfigs.filter((proxy) => proxy.name !== proxyName)
|
||||||
$clientProxyConfigs.set(newProxies)
|
$clientProxyConfigs.set(newProxies)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFrpc = useMutation({ mutationFn: updateFRPC, })
|
const updateFrpc = useMutation({ mutationFn: updateFRPC })
|
||||||
|
|
||||||
const handleUpdate = async () => {
|
const handleUpdate = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await updateFrpc.mutateAsync({
|
const res = await updateFrpc.mutateAsync({
|
||||||
config: Buffer.from(JSON.stringify({
|
config: Buffer.from(
|
||||||
|
JSON.stringify({
|
||||||
proxies: clientProxyConfigs,
|
proxies: clientProxyConfigs,
|
||||||
} as ClientConfig)), serverId: serverID, clientId: clientID
|
} as ClientConfig),
|
||||||
|
),
|
||||||
|
serverId: serverID,
|
||||||
|
clientId: clientID,
|
||||||
})
|
})
|
||||||
toast({ title: "更新隧道状态", description: res.status?.code === RespCode.SUCCESS ? "更新成功" : "更新失败" })
|
toast({ title: '更新隧道状态', description: res.status?.code === RespCode.SUCCESS ? '更新成功' : '更新失败' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
toast({ title: "更新隧道状态", description: "更新失败" })
|
toast({ title: '更新隧道状态', description: '更新失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +98,11 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<Label className="text-sm font-medium">名称</Label>
|
<Label className="text-sm font-medium">名称</Label>
|
||||||
<Input onChange={(e) => { setProxyName(e.target.value) }} />
|
<Input
|
||||||
|
onChange={(e) => {
|
||||||
|
setProxyName(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Select onValueChange={handleTypeChange} defaultValue={proxyType}>
|
<Select onValueChange={handleTypeChange} defaultValue={proxyType}>
|
||||||
<Label className="text-sm font-medium">协议</Label>
|
<Label className="text-sm font-medium">协议</Label>
|
||||||
<SelectTrigger className="my-2">
|
<SelectTrigger className="my-2">
|
||||||
@@ -121,50 +114,56 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
|||||||
<SelectItem value="udp">udp</SelectItem>
|
<SelectItem value="udp">udp</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button variant={"outline"} onClick={handleAddProxy}>确定</Button>
|
<Button variant={'outline'} onClick={handleAddProxy}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<div className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-4" key={clientID + serverID + client}>
|
<div className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-4" key={clientID + serverID + client}>
|
||||||
{
|
{clientProxyConfigs.map((item) => {
|
||||||
clientProxyConfigs.map((item) => {
|
return (
|
||||||
return (<Card key={item.name}>
|
<Card key={item.name}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col w-full pt-2">
|
<div className="flex flex-col w-full pt-2">
|
||||||
<Accordion type="single" collapsible>
|
<Accordion type="single" collapsible>
|
||||||
<AccordionItem value={item.name}>
|
<AccordionItem value={item.name}>
|
||||||
<AccordionHeader className="flex flex-row justify-between">
|
<AccordionHeader className="flex flex-row justify-between">
|
||||||
<div>隧道名称:{item.name}</div>
|
<div>隧道名称:{item.name}</div>
|
||||||
<Button variant={"outline"} onClick={() => { handleDeleteProxy(item.name) }}>删除</Button>
|
<Button
|
||||||
|
variant={'outline'}
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteProxy(item.name)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
</AccordionHeader>
|
</AccordionHeader>
|
||||||
<AccordionTrigger>类型:「{item.type}」</AccordionTrigger>
|
<AccordionTrigger>类型:「{item.type}」</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
{
|
{item.type === 'tcp' && serverID && clientID && (
|
||||||
item.type === 'tcp' && serverID && clientID &&
|
|
||||||
<TCPProxyForm
|
<TCPProxyForm
|
||||||
defaultProxyConfig={item}
|
defaultProxyConfig={item}
|
||||||
proxyName={item.name}
|
proxyName={item.name}
|
||||||
serverID={serverID}
|
serverID={serverID}
|
||||||
clientID={clientID}
|
clientID={clientID}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{
|
{item.type === 'udp' && serverID && clientID && (
|
||||||
item.type === 'udp' && serverID && clientID &&
|
|
||||||
<UDPProxyForm
|
<UDPProxyForm
|
||||||
defaultProxyConfig={item}
|
defaultProxyConfig={item}
|
||||||
proxyName={item.name}
|
proxyName={item.name}
|
||||||
serverID={serverID}
|
serverID={serverID}
|
||||||
clientID={clientID}
|
clientID={clientID}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{
|
{item.type === 'http' && serverID && clientID && (
|
||||||
item.type === 'http' && serverID && clientID &&
|
|
||||||
<HTTPProxyForm
|
<HTTPProxyForm
|
||||||
defaultProxyConfig={item}
|
defaultProxyConfig={item}
|
||||||
proxyName={item.name}
|
proxyName={item.name}
|
||||||
serverID={serverID}
|
serverID={serverID}
|
||||||
clientID={clientID}
|
clientID={clientID}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@@ -172,10 +171,16 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID }) => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<Button className="mt-2" onClick={() => { handleUpdate() }}>提交</Button>
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdate()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
Card,
|
import { Label } from './ui/label'
|
||||||
CardContent,
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'
|
||||||
CardDescription,
|
import { getServer, listServer } from '@/api/server'
|
||||||
CardFooter,
|
import { useQuery } from '@tanstack/react-query'
|
||||||
CardHeader,
|
import { Switch } from './ui/switch'
|
||||||
CardTitle,
|
import { FRPSEditor } from './frps_editor'
|
||||||
} from "@/components/ui/card"
|
import FRPSForm from './frps_form'
|
||||||
import { Label } from "./ui/label"
|
import { useSearchParams } from 'next/navigation'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
|
|
||||||
import { getServer, listServer } from "@/api/server"
|
|
||||||
import { useQuery } from "@tanstack/react-query"
|
|
||||||
import { Switch } from "./ui/switch"
|
|
||||||
import { FRPSEditor } from "./frps_editor"
|
|
||||||
import FRPSForm from "./frps_form"
|
|
||||||
import { useSearchParams } from "next/navigation"
|
|
||||||
|
|
||||||
export interface FRPSFormCardProps {
|
export interface FRPSFormCardProps {
|
||||||
serverID?: string
|
serverID?: string
|
||||||
@@ -25,15 +18,17 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const paramServerID = searchParams.get('serverID')
|
const paramServerID = searchParams.get('serverID')
|
||||||
const { data: serverList, refetch: refetchServers } = useQuery({
|
const { data: serverList, refetch: refetchServers } = useQuery({
|
||||||
queryKey: ["listServer"], queryFn: () => {
|
queryKey: ['listServer'],
|
||||||
|
queryFn: () => {
|
||||||
return listServer({ page: 1, pageSize: 100 })
|
return listServer({ page: 1, pageSize: 100 })
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
const { data: server, refetch: refetchServer } = useQuery({
|
const { data: server, refetch: refetchServer } = useQuery({
|
||||||
queryKey: ["getServer", serverID], queryFn: () => {
|
queryKey: ['getServer', serverID],
|
||||||
|
queryFn: () => {
|
||||||
return getServer({ serverId: serverID })
|
return getServer({ serverId: serverID })
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultServerID) {
|
if (defaultServerID) {
|
||||||
@@ -51,7 +46,8 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
setServerID(value)
|
setServerID(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<Card className="w-full">
|
return (
|
||||||
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>服务端配置</CardTitle>
|
<CardTitle>服务端配置</CardTitle>
|
||||||
<CardDescription>选择服务端以管理Frps服务</CardDescription>
|
<CardDescription>选择服务端以管理Frps服务</CardDescription>
|
||||||
@@ -59,18 +55,15 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className=" flex items-center space-x-4 rounded-md border p-4">
|
<div className=" flex items-center space-x-4 rounded-md border p-4">
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<p className="text-sm font-medium leading-none">
|
<p className="text-sm font-medium leading-none">高级模式</p>
|
||||||
高级模式
|
<p className="text-sm text-muted-foreground">编辑服务端原始配置文件</p>
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
编辑服务端原始配置文件
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Switch onCheckedChange={setAdvanceMode} />
|
<Switch onCheckedChange={setAdvanceMode} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full pt-2">
|
<div className="flex flex-col w-full pt-2">
|
||||||
<Label className="text-sm font-medium">服务端</Label>
|
<Label className="text-sm font-medium">服务端</Label>
|
||||||
<Select onValueChange={handleServerChange}
|
<Select
|
||||||
|
onValueChange={handleServerChange}
|
||||||
value={serverID}
|
value={serverID}
|
||||||
onOpenChange={() => {
|
onOpenChange={() => {
|
||||||
refetchServers()
|
refetchServers()
|
||||||
@@ -82,15 +75,22 @@ export const FRPSFormCard: React.FC<FRPSFormCardProps> = ({ serverID: defaultSer
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{serverList?.servers.map(
|
{serverList?.servers.map(
|
||||||
serverItem => serverItem.id && <SelectItem key={serverItem.id} value={serverItem.id}>{serverItem.id}
|
(serverItem) =>
|
||||||
</SelectItem>)}
|
serverItem.id && (
|
||||||
|
<SelectItem key={serverItem.id} value={serverItem.id}>
|
||||||
|
{serverItem.id}
|
||||||
|
</SelectItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
{serverID && server && server.server && !advanceMode && <FRPSForm serverID={serverID} server={server.server} />}
|
{serverID && server && server.server && !advanceMode && <FRPSForm serverID={serverID} server={server.server} />}
|
||||||
{serverID && server && server.server && advanceMode && <FRPSEditor serverID={serverID} server={server.server} />}
|
{serverID && server && server.server && advanceMode && (
|
||||||
|
<FRPSEditor serverID={serverID} server={server.server} />
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter></CardFooter>
|
||||||
</CardFooter>
|
</Card>
|
||||||
</Card>)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,76 +1,104 @@
|
|||||||
import { Label } from "@radix-ui/react-label"
|
import { Label } from '@radix-ui/react-label'
|
||||||
import { Textarea } from "./ui/textarea"
|
import { Textarea } from './ui/textarea'
|
||||||
import { FRPSFormProps } from "./frps_form"
|
import { FRPSFormProps } from './frps_form'
|
||||||
import { Button } from "./ui/button"
|
import { Button } from './ui/button'
|
||||||
import { useToast } from "./ui/use-toast"
|
import { useToast } from './ui/use-toast'
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { getServer } from "@/api/server"
|
import { getServer } from '@/api/server'
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from 'react'
|
||||||
import { updateFRPS } from "@/api/frp"
|
import { updateFRPS } from '@/api/frp'
|
||||||
import { RespCode } from "@/lib/pb/common"
|
import { RespCode } from '@/lib/pb/common'
|
||||||
|
|
||||||
export const FRPSEditor: React.FC<FRPSFormProps> = ({ server, serverID }) => {
|
export const FRPSEditor: React.FC<FRPSFormProps> = ({ server, serverID }) => {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const { data: serverResp, refetch: refetchServer } = useQuery({
|
const { data: serverResp, refetch: refetchServer } = useQuery({
|
||||||
queryKey: ["getServer", serverID], queryFn: () => {
|
queryKey: ['getServer', serverID],
|
||||||
|
queryFn: () => {
|
||||||
return getServer({ serverId: serverID })
|
return getServer({ serverId: serverID })
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const [configContent, setConfigContent] = useState<string>("{}")
|
const [configContent, setConfigContent] = useState<string>('{}')
|
||||||
const updateFrps = useMutation({ mutationFn: updateFRPS, })
|
const updateFrps = useMutation({ mutationFn: updateFRPS })
|
||||||
const [editorValue, setEditorValue] = useState<string>("")
|
const [editorValue, setEditorValue] = useState<string>('')
|
||||||
const [serverComment, setServerComment] = useState<string>("")
|
const [serverComment, setServerComment] = useState<string>('')
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await updateFrps.mutateAsync({
|
let res = await updateFrps.mutateAsync({
|
||||||
serverId: serverID, config: Buffer.from(editorValue),
|
serverId: serverID,
|
||||||
comment: serverComment
|
config: Buffer.from(editorValue),
|
||||||
|
comment: serverComment,
|
||||||
})
|
})
|
||||||
if (res.status?.code !== RespCode.SUCCESS) {
|
if (res.status?.code !== RespCode.SUCCESS) {
|
||||||
toast({ title: "更新失败" })
|
toast({ title: '更新失败' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toast({ title: "更新成功" })
|
toast({ title: '更新成功' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ title: "更新失败" })
|
toast({ title: '更新失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refetchServer()
|
refetchServer()
|
||||||
try {
|
try {
|
||||||
setConfigContent(JSON.stringify(JSON.parse(serverResp?.server?.config == undefined ||
|
setConfigContent(
|
||||||
serverResp?.server?.config == "" ? "{}" : serverResp?.server?.config), null, 2))
|
JSON.stringify(
|
||||||
setEditorValue(JSON.stringify(JSON.parse(serverResp?.server?.config == undefined ||
|
JSON.parse(
|
||||||
serverResp?.server?.config == "" ? "{}" : serverResp?.server?.config), null, 2))
|
serverResp?.server?.config == undefined || serverResp?.server?.config == ''
|
||||||
setServerComment(serverResp?.server?.comment || "")
|
? '{}'
|
||||||
|
: serverResp?.server?.config,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setEditorValue(
|
||||||
|
JSON.stringify(
|
||||||
|
JSON.parse(
|
||||||
|
serverResp?.server?.config == undefined || serverResp?.server?.config == ''
|
||||||
|
? '{}'
|
||||||
|
: serverResp?.server?.config,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setServerComment(serverResp?.server?.comment || '')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setConfigContent("{}")
|
setConfigContent('{}')
|
||||||
setEditorValue("{}")
|
setEditorValue('{}')
|
||||||
setServerComment("")
|
setServerComment('')
|
||||||
}
|
}
|
||||||
}, [serverResp, refetchServer])
|
}, [serverResp, refetchServer])
|
||||||
|
|
||||||
return (<div className="grid w-full gap-1.5">
|
return (
|
||||||
|
<div className="grid w-full gap-1.5">
|
||||||
<Label className="text-sm font-medium">节点 {serverID} 的备注</Label>
|
<Label className="text-sm font-medium">节点 {serverID} 的备注</Label>
|
||||||
<Textarea key={serverResp?.server?.comment}
|
<Textarea
|
||||||
placeholder="备注" id="message" defaultValue={serverResp?.server?.comment}
|
key={serverResp?.server?.comment}
|
||||||
|
placeholder="备注"
|
||||||
|
id="message"
|
||||||
|
defaultValue={serverResp?.server?.comment}
|
||||||
onChange={(e) => setServerComment(e.target.value)}
|
onChange={(e) => setServerComment(e.target.value)}
|
||||||
className="h-12"
|
className="h-12"
|
||||||
/>
|
/>
|
||||||
<Label className="text-sm font-medium">节点 {serverID} 配置文件`frps.json`内容</Label>
|
<Label className="text-sm font-medium">节点 {serverID} 配置文件`frps.json`内容</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">只需要配置端口和IP等字段,认证信息会由系统补全</p>
|
||||||
只需要配置端口和IP等字段,认证信息会由系统补全
|
<Textarea
|
||||||
</p>
|
key={configContent}
|
||||||
<Textarea key={configContent}
|
placeholder="配置文件内容"
|
||||||
placeholder="配置文件内容" id="message" defaultValue={configContent}
|
id="message"
|
||||||
|
defaultValue={configContent}
|
||||||
onChange={(e) => setEditorValue(e.target.value)}
|
onChange={(e) => setEditorValue(e.target.value)}
|
||||||
className="h-72"
|
className="h-72"
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-2 gap-2 mt-1">
|
<div className="grid grid-cols-2 gap-2 mt-1">
|
||||||
<Button size="sm" onClick={handleSubmit} >提交</Button>
|
<Button size="sm" onClick={handleSubmit}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,27 @@
|
|||||||
import { ServerConfig } from "@/types/server"
|
import { ServerConfig } from '@/types/server'
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from 'react'
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from 'react-hook-form'
|
||||||
import * as z from "zod"
|
import * as z from 'zod'
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||||
Form,
|
import { Input } from '@/components/ui/input'
|
||||||
FormControl,
|
import { ZodIPSchema, ZodPortSchema, ZodStringSchema } from '@/lib/consts'
|
||||||
FormField,
|
import { RespCode, Server } from '@/lib/pb/common'
|
||||||
FormItem,
|
import { updateFRPS } from '@/api/frp'
|
||||||
FormLabel,
|
import { useMutation } from '@tanstack/react-query'
|
||||||
FormMessage,
|
import { useToast } from './ui/use-toast'
|
||||||
} from "@/components/ui/form"
|
import { Label } from '@radix-ui/react-label'
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { ZodIPSchema, ZodPortSchema, ZodStringSchema } from "@/lib/consts"
|
|
||||||
import { RespCode, Server } from "@/lib/pb/common"
|
|
||||||
import { updateFRPS } from "@/api/frp"
|
|
||||||
import { useMutation } from "@tanstack/react-query"
|
|
||||||
import { useToast } from "./ui/use-toast"
|
|
||||||
import { Label } from "@radix-ui/react-label"
|
|
||||||
|
|
||||||
const ServerConfigSchema = z.object({
|
const ServerConfigSchema = z.object({
|
||||||
bindAddr: ZodIPSchema.default("0.0.0.0").optional(),
|
bindAddr: ZodIPSchema.default('0.0.0.0').optional(),
|
||||||
bindPort: ZodPortSchema.default(7000),
|
bindPort: ZodPortSchema.default(7000),
|
||||||
proxyBindAddr: ZodIPSchema.optional(),
|
proxyBindAddr: ZodIPSchema.optional(),
|
||||||
vhostHTTPPort: ZodPortSchema.optional(),
|
vhostHTTPPort: ZodPortSchema.optional(),
|
||||||
subDomainHost: ZodStringSchema.optional(),
|
subDomainHost: ZodStringSchema.optional(),
|
||||||
});
|
})
|
||||||
|
|
||||||
export const ServerConfigZodSchema = ServerConfigSchema;
|
|
||||||
|
|
||||||
|
export const ServerConfigZodSchema = ServerConfigSchema
|
||||||
|
|
||||||
export interface FRPSFormProps {
|
export interface FRPSFormProps {
|
||||||
serverID: string
|
serverID: string
|
||||||
@@ -51,24 +43,27 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset(JSON.parse(server?.config || "{}") as ServerConfig)
|
form.reset(JSON.parse(server?.config || '{}') as ServerConfig)
|
||||||
}, [server])
|
}, [server])
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof ServerConfigZodSchema>) => {
|
const onSubmit = async (values: z.infer<typeof ServerConfigZodSchema>) => {
|
||||||
setFrpsConfig({ ...values })
|
setFrpsConfig({ ...values })
|
||||||
try {
|
try {
|
||||||
let resp = await updateFrps.mutateAsync({
|
let resp = await updateFrps.mutateAsync({
|
||||||
serverId: serverID, config: Buffer.from(JSON.stringify({
|
serverId: serverID,
|
||||||
...values
|
config: Buffer.from(
|
||||||
} as ServerConfig))
|
JSON.stringify({
|
||||||
|
...values,
|
||||||
|
} as ServerConfig),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
toast({
|
toast({
|
||||||
title: resp.status?.code === RespCode.SUCCESS ? "创建成功" : "创建失败",
|
title: resp.status?.code === RespCode.SUCCESS ? '创建成功' : '创建失败',
|
||||||
description: resp.status?.message,
|
description: resp.status?.message,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
toast({ title: "创建服务端状态", description: "创建失败" })
|
toast({ title: '创建服务端状态', description: '创建失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -76,9 +71,10 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
|
|||||||
<Label className="text-sm font-medium">节点 {serverID} 的备注</Label>
|
<Label className="text-sm font-medium">节点 {serverID} 的备注</Label>
|
||||||
<p className="text-sm text-muted-foreground">可以到高级模式修改备注哦!</p>
|
<p className="text-sm text-muted-foreground">可以到高级模式修改备注哦!</p>
|
||||||
<p className="text-sm border rounded p-2 my-2">
|
<p className="text-sm border rounded p-2 my-2">
|
||||||
{server?.comment == undefined || server?.comment === "" ? "空空如也" : server?.comment}
|
{server?.comment == undefined || server?.comment === '' ? '空空如也' : server?.comment}
|
||||||
</p>
|
</p>
|
||||||
{serverID && <Form {...form}>
|
{serverID && (
|
||||||
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -149,11 +145,10 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
|
|||||||
/>
|
/>
|
||||||
<Button type="submit">提交</Button>
|
<Button type="submit">提交</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>}
|
</Form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default FRPSForm
|
export default FRPSForm
|
||||||
@@ -1,28 +1,24 @@
|
|||||||
import { TbBuildingTunnel } from "react-icons/tb"
|
import { TbBuildingTunnel } from 'react-icons/tb'
|
||||||
import { Button } from "./ui/button"
|
import { Button } from './ui/button'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from 'next/router'
|
||||||
import { $platformInfo, $userInfo } from "@/store/user";
|
import { $platformInfo, $userInfo } from '@/store/user'
|
||||||
import { getUserInfo } from "@/api/user";
|
import { getUserInfo } from '@/api/user'
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useEffect } from "react";
|
import { useEffect } from 'react'
|
||||||
import Gravatar from 'react-gravatar'
|
import Gravatar from 'react-gravatar'
|
||||||
import {
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
Avatar,
|
import { LOCAL_STORAGE_TOKEN_KEY } from '@/lib/consts'
|
||||||
AvatarFallback,
|
import { logout } from '@/api/auth'
|
||||||
AvatarImage,
|
import { getPlatformInfo } from '@/api/platform'
|
||||||
} from "@/components/ui/avatar"
|
|
||||||
import { LOCAL_STORAGE_TOKEN_KEY } from "@/lib/consts";
|
|
||||||
import { logout } from "@/api/auth";
|
|
||||||
import { getPlatformInfo } from "@/api/platform";
|
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const userInfo = useStore($userInfo)
|
const userInfo = useStore($userInfo)
|
||||||
|
|
||||||
const platformInfo = useQuery({
|
const platformInfo = useQuery({
|
||||||
queryKey: ['platformInfo'],
|
queryKey: ['platformInfo'],
|
||||||
queryFn: getPlatformInfo
|
queryFn: getPlatformInfo,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -30,8 +26,8 @@ export const Header = () => {
|
|||||||
}, [platformInfo])
|
}, [platformInfo])
|
||||||
|
|
||||||
const userInfoQuery = useQuery({
|
const userInfoQuery = useQuery({
|
||||||
queryKey: ["userInfo"],
|
queryKey: ['userInfo'],
|
||||||
queryFn: getUserInfo
|
queryFn: getUserInfo,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,27 +35,48 @@ export const Header = () => {
|
|||||||
}, [userInfoQuery])
|
}, [userInfoQuery])
|
||||||
|
|
||||||
const redirToHome = () => {
|
const redirToHome = () => {
|
||||||
router.push("/")
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row h-10 items-center px-4 border-b">
|
<div className="flex flex-row h-10 items-center px-4 border-b">
|
||||||
<TbBuildingTunnel />
|
<TbBuildingTunnel />
|
||||||
<p className="ml-2 font-mono" onClick={redirToHome}>frp-panel</p>
|
<p className="ml-2 font-mono" onClick={redirToHome}>
|
||||||
{!userInfo && <Button variant={"ghost"} className="ml-auto" size={"sm"} onClick={() => router.push("/login")}>登录</Button>}
|
frp-panel
|
||||||
{!userInfo && <Button variant={"ghost"} className="ml-2" size={"sm"} onClick={() => router.push("/register")}>注册</Button>}
|
</p>
|
||||||
{userInfo && <Button variant={"ghost"} className="ml-auto" size={"sm"} onClick={async () => {
|
{!userInfo && (
|
||||||
|
<Button variant={'ghost'} className="ml-auto" size={'sm'} onClick={() => router.push('/login')}>
|
||||||
|
登录
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!userInfo && (
|
||||||
|
<Button variant={'ghost'} className="ml-2" size={'sm'} onClick={() => router.push('/register')}>
|
||||||
|
注册
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{userInfo && (
|
||||||
|
<Button
|
||||||
|
variant={'ghost'}
|
||||||
|
className="ml-auto"
|
||||||
|
size={'sm'}
|
||||||
|
onClick={async () => {
|
||||||
$userInfo.set(undefined)
|
$userInfo.set(undefined)
|
||||||
localStorage.removeItem(LOCAL_STORAGE_TOKEN_KEY)
|
localStorage.removeItem(LOCAL_STORAGE_TOKEN_KEY)
|
||||||
await logout()
|
await logout()
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}}>退出</Button>}
|
}}
|
||||||
{userInfo && <Avatar className="ml-2 w-7 h-7">
|
>
|
||||||
<AvatarImage alt={"@" + userInfo.userName} asChild>
|
退出
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{userInfo && (
|
||||||
|
<Avatar className="ml-2 w-7 h-7">
|
||||||
|
<AvatarImage alt={'@' + userInfo.userName} asChild>
|
||||||
<Gravatar email={userInfo.email} />
|
<Gravatar email={userInfo.email} />
|
||||||
</AvatarImage>
|
</AvatarImage>
|
||||||
<AvatarFallback>{userInfo.userName}</AvatarFallback>
|
<AvatarFallback>{userInfo.userName}</AvatarFallback>
|
||||||
</Avatar>}
|
</Avatar>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
import { Providers } from "./providers"
|
import { Providers } from './providers'
|
||||||
import { Toaster } from "./ui/toaster"
|
import { Toaster } from './ui/toaster'
|
||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ['latin'] })
|
||||||
|
|
||||||
export const RootLayout = ({ children, header, sidebar }:
|
export const RootLayout = ({
|
||||||
{ children: React.ReactNode, header: React.ReactNode, sidebar?: React.ReactNode }) => {
|
children,
|
||||||
|
header,
|
||||||
|
sidebar,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
header: React.ReactNode
|
||||||
|
sidebar?: React.ReactNode
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<main className={`${inter.className}`}>
|
<main className={`${inter.className}`}>
|
||||||
<div><Providers>{header}</Providers></div>
|
<div>
|
||||||
|
<Providers>{header}</Providers>
|
||||||
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{sidebar}
|
{sidebar}
|
||||||
<div className="my-2 ml-0 mr-2 max-w-full w-full">
|
<div className="my-2 ml-0 mr-2 max-w-full w-full">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,33 +1,23 @@
|
|||||||
import { ZodStringSchema } from "@/lib/consts"
|
import { ZodStringSchema } from '@/lib/consts'
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from 'react-hook-form'
|
||||||
import * as z from "zod"
|
import * as z from 'zod'
|
||||||
import {
|
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'
|
||||||
Form,
|
import { Input } from './ui/input'
|
||||||
FormControl,
|
import { login } from '@/api/auth'
|
||||||
FormField,
|
import { Button } from './ui/button'
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form"
|
|
||||||
import { Input } from "./ui/input"
|
|
||||||
import { login } from "@/api/auth"
|
|
||||||
import { Button } from "./ui/button"
|
|
||||||
|
|
||||||
import { ExclamationTriangleIcon } from "@radix-ui/react-icons"
|
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
import {
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
Alert,
|
import { useState } from 'react'
|
||||||
AlertDescription,
|
import { useToast } from './ui/use-toast'
|
||||||
AlertTitle,
|
import { RespCode } from '@/lib/pb/common'
|
||||||
} from "@/components/ui/alert"
|
import { useRouter } from 'next/router'
|
||||||
import { useState } from "react"
|
|
||||||
import { useToast } from "./ui/use-toast"
|
|
||||||
import { RespCode } from "@/lib/pb/common"
|
|
||||||
import { useRouter } from "next/router"
|
|
||||||
|
|
||||||
export const LoginSchema = z.object({
|
export const LoginSchema = z.object({
|
||||||
username: ZodStringSchema,
|
username: ZodStringSchema,
|
||||||
password: ZodStringSchema
|
password: ZodStringSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const LoginComponent = () => {
|
export const LoginComponent = () => {
|
||||||
@@ -40,22 +30,22 @@ export const LoginComponent = () => {
|
|||||||
const [loginAlert, setLoginAlert] = useState(false)
|
const [loginAlert, setLoginAlert] = useState(false)
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof LoginSchema>) => {
|
const onSubmit = async (values: z.infer<typeof LoginSchema>) => {
|
||||||
toast({ title: "登录中,请稍候" })
|
toast({ title: '登录中,请稍候' })
|
||||||
try {
|
try {
|
||||||
const res = await login({ ...values })
|
const res = await login({ ...values })
|
||||||
if (res.status?.code === RespCode.SUCCESS) {
|
if (res.status?.code === RespCode.SUCCESS) {
|
||||||
toast({ title: "登录成功,正在跳转到首页" })
|
toast({ title: '登录成功,正在跳转到首页' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push('/');
|
router.push('/')
|
||||||
}, 3000);
|
}, 3000)
|
||||||
setLoginAlert(false)
|
setLoginAlert(false)
|
||||||
} else {
|
} else {
|
||||||
toast({ title: "登录失败" })
|
toast({ title: '登录失败' })
|
||||||
setLoginAlert(true)
|
setLoginAlert(true)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({ title: "登录失败" })
|
toast({ title: '登录失败' })
|
||||||
console.log("login error", e)
|
console.log('login error', e)
|
||||||
setLoginAlert(true)
|
setLoginAlert(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,21 +53,14 @@ export const LoginComponent = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col gap-6">
|
<div className="w-full flex flex-col gap-6">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4">
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="flex flex-col gap-4"
|
|
||||||
>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input type="text" placeholder="用户名" {...field} />
|
||||||
type="text"
|
|
||||||
placeholder="用户名"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -89,23 +72,19 @@ export const LoginComponent = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input type="password" placeholder="密码" {...field} />
|
||||||
type="password"
|
|
||||||
placeholder="密码"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{loginAlert && <Alert variant="destructive">
|
{loginAlert && (
|
||||||
|
<Alert variant="destructive">
|
||||||
<ExclamationTriangleIcon className="h-4 w-4" />
|
<ExclamationTriangleIcon className="h-4 w-4" />
|
||||||
<AlertTitle>错误</AlertTitle>
|
<AlertTitle>错误</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>登录失败,请重试</AlertDescription>
|
||||||
登录失败,请重试
|
</Alert>
|
||||||
</AlertDescription>
|
)}
|
||||||
</Alert>}
|
|
||||||
<Button type="submit">登录</Button>
|
<Button type="submit">登录</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||||
import { TbDeviceHeartMonitor, TbEngine, TbEngineOff, TbServer2, TbServerBolt, TbServerOff } from 'react-icons/tb';
|
import { TbDeviceHeartMonitor, TbEngine, TbEngineOff, TbServer2, TbServerBolt, TbServerOff } from 'react-icons/tb'
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react'
|
||||||
import { $platformInfo } from '@/store/user';
|
import { $platformInfo } from '@/store/user'
|
||||||
import { getPlatformInfo } from '@/api/platform';
|
import { getPlatformInfo } from '@/api/platform'
|
||||||
export const PlatformInfo = () => {
|
export const PlatformInfo = () => {
|
||||||
const platformInfo = useQuery({
|
const platformInfo = useQuery({
|
||||||
queryKey: ['platformInfo'],
|
queryKey: ['platformInfo'],
|
||||||
queryFn: getPlatformInfo
|
queryFn: getPlatformInfo,
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
$platformInfo.set(platformInfo.data)
|
$platformInfo.set(platformInfo.data)
|
||||||
@@ -16,85 +16,73 @@ export const PlatformInfo = () => {
|
|||||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
|
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
<h3 className='tracking-tight text-sm font-medium'>
|
<h3 className="tracking-tight text-sm font-medium">已配置服务端数</h3>
|
||||||
已配置服务端数
|
<TbServerBolt className="mt-1" />
|
||||||
</h3>
|
|
||||||
<TbServerBolt className='mt-1' />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='text-2xl font-bold'>{platformInfo.data?.configuredServerCount} 个</div>
|
<div className="text-2xl font-bold">{platformInfo.data?.configuredServerCount} 个</div>
|
||||||
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
<h3 className='tracking-tight text-sm font-medium'>
|
<h3 className="tracking-tight text-sm font-medium">已配置客户端数</h3>
|
||||||
已配置客户端数
|
<TbEngine className="mt-1" />
|
||||||
</h3>
|
|
||||||
<TbEngine className='mt-1' />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='text-2xl font-bold'>{platformInfo.data?.configuredClientCount} 个</div>
|
<div className="text-2xl font-bold">{platformInfo.data?.configuredClientCount} 个</div>
|
||||||
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
<h3 className='tracking-tight text-sm font-medium'>
|
<h3 className="tracking-tight text-sm font-medium">未配置服务端数</h3>
|
||||||
未配置服务端数
|
<TbServerOff className="mt-1" />
|
||||||
</h3>
|
|
||||||
<TbServerOff className='mt-1' />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='text-2xl font-bold'>{platformInfo.data?.unconfiguredServerCount} 个</div>
|
<div className="text-2xl font-bold">{platformInfo.data?.unconfiguredServerCount} 个</div>
|
||||||
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
<h3 className='tracking-tight text-sm font-medium'>
|
<h3 className="tracking-tight text-sm font-medium">未配置客户端数</h3>
|
||||||
未配置客户端数
|
<TbEngineOff className="mt-1" />
|
||||||
</h3>
|
|
||||||
<TbEngineOff className='mt-1' />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='text-2xl font-bold'>{platformInfo.data?.unconfiguredClientCount} 个</div>
|
<div className="text-2xl font-bold">{platformInfo.data?.unconfiguredClientCount} 个</div>
|
||||||
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
<h3 className='tracking-tight text-sm font-medium'>
|
<h3 className="tracking-tight text-sm font-medium">服务端总数</h3>
|
||||||
服务端总数
|
<TbServer2 className="mt-1" />
|
||||||
</h3>
|
|
||||||
<TbServer2 className='mt-1' />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='text-2xl font-bold'>{platformInfo.data?.totalServerCount} 个</div>
|
<div className="text-2xl font-bold">{platformInfo.data?.totalServerCount} 个</div>
|
||||||
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
<h3 className='tracking-tight text-sm font-medium'>
|
<h3 className="tracking-tight text-sm font-medium">客户端总数</h3>
|
||||||
客户端总数
|
<TbDeviceHeartMonitor className="mt-1" />
|
||||||
</h3>
|
|
||||||
<TbDeviceHeartMonitor className='mt-1' />
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='text-2xl font-bold'>{platformInfo.data?.totalClientCount} 个</div>
|
<div className="text-2xl font-bold">{platformInfo.data?.totalClientCount} 个</div>
|
||||||
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
<p className="text-xs text-muted-foreground">请前往左侧🫲菜单修改</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import {
|
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||||
TooltipProvider,
|
|
||||||
} from "@/components/ui/tooltip"
|
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
export const Providers = ({ children }: { children: React.ReactNode }) => {
|
export const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
{children}
|
|
||||||
</QueryClientProvider>
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,35 @@
|
|||||||
import { HTTPProxyConfig, TCPProxyConfig, TypedProxyConfig, UDPProxyConfig } from "@/types/proxy"
|
import { HTTPProxyConfig, TCPProxyConfig, TypedProxyConfig, UDPProxyConfig } from '@/types/proxy'
|
||||||
import * as z from "zod"
|
import * as z from 'zod'
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { ZodIPSchema, ZodPortSchema, ZodStringSchema } from "@/lib/consts"
|
import { ZodIPSchema, ZodPortSchema, ZodStringSchema } from '@/lib/consts'
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from 'react'
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from 'react-hook-form'
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||||
Form,
|
import { Input } from '@/components/ui/input'
|
||||||
FormControl,
|
import { $clientProxyConfigs } from '@/store/proxy'
|
||||||
FormField,
|
import { useStore } from '@nanostores/react'
|
||||||
FormItem,
|
import { YesIcon } from './ui/icon'
|
||||||
FormLabel,
|
import { Label } from './ui/label'
|
||||||
FormMessage,
|
import { useQuery } from '@tanstack/react-query'
|
||||||
} from "@/components/ui/form"
|
import { getServer } from '@/api/server'
|
||||||
import { Input } from "@/components/ui/input"
|
import { ServerConfig } from '@/types/server'
|
||||||
import { $clientProxyConfigs } from "@/store/proxy"
|
|
||||||
import { useStore } from "@nanostores/react"
|
|
||||||
import { YesIcon } from "./ui/icon"
|
|
||||||
import { Label } from "./ui/label"
|
|
||||||
import { useQuery } from "@tanstack/react-query"
|
|
||||||
import { getServer } from "@/api/server"
|
|
||||||
import { ServerConfig } from "@/types/server"
|
|
||||||
export const TCPConfigSchema = z.object({
|
export const TCPConfigSchema = z.object({
|
||||||
remotePort: ZodPortSchema,
|
remotePort: ZodPortSchema,
|
||||||
localIP: ZodIPSchema.default("127.0.0.1"),
|
localIP: ZodIPSchema.default('127.0.0.1'),
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const UDPConfigSchema = z.object({
|
export const UDPConfigSchema = z.object({
|
||||||
remotePort: ZodPortSchema.optional(),
|
remotePort: ZodPortSchema.optional(),
|
||||||
localIP: ZodIPSchema.default("127.0.0.1"),
|
localIP: ZodIPSchema.default('127.0.0.1'),
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const HTTPConfigSchema = z.object({
|
export const HTTPConfigSchema = z.object({
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema,
|
||||||
localIP: ZodIPSchema.default("127.0.0.1"),
|
localIP: ZodIPSchema.default('127.0.0.1'),
|
||||||
subDomain: ZodStringSchema,
|
subDomain: ZodStringSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -61,29 +54,30 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
const clientProxyConfigs = useStore($clientProxyConfigs)
|
const clientProxyConfigs = useStore($clientProxyConfigs)
|
||||||
const onSubmit = async (values: z.infer<typeof TCPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof TCPConfigSchema>) => {
|
||||||
handleSave()
|
handleSave()
|
||||||
setTCPConfig({ type: "tcp", ...values, name: proxyName })
|
setTCPConfig({ type: 'tcp', ...values, name: proxyName })
|
||||||
const newProxiyConfigs = clientProxyConfigs.map(proxyCfg => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: "tcp", name: proxyName } as TCPProxyConfig
|
return { ...values, type: 'tcp', name: proxyName } as TCPProxyConfig
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
$clientProxyConfigs.set(newProxiyConfigs)
|
$clientProxyConfigs.set(newProxiyConfigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isSaveDisabled, setSaveDisabled] = useState(false);
|
const [isSaveDisabled, setSaveDisabled] = useState(false)
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setSaveDisabled(true);
|
setSaveDisabled(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSaveDisabled(false);
|
setSaveDisabled(false)
|
||||||
}, 3000);
|
}, 3000)
|
||||||
};
|
}
|
||||||
|
|
||||||
const { data: server } = useQuery({
|
const { data: server } = useQuery({
|
||||||
queryKey: ["getServer", serverID], queryFn: () => {
|
queryKey: ['getServer', serverID],
|
||||||
|
queryFn: () => {
|
||||||
return getServer({ serverId: serverID })
|
return getServer({ serverId: serverID })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -91,7 +85,8 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<Label className="text-sm font-medium">访问方式</Label>
|
<Label className="text-sm font-medium">访问方式</Label>
|
||||||
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
|
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
|
||||||
{`${server?.server?.ip}:${(defaultProxyConfig as TCPProxyConfig).remotePort
|
{`${server?.server?.ip}:${
|
||||||
|
(defaultProxyConfig as TCPProxyConfig).remotePort
|
||||||
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
|
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
|
||||||
</p>
|
</p>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -120,7 +115,7 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
defaultValue={defaultProxyConfig === undefined ? "127.0.0.1" : defaultProxyConfig.localIP}
|
defaultValue={defaultProxyConfig === undefined ? '127.0.0.1' : defaultProxyConfig.localIP}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -136,12 +131,13 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
)}
|
)}
|
||||||
defaultValue={defaultProxyConfig === undefined ? 4321 : (defaultProxyConfig as TCPProxyConfig).remotePort}
|
defaultValue={defaultProxyConfig === undefined ? 4321 : (defaultProxyConfig as TCPProxyConfig).remotePort}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={"outline"}>
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? "" : "hidden"}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
暂存修改</Button>
|
暂存修改
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName }) => {
|
export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName }) => {
|
||||||
@@ -155,33 +151,33 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
form.reset({})
|
form.reset({})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
const clientProxyConfigs = useStore($clientProxyConfigs)
|
const clientProxyConfigs = useStore($clientProxyConfigs)
|
||||||
const onSubmit = async (values: z.infer<typeof UDPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof UDPConfigSchema>) => {
|
||||||
handleSave()
|
handleSave()
|
||||||
setUDPConfig({ type: "udp", ...values, name: proxyName })
|
setUDPConfig({ type: 'udp', ...values, name: proxyName })
|
||||||
const newProxiyConfigs = clientProxyConfigs.map(proxyCfg => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: "udp", name: proxyName } as UDPProxyConfig
|
return { ...values, type: 'udp', name: proxyName } as UDPProxyConfig
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
$clientProxyConfigs.set(newProxiyConfigs)
|
$clientProxyConfigs.set(newProxiyConfigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isSaveDisabled, setSaveDisabled] = useState(false);
|
const [isSaveDisabled, setSaveDisabled] = useState(false)
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setSaveDisabled(true);
|
setSaveDisabled(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSaveDisabled(false);
|
setSaveDisabled(false)
|
||||||
}, 3000);
|
}, 3000)
|
||||||
};
|
}
|
||||||
|
|
||||||
const { data: server } = useQuery({
|
const { data: server } = useQuery({
|
||||||
queryKey: ["getServer", serverID], queryFn: () => {
|
queryKey: ['getServer', serverID],
|
||||||
|
queryFn: () => {
|
||||||
return getServer({ serverId: serverID })
|
return getServer({ serverId: serverID })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -189,7 +185,8 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<Label className="text-sm font-medium">访问方式</Label>
|
<Label className="text-sm font-medium">访问方式</Label>
|
||||||
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
|
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
|
||||||
{`${server?.server?.ip}:${(defaultProxyConfig as UDPProxyConfig).remotePort
|
{`${server?.server?.ip}:${
|
||||||
|
(defaultProxyConfig as UDPProxyConfig).remotePort
|
||||||
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
|
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
|
||||||
</p>
|
</p>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -218,7 +215,7 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
defaultValue={defaultProxyConfig === undefined ? "127.0.0.1" : defaultProxyConfig.localIP}
|
defaultValue={defaultProxyConfig === undefined ? '127.0.0.1' : defaultProxyConfig.localIP}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -230,15 +227,17 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
<Input type="number" {...field} />
|
<Input type="number" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>)}
|
</FormItem>
|
||||||
|
)}
|
||||||
defaultValue={defaultProxyConfig === undefined ? 4321 : (defaultProxyConfig as UDPProxyConfig).remotePort}
|
defaultValue={defaultProxyConfig === undefined ? 4321 : (defaultProxyConfig as UDPProxyConfig).remotePort}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={"outline"}>
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? "" : "hidden"}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
暂存修改</Button>
|
暂存修改
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName }) => {
|
export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName }) => {
|
||||||
@@ -256,29 +255,30 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
const clientProxyConfigs = useStore($clientProxyConfigs)
|
const clientProxyConfigs = useStore($clientProxyConfigs)
|
||||||
const onSubmit = async (values: z.infer<typeof HTTPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof HTTPConfigSchema>) => {
|
||||||
handleSave()
|
handleSave()
|
||||||
setHTTPConfig({ ...values, type: "http", name: proxyName })
|
setHTTPConfig({ ...values, type: 'http', name: proxyName })
|
||||||
const newProxiyConfigs = clientProxyConfigs.map(proxyCfg => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: "http", name: proxyName } as HTTPProxyConfig
|
return { ...values, type: 'http', name: proxyName } as HTTPProxyConfig
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
$clientProxyConfigs.set(newProxiyConfigs)
|
$clientProxyConfigs.set(newProxiyConfigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isSaveDisabled, setSaveDisabled] = useState(false);
|
const [isSaveDisabled, setSaveDisabled] = useState(false)
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setSaveDisabled(true);
|
setSaveDisabled(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSaveDisabled(false);
|
setSaveDisabled(false)
|
||||||
}, 3000);
|
}, 3000)
|
||||||
};
|
}
|
||||||
|
|
||||||
const { data: server } = useQuery({
|
const { data: server } = useQuery({
|
||||||
queryKey: ["getServer", serverID], queryFn: () => {
|
queryKey: ['getServer', serverID],
|
||||||
|
queryFn: () => {
|
||||||
return getServer({ serverId: serverID })
|
return getServer({ serverId: serverID })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -292,7 +292,8 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<Label className="text-sm font-medium">访问方式</Label>
|
<Label className="text-sm font-medium">访问方式</Label>
|
||||||
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
|
<p className="text-sm border rounded p-2 my-2 font-mono overflow-auto">
|
||||||
{`http://${(defaultProxyConfig as HTTPProxyConfig).subdomain}.${serverConfig?.subDomainHost}:${serverConfig?.vhostHTTPPort
|
{`http://${(defaultProxyConfig as HTTPProxyConfig).subdomain}.${serverConfig?.subDomainHost}:${
|
||||||
|
serverConfig?.vhostHTTPPort
|
||||||
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
|
} -> ${defaultProxyConfig?.localIP}:${defaultProxyConfig?.localPort}`}
|
||||||
</p>
|
</p>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -321,7 +322,7 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
defaultValue={defaultProxyConfig === undefined ? "127.0.0.1" : defaultProxyConfig.localIP}
|
defaultValue={defaultProxyConfig === undefined ? '127.0.0.1' : defaultProxyConfig.localIP}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -333,12 +334,14 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
<Input type="text" {...field} />
|
<Input type="text" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>)}
|
</FormItem>
|
||||||
defaultValue={defaultProxyConfig === undefined ? "" : (defaultProxyConfig as HTTPProxyConfig).subdomain}
|
)}
|
||||||
|
defaultValue={defaultProxyConfig === undefined ? '' : (defaultProxyConfig as HTTPProxyConfig).subdomain}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={"outline"}>
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'}>
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? "" : "hidden"}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
暂存修改</Button>
|
暂存修改
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
import { ZodEmailSchema, ZodStringSchema } from "@/lib/consts"
|
import { ZodEmailSchema, ZodStringSchema } from '@/lib/consts'
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from 'react-hook-form'
|
||||||
import * as z from "zod"
|
import * as z from 'zod'
|
||||||
import {
|
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'
|
||||||
Form,
|
import { Input } from './ui/input'
|
||||||
FormControl,
|
import { register } from '@/api/auth'
|
||||||
FormField,
|
import { Button } from './ui/button'
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form"
|
|
||||||
import { Input } from "./ui/input"
|
|
||||||
import { register } from "@/api/auth"
|
|
||||||
import { Button } from "./ui/button"
|
|
||||||
|
|
||||||
import { ExclamationTriangleIcon } from "@radix-ui/react-icons"
|
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
import {
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
Alert,
|
import { useState } from 'react'
|
||||||
AlertDescription,
|
import { useToast } from './ui/use-toast'
|
||||||
AlertTitle,
|
import { RespCode } from '@/lib/pb/common'
|
||||||
} from "@/components/ui/alert"
|
|
||||||
import { useState } from "react"
|
|
||||||
import { useToast } from "./ui/use-toast"
|
|
||||||
import { RespCode } from "@/lib/pb/common"
|
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { Toast } from "./ui/toast"
|
import { Toast } from './ui/toast'
|
||||||
|
|
||||||
export const RegisterSchema = z.object({
|
export const RegisterSchema = z.object({
|
||||||
username: ZodStringSchema,
|
username: ZodStringSchema,
|
||||||
@@ -41,25 +31,25 @@ export const RegisterComponent = () => {
|
|||||||
|
|
||||||
const [registerAlert, setRegisterAlert] = useState(false)
|
const [registerAlert, setRegisterAlert] = useState(false)
|
||||||
const sleep = async (ms: number): Promise<void> => {
|
const sleep = async (ms: number): Promise<void> => {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof RegisterSchema>) => {
|
const onSubmit = async (values: z.infer<typeof RegisterSchema>) => {
|
||||||
toast({ title: "注册中,请稍候" })
|
toast({ title: '注册中,请稍候' })
|
||||||
try {
|
try {
|
||||||
const res = await register({ ...values })
|
const res = await register({ ...values })
|
||||||
if (res.status?.code === RespCode.SUCCESS) {
|
if (res.status?.code === RespCode.SUCCESS) {
|
||||||
toast({ title: "注册成功,正在跳转到登录" })
|
toast({ title: '注册成功,正在跳转到登录' })
|
||||||
setRegisterAlert(false)
|
setRegisterAlert(false)
|
||||||
await sleep(3000)
|
await sleep(3000)
|
||||||
router.push("/login")
|
router.push('/login')
|
||||||
} else {
|
} else {
|
||||||
toast({ title: "注册失败" })
|
toast({ title: '注册失败' })
|
||||||
setRegisterAlert(true)
|
setRegisterAlert(true)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({ title: "注册失败" })
|
toast({ title: '注册失败' })
|
||||||
console.log("register error", e)
|
console.log('register error', e)
|
||||||
setRegisterAlert(true)
|
setRegisterAlert(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,21 +57,14 @@ export const RegisterComponent = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col gap-6">
|
<div className="w-full flex flex-col gap-6">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4">
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="flex flex-col gap-4"
|
|
||||||
>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input type="text" placeholder="用户名" {...field} />
|
||||||
type="text"
|
|
||||||
placeholder="用户名"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -93,11 +76,7 @@ export const RegisterComponent = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input type="email" placeholder="邮箱地址" {...field} />
|
||||||
type="email"
|
|
||||||
placeholder="邮箱地址"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -109,23 +88,19 @@ export const RegisterComponent = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input type="password" placeholder="密码" {...field} />
|
||||||
type="password"
|
|
||||||
placeholder="密码"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{registerAlert && <Alert variant="destructive">
|
{registerAlert && (
|
||||||
|
<Alert variant="destructive">
|
||||||
<ExclamationTriangleIcon className="h-4 w-4" />
|
<ExclamationTriangleIcon className="h-4 w-4" />
|
||||||
<AlertTitle>错误</AlertTitle>
|
<AlertTitle>错误</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>注册失败,请重试</AlertDescription>
|
||||||
注册失败,请重试
|
</Alert>
|
||||||
</AlertDescription>
|
)}
|
||||||
</Alert>}
|
|
||||||
<Button type="submit">注册</Button>
|
<Button type="submit">注册</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useState } from "react"
|
import { useState } from 'react'
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { initServer, listServer } from "@/api/server"
|
import { initServer, listServer } from '@/api/server'
|
||||||
import { Label } from "./ui/label"
|
import { Label } from './ui/label'
|
||||||
import { Input } from "./ui/input"
|
import { Input } from './ui/input'
|
||||||
import { Button } from "./ui/button"
|
import { Button } from './ui/button'
|
||||||
import { useToast } from "./ui/use-toast"
|
import { useToast } from './ui/use-toast'
|
||||||
import { RespCode } from "@/lib/pb/common"
|
import { RespCode } from '@/lib/pb/common'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -14,19 +14,19 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from '@/components/ui/dialog'
|
||||||
|
|
||||||
export const CreateServerDialog = () => {
|
export const CreateServerDialog = () => {
|
||||||
const [serverID, setServerID] = useState<string | undefined>()
|
const [serverID, setServerID] = useState<string | undefined>()
|
||||||
const [serverIP, setServerIP] = useState<string | undefined>()
|
const [serverIP, setServerIP] = useState<string | undefined>()
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ["listServer", { pageIndex: 0, pageSize: 10 }],
|
queryKey: ['listServer', { pageIndex: 0, pageSize: 10 }],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await listServer({
|
return await listServer({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10
|
pageSize: 10,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const newServer = useMutation({
|
const newServer = useMutation({
|
||||||
mutationFn: initServer,
|
mutationFn: initServer,
|
||||||
@@ -34,25 +34,26 @@ export const CreateServerDialog = () => {
|
|||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
const handleNewServer = async () => {
|
const handleNewServer = async () => {
|
||||||
toast({ title: "已提交创建请求" })
|
toast({ title: '已提交创建请求' })
|
||||||
try {
|
try {
|
||||||
let resp = await newServer.mutateAsync({ serverId: serverID, serverIp: serverIP })
|
let resp = await newServer.mutateAsync({ serverId: serverID, serverIp: serverIP })
|
||||||
if (resp.status?.code !== RespCode.SUCCESS) {
|
if (resp.status?.code !== RespCode.SUCCESS) {
|
||||||
toast({ title: "创建服务端失败" })
|
toast({ title: '创建服务端失败' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toast({ title: "创建服务端成功" })
|
toast({ title: '创建服务端成功' })
|
||||||
dataQuery.refetch()
|
dataQuery.refetch()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ title: "创建服务端失败" })
|
toast({ title: '创建服务端失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" size={"sm"}>新建</Button>
|
<Button variant="outline" size={'sm'}>
|
||||||
|
新建
|
||||||
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ColumnDef, Table } from "@tanstack/react-table"
|
import { ColumnDef, Table } from '@tanstack/react-table'
|
||||||
import { MoreHorizontal } from "lucide-react"
|
import { MoreHorizontal } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from '@/components/ui/dialog'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -19,24 +19,24 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { useToast } from "./ui/use-toast"
|
import { useToast } from './ui/use-toast'
|
||||||
import React, { useState } from "react"
|
import React, { useState } from 'react'
|
||||||
import { ExecCommandStr, LinuxInstallCommand, WindowsInstallCommand } from "@/lib/consts"
|
import { ExecCommandStr, LinuxInstallCommand, WindowsInstallCommand } from '@/lib/consts'
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { deleteServer, listServer } from "@/api/server"
|
import { deleteServer, listServer } from '@/api/server'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { getUserInfo } from "@/api/user"
|
import { getUserInfo } from '@/api/user'
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from '@nanostores/react'
|
||||||
import { $platformInfo } from "@/store/user"
|
import { $platformInfo } from '@/store/user'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'
|
||||||
import { getClientsStatus } from "@/api/platform"
|
import { getClientsStatus } from '@/api/platform'
|
||||||
import { ClientType } from "@/lib/pb/common"
|
import { ClientType } from '@/lib/pb/common'
|
||||||
import { ClientStatus, ClientStatus_Status } from "@/lib/pb/api_master"
|
import { ClientStatus, ClientStatus_Status } from '@/lib/pb/api_master'
|
||||||
|
|
||||||
export type ServerTableSchema = {
|
export type ServerTableSchema = {
|
||||||
id: string,
|
id: string
|
||||||
status: "invalid" | "valid"
|
status: 'invalid' | 'valid'
|
||||||
secret: string
|
secret: string
|
||||||
info?: string
|
info?: string
|
||||||
ip: string
|
ip: string
|
||||||
@@ -45,66 +45,73 @@ export type ServerTableSchema = {
|
|||||||
|
|
||||||
export const columns: ColumnDef<ServerTableSchema>[] = [
|
export const columns: ColumnDef<ServerTableSchema>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "id",
|
accessorKey: 'id',
|
||||||
header: "ID",
|
header: 'ID',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <ServerID server={row.original} />
|
return <ServerID server={row.original} />
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "status",
|
accessorKey: 'status',
|
||||||
header: "是否配置",
|
header: '是否配置',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const Server = row.original
|
const Server = row.original
|
||||||
return <div className={`font-mono ${Server.status === "valid" ? "text-green-500" : "text-red-500"} min-w-12`}>{
|
return (
|
||||||
|
<div className={`font-mono ${Server.status === 'valid' ? 'text-green-500' : 'text-red-500'} min-w-12`}>
|
||||||
{
|
{
|
||||||
valid: "已配置",
|
{
|
||||||
invalid: "未配置",
|
valid: '已配置',
|
||||||
|
invalid: '未配置',
|
||||||
}[Server.status]
|
}[Server.status]
|
||||||
}</div>
|
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "info",
|
accessorKey: 'info',
|
||||||
header: "运行信息",
|
header: '运行信息',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const Server = row.original
|
const Server = row.original
|
||||||
return <ServerInfo server={Server} />
|
return <ServerInfo server={Server} />
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "ip",
|
accessorKey: 'ip',
|
||||||
header: "IP",
|
header: 'IP',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const Server = row.original
|
const Server = row.original
|
||||||
return <div className="font-mono">{Server.ip}</div>
|
return <div className="font-mono">{Server.ip}</div>
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "secret",
|
accessorKey: 'secret',
|
||||||
header: "连接密钥",
|
header: '连接密钥',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const Server = row.original
|
const Server = row.original
|
||||||
return <ServerSecret server={Server} />
|
return <ServerSecret server={Server} />
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "action",
|
id: 'action',
|
||||||
cell: ({ row, table }) => {
|
cell: ({ row, table }) => {
|
||||||
const Server = row.original
|
const Server = row.original
|
||||||
return (<ServerActions server={Server} table={table} />)
|
return <ServerActions server={Server} table={table} />
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const ServerID = ({ server }: { server: ServerTableSchema }) => {
|
export const ServerID = ({ server }: { server: ServerTableSchema }) => {
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
return <Popover >
|
return (
|
||||||
<PopoverTrigger asChild><div className="font-mono">{server.id}</div></PopoverTrigger>
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div className="font-mono">{server.id}</div>
|
||||||
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-fit overflow-auto max-w-72 max-h-72">
|
<PopoverContent className="w-fit overflow-auto max-w-72 max-h-72">
|
||||||
<div>Linux安装到systemd</div>
|
<div>Linux安装到systemd</div>
|
||||||
<div className="p-2 border rounded font-mono w-fit">
|
<div className="p-2 border rounded font-mono w-fit">
|
||||||
{platformInfo === undefined ? "获取平台信息失败" : LinuxInstallCommand("server", server, platformInfo)}
|
{platformInfo === undefined ? '获取平台信息失败' : LinuxInstallCommand('server', server, platformInfo)}
|
||||||
</div>
|
</div>
|
||||||
{/* <div>Windows</div>
|
{/* <div>Windows</div>
|
||||||
<div className="p-2 border rounded font-mono w-fit">
|
<div className="p-2 border rounded font-mono w-fit">
|
||||||
@@ -112,69 +119,79 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {
|
|||||||
</div> */}
|
</div> */}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ServerInfo = ({ server }: { server: ServerTableSchema }) => {
|
export const ServerInfo = ({ server }: { server: ServerTableSchema }) => {
|
||||||
const clientsInfo = useQuery({
|
const clientsInfo = useQuery({
|
||||||
queryKey: ["getClientsStatus", [server.id]],
|
queryKey: ['getClientsStatus', [server.id]],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await getClientsStatus({
|
return await getClientsStatus({
|
||||||
clientIds: [server.id],
|
clientIds: [server.id],
|
||||||
clientType: ClientType.FRPS,
|
clientType: ClientType.FRPS,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const trans = (info: ClientStatus | undefined) => {
|
const trans = (info: ClientStatus | undefined) => {
|
||||||
let statusText: "在线" | "离线" | "错误" | "未知" = "未知";
|
let statusText: '在线' | '离线' | '错误' | '未知' = '未知'
|
||||||
if (info === undefined) {
|
if (info === undefined) {
|
||||||
return statusText;
|
return statusText
|
||||||
}
|
}
|
||||||
if (info.status === ClientStatus_Status.ONLINE) {
|
if (info.status === ClientStatus_Status.ONLINE) {
|
||||||
statusText = "在线";
|
statusText = '在线'
|
||||||
} else if (info.status === ClientStatus_Status.OFFLINE) {
|
} else if (info.status === ClientStatus_Status.OFFLINE) {
|
||||||
statusText = "离线";
|
statusText = '离线'
|
||||||
} else if (info.status === ClientStatus_Status.ERROR) {
|
} else if (info.status === ClientStatus_Status.ERROR) {
|
||||||
statusText = "错误";
|
statusText = '错误'
|
||||||
} return statusText;
|
}
|
||||||
|
return statusText
|
||||||
}
|
}
|
||||||
|
|
||||||
const infoColor = clientsInfo.data?.clients[server.id]?.status === ClientStatus_Status.ONLINE ? "text-green-500" : "text-red-500"
|
const infoColor =
|
||||||
|
clientsInfo.data?.clients[server.id]?.status === ClientStatus_Status.ONLINE ? 'text-green-500' : 'text-red-500'
|
||||||
|
|
||||||
return <div className={`p-2 border rounded font-mono w-fit ${infoColor}`}>
|
return (
|
||||||
|
<div className={`p-2 border rounded font-mono w-fit ${infoColor}`}>
|
||||||
{`${clientsInfo.data?.clients[server.id].ping}ms, ${trans(clientsInfo.data?.clients[server.id])}`}
|
{`${clientsInfo.data?.clients[server.id].ping}ms, ${trans(clientsInfo.data?.clients[server.id])}`}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ServerSecret = ({ server }: { server: ServerTableSchema }) => {
|
export const ServerSecret = ({ server }: { server: ServerTableSchema }) => {
|
||||||
const [showSecrect, setShowSecrect] = useState<boolean>(false)
|
const [showSecrect, setShowSecrect] = useState<boolean>(false)
|
||||||
const fakeSecret = Array.from({ length: server.secret.length }).map(() => '*').join('')
|
const fakeSecret = Array.from({ length: server.secret.length })
|
||||||
|
.map(() => '*')
|
||||||
|
.join('')
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
|
|
||||||
return <Popover>
|
return (
|
||||||
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div
|
<div
|
||||||
onMouseEnter={() => setShowSecrect(true)}
|
onMouseEnter={() => setShowSecrect(true)}
|
||||||
onMouseLeave={() => setShowSecrect(false)}
|
onMouseLeave={() => setShowSecrect(false)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (platformInfo) {
|
if (platformInfo) {
|
||||||
navigator.clipboard.writeText(ExecCommandStr("server", server, platformInfo));
|
navigator.clipboard.writeText(ExecCommandStr('server', server, platformInfo))
|
||||||
toast({ description: "复制成功", });
|
toast({ description: '复制成功' })
|
||||||
} else {
|
} else {
|
||||||
toast({ description: "获取平台信息失败", });
|
toast({ description: '获取平台信息失败' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="font-medium hover:rounded hover:bg-slate-100 p-2 font-mono whitespace-nowrap">{
|
className="font-medium hover:rounded hover:bg-slate-100 p-2 font-mono whitespace-nowrap"
|
||||||
showSecrect ? server.secret : fakeSecret
|
>
|
||||||
}</div>
|
{showSecrect ? server.secret : fakeSecret}
|
||||||
|
</div>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-fit overflow-auto max-w-48">
|
<PopoverContent className="w-fit overflow-auto max-w-48">
|
||||||
<div className="p-2 border rounded font-mono w-fit">
|
<div className="p-2 border rounded font-mono w-fit">
|
||||||
{platformInfo === undefined ? "获取平台信息失败" : ExecCommandStr("server", server, platformInfo)}
|
{platformInfo === undefined ? '获取平台信息失败' : ExecCommandStr('server', server, platformInfo)}
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerItemProps {
|
export interface ServerItemProps {
|
||||||
@@ -184,7 +201,7 @@ export interface ServerItemProps {
|
|||||||
|
|
||||||
export const ServerActions: React.FC<ServerItemProps> = ({ server, table }) => {
|
export const ServerActions: React.FC<ServerItemProps> = ({ server, table }) => {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
|
|
||||||
const fetchDataOptions = {
|
const fetchDataOptions = {
|
||||||
@@ -193,25 +210,26 @@ export const ServerActions: React.FC<ServerItemProps> = ({ server, table }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ["listServer", fetchDataOptions],
|
queryKey: ['listServer', fetchDataOptions],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await listServer({
|
return await listServer({
|
||||||
page: fetchDataOptions.pageIndex + 1,
|
page: fetchDataOptions.pageIndex + 1,
|
||||||
pageSize: fetchDataOptions.pageSize
|
pageSize: fetchDataOptions.pageSize,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const removeServer = useMutation({
|
const removeServer = useMutation({
|
||||||
mutationFn: deleteServer,
|
mutationFn: deleteServer,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast({ description: "删除成功" })
|
toast({ description: '删除成功' })
|
||||||
dataQuery.refetch()
|
dataQuery.refetch()
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({ description: "删除失败" })
|
toast({ description: '删除失败' })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
return <Dialog>
|
return (
|
||||||
|
<Dialog>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
@@ -224,26 +242,28 @@ export const ServerActions: React.FC<ServerItemProps> = ({ server, table }) => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (platformInfo) {
|
if (platformInfo) {
|
||||||
navigator.clipboard.writeText(ExecCommandStr("server", server, platformInfo));
|
navigator.clipboard.writeText(ExecCommandStr('server', server, platformInfo))
|
||||||
toast({ description: "复制成功,如果复制不成功,请点击ID字段手动复制", });
|
toast({ description: '复制成功,如果复制不成功,请点击ID字段手动复制' })
|
||||||
} else {
|
} else {
|
||||||
toast({ description: "获取平台信息失败,如果复制不成功,请点击ID字段手动复制", });
|
toast({ description: '获取平台信息失败,如果复制不成功,请点击ID字段手动复制' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
复制启动命令
|
复制启动命令
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={
|
<DropdownMenuItem
|
||||||
() => {
|
onClick={() => {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: "/serveredit",
|
pathname: '/serveredit',
|
||||||
query: {
|
query: {
|
||||||
serverID: server.id
|
serverID: server.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
}>修改</DropdownMenuItem>
|
>
|
||||||
|
修改
|
||||||
|
</DropdownMenuItem>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<DropdownMenuItem className="text-destructive">删除</DropdownMenuItem>
|
<DropdownMenuItem className="text-destructive">删除</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
@@ -254,14 +274,19 @@ export const ServerActions: React.FC<ServerItemProps> = ({ server, table }) => {
|
|||||||
<DialogTitle>确定删除该客户端?</DialogTitle>
|
<DialogTitle>确定删除该客户端?</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<p className="text-destructive">此操作无法撤消。您确定要永久从我们的服务器中删除该客户端?</p>
|
<p className="text-destructive">此操作无法撤消。您确定要永久从我们的服务器中删除该客户端?</p>
|
||||||
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">删除后运行中的客户端将无法通过现有参数再次连接,如果您需要删除客户端对外的连接,可以选择清空配置</p>
|
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">
|
||||||
|
删除后运行中的客户端将无法通过现有参数再次连接,如果您需要删除客户端对外的连接,可以选择清空配置
|
||||||
|
</p>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type="submit" onClick={() => removeServer.mutate({ serverId: server.id })}>确定</Button>
|
<Button type="submit" onClick={() => removeServer.mutate({ serverId: server.id })}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Server } from "@/lib/pb/common";
|
import { Server } from '@/lib/pb/common'
|
||||||
import { ServerTableSchema, columns as serverColumnsDef } from "./server_item";
|
import { ServerTableSchema, columns as serverColumnsDef } from './server_item'
|
||||||
import { DataTable } from "./data_table";
|
import { DataTable } from './data_table'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
@@ -11,11 +11,11 @@ import {
|
|||||||
getPaginationRowModel,
|
getPaginationRowModel,
|
||||||
SortingState,
|
SortingState,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
} from "@tanstack/react-table"
|
} from '@tanstack/react-table'
|
||||||
|
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { listServer } from "@/api/server";
|
import { listServer } from '@/api/server'
|
||||||
|
|
||||||
export interface ServerListProps {
|
export interface ServerListProps {
|
||||||
Servers: Server[]
|
Servers: Server[]
|
||||||
@@ -23,19 +23,18 @@ export interface ServerListProps {
|
|||||||
|
|
||||||
export const ServerList: React.FC<ServerListProps> = ({ Servers }) => {
|
export const ServerList: React.FC<ServerListProps> = ({ Servers }) => {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
||||||
[]
|
const data = Servers.map(
|
||||||
)
|
(server) =>
|
||||||
const data = Servers.map((server) =>
|
|
||||||
({
|
({
|
||||||
id: server.id == undefined ? "" : server.id,
|
id: server.id == undefined ? '' : server.id,
|
||||||
status: server.config == undefined || server.config == "" ? "invalid" : "valid",
|
status: server.config == undefined || server.config == '' ? 'invalid' : 'valid',
|
||||||
secret: server.secret == undefined ? "" : server.secret,
|
secret: server.secret == undefined ? '' : server.secret,
|
||||||
config: server.config
|
config: server.config,
|
||||||
} as ServerTableSchema))
|
}) as ServerTableSchema,
|
||||||
|
)
|
||||||
|
|
||||||
const [{ pageIndex, pageSize }, setPagination] =
|
const [{ pageIndex, pageSize }, setPagination] = React.useState<PaginationState>({
|
||||||
React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
})
|
})
|
||||||
@@ -49,27 +48,30 @@ export const ServerList: React.FC<ServerListProps> = ({ Servers }) => {
|
|||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
}),
|
}),
|
||||||
[pageIndex, pageSize]
|
[pageIndex, pageSize],
|
||||||
)
|
)
|
||||||
|
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ["listServer", fetchDataOptions],
|
queryKey: ['listServer', fetchDataOptions],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await listServer({ page: fetchDataOptions.pageIndex + 1, pageSize: fetchDataOptions.pageSize })
|
return await listServer({ page: fetchDataOptions.pageIndex + 1, pageSize: fetchDataOptions.pageSize })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataQuery.data?.servers.map((server) => {
|
data:
|
||||||
|
dataQuery.data?.servers.map((server) => {
|
||||||
return {
|
return {
|
||||||
id: server.id == undefined ? "" : server.id,
|
id: server.id == undefined ? '' : server.id,
|
||||||
status: server.config == undefined || server.config == "" ? "invalid" : "valid",
|
status: server.config == undefined || server.config == '' ? 'invalid' : 'valid',
|
||||||
secret: server.secret == undefined ? "" : server.secret,
|
secret: server.secret == undefined ? '' : server.secret,
|
||||||
ip: server.ip,
|
ip: server.ip,
|
||||||
config: server.config
|
config: server.config,
|
||||||
} as ServerTableSchema
|
} as ServerTableSchema
|
||||||
}) ?? data,
|
}) ?? data,
|
||||||
pageCount: Math.ceil((dataQuery.data?.total == undefined ? 0 : dataQuery.data?.total) / fetchDataOptions.pageSize ?? 0),
|
pageCount: Math.ceil(
|
||||||
|
(dataQuery.data?.total == undefined ? 0 : dataQuery.data?.total) / fetchDataOptions.pageSize ?? 0,
|
||||||
|
),
|
||||||
columns: serverColumnsDef,
|
columns: serverColumnsDef,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
@@ -86,4 +88,4 @@ export const ServerList: React.FC<ServerListProps> = ({ Servers }) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
return <DataTable table={table} columns={serverColumnsDef} />
|
return <DataTable table={table} columns={serverColumnsDef} />
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from 'react'
|
||||||
import { Button } from "./ui/button"
|
import { Button } from './ui/button'
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
export interface SideBarItem {
|
export interface SideBarItem {
|
||||||
id: string
|
id: string
|
||||||
@@ -13,24 +13,71 @@ export interface SideBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SideBar: React.FC<SideBarProps> = ({ items }) => {
|
export const SideBar: React.FC<SideBarProps> = ({ items }) => {
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const defaultItems =
|
const defaultItems = [
|
||||||
[
|
{
|
||||||
{ id: "clients", label: "客户端", eventHandler: () => { router.push("/clients") } },
|
id: 'clients',
|
||||||
{ id: "servers", label: "服务端", eventHandler: () => { router.push("/servers") } },
|
label: '客户端',
|
||||||
{ id: "clientedit", label: "编辑隧道", eventHandler: () => { router.push("/clientedit") } },
|
eventHandler: () => {
|
||||||
{ id: "serveredit", label: "编辑端点", eventHandler: () => { router.push("/serveredit") } },
|
router.push('/clients')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'servers',
|
||||||
|
label: '服务端',
|
||||||
|
eventHandler: () => {
|
||||||
|
router.push('/servers')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'clientedit',
|
||||||
|
label: '编辑隧道',
|
||||||
|
eventHandler: () => {
|
||||||
|
router.push('/clientedit')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'serveredit',
|
||||||
|
label: '编辑端点',
|
||||||
|
eventHandler: () => {
|
||||||
|
router.push('/serveredit')
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
<div className="w-48 h-full grid grid-cols-1 mt-1 min-w-24">
|
<div className="w-48 h-full grid grid-cols-1 mt-1 min-w-24">
|
||||||
{items && items.map(item => <Button className={`mx-2 my-1 justify-start ${router.pathname.includes(item.id) ?
|
{items &&
|
||||||
"bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground" : ""}`} variant={"ghost"}
|
items.map((item) => (
|
||||||
size={"sm"} key={item.id}
|
<Button
|
||||||
onClick={item.eventHandler}>{item.label}</Button>)}
|
className={`mx-2 my-1 justify-start ${
|
||||||
{!items && defaultItems.map(item => <Button className={`mx-2 my-1 justify-start ${router.pathname.includes(item.id) ?
|
router.pathname.includes(item.id)
|
||||||
"bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground" : ""}`} variant={"ghost"}
|
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground'
|
||||||
size={"sm"} key={item.id}
|
: ''
|
||||||
onClick={item.eventHandler}>{item.label}</Button>)}
|
}`}
|
||||||
|
variant={'ghost'}
|
||||||
|
size={'sm'}
|
||||||
|
key={item.id}
|
||||||
|
onClick={item.eventHandler}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
{!items &&
|
||||||
|
defaultItems.map((item) => (
|
||||||
|
<Button
|
||||||
|
className={`mx-2 my-1 justify-start ${
|
||||||
|
router.pathname.includes(item.id)
|
||||||
|
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
variant={'ghost'}
|
||||||
|
size={'sm'}
|
||||||
|
key={item.id}
|
||||||
|
onClick={item.eventHandler}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
import * as AccordionPrimitive from '@radix-ui/react-accordion'
|
||||||
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
import { ChevronDownIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Accordion = AccordionPrimitive.Root
|
const Accordion = AccordionPrimitive.Root
|
||||||
|
|
||||||
@@ -10,13 +10,9 @@ const AccordionItem = React.forwardRef<
|
|||||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AccordionPrimitive.Item
|
<AccordionPrimitive.Item ref={ref} className={cn('border-b', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("border-b", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
AccordionItem.displayName = "AccordionItem"
|
AccordionItem.displayName = 'AccordionItem'
|
||||||
|
|
||||||
const AccordionTrigger = React.forwardRef<
|
const AccordionTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
@@ -26,8 +22,8 @@ const AccordionTrigger = React.forwardRef<
|
|||||||
<AccordionPrimitive.Trigger
|
<AccordionPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -47,7 +43,7 @@ const AccordionContent = React.forwardRef<
|
|||||||
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
<div className={cn('pb-4 pt-0', className)}>{children}</div>
|
||||||
</AccordionPrimitive.Content>
|
</AccordionPrimitive.Content>
|
||||||
))
|
))
|
||||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||||
|
|||||||
@@ -1,59 +1,43 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-background text-foreground",
|
default: 'bg-background text-foreground',
|
||||||
destructive:
|
destructive: 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
||||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const Alert = React.forwardRef<
|
const Alert = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
>(({ className, variant, ...props }, ref) => (
|
>(({ className, variant, ...props }, ref) => (
|
||||||
<div
|
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
role="alert"
|
|
||||||
className={cn(alertVariants({ variant }), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
Alert.displayName = "Alert"
|
Alert.displayName = 'Alert'
|
||||||
|
|
||||||
const AlertTitle = React.forwardRef<
|
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLHeadingElement>
|
<h5 ref={ref} className={cn('mb-1 font-medium leading-none tracking-tight', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<h5
|
)
|
||||||
ref={ref}
|
AlertTitle.displayName = 'AlertTitle'
|
||||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
AlertTitle.displayName = "AlertTitle"
|
|
||||||
|
|
||||||
const AlertDescription = React.forwardRef<
|
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
<div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<div
|
)
|
||||||
ref={ref}
|
AlertDescription.displayName = 'AlertDescription'
|
||||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
AlertDescription.displayName = "AlertDescription"
|
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription }
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Avatar = React.forwardRef<
|
const Avatar = React.forwardRef<
|
||||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||||
@@ -9,10 +9,7 @@ const Avatar = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AvatarPrimitive.Root
|
<AvatarPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
|
||||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@@ -22,11 +19,7 @@ const AvatarImage = React.forwardRef<
|
|||||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AvatarPrimitive.Image
|
<AvatarPrimitive.Image ref={ref} className={cn('aspect-square h-full w-full', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("aspect-square h-full w-full", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||||
|
|
||||||
@@ -36,10 +29,7 @@ const AvatarFallback = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
|
||||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,37 +1,33 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||||
destructive:
|
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||||
outline:
|
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
secondary:
|
|
||||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2",
|
default: 'h-9 px-4 py-2',
|
||||||
sm: "h-8 rounded-md px-3 text-xs",
|
sm: 'h-8 rounded-md px-3 text-xs',
|
||||||
lg: "h-10 rounded-md px-8",
|
lg: 'h-10 rounded-md px-8',
|
||||||
icon: "h-9 w-9",
|
icon: 'h-9 w-9',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
size: "default",
|
size: 'default',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
@@ -42,16 +38,10 @@ export interface ButtonProps
|
|||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : 'button'
|
||||||
return (
|
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
||||||
<Comp
|
},
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
Button.displayName = 'Button'
|
||||||
)
|
|
||||||
Button.displayName = "Button"
|
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants }
|
||||||
|
|||||||
@@ -1,76 +1,43 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||||
HTMLDivElement,
|
<div ref={ref} className={cn('rounded-xl border bg-card text-card-foreground shadow', className)} {...props} />
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"rounded-xl border bg-card text-card-foreground shadow",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
Card.displayName = "Card"
|
Card.displayName = 'Card'
|
||||||
|
|
||||||
const CardHeader = React.forwardRef<
|
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
HTMLDivElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<div
|
)
|
||||||
ref={ref}
|
CardHeader.displayName = 'CardHeader'
|
||||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
CardHeader.displayName = "CardHeader"
|
|
||||||
|
|
||||||
const CardTitle = React.forwardRef<
|
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLHeadingElement>
|
<h3 ref={ref} className={cn('font-semibold leading-none tracking-tight', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<h3
|
)
|
||||||
ref={ref}
|
CardTitle.displayName = 'CardTitle'
|
||||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
CardTitle.displayName = "CardTitle"
|
|
||||||
|
|
||||||
const CardDescription = React.forwardRef<
|
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<p
|
)
|
||||||
ref={ref}
|
CardDescription.displayName = 'CardDescription'
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
CardDescription.displayName = "CardDescription"
|
|
||||||
|
|
||||||
const CardContent = React.forwardRef<
|
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
HTMLDivElement,
|
({ className, ...props }, ref) => <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
)
|
||||||
>(({ className, ...props }, ref) => (
|
CardContent.displayName = 'CardContent'
|
||||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
||||||
))
|
|
||||||
CardContent.displayName = "CardContent"
|
|
||||||
|
|
||||||
const CardFooter = React.forwardRef<
|
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
HTMLDivElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<div
|
)
|
||||||
ref={ref}
|
CardFooter.displayName = 'CardFooter'
|
||||||
className={cn("flex items-center p-6 pt-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
CardFooter.displayName = "CardFooter"
|
|
||||||
|
|
||||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@ const DialogOverlay = React.forwardRef<
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -36,8 +36,8 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -51,33 +51,15 @@ const DialogContent = React.forwardRef<
|
|||||||
))
|
))
|
||||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
const DialogHeader = ({
|
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
className,
|
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
DialogHeader.displayName = "DialogHeader"
|
DialogHeader.displayName = 'DialogHeader'
|
||||||
|
|
||||||
const DialogFooter = ({
|
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
className,
|
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
DialogFooter.displayName = "DialogFooter"
|
DialogFooter.displayName = 'DialogFooter'
|
||||||
|
|
||||||
const DialogTitle = React.forwardRef<
|
const DialogTitle = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
@@ -85,10 +67,7 @@ const DialogTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
||||||
"text-lg font-semibold leading-none tracking-tight",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@@ -98,11 +77,7 @@ const DialogDescription = React.forwardRef<
|
|||||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||||
import {
|
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons'
|
||||||
CheckIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
DotFilledIcon,
|
|
||||||
} from "@radix-ui/react-icons"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||||
|
|
||||||
@@ -29,9 +25,9 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||||
inset && "pl-8",
|
inset && 'pl-8',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -39,8 +35,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
))
|
))
|
||||||
DropdownMenuSubTrigger.displayName =
|
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
|
||||||
DropdownMenuPrimitive.SubTrigger.displayName
|
|
||||||
|
|
||||||
const DropdownMenuSubContent = React.forwardRef<
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
@@ -49,14 +44,13 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
DropdownMenuSubContent.displayName =
|
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
|
||||||
DropdownMenuPrimitive.SubContent.displayName
|
|
||||||
|
|
||||||
const DropdownMenuContent = React.forwardRef<
|
const DropdownMenuContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
@@ -67,9 +61,9 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -86,9 +80,9 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
inset && "pl-8",
|
inset && 'pl-8',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -102,8 +96,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -116,8 +110,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
))
|
))
|
||||||
DropdownMenuCheckboxItem.displayName =
|
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
|
||||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
|
||||||
|
|
||||||
const DropdownMenuRadioItem = React.forwardRef<
|
const DropdownMenuRadioItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
@@ -126,8 +119,8 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -149,11 +142,7 @@ const DropdownMenuLabel = React.forwardRef<
|
|||||||
>(({ className, inset, ...props }, ref) => (
|
>(({ className, inset, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Label
|
<DropdownMenuPrimitive.Label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
|
||||||
"px-2 py-1.5 text-sm font-semibold",
|
|
||||||
inset && "pl-8",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@@ -163,26 +152,14 @@ const DropdownMenuSeparator = React.forwardRef<
|
|||||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Separator
|
<DropdownMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
const DropdownMenuShortcut = ({
|
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
className,
|
return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|||||||
@@ -1,34 +1,25 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
import {
|
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from 'react-hook-form'
|
||||||
Controller,
|
|
||||||
ControllerProps,
|
|
||||||
FieldPath,
|
|
||||||
FieldValues,
|
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
} from "react-hook-form"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|
||||||
type FormFieldContextValue<
|
type FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
> = {
|
> = {
|
||||||
name: TName
|
name: TName
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
|
||||||
{} as FormFieldContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
>({
|
>({
|
||||||
...props
|
...props
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
@@ -47,7 +38,7 @@ const useFormField = () => {
|
|||||||
const fieldState = getFieldState(fieldContext.name, formState)
|
const fieldState = getFieldState(fieldContext.name, formState)
|
||||||
|
|
||||||
if (!fieldContext) {
|
if (!fieldContext) {
|
||||||
throw new Error("useFormField should be used within <FormField>")
|
throw new Error('useFormField should be used within <FormField>')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = itemContext
|
const { id } = itemContext
|
||||||
@@ -66,23 +57,20 @@ type FormItemContextValue = {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue)
|
||||||
{} as FormItemContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormItem = React.forwardRef<
|
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
HTMLDivElement,
|
({ className, ...props }, ref) => {
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const id = React.useId()
|
const id = React.useId()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItemContext.Provider value={{ id }}>
|
<FormItemContext.Provider value={{ id }}>
|
||||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
<div ref={ref} className={cn('space-y-2', className)} {...props} />
|
||||||
</FormItemContext.Provider>
|
</FormItemContext.Provider>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
FormItem.displayName = "FormItem"
|
)
|
||||||
|
FormItem.displayName = 'FormItem'
|
||||||
|
|
||||||
const FormLabel = React.forwardRef<
|
const FormLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
@@ -90,60 +78,40 @@ const FormLabel = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const { error, formItemId } = useFormField()
|
const { error, formItemId } = useFormField()
|
||||||
|
|
||||||
return (
|
return <Label ref={ref} className={cn(error && 'text-destructive', className)} htmlFor={formItemId} {...props} />
|
||||||
<Label
|
|
||||||
ref={ref}
|
|
||||||
className={cn(error && "text-destructive", className)}
|
|
||||||
htmlFor={formItemId}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
FormLabel.displayName = "FormLabel"
|
FormLabel.displayName = 'FormLabel'
|
||||||
|
|
||||||
const FormControl = React.forwardRef<
|
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
|
||||||
React.ElementRef<typeof Slot>,
|
({ ...props }, ref) => {
|
||||||
React.ComponentPropsWithoutRef<typeof Slot>
|
|
||||||
>(({ ...props }, ref) => {
|
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
FormControl.displayName = "FormControl"
|
)
|
||||||
|
FormControl.displayName = 'FormControl'
|
||||||
|
|
||||||
const FormDescription = React.forwardRef<
|
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => {
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { formDescriptionId } = useFormField()
|
const { formDescriptionId } = useFormField()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p ref={ref} id={formDescriptionId} className={cn('text-[0.8rem] text-muted-foreground', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
id={formDescriptionId}
|
|
||||||
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
FormDescription.displayName = "FormDescription"
|
)
|
||||||
|
FormDescription.displayName = 'FormDescription'
|
||||||
|
|
||||||
const FormMessage = React.forwardRef<
|
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, children, ...props }, ref) => {
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
|
||||||
>(({ className, children, ...props }, ref) => {
|
|
||||||
const { error, formMessageId } = useFormField()
|
const { error, formMessageId } = useFormField()
|
||||||
const body = error ? String(error?.message) : children
|
const body = error ? String(error?.message) : children
|
||||||
|
|
||||||
@@ -155,22 +123,14 @@ const FormMessage = React.forwardRef<
|
|||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
className={cn('text-[0.8rem] font-medium text-destructive', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
FormMessage.displayName = "FormMessage"
|
)
|
||||||
|
FormMessage.displayName = 'FormMessage'
|
||||||
|
|
||||||
export {
|
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField }
|
||||||
useFormField,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
|
||||||
FormField,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const HoverCard = HoverCardPrimitive.Root
|
const HoverCard = HoverCardPrimitive.Root
|
||||||
|
|
||||||
@@ -10,14 +10,14 @@ const HoverCardTrigger = HoverCardPrimitive.Trigger
|
|||||||
const HoverCardContent = React.forwardRef<
|
const HoverCardContent = React.forwardRef<
|
||||||
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
||||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
||||||
<HoverCardPrimitive.Content
|
<HoverCardPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
export const YesIcon = ({ className }: { className?: string }) => {
|
export const YesIcon = ({ className }: { className?: string }) => {
|
||||||
return <div className={className}>
|
return (
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
<div className={className}>
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
|
||||||
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
|
||||||
({ className, type, ...props }, ref) => {
|
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
Input.displayName = 'Input'
|
||||||
Input.displayName = "Input"
|
|
||||||
|
|
||||||
export { Input }
|
export { Input }
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70')
|
||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
)
|
|
||||||
|
|
||||||
const Label = React.forwardRef<
|
const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
||||||
VariantProps<typeof labelVariants>
|
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<LabelPrimitive.Root
|
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn(labelVariants(), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
Label.displayName = LabelPrimitive.Root.displayName
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
@@ -12,15 +12,15 @@ const PopoverAnchor = PopoverPrimitive.Anchor
|
|||||||
const PopoverContent = React.forwardRef<
|
const PopoverContent = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
||||||
<PopoverPrimitive.Portal>
|
<PopoverPrimitive.Portal>
|
||||||
<PopoverPrimitive.Content
|
<PopoverPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import {
|
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'
|
||||||
CaretSortIcon,
|
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||||
CheckIcon,
|
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronUpIcon,
|
|
||||||
} from "@radix-ui/react-icons"
|
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
@@ -22,8 +17,8 @@ const SelectTrigger = React.forwardRef<
|
|||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -41,10 +36,7 @@ const SelectScrollUpButton = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SelectPrimitive.ScrollUpButton
|
<SelectPrimitive.ScrollUpButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||||
"flex cursor-default items-center justify-center py-1",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon />
|
<ChevronUpIcon />
|
||||||
@@ -58,30 +50,26 @@ const SelectScrollDownButton = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SelectPrimitive.ScrollDownButton
|
<SelectPrimitive.ScrollDownButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||||
"flex cursor-default items-center justify-center py-1",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
))
|
))
|
||||||
SelectScrollDownButton.displayName =
|
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
||||||
SelectPrimitive.ScrollDownButton.displayName
|
|
||||||
|
|
||||||
const SelectContent = React.forwardRef<
|
const SelectContent = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
>(({ className, children, position = 'popper', ...props }, ref) => (
|
||||||
<SelectPrimitive.Portal>
|
<SelectPrimitive.Portal>
|
||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
position === "popper" &&
|
position === 'popper' &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -89,9 +77,9 @@ const SelectContent = React.forwardRef<
|
|||||||
<SelectScrollUpButton />
|
<SelectScrollUpButton />
|
||||||
<SelectPrimitive.Viewport
|
<SelectPrimitive.Viewport
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
'p-1',
|
||||||
position === "popper" &&
|
position === 'popper' &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -106,11 +94,7 @@ const SelectLabel = React.forwardRef<
|
|||||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SelectPrimitive.Label
|
<SelectPrimitive.Label ref={ref} className={cn('px-2 py-1.5 text-sm font-semibold', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||||
|
|
||||||
@@ -121,8 +105,8 @@ const SelectItem = React.forwardRef<
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -140,11 +124,7 @@ const SelectSeparator = React.forwardRef<
|
|||||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SelectPrimitive.Separator
|
<SelectPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Separator = React.forwardRef<
|
const Separator = React.forwardRef<
|
||||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
>(
|
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
||||||
(
|
|
||||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
|
||||||
ref
|
|
||||||
) => (
|
|
||||||
<SeparatorPrimitive.Root
|
<SeparatorPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
decorative={decorative}
|
decorative={decorative}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn('shrink-0 bg-border', orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]', className)}
|
||||||
"shrink-0 bg-border",
|
|
||||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
))
|
||||||
)
|
|
||||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||||
|
|
||||||
export { Separator }
|
export { Separator }
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import { useTheme } from "next-themes"
|
import { useTheme } from 'next-themes'
|
||||||
import { Toaster as Sonner } from "sonner"
|
import { Toaster as Sonner } from 'sonner'
|
||||||
|
|
||||||
type ToasterProps = React.ComponentProps<typeof Sonner>
|
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = 'system' } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={theme as ToasterProps['theme']}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
toastOptions={{
|
toastOptions={{
|
||||||
classNames: {
|
classNames: {
|
||||||
toast:
|
toast:
|
||||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||||
description: "group-[.toast]:text-muted-foreground",
|
description: 'group-[.toast]:text-muted-foreground',
|
||||||
actionButton:
|
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||||
cancelButton:
|
|
||||||
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
import * as SwitchPrimitives from '@radix-ui/react-switch'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Switch = React.forwardRef<
|
const Switch = React.forwardRef<
|
||||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
@@ -9,15 +9,15 @@ const Switch = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SwitchPrimitives.Root
|
<SwitchPrimitives.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<SwitchPrimitives.Thumb
|
<SwitchPrimitives.Thumb
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</SwitchPrimitives.Root>
|
</SwitchPrimitives.Root>
|
||||||
|
|||||||
@@ -1,120 +1,76 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Table = React.forwardRef<
|
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||||
HTMLTableElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLTableElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div className="relative w-full overflow-auto">
|
<div className="relative w-full overflow-auto">
|
||||||
<table
|
<table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
))
|
),
|
||||||
Table.displayName = "Table"
|
)
|
||||||
|
Table.displayName = 'Table'
|
||||||
|
|
||||||
const TableHeader = React.forwardRef<
|
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
HTMLTableSectionElement,
|
({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
)
|
||||||
>(({ className, ...props }, ref) => (
|
TableHeader.displayName = 'TableHeader'
|
||||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
|
||||||
))
|
|
||||||
TableHeader.displayName = "TableHeader"
|
|
||||||
|
|
||||||
const TableBody = React.forwardRef<
|
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
HTMLTableSectionElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<tbody
|
)
|
||||||
ref={ref}
|
TableBody.displayName = 'TableBody'
|
||||||
className={cn("[&_tr:last-child]:border-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableBody.displayName = "TableBody"
|
|
||||||
|
|
||||||
const TableFooter = React.forwardRef<
|
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
HTMLTableSectionElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
<tfoot ref={ref} className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<tfoot
|
)
|
||||||
ref={ref}
|
TableFooter.displayName = 'TableFooter'
|
||||||
className={cn(
|
|
||||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableFooter.displayName = "TableFooter"
|
|
||||||
|
|
||||||
const TableRow = React.forwardRef<
|
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||||
HTMLTableRowElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLTableRowElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tr
|
<tr
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
|
||||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
),
|
||||||
TableRow.displayName = "TableRow"
|
)
|
||||||
|
TableRow.displayName = 'TableRow'
|
||||||
|
|
||||||
const TableHead = React.forwardRef<
|
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
|
||||||
HTMLTableCellElement,
|
({ className, ...props }, ref) => (
|
||||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<th
|
<th
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
),
|
||||||
TableHead.displayName = "TableHead"
|
)
|
||||||
|
TableHead.displayName = 'TableHead'
|
||||||
|
|
||||||
const TableCell = React.forwardRef<
|
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
|
||||||
HTMLTableCellElement,
|
({ className, ...props }, ref) => (
|
||||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<td
|
<td
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className)}
|
||||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
),
|
||||||
TableCell.displayName = "TableCell"
|
)
|
||||||
|
TableCell.displayName = 'TableCell'
|
||||||
|
|
||||||
const TableCaption = React.forwardRef<
|
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
||||||
HTMLTableCaptionElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
<caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<caption
|
)
|
||||||
ref={ref}
|
TableCaption.displayName = 'TableCaption'
|
||||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableCaption.displayName = "TableCaption"
|
|
||||||
|
|
||||||
export {
|
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
export interface TextareaProps
|
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
|
||||||
|
|
||||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
|
||||||
({ className, ...props }, ref) => {
|
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
Textarea.displayName = 'Textarea'
|
||||||
Textarea.displayName = "Textarea"
|
|
||||||
|
|
||||||
export { Textarea }
|
export { Textarea }
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
import * as ToastPrimitives from '@radix-ui/react-toast'
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const ToastProvider = ToastPrimitives.Provider
|
const ToastProvider = ToastPrimitives.Provider
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ const ToastViewport = React.forwardRef<
|
|||||||
<ToastPrimitives.Viewport
|
<ToastPrimitives.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -23,33 +23,25 @@ const ToastViewport = React.forwardRef<
|
|||||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||||
|
|
||||||
const toastVariants = cva(
|
const toastVariants = cva(
|
||||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "border bg-background text-foreground",
|
default: 'border bg-background text-foreground',
|
||||||
destructive:
|
destructive: 'destructive group border-destructive bg-destructive text-destructive-foreground',
|
||||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const Toast = React.forwardRef<
|
const Toast = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
|
||||||
VariantProps<typeof toastVariants>
|
|
||||||
>(({ className, variant, ...props }, ref) => {
|
>(({ className, variant, ...props }, ref) => {
|
||||||
return (
|
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />
|
||||||
<ToastPrimitives.Root
|
|
||||||
ref={ref}
|
|
||||||
className={cn(toastVariants({ variant }), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
Toast.displayName = ToastPrimitives.Root.displayName
|
Toast.displayName = ToastPrimitives.Root.displayName
|
||||||
|
|
||||||
@@ -60,8 +52,8 @@ const ToastAction = React.forwardRef<
|
|||||||
<ToastPrimitives.Action
|
<ToastPrimitives.Action
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -75,8 +67,8 @@ const ToastClose = React.forwardRef<
|
|||||||
<ToastPrimitives.Close
|
<ToastPrimitives.Close
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
toast-close=""
|
toast-close=""
|
||||||
{...props}
|
{...props}
|
||||||
@@ -90,11 +82,7 @@ const ToastTitle = React.forwardRef<
|
|||||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<ToastPrimitives.Title
|
<ToastPrimitives.Title ref={ref} className={cn('text-sm font-semibold [&+div]:text-xs', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
||||||
|
|
||||||
@@ -102,11 +90,7 @@ const ToastDescription = React.forwardRef<
|
|||||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<ToastPrimitives.Description
|
<ToastPrimitives.Description ref={ref} className={cn('text-sm opacity-90', className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("text-sm opacity-90", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import {
|
import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from '@/components/ui/toast'
|
||||||
Toast,
|
import { useToast } from '@/components/ui/use-toast'
|
||||||
ToastClose,
|
|
||||||
ToastDescription,
|
|
||||||
ToastProvider,
|
|
||||||
ToastTitle,
|
|
||||||
ToastViewport,
|
|
||||||
} from "@/components/ui/toast"
|
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
|
||||||
|
|
||||||
export function Toaster() {
|
export function Toaster() {
|
||||||
const { toasts } = useToast()
|
const { toasts } = useToast()
|
||||||
@@ -18,9 +11,7 @@ export function Toaster() {
|
|||||||
<Toast key={id} {...props}>
|
<Toast key={id} {...props}>
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
{title && <ToastTitle>{title}</ToastTitle>}
|
{title && <ToastTitle>{title}</ToastTitle>}
|
||||||
{description && (
|
{description && <ToastDescription>{description}</ToastDescription>}
|
||||||
<ToastDescription>{description}</ToastDescription>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{action}
|
{action}
|
||||||
<ToastClose />
|
<ToastClose />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ const TooltipContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
// Inspired by react-hot-toast library
|
// Inspired by react-hot-toast library
|
||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
|
|
||||||
import type {
|
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
|
||||||
ToastActionElement,
|
|
||||||
ToastProps,
|
|
||||||
} from "@/components/ui/toast"
|
|
||||||
|
|
||||||
const TOAST_LIMIT = 1
|
const TOAST_LIMIT = 1
|
||||||
const TOAST_REMOVE_DELAY = 1000000
|
const TOAST_REMOVE_DELAY = 1000000
|
||||||
@@ -17,10 +14,10 @@ type ToasterToast = ToastProps & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actionTypes = {
|
const actionTypes = {
|
||||||
ADD_TOAST: "ADD_TOAST",
|
ADD_TOAST: 'ADD_TOAST',
|
||||||
UPDATE_TOAST: "UPDATE_TOAST",
|
UPDATE_TOAST: 'UPDATE_TOAST',
|
||||||
DISMISS_TOAST: "DISMISS_TOAST",
|
DISMISS_TOAST: 'DISMISS_TOAST',
|
||||||
REMOVE_TOAST: "REMOVE_TOAST",
|
REMOVE_TOAST: 'REMOVE_TOAST',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
let count = 0
|
let count = 0
|
||||||
@@ -34,20 +31,20 @@ type ActionType = typeof actionTypes
|
|||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| {
|
| {
|
||||||
type: ActionType["ADD_TOAST"]
|
type: ActionType['ADD_TOAST']
|
||||||
toast: ToasterToast
|
toast: ToasterToast
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["UPDATE_TOAST"]
|
type: ActionType['UPDATE_TOAST']
|
||||||
toast: Partial<ToasterToast>
|
toast: Partial<ToasterToast>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["DISMISS_TOAST"]
|
type: ActionType['DISMISS_TOAST']
|
||||||
toastId?: ToasterToast["id"]
|
toastId?: ToasterToast['id']
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["REMOVE_TOAST"]
|
type: ActionType['REMOVE_TOAST']
|
||||||
toastId?: ToasterToast["id"]
|
toastId?: ToasterToast['id']
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@@ -64,7 +61,7 @@ const addToRemoveQueue = (toastId: string) => {
|
|||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
toastTimeouts.delete(toastId)
|
toastTimeouts.delete(toastId)
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "REMOVE_TOAST",
|
type: 'REMOVE_TOAST',
|
||||||
toastId: toastId,
|
toastId: toastId,
|
||||||
})
|
})
|
||||||
}, TOAST_REMOVE_DELAY)
|
}, TOAST_REMOVE_DELAY)
|
||||||
@@ -74,21 +71,19 @@ const addToRemoveQueue = (toastId: string) => {
|
|||||||
|
|
||||||
export const reducer = (state: State, action: Action): State => {
|
export const reducer = (state: State, action: Action): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "ADD_TOAST":
|
case 'ADD_TOAST':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||||
}
|
}
|
||||||
|
|
||||||
case "UPDATE_TOAST":
|
case 'UPDATE_TOAST':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: state.toasts.map((t) =>
|
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
||||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "DISMISS_TOAST": {
|
case 'DISMISS_TOAST': {
|
||||||
const { toastId } = action
|
const { toastId } = action
|
||||||
|
|
||||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||||
@@ -109,11 +104,11 @@ export const reducer = (state: State, action: Action): State => {
|
|||||||
...t,
|
...t,
|
||||||
open: false,
|
open: false,
|
||||||
}
|
}
|
||||||
: t
|
: t,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "REMOVE_TOAST":
|
case 'REMOVE_TOAST':
|
||||||
if (action.toastId === undefined) {
|
if (action.toastId === undefined) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -138,20 +133,20 @@ function dispatch(action: Action) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Toast = Omit<ToasterToast, "id">
|
type Toast = Omit<ToasterToast, 'id'>
|
||||||
|
|
||||||
function toast({ ...props }: Toast) {
|
function toast({ ...props }: Toast) {
|
||||||
const id = genId()
|
const id = genId()
|
||||||
|
|
||||||
const update = (props: ToasterToast) =>
|
const update = (props: ToasterToast) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "UPDATE_TOAST",
|
type: 'UPDATE_TOAST',
|
||||||
toast: { ...props, id },
|
toast: { ...props, id },
|
||||||
})
|
})
|
||||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "ADD_TOAST",
|
type: 'ADD_TOAST',
|
||||||
toast: {
|
toast: {
|
||||||
...props,
|
...props,
|
||||||
id,
|
id,
|
||||||
@@ -185,7 +180,7 @@ function useToast() {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toast,
|
toast,
|
||||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,38 @@
|
|||||||
import * as z from "zod"
|
import * as z from 'zod'
|
||||||
import { Client, Server } from "./pb/common"
|
import { Client, Server } from './pb/common'
|
||||||
import { GetPlatformInfoResponse } from "./pb/api_user"
|
import { GetPlatformInfoResponse } from './pb/api_user'
|
||||||
|
|
||||||
export const API_PATH = '/api/v1'
|
export const API_PATH = '/api/v1'
|
||||||
export const SET_TOKEN_HEADER = 'x-set-authorization'
|
export const SET_TOKEN_HEADER = 'x-set-authorization'
|
||||||
export const X_CLIENT_REQUEST_ID = 'x-client-request-id'
|
export const X_CLIENT_REQUEST_ID = 'x-client-request-id'
|
||||||
export const LOCAL_STORAGE_TOKEN_KEY = 'token'
|
export const LOCAL_STORAGE_TOKEN_KEY = 'token'
|
||||||
export const ZodPortSchema = z.coerce.number().min(1, {
|
export const ZodPortSchema = z.coerce
|
||||||
message: "端口号不能小于 1",
|
.number()
|
||||||
}).max(65535, { message: "端口号不能大于 65535" })
|
.min(1, {
|
||||||
export const ZodIPSchema = z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
|
message: '端口号不能小于 1',
|
||||||
{ message: "请输入正确的IP地址" })
|
})
|
||||||
export const ZodStringSchema = z.string().min(1,
|
.max(65535, { message: '端口号不能大于 65535' })
|
||||||
{ message: "不能为空" })
|
export const ZodIPSchema = z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, { message: '请输入正确的IP地址' })
|
||||||
export const ZodEmailSchema = z.string()
|
export const ZodStringSchema = z.string().min(1, { message: '不能为空' })
|
||||||
.min(1, { message: "不能为空" })
|
export const ZodEmailSchema = z.string().min(1, { message: '不能为空' }).email('是不是输错了邮箱地址呢?')
|
||||||
.email("是不是输错了邮箱地址呢?")
|
|
||||||
// .refine((e) => e === "abcd@fg.com", "This email is not in our database")
|
// .refine((e) => e === "abcd@fg.com", "This email is not in our database")
|
||||||
|
|
||||||
export const ExecCommandStr = <T extends Client | Server>(type: string, item: T, info: GetPlatformInfoResponse, fileName?: string) => {
|
export const ExecCommandStr = <T extends Client | Server>(
|
||||||
return `${fileName || 'frp-panel'} ${type
|
type: string,
|
||||||
} -s ${item.secret
|
item: T,
|
||||||
} -i ${item.id
|
info: GetPlatformInfoResponse,
|
||||||
} -a ${info.globalSecret
|
fileName?: string,
|
||||||
} -r ${info.masterRpcHost
|
) => {
|
||||||
} -c ${info.masterRpcPort
|
return `${fileName || 'frp-panel'} ${type} -s ${item.secret} -i ${item.id} -a ${info.globalSecret} -r ${
|
||||||
} -p ${info.masterApiPort
|
info.masterRpcHost
|
||||||
} -e ${info.masterApiScheme
|
} -c ${info.masterRpcPort} -p ${info.masterApiPort} -e ${info.masterApiScheme}`
|
||||||
}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WindowsInstallCommand = <T extends Client | Server>(type: string, item: T, info: GetPlatformInfoResponse) => {
|
export const WindowsInstallCommand = <T extends Client | Server>(
|
||||||
|
type: string,
|
||||||
|
item: T,
|
||||||
|
info: GetPlatformInfoResponse,
|
||||||
|
) => {
|
||||||
return `Invoke-WebRequest -Uri 'https://github.com/your_repository/frp-panel/releases/latest/download/frp-panel-amd64.exe' -OutFile 'frp-panel.exe'
|
return `Invoke-WebRequest -Uri 'https://github.com/your_repository/frp-panel/releases/latest/download/frp-panel-amd64.exe' -OutFile 'frp-panel.exe'
|
||||||
Move-Item .\\frp-panel.exe C:\\Tools\\frp-panel.exe
|
Move-Item .\\frp-panel.exe C:\\Tools\\frp-panel.exe
|
||||||
$command = "C:\\Tools\\${ExecCommandStr(type, item, info, 'frp-panel.exe')}"
|
$command = "C:\\Tools\\${ExecCommandStr(type, item, info, 'frp-panel.exe')}"
|
||||||
@@ -38,6 +40,10 @@ export const WindowsInstallCommand = <T extends Client | Server>(type: string, i
|
|||||||
New-Service -Name 'FRPPanel' -BinaryPathName 'C:\\Tools\\frp-panel.exe' -StartupType Automatic | Start-Service`
|
New-Service -Name 'FRPPanel' -BinaryPathName 'C:\\Tools\\frp-panel.exe' -StartupType Automatic | Start-Service`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinuxInstallCommand = <T extends Client | Server>(type: string, item: T, info: GetPlatformInfoResponse) => {
|
export const LinuxInstallCommand = <T extends Client | Server>(
|
||||||
return `curl -sSL https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s --${ExecCommandStr(type, item, info, " ")}`
|
type: string,
|
||||||
|
item: T,
|
||||||
|
info: GetPlatformInfoResponse,
|
||||||
|
) => {
|
||||||
|
return `curl -sSL https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s --${ExecCommandStr(type, item, info, ' ')}`
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { SideBar } from '@/components/sidebar';
|
import { SideBar } from '@/components/sidebar'
|
||||||
import { FRPCFormCard } from '@/components/frpc_card';
|
import { FRPCFormCard } from '@/components/frpc_card'
|
||||||
|
|
||||||
export default function ClientEditPage() {
|
export default function ClientEditPage() {
|
||||||
return (
|
return (
|
||||||
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className='w-full'>
|
<div className="w-full">
|
||||||
<div className='flex-1 flex-col'>
|
<div className="flex-1 flex-col">
|
||||||
<FRPCFormCard />
|
<FRPCFormCard />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { ClientList } from '@/components/client_list';
|
import { ClientList } from '@/components/client_list'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { SideBar } from '@/components/sidebar';
|
import { SideBar } from '@/components/sidebar'
|
||||||
import { CreateClientDialog } from '@/components/client_create_dialog';
|
import { CreateClientDialog } from '@/components/client_create_dialog'
|
||||||
|
|
||||||
export default function ClientListPage() {
|
export default function ClientListPage() {
|
||||||
return (
|
return (
|
||||||
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className='w-full'>
|
<div className="w-full">
|
||||||
<div className='flex-1 flex-col'>
|
<div className="flex-1 flex-col">
|
||||||
<div className="flex-1 flex-row mb-2">
|
<div className="flex-1 flex-row mb-2">
|
||||||
<CreateClientDialog />
|
<CreateClientDialog />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { ServerList } from '@/components/server_list';
|
import { ServerList } from '@/components/server_list'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { SideBar } from '@/components/sidebar';
|
import { SideBar } from '@/components/sidebar'
|
||||||
import { PlatformInfo } from '@/components/platforminfo';
|
import { PlatformInfo } from '@/components/platforminfo'
|
||||||
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
||||||
<Providers>
|
<Providers>
|
||||||
|
|||||||
@@ -1,53 +1,58 @@
|
|||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { TbBuildingTunnel } from "react-icons/tb";
|
import { TbBuildingTunnel } from 'react-icons/tb'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { LoginComponent } from '@/components/login';
|
import { LoginComponent } from '@/components/login'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router'
|
||||||
import { Toaster } from '@/components/ui/toaster';
|
import { Toaster } from '@/components/ui/toaster'
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ['latin'] })
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
return (
|
return (
|
||||||
<main
|
<main className={`${inter.className}`}>
|
||||||
className={`${inter.className}`}
|
|
||||||
>
|
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className="absolute text-lg font-medium left-1/2 transform -translate-x-1/2 mt-3 lg:hidden"
|
<div
|
||||||
|
className="absolute text-lg font-medium left-1/2 transform -translate-x-1/2 mt-3 lg:hidden"
|
||||||
onClick={() => router.push('/')}
|
onClick={() => router.push('/')}
|
||||||
>
|
>
|
||||||
<div className='flex rounded hover:bg-slate-100 p-2'>
|
<div className="flex rounded hover:bg-slate-100 p-2">
|
||||||
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
||||||
FRP Panel
|
FRP Panel
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='container h-screen flex-col items-center justify-center grid lg:max-w-none lg:grid-cols-2 lg:px-0'>
|
<div className="container h-screen flex-col items-center justify-center grid lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||||
<div className='relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r'>
|
<div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r">
|
||||||
<div className='absolute inset-0 bg-zinc-900'></div>
|
<div className="absolute inset-0 bg-zinc-900"></div>
|
||||||
<div className="relative flex items-center text-lg font-medium"
|
<div className="relative flex items-center text-lg font-medium" onClick={() => router.push('/')}>
|
||||||
onClick={() => router.push('/')}
|
<div className="flex rounded hover:bg-zinc-800 p-2">
|
||||||
>
|
|
||||||
<div className='flex rounded hover:bg-zinc-800 p-2'>
|
|
||||||
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
||||||
FRP Panel
|
FRP Panel
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-20 mt-auto">
|
<div className="relative z-20 mt-auto">
|
||||||
<blockquote className="space-y-2">
|
<blockquote className="space-y-2">
|
||||||
<p className="text-lg">A multi node frp webui and for <a href='https://github.com/fatedier/frp'>[FRP]</a> server and client management, which makes this project a [Cloudflare Tunnel] or [Tailscale Funnel] open source alternative
|
<p className="text-lg">
|
||||||
</p><footer className="text-sm">navigate to: <a href='https://github.com/VaalaCat/frp-panel'>VaalaCat/frp-panel</a></footer></blockquote></div>
|
A multi node frp webui and for <a href="https://github.com/fatedier/frp">[FRP]</a> server and client
|
||||||
|
management, which makes this project a [Cloudflare Tunnel] or [Tailscale Funnel] open source
|
||||||
|
alternative
|
||||||
|
</p>
|
||||||
|
<footer className="text-sm">
|
||||||
|
navigate to: <a href="https://github.com/VaalaCat/frp-panel">VaalaCat/frp-panel</a>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div className='lg:p-8 justify-center w-[300px]'>
|
</div>
|
||||||
<div className='flex flex-col justify-center space-y-6 w-[300px]'>
|
<div className="lg:p-8 justify-center w-[300px]">
|
||||||
|
<div className="flex flex-col justify-center space-y-6 w-[300px]">
|
||||||
<div className="flex flex-col space-y-2 text-center">
|
<div className="flex flex-col space-y-2 text-center">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">登录</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">登录</h1>
|
||||||
<p className="text-sm text-muted-foreground">输入您的账号信息</p>
|
<p className="text-sm text-muted-foreground">输入您的账号信息</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full justify-center'>
|
<div className="w-full justify-center">
|
||||||
<div className='w-[300px]'>
|
<div className="w-[300px]">
|
||||||
<LoginComponent />
|
<LoginComponent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,51 +1,56 @@
|
|||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { TbBuildingTunnel } from "react-icons/tb";
|
import { TbBuildingTunnel } from 'react-icons/tb'
|
||||||
import { RegisterComponent } from '@/components/register';
|
import { RegisterComponent } from '@/components/register'
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router'
|
||||||
import { Toaster } from '@/components/ui/toaster';
|
import { Toaster } from '@/components/ui/toaster'
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ['latin'] })
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
return (
|
return (
|
||||||
<main
|
<main className={`${inter.className}`}>
|
||||||
className={`${inter.className}`}
|
|
||||||
>
|
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className="absolute text-lg font-medium left-1/2 transform -translate-x-1/2 mt-3 lg:hidden"
|
<div
|
||||||
|
className="absolute text-lg font-medium left-1/2 transform -translate-x-1/2 mt-3 lg:hidden"
|
||||||
onClick={() => router.push('/')}
|
onClick={() => router.push('/')}
|
||||||
>
|
>
|
||||||
<div className='flex rounded hover:bg-slate-100 p-2'>
|
<div className="flex rounded hover:bg-slate-100 p-2">
|
||||||
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
||||||
FRP Panel
|
FRP Panel
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='container h-screen flex-col items-center justify-center grid lg:max-w-none lg:grid-cols-2 lg:px-0'>
|
<div className="container h-screen flex-col items-center justify-center grid lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||||
<div className='relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r'>
|
<div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r">
|
||||||
<div className='absolute inset-0 bg-zinc-900'></div>
|
<div className="absolute inset-0 bg-zinc-900"></div>
|
||||||
<div className="relative flex items-center text-lg font-medium"
|
<div className="relative flex items-center text-lg font-medium" onClick={() => router.push('/')}>
|
||||||
onClick={() => router.push('/')}
|
<div className="flex rounded hover:bg-zinc-800 p-2">
|
||||||
>
|
|
||||||
<div className='flex rounded hover:bg-zinc-800 p-2'>
|
|
||||||
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
<TbBuildingTunnel className="mr-2 h-8 w-8 pb-1" />
|
||||||
FRP Panel
|
FRP Panel
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-20 mt-auto">
|
<div className="relative z-20 mt-auto">
|
||||||
<blockquote className="space-y-2">
|
<blockquote className="space-y-2">
|
||||||
<p className="text-lg">A multi node frp webui and for <a href='https://github.com/fatedier/frp'>[FRP]</a> server and client management, which makes this project a [Cloudflare Tunnel] or [Tailscale Funnel] open source alternative
|
<p className="text-lg">
|
||||||
</p><footer className="text-sm">navigate to: <a href='https://github.com/VaalaCat/frp-panel'>VaalaCat/frp-panel</a></footer></blockquote></div>
|
A multi node frp webui and for <a href="https://github.com/fatedier/frp">[FRP]</a> server and client
|
||||||
|
management, which makes this project a [Cloudflare Tunnel] or [Tailscale Funnel] open source
|
||||||
|
alternative
|
||||||
|
</p>
|
||||||
|
<footer className="text-sm">
|
||||||
|
navigate to: <a href="https://github.com/VaalaCat/frp-panel">VaalaCat/frp-panel</a>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div className='lg:p-8 justify-center w-[300px]'>
|
</div>
|
||||||
<div className='flex flex-col justify-center space-y-6 w-[300px]'>
|
<div className="lg:p-8 justify-center w-[300px]">
|
||||||
|
<div className="flex flex-col justify-center space-y-6 w-[300px]">
|
||||||
<div className="flex flex-col space-y-2 text-center">
|
<div className="flex flex-col space-y-2 text-center">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">注册</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">注册</h1>
|
||||||
<p className="text-sm text-muted-foreground">输入您的账号信息</p>
|
<p className="text-sm text-muted-foreground">输入您的账号信息</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full justify-center'>
|
<div className="w-full justify-center">
|
||||||
<div className='w-[300px]'>
|
<div className="w-[300px]">
|
||||||
<RegisterComponent />
|
<RegisterComponent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { SideBar } from '@/components/sidebar';
|
import { SideBar } from '@/components/sidebar'
|
||||||
import { FRPSFormCard } from '@/components/frps_card';
|
import { FRPSFormCard } from '@/components/frps_card'
|
||||||
|
|
||||||
export default function ServerListPage() {
|
export default function ServerListPage() {
|
||||||
return (
|
return (
|
||||||
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className='w-full'>
|
<div className="w-full">
|
||||||
<div className='flex-1 flex-col'>
|
<div className="flex-1 flex-col">
|
||||||
<FRPSFormCard />
|
<FRPSFormCard />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { ServerList } from '@/components/server_list';
|
import { ServerList } from '@/components/server_list'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { SideBar } from '@/components/sidebar';
|
import { SideBar } from '@/components/sidebar'
|
||||||
import { CreateServerDialog } from '@/components/server_create_dialog';
|
import { CreateServerDialog } from '@/components/server_create_dialog'
|
||||||
|
|
||||||
|
|
||||||
export default function ServerListPage() {
|
export default function ServerListPage() {
|
||||||
return (
|
return (
|
||||||
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
<RootLayout header={<Header />} sidebar={<SideBar />}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className='w-full'>
|
<div className="w-full">
|
||||||
<div className='flex-1 flex-col'>
|
<div className="flex-1 flex-col">
|
||||||
<div className="flex-1 flex-row mb-2">
|
<div className="flex-1 flex-row mb-2">
|
||||||
<CreateServerDialog />
|
<CreateServerDialog />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { FRPCFormCard } from '@/components/frpc_card';
|
import { FRPCFormCard } from '@/components/frpc_card'
|
||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers'
|
||||||
import { APITest } from '@/components/apitest';
|
import { APITest } from '@/components/apitest'
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { FRPSFormCard } from '@/components/frps_card';
|
import { FRPSFormCard } from '@/components/frps_card'
|
||||||
import { RootLayout } from '@/components/layout';
|
import { RootLayout } from '@/components/layout'
|
||||||
import { Header } from '@/components/header';
|
import { Header } from '@/components/header'
|
||||||
import { SideBar } from '@/components/sidebar';
|
import { SideBar } from '@/components/sidebar'
|
||||||
|
|
||||||
|
|
||||||
export default function Test() {
|
export default function Test() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
2656
www/pnpm-lock.yaml
generated
2656
www/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import { TypedProxyConfig } from "@/types/proxy"
|
import { TypedProxyConfig } from '@/types/proxy'
|
||||||
import { atom } from "nanostores"
|
import { atom } from 'nanostores'
|
||||||
|
|
||||||
export const $clientProxyConfigs = atom<TypedProxyConfig[]>([])
|
export const $clientProxyConfigs = atom<TypedProxyConfig[]>([])
|
||||||
@@ -1,77 +1,72 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ["class"],
|
darkMode: ['class'],
|
||||||
content: [
|
content: ['./pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}'],
|
||||||
'./pages/**/*.{ts,tsx}',
|
prefix: '',
|
||||||
'./components/**/*.{ts,tsx}',
|
|
||||||
'./app/**/*.{ts,tsx}',
|
|
||||||
'./src/**/*.{ts,tsx}',
|
|
||||||
],
|
|
||||||
prefix: "",
|
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: "2rem",
|
padding: '2rem',
|
||||||
screens: {
|
screens: {
|
||||||
"2xl": "1400px",
|
'2xl': '1400px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
border: 'hsl(var(--border))',
|
||||||
input: "hsl(var(--input))",
|
input: 'hsl(var(--input))',
|
||||||
ring: "hsl(var(--ring))",
|
ring: 'hsl(var(--ring))',
|
||||||
background: "hsl(var(--background))",
|
background: 'hsl(var(--background))',
|
||||||
foreground: "hsl(var(--foreground))",
|
foreground: 'hsl(var(--foreground))',
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "hsl(var(--primary))",
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: "hsl(var(--muted))",
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "hsl(var(--accent))",
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: "hsl(var(--popover))",
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: 'var(--radius)',
|
||||||
md: "calc(var(--radius) - 2px)",
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: "calc(var(--radius) - 4px)",
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"accordion-down": {
|
'accordion-down': {
|
||||||
from: { height: "0" },
|
from: { height: '0' },
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
to: { height: 'var(--radix-accordion-content-height)' },
|
||||||
},
|
},
|
||||||
"accordion-up": {
|
'accordion-up': {
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
from: { height: 'var(--radix-accordion-content-height)' },
|
||||||
to: { height: "0" },
|
to: { height: '0' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require('tailwindcss-animate')],
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,7 @@ const config: Config = {
|
|||||||
extend: {
|
extend: {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||||
'gradient-conic':
|
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export interface APIMetadata {
|
export interface APIMetadata {
|
||||||
version: string;
|
version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseResponse {
|
export interface BaseResponse {
|
||||||
code: number;
|
code: number
|
||||||
msg: string;
|
msg: string
|
||||||
body?: any;
|
body?: any
|
||||||
}
|
}
|
||||||
@@ -1,84 +1,84 @@
|
|||||||
import { AuthMethod, AuthScope, LogConfig, QUICOptions, TLSConfig, WebServerConfig } from "./common";
|
import { AuthMethod, AuthScope, LogConfig, QUICOptions, TLSConfig, WebServerConfig } from './common'
|
||||||
import { TypedProxyConfig } from "./proxy";
|
import { TypedProxyConfig } from './proxy'
|
||||||
import { TypedVisitorConfig } from "./visitor";
|
import { TypedVisitorConfig } from './visitor'
|
||||||
|
|
||||||
export interface AuthOIDCClientConfig {
|
export interface AuthOIDCClientConfig {
|
||||||
clientID?: string;
|
clientID?: string
|
||||||
clientSecret?: string;
|
clientSecret?: string
|
||||||
audience?: string;
|
audience?: string
|
||||||
scope?: string;
|
scope?: string
|
||||||
tokenEndpointURL?: string;
|
tokenEndpointURL?: string
|
||||||
additionalEndpointParams?: { [key: string]: string };
|
additionalEndpointParams?: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthClientConfig {
|
export interface AuthClientConfig {
|
||||||
method?: AuthMethod;
|
method?: AuthMethod
|
||||||
additionalScopes?: AuthScope[];
|
additionalScopes?: AuthScope[]
|
||||||
token?: string;
|
token?: string
|
||||||
oidc?: AuthOIDCClientConfig;
|
oidc?: AuthOIDCClientConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientTransportConfig {
|
export interface ClientTransportConfig {
|
||||||
protocol?: string;
|
protocol?: string
|
||||||
dialServerTimeout?: number;
|
dialServerTimeout?: number
|
||||||
dialServerKeepAlive?: number;
|
dialServerKeepAlive?: number
|
||||||
connectServerLocalIP?: string;
|
connectServerLocalIP?: string
|
||||||
proxyURL?: string;
|
proxyURL?: string
|
||||||
poolCount?: number;
|
poolCount?: number
|
||||||
tcpMux?: boolean;
|
tcpMux?: boolean
|
||||||
tcpMuxKeepaliveInterval?: number;
|
tcpMuxKeepaliveInterval?: number
|
||||||
quic?: QUICOptions;
|
quic?: QUICOptions
|
||||||
heartbeatInterval?: number;
|
heartbeatInterval?: number
|
||||||
heartbeatTimeout?: number;
|
heartbeatTimeout?: number
|
||||||
tls?: TLSClientConfig;
|
tls?: TLSClientConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TLSClientConfig {
|
export interface TLSClientConfig {
|
||||||
enable?: boolean;
|
enable?: boolean
|
||||||
disableCustomTLSFirstByte?: boolean;
|
disableCustomTLSFirstByte?: boolean
|
||||||
tls?: TLSConfig;
|
tls?: TLSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompleteTLSClientConfig extends TLSClientConfig {
|
export interface CompleteTLSClientConfig extends TLSClientConfig {
|
||||||
enable: boolean;
|
enable: boolean
|
||||||
disableCustomTLSFirstByte: boolean;
|
disableCustomTLSFirstByte: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthClientConfig {
|
export interface AuthClientConfig {
|
||||||
auth?: AuthClientConfig;
|
auth?: AuthClientConfig
|
||||||
user?: string;
|
user?: string
|
||||||
serverAddr?: string;
|
serverAddr?: string
|
||||||
serverPort?: number;
|
serverPort?: number
|
||||||
natHoleStunServer?: string;
|
natHoleStunServer?: string
|
||||||
dnsServer?: string;
|
dnsServer?: string
|
||||||
loginFailExit?: boolean;
|
loginFailExit?: boolean
|
||||||
start?: string[];
|
start?: string[]
|
||||||
log?: LogConfig;
|
log?: LogConfig
|
||||||
webServer?: WebServerConfig;
|
webServer?: WebServerConfig
|
||||||
transport?: ClientTransportConfig;
|
transport?: ClientTransportConfig
|
||||||
udpPacketSize?: number;
|
udpPacketSize?: number
|
||||||
metadatas?: { [key: string]: string };
|
metadatas?: { [key: string]: string }
|
||||||
includes?: string[];
|
includes?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientConfig extends ClientCommonConfig {
|
export interface ClientConfig extends ClientCommonConfig {
|
||||||
proxies?: TypedProxyConfig[];
|
proxies?: TypedProxyConfig[]
|
||||||
visitors?: TypedVisitorConfig[];
|
visitors?: TypedVisitorConfig[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientCommonConfig extends AuthClientConfig {
|
export interface ClientCommonConfig extends AuthClientConfig {
|
||||||
auth?: AuthClientConfig;
|
auth?: AuthClientConfig
|
||||||
user?: string;
|
user?: string
|
||||||
serverAddr: string;
|
serverAddr: string
|
||||||
serverPort: number;
|
serverPort: number
|
||||||
natHoleStunServer?: string;
|
natHoleStunServer?: string
|
||||||
dnsServer?: string;
|
dnsServer?: string
|
||||||
loginFailExit?: boolean;
|
loginFailExit?: boolean
|
||||||
start?: string[];
|
start?: string[]
|
||||||
log?: LogConfig;
|
log?: LogConfig
|
||||||
webServer?: WebServerConfig;
|
webServer?: WebServerConfig
|
||||||
transport?: ClientTransportConfig;
|
transport?: ClientTransportConfig
|
||||||
udpPacketSize?: number;
|
udpPacketSize?: number
|
||||||
metadatas?: { [key: string]: string };
|
metadatas?: { [key: string]: string }
|
||||||
includes?: string[];
|
includes?: string[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
export interface QUICOptions {
|
export interface QUICOptions {
|
||||||
keepalivePeriod?: number;
|
keepalivePeriod?: number
|
||||||
maxIdleTimeout?: number;
|
maxIdleTimeout?: number
|
||||||
maxIncomingStreams?: number;
|
maxIncomingStreams?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebServerConfig {
|
export interface WebServerConfig {
|
||||||
addr?: string;
|
addr?: string
|
||||||
port?: number;
|
port?: number
|
||||||
user?: string;
|
user?: string
|
||||||
password?: string;
|
password?: string
|
||||||
assetsDir?: string;
|
assetsDir?: string
|
||||||
pprofEnable?: boolean;
|
pprofEnable?: boolean
|
||||||
tls?: TLSConfig;
|
tls?: TLSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TLSConfig {
|
export interface TLSConfig {
|
||||||
certFile?: string;
|
certFile?: string
|
||||||
keyFile?: string;
|
keyFile?: string
|
||||||
trustedCaFile?: string;
|
trustedCaFile?: string
|
||||||
serverName?: string;
|
serverName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogConfig {
|
export interface LogConfig {
|
||||||
to?: string;
|
to?: string
|
||||||
level?: string;
|
level?: string
|
||||||
maxDays: number;
|
maxDays: number
|
||||||
disablePrintColor?: boolean;
|
disablePrintColor?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPPluginOptions {
|
export interface HTTPPluginOptions {
|
||||||
name: string;
|
name: string
|
||||||
addr: string;
|
addr: string
|
||||||
path: string;
|
path: string
|
||||||
ops: string[];
|
ops: string[]
|
||||||
tlsVerify?: boolean;
|
tlsVerify?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HeaderOperations {
|
export interface HeaderOperations {
|
||||||
set?: { [key: string]: string };
|
set?: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthMethod = "token" | "oidc";
|
export type AuthMethod = 'token' | 'oidc'
|
||||||
|
|
||||||
export const AuthMethodToken: AuthMethod = "token";
|
export const AuthMethodToken: AuthMethod = 'token'
|
||||||
export const AuthMethodOIDC: AuthMethod = "oidc";
|
export const AuthMethodOIDC: AuthMethod = 'oidc'
|
||||||
|
|
||||||
export type AuthScope = "HeartBeats" | "NewWorkConns";
|
export type AuthScope = 'HeartBeats' | 'NewWorkConns'
|
||||||
|
|
||||||
export const AuthScopeHeartBeats: AuthScope = "HeartBeats";
|
export const AuthScopeHeartBeats: AuthScope = 'HeartBeats'
|
||||||
export const AuthScopeNewWorkConns: AuthScope = "NewWorkConns";
|
export const AuthScopeNewWorkConns: AuthScope = 'NewWorkConns'
|
||||||
|
|
||||||
export interface PortsRange {
|
export interface PortsRange {
|
||||||
start?: number;
|
start?: number
|
||||||
end?: number;
|
end?: number
|
||||||
single?: number;
|
single?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BandwidthUnit = "MB" | "KB";
|
export type BandwidthUnit = 'MB' | 'KB'
|
||||||
|
|
||||||
export interface BandwidthQuantity {
|
export interface BandwidthQuantity {
|
||||||
s: BandwidthUnit; // MB or KB
|
s: BandwidthUnit // MB or KB
|
||||||
i: number; // bytes
|
i: number // bytes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,58 @@
|
|||||||
import { HeaderOperations } from "./common";
|
import { HeaderOperations } from './common'
|
||||||
|
|
||||||
export interface ClientPluginOptions {}
|
export interface ClientPluginOptions {}
|
||||||
|
|
||||||
export interface TypedClientPluginOptions {
|
export interface TypedClientPluginOptions {
|
||||||
type: string;
|
type: string
|
||||||
clientPluginOptions?: ClientPluginOptions;
|
clientPluginOptions?: ClientPluginOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTP2HTTPSPluginOptions {
|
export interface HTTP2HTTPSPluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
localAddr?: string;
|
localAddr?: string
|
||||||
hostHeaderRewrite?: string;
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations;
|
requestHeaders?: HeaderOperations
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPProxyPluginOptions {
|
export interface HTTPProxyPluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
httpUser?: string;
|
httpUser?: string
|
||||||
httpPassword?: string;
|
httpPassword?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPS2HTTPPluginOptions {
|
export interface HTTPS2HTTPPluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
localAddr?: string;
|
localAddr?: string
|
||||||
hostHeaderRewrite?: string;
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations;
|
requestHeaders?: HeaderOperations
|
||||||
crtPath?: string;
|
crtPath?: string
|
||||||
keyPath?: string;
|
keyPath?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPS2HTTPSPluginOptions {
|
export interface HTTPS2HTTPSPluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
localAddr?: string;
|
localAddr?: string
|
||||||
hostHeaderRewrite?: string;
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations;
|
requestHeaders?: HeaderOperations
|
||||||
crtPath?: string;
|
crtPath?: string
|
||||||
keyPath?: string;
|
keyPath?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Socks5PluginOptions {
|
export interface Socks5PluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
username?: string;
|
username?: string
|
||||||
password?: string;
|
password?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StaticFilePluginOptions {
|
export interface StaticFilePluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
localPath?: string;
|
localPath?: string
|
||||||
stripPrefix?: string;
|
stripPrefix?: string
|
||||||
httpUser?: string;
|
httpUser?: string
|
||||||
httpPassword?: string;
|
httpPassword?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnixDomainSocketPluginOptions {
|
export interface UnixDomainSocketPluginOptions {
|
||||||
type?: string;
|
type?: string
|
||||||
unixPath?: string;
|
unixPath?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +1,110 @@
|
|||||||
import { BandwidthQuantity, HeaderOperations } from "./common";
|
import { BandwidthQuantity, HeaderOperations } from './common'
|
||||||
import { TypedClientPluginOptions } from "./plugin";
|
import { TypedClientPluginOptions } from './plugin'
|
||||||
|
|
||||||
export interface ProxyTransport {
|
export interface ProxyTransport {
|
||||||
useEncryption?: boolean;
|
useEncryption?: boolean
|
||||||
useCompression?: boolean;
|
useCompression?: boolean
|
||||||
bandwidthLimit?: BandwidthQuantity;
|
bandwidthLimit?: BandwidthQuantity
|
||||||
bandwidthLimitMode?: string;
|
bandwidthLimitMode?: string
|
||||||
proxyProtocolVersion?: string;
|
proxyProtocolVersion?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadBalancerConfig {
|
export interface LoadBalancerConfig {
|
||||||
group: string;
|
group: string
|
||||||
groupKey?: string;
|
groupKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProxyBackend {
|
export interface ProxyBackend {
|
||||||
localIP?: string;
|
localIP?: string
|
||||||
localPort?: number;
|
localPort?: number
|
||||||
plugin?: TypedClientPluginOptions;
|
plugin?: TypedClientPluginOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckConfig {
|
export interface HealthCheckConfig {
|
||||||
type: string;
|
type: string
|
||||||
timeoutSeconds?: number;
|
timeoutSeconds?: number
|
||||||
maxFailed?: number;
|
maxFailed?: number
|
||||||
intervalSeconds: number;
|
intervalSeconds: number
|
||||||
path?: string;
|
path?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DomainConfig {
|
export interface DomainConfig {
|
||||||
customDomains?: string[];
|
customDomains?: string[]
|
||||||
subdomain?: string;
|
subdomain?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProxyBaseConfig {
|
export interface ProxyBaseConfig {
|
||||||
name: string;
|
name: string
|
||||||
type: string;
|
type: string
|
||||||
transport?: ProxyTransport;
|
transport?: ProxyTransport
|
||||||
metadatas?: { [key: string]: string };
|
metadatas?: { [key: string]: string }
|
||||||
loadBalancer?: LoadBalancerConfig;
|
loadBalancer?: LoadBalancerConfig
|
||||||
healthCheck?: HealthCheckConfig;
|
healthCheck?: HealthCheckConfig
|
||||||
localIP?: string;
|
localIP?: string
|
||||||
localPort?: number;
|
localPort?: number
|
||||||
plugin?: TypedClientPluginOptions;
|
plugin?: TypedClientPluginOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TypedProxyConfig = TCPProxyConfig |
|
export type TypedProxyConfig =
|
||||||
UDPProxyConfig | HTTPProxyConfig |
|
| TCPProxyConfig
|
||||||
HTTPSProxyConfig | TCPMuxProxyConfig |
|
| UDPProxyConfig
|
||||||
STCPProxyConfig | XTCPProxyConfig |
|
| HTTPProxyConfig
|
||||||
SUDPProxyConfig;
|
| HTTPSProxyConfig
|
||||||
|
| TCPMuxProxyConfig
|
||||||
|
| STCPProxyConfig
|
||||||
|
| XTCPProxyConfig
|
||||||
|
| SUDPProxyConfig
|
||||||
|
|
||||||
export type ProxyType = "tcp" | "udp" | "tcpmux" | "http" | "https" | "stcp" | "xtcp" | "sudp";
|
export type ProxyType = 'tcp' | 'udp' | 'tcpmux' | 'http' | 'https' | 'stcp' | 'xtcp' | 'sudp'
|
||||||
|
|
||||||
export interface TCPProxyConfig extends ProxyBaseConfig {
|
export interface TCPProxyConfig extends ProxyBaseConfig {
|
||||||
type: "tcp"
|
type: 'tcp'
|
||||||
remotePort?: number;
|
remotePort?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UDPProxyConfig extends ProxyBaseConfig {
|
export interface UDPProxyConfig extends ProxyBaseConfig {
|
||||||
type: "udp"
|
type: 'udp'
|
||||||
remotePort?: number;
|
remotePort?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPProxyConfig extends ProxyBaseConfig, DomainConfig {
|
export interface HTTPProxyConfig extends ProxyBaseConfig, DomainConfig {
|
||||||
type: "http"
|
type: 'http'
|
||||||
locations?: string[];
|
locations?: string[]
|
||||||
httpUser?: string;
|
httpUser?: string
|
||||||
httpPassword?: string;
|
httpPassword?: string
|
||||||
hostHeaderRewrite?: string;
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations;
|
requestHeaders?: HeaderOperations
|
||||||
routeByHTTPUser?: string;
|
routeByHTTPUser?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPSProxyConfig extends ProxyBaseConfig, DomainConfig {
|
export interface HTTPSProxyConfig extends ProxyBaseConfig, DomainConfig {
|
||||||
type: "https"
|
type: 'https'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCPMultiplexerType = "httpconnect";
|
export type TCPMultiplexerType = 'httpconnect'
|
||||||
|
|
||||||
export interface TCPMuxProxyConfig extends ProxyBaseConfig, DomainConfig {
|
export interface TCPMuxProxyConfig extends ProxyBaseConfig, DomainConfig {
|
||||||
type: "tcpmux"
|
type: 'tcpmux'
|
||||||
httpUser?: string;
|
httpUser?: string
|
||||||
httpPassword?: string;
|
httpPassword?: string
|
||||||
routeByHTTPUser?: string;
|
routeByHTTPUser?: string
|
||||||
multiplexer?: string;
|
multiplexer?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface STCPProxyConfig extends ProxyBaseConfig {
|
export interface STCPProxyConfig extends ProxyBaseConfig {
|
||||||
type: "stcp"
|
type: 'stcp'
|
||||||
secretKey?: string;
|
secretKey?: string
|
||||||
allowUsers?: string[];
|
allowUsers?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XTCPProxyConfig extends ProxyBaseConfig {
|
export interface XTCPProxyConfig extends ProxyBaseConfig {
|
||||||
type: "xtcp"
|
type: 'xtcp'
|
||||||
secretKey?: string;
|
secretKey?: string
|
||||||
allowUsers?: string[];
|
allowUsers?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SUDPProxyConfig extends ProxyBaseConfig {
|
export interface SUDPProxyConfig extends ProxyBaseConfig {
|
||||||
type: "sudp"
|
type: 'sudp'
|
||||||
secretKey?: string;
|
secretKey?: string
|
||||||
allowUsers?: string[];
|
allowUsers?: string[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,64 @@
|
|||||||
import { AuthMethod, AuthScope, HTTPPluginOptions, LogConfig, PortsRange, QUICOptions, WebServerConfig } from "./common";
|
import { AuthMethod, AuthScope, HTTPPluginOptions, LogConfig, PortsRange, QUICOptions, WebServerConfig } from './common'
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
auth?: AuthServerConfig;
|
auth?: AuthServerConfig
|
||||||
bindAddr?: string;
|
bindAddr?: string
|
||||||
bindPort?: number;
|
bindPort?: number
|
||||||
kcpBindPort?: number;
|
kcpBindPort?: number
|
||||||
quicBindPort?: number;
|
quicBindPort?: number
|
||||||
proxyBindAddr?: string;
|
proxyBindAddr?: string
|
||||||
vhostHTTPPort?: number;
|
vhostHTTPPort?: number
|
||||||
vhostHTTPTimeout?: number;
|
vhostHTTPTimeout?: number
|
||||||
vhostHTTPSPort?: number;
|
vhostHTTPSPort?: number
|
||||||
tcpmuxHTTPConnectPort?: number;
|
tcpmuxHTTPConnectPort?: number
|
||||||
tcpmuxPassthrough?: boolean;
|
tcpmuxPassthrough?: boolean
|
||||||
subDomainHost?: string;
|
subDomainHost?: string
|
||||||
custom404Page?: string;
|
custom404Page?: string
|
||||||
sshTunnelGateway?: SSHTunnelGateway;
|
sshTunnelGateway?: SSHTunnelGateway
|
||||||
webServer?: WebServerConfig;
|
webServer?: WebServerConfig
|
||||||
enablePrometheus?: boolean;
|
enablePrometheus?: boolean
|
||||||
log?: LogConfig;
|
log?: LogConfig
|
||||||
transport?: ServerTransportConfig;
|
transport?: ServerTransportConfig
|
||||||
detailedErrorsToClient?: boolean;
|
detailedErrorsToClient?: boolean
|
||||||
maxPortsPerClient?: number;
|
maxPortsPerClient?: number
|
||||||
userConnTimeout?: number;
|
userConnTimeout?: number
|
||||||
udpPacketSize?: number;
|
udpPacketSize?: number
|
||||||
natholeAnalysisDataReserveHours?: number;
|
natholeAnalysisDataReserveHours?: number
|
||||||
allowPorts?: PortsRange[];
|
allowPorts?: PortsRange[]
|
||||||
httpPlugins?: HTTPPluginOptions[];
|
httpPlugins?: HTTPPluginOptions[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface AuthServerConfig {
|
export interface AuthServerConfig {
|
||||||
method?: AuthMethod;
|
method?: AuthMethod
|
||||||
additionalScopes?: AuthScope[];
|
additionalScopes?: AuthScope[]
|
||||||
token?: string;
|
token?: string
|
||||||
oidc?: AuthOIDCServerConfig;
|
oidc?: AuthOIDCServerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthOIDCServerConfig {
|
export interface AuthOIDCServerConfig {
|
||||||
issuer?: string;
|
issuer?: string
|
||||||
audience?: string;
|
audience?: string
|
||||||
skipExpiryCheck?: boolean;
|
skipExpiryCheck?: boolean
|
||||||
skipIssuerCheck?: boolean;
|
skipIssuerCheck?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerTransportConfig {
|
export interface ServerTransportConfig {
|
||||||
tcpMux?: boolean;
|
tcpMux?: boolean
|
||||||
tcpMuxKeepaliveInterval?: number;
|
tcpMuxKeepaliveInterval?: number
|
||||||
tcpKeepalive?: number;
|
tcpKeepalive?: number
|
||||||
maxPoolCount?: number;
|
maxPoolCount?: number
|
||||||
heartbeatTimeout?: number;
|
heartbeatTimeout?: number
|
||||||
quic?: QUICOptions;
|
quic?: QUICOptions
|
||||||
tls?: TLSServerConfig;
|
tls?: TLSServerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TLSServerConfig {
|
export interface TLSServerConfig {
|
||||||
force?: boolean;
|
force?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SSHTunnelGateway {
|
export interface SSHTunnelGateway {
|
||||||
bindPort?: number;
|
bindPort?: number
|
||||||
privateKeyFile?: string;
|
privateKeyFile?: string
|
||||||
autoGenPrivateKeyPath?: string;
|
autoGenPrivateKeyPath?: string
|
||||||
authorizedKeysFile?: string;
|
authorizedKeysFile?: string
|
||||||
}
|
}
|
||||||
@@ -1,41 +1,41 @@
|
|||||||
export interface VisitorTransport {
|
export interface VisitorTransport {
|
||||||
useEncryption?: boolean;
|
useEncryption?: boolean
|
||||||
useCompression?: boolean;
|
useCompression?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VisitorBaseConfig {
|
export interface VisitorBaseConfig {
|
||||||
name: string;
|
name: string
|
||||||
type: string;
|
type: string
|
||||||
transport?: VisitorTransport;
|
transport?: VisitorTransport
|
||||||
secretKey?: string;
|
secretKey?: string
|
||||||
serverUser?: string;
|
serverUser?: string
|
||||||
serverName?: string;
|
serverName?: string
|
||||||
bindAddr?: string;
|
bindAddr?: string
|
||||||
bindPort?: number;
|
bindPort?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VisitorType = "stcp" | "xtcp" | "sudp";
|
export type VisitorType = 'stcp' | 'xtcp' | 'sudp'
|
||||||
|
|
||||||
export type TypedVisitorConfig = STCPVisitorConfig | SUDPVisitorConfig | XTCPVisitorConfig;
|
export type TypedVisitorConfig = STCPVisitorConfig | SUDPVisitorConfig | XTCPVisitorConfig
|
||||||
|
|
||||||
export interface STCPVisitorConfig extends VisitorBaseConfig {
|
export interface STCPVisitorConfig extends VisitorBaseConfig {
|
||||||
type: "stcp"
|
type: 'stcp'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SUDPVisitorConfig extends VisitorBaseConfig {
|
export interface SUDPVisitorConfig extends VisitorBaseConfig {
|
||||||
type: "sudp";
|
type: 'sudp'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XTCPVisitorConfig extends VisitorBaseConfig {
|
export interface XTCPVisitorConfig extends VisitorBaseConfig {
|
||||||
type: "xtcp";
|
type: 'xtcp'
|
||||||
protocol?: string;
|
protocol?: string
|
||||||
keepTunnelOpen?: boolean;
|
keepTunnelOpen?: boolean
|
||||||
maxRetriesAnHour?: number;
|
maxRetriesAnHour?: number
|
||||||
minRetryInterval?: number;
|
minRetryInterval?: number
|
||||||
fallbackTo?: string;
|
fallbackTo?: string
|
||||||
fallbackTimeoutMs?: number;
|
fallbackTimeoutMs?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientCommonConfig {
|
export interface ClientCommonConfig {
|
||||||
user: string;
|
user: string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user