Merge pull request #3968 from fatedier/dev

bump version
This commit is contained in:
fatedier 2024-02-01 14:29:17 +08:00 committed by GitHub
commit d689f0fc53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 2085 additions and 2986 deletions

View File

@ -16,19 +16,20 @@ jobs:
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
actions: write
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
stale-issue-message: 'Issues go stale after 21d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-pr-message: "PRs go stale after 21d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
stale-issue-label: 'lifecycle/stale'
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
stale-pr-label: 'lifecycle/stale'
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
days-before-stale: 30
days-before-stale: 21
days-before-close: 7
debug-only: ${{ github.event.inputs.debug-only }}
exempt-all-pr-milestones: true
exempt-all-pr-assignees: true
operations-per-run: 200

2
.gitignore vendored
View File

@ -34,6 +34,8 @@ dist/
.idea/
.vscode/
.autogen_ssh_key
client.crt
client.key
# Cache
*.swp

View File

@ -1,3 +1,11 @@
### Deprecation Notices
* Using an underscore in a flag name is deprecated and has been replaced by a hyphen. The underscore format will remain compatible for some time, until it is completely removed in a future version. For example, `--remote_port` is replaced with `--remote-port`.
### Features
* The `Refresh` and `ClearOfflineProxies` buttons have been added to the Dashboard of frps.
### Fixes
* frpc has a certain chance to panic when login: close of closed channel.
* The host/domain matching in the routing rules has been changed to be case-insensitive.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,13 +4,12 @@
<head>
<meta charset="utf-8">
<title>frp client admin UI</title>
<script type="module" crossorigin src="./index-1c7ed8b0.js"></script>
<link rel="stylesheet" href="./index-1e2a7ce0.css">
<script type="module" crossorigin src="./index-bLBhaJo8.js"></script>
<link rel="stylesheet" crossorigin href="./index-iuf46MlF.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,13 +4,12 @@
<head>
<meta charset="utf-8">
<title>frps dashboard</title>
<script type="module" crossorigin src="./index-c322b7dd.js"></script>
<link rel="stylesheet" href="./index-1e0c7400.css">
<script type="module" crossorigin src="./index-1gecbKzv.js"></script>
<link rel="stylesheet" crossorigin href="./index-Lf6B06jY.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -35,7 +35,7 @@ import (
"github.com/fatedier/frp/pkg/util/xlog"
)
// Connector is a interface for establishing connections to the server.
// Connector is an interface for establishing connections to the server.
type Connector interface {
Open() error
Connect() (net.Conn, error)
@ -59,7 +59,7 @@ func NewConnector(ctx context.Context, cfg *v1.ClientCommonConfig) Connector {
}
}
// Open opens a underlying connection to the server.
// Open opens an underlying connection to the server.
// The underlying connection is either a TCP connection or a QUIC connection.
// After the underlying connection is established, you can call Connect() to get a stream.
// If TCPMux isn't enabled, the underlying connection is nil, you will get a new real TCP connection every time you call Connect().

View File

@ -133,6 +133,7 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) {
}
if err = ctl.sessionCtx.AuthSetter.SetNewWorkConn(m); err != nil {
xl.Warn("error during NewWorkConn authentication: %v", err)
workConn.Close()
return
}
if err = msg.WriteMsg(workConn, m); err != nil {

View File

@ -97,6 +97,7 @@ func runMultipleClients(cfgDir string) error {
}
func Execute() {
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}

View File

@ -92,6 +92,7 @@ var rootCmd = &cobra.Command{
}
func Execute() {
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}

View File

@ -41,7 +41,7 @@ transport.maxPoolCount = 5
# transport.tcpKeepalive = 7200
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
tls.force = false
transport.tls.force = false
# transport.tls.certFile = "server.crt"
# transport.tls.keyFile = "server.key"
@ -129,7 +129,7 @@ allowPorts = [
maxPortsPerClient = 0
# If subDomainHost is not empty, you can set subdomain when type is http or https in frpc's configure file
# When subdomain is est, the host used by routing is test.frps.com
# When subdomain is test, the host used by routing is test.frps.com
subDomainHost = "frps.com"
# custom 404 page for HTTP requests

8
go.mod
View File

@ -18,13 +18,13 @@ require (
github.com/pion/stun v0.6.1
github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.16.0
github.com/quic-go/quic-go v0.37.4
github.com/quic-go/quic-go v0.37.7
github.com/rodaine/table v1.1.0
github.com/samber/lo v1.38.1
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.15.0
golang.org/x/crypto v0.17.0
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.10.0
golang.org/x/sync v0.3.0
@ -39,7 +39,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
@ -67,7 +67,7 @@ require (
github.com/tjfoc/gmsm v1.4.1 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.9.3 // indirect
google.golang.org/appengine v1.6.7 // indirect

18
go.sum
View File

@ -32,8 +32,8 @@ github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@ -119,8 +119,8 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
github.com/quic-go/quic-go v0.37.7 h1:AgKsQLZ1+YCwZd2GYhBUsJDYZwEkA5gENtAjb+MxONU=
github.com/quic-go/quic-go v0.37.7/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
@ -157,8 +157,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
@ -210,13 +210,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@ -26,5 +26,9 @@ frpsPath=${ROOT}/bin/frps
if [ "${FRPS_PATH}" ]; then
frpsPath="${FRPS_PATH}"
fi
concurrency="16"
if [ "${CONCURRENCY}" ]; then
concurrency="${CONCURRENCY}"
fi
ginkgo -nodes=8 --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
ginkgo -nodes=${concurrency} --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}

View File

@ -17,14 +17,24 @@ package config
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
)
// WordSepNormalizeFunc changes all flags that contain "_" separators
func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
if strings.Contains(name, "_") {
return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-"))
}
return pflag.NormalizedName(name)
}
type RegisterFlagOption func(*registerFlagOptions)
type registerFlagOptions struct {

View File

@ -195,7 +195,7 @@ type ProxyConfigurer interface {
// MarshalToMsg marshals this config into a msg.NewProxy message. This
// function will be called on the frpc side.
MarshalToMsg(*msg.NewProxy)
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
// UnmarshalFromMsg unmarshal a msg.NewProxy message into this config.
// This function will be called on the frps side.
UnmarshalFromMsg(*msg.NewProxy)
}

View File

@ -61,23 +61,23 @@ func (m *serverMetrics) run() {
for {
time.Sleep(12 * time.Hour)
start := time.Now()
count, total := m.clearUselessInfo()
count, total := m.clearUselessInfo(time.Duration(7*24) * time.Hour)
log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
}
}()
}
func (m *serverMetrics) clearUselessInfo() (int, int) {
func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration time.Duration) (int, int) {
count := 0
total := 0
// To check if there are proxies that closed than 7 days and drop them.
// To check if there are any proxies that have been closed for more than continuousOfflineDuration and remove them.
m.mu.Lock()
defer m.mu.Unlock()
total = len(m.info.ProxyStatistics)
for name, data := range m.info.ProxyStatistics {
if !data.LastCloseTime.IsZero() &&
data.LastStartTime.Before(data.LastCloseTime) &&
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
time.Since(data.LastCloseTime) > continuousOfflineDuration {
delete(m.info.ProxyStatistics, name)
count++
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
@ -86,6 +86,10 @@ func (m *serverMetrics) clearUselessInfo() (int, int) {
return count, total
}
func (m *serverMetrics) ClearOfflineProxies() (int, int) {
return m.clearUselessInfo(0)
}
func (m *serverMetrics) NewClient() {
m.info.ClientCounts.Inc(1)
}

View File

@ -79,4 +79,5 @@ type Collector interface {
GetProxiesByType(proxyType string) []*ProxyStats
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
GetProxyTraffic(name string) *ProxyTrafficInfo
ClearOfflineProxies() (int, int)
}

View File

@ -254,6 +254,8 @@ func (s *TunnelServer) parseClientAndProxyConfigurer(_ *tcpipForward, extraPaylo
Short: "ssh v0@{address} [command]",
Run: func(*cobra.Command, []string) {},
}
cmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
args := strings.Split(extraPayload, " ")
if len(args) < 1 {
return nil, nil, helpMessage, fmt.Errorf("invalid extra payload")

View File

@ -19,7 +19,7 @@ import (
"strings"
)
var version = "0.53.2"
var version = "0.54.0"
func Full() string {
return version

View File

@ -33,6 +33,8 @@ func NewRouters() *Routers {
}
func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error {
domain = strings.ToLower(domain)
r.mutex.Lock()
defer r.mutex.Unlock()
@ -64,6 +66,8 @@ func (r *Routers) Add(domain, location, httpUser string, payload interface{}) er
}
func (r *Routers) Del(domain, location, httpUser string) {
domain = strings.ToLower(domain)
r.mutex.Lock()
defer r.mutex.Unlock()
@ -86,6 +90,8 @@ func (r *Routers) Del(domain, location, httpUser string) {
}
func (r *Routers) Get(host, path, httpUser string) (vr *Router, exist bool) {
host = strings.ToLower(host)
r.mutex.RLock()
defer r.mutex.RUnlock()

View File

@ -17,6 +17,7 @@ package server
import (
"encoding/json"
"net/http"
"sort"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
@ -53,6 +54,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper)
subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE")
// view
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
@ -226,6 +228,9 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
proxyInfoResp := GetProxyInfoResp{}
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
sort.Slice(proxyInfoResp.Proxies, func(i, j int) bool {
return proxyInfoResp.Proxies[i].Name < proxyInfoResp.Proxies[j].Name
})
buf, _ := json.Marshal(&proxyInfoResp)
res.Msg = string(buf)
@ -376,3 +381,26 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
buf, _ := json.Marshal(&trafficResp)
res.Msg = string(buf)
}
// DELETE /api/proxies?status=offline
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
log.Info("Http request: [%s]", r.URL.Path)
defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
_, _ = w.Write([]byte(res.Msg))
}
}()
status := r.URL.Query().Get("status")
if status != "offline" {
res.Code = 400
res.Msg = "status only support offline"
return
}
cleared, total := mem.StatsCollector.ClearOfflineProxies()
log.Info("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
}

View File

@ -67,7 +67,7 @@ func NewDefaultFramework() *Framework {
TotalParallelNode: suiteConfig.ParallelTotal,
CurrentNodeIndex: suiteConfig.ParallelProcess,
FromPortIndex: 10000,
ToPortIndex: 60000,
ToPortIndex: 30000,
}
return NewFramework(options)
}

View File

@ -42,7 +42,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
ExpectNoError(err)
time.Sleep(500 * time.Millisecond)
}
time.Sleep(1 * time.Second)
time.Sleep(2 * time.Second)
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
for i := range clientTemplates {
@ -76,7 +76,7 @@ func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
return p, p.StdOutput(), err
}
// sleep for a while to get std output
time.Sleep(time.Second)
time.Sleep(2 * time.Second)
return p, p.StdOutput(), nil
}
@ -87,7 +87,7 @@ func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
if err != nil {
return p, p.StdOutput(), err
}
time.Sleep(time.Second)
time.Sleep(2 * time.Second)
return p, p.StdOutput(), nil
}

View File

@ -22,11 +22,11 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
clientConf := consts.LegacyDefaultClientConfig
serverConf += `
allow_ports = 20000-25000,25002,30000-50000
allow_ports = 10000-11000,11002,12000-13000
`
tcpPortName := port.GenName("TCP", port.WithRangePorts(20000, 25000))
udpPortName := port.GenName("UDP", port.WithRangePorts(30000, 50000))
tcpPortName := port.GenName("TCP", port.WithRangePorts(10000, 11000))
udpPortName := port.GenName("UDP", port.WithRangePorts(12000, 13000))
clientConf += fmt.Sprintf(`
[tcp-allowded-in-range]
type = tcp
@ -37,7 +37,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
[tcp-port-not-allowed]
type = tcp
local_port = {{ .%s }}
remote_port = 25001
remote_port = 11001
`, framework.TCPEchoServerPort)
clientConf += fmt.Sprintf(`
[tcp-port-unavailable]
@ -55,7 +55,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
[udp-port-not-allowed]
type = udp
local_port = {{ .%s }}
remote_port = 25003
remote_port = 11003
`, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
@ -65,7 +65,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).Port(25001).ExpectError(true).Ensure()
framework.NewRequestExpect(f).Port(11001).ExpectError(true).Ensure()
// Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
@ -76,7 +76,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
// Not Allowed
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.UDP().Port(25003)
r.UDP().Port(11003)
}).ExpectError(true).Ensure()
})

View File

@ -79,6 +79,7 @@ func (pa *Allocator) GetByName(portName string) int {
udpConn.Close()
pa.used.Insert(port)
pa.reserved.Delete(port)
return port
}
return 0

View File

@ -90,12 +90,12 @@ var _ = ginkgo.Describe("[Feature: Cmd]", func() {
ginkgo.It("HTTP", func() {
serverPort := f.AllocPort()
vhostHTTPPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort), "--vhost_http_port", strconv.Itoa(vhostHTTPPort))
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort), "--vhost-http-port", strconv.Itoa(vhostHTTPPort))
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("http", "-s", "127.0.0.1", "-P", strconv.Itoa(serverPort), "-t", "123", "-u", "test",
"-n", "udp_test", "-l", strconv.Itoa(f.PortByName(framework.HTTPSimpleServerPort)),
"--custom_domain", "test.example.com")
"--custom-domain", "test.example.com")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(vhostHTTPPort).

View File

@ -23,14 +23,14 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
serverConf += `
allowPorts = [
{ start = 20000, end = 25000 },
{ single = 25002 },
{ start = 30000, end = 50000 },
{ start = 10000, end = 11000 },
{ single = 11002 },
{ start = 12000, end = 13000 },
]
`
tcpPortName := port.GenName("TCP", port.WithRangePorts(20000, 25000))
udpPortName := port.GenName("UDP", port.WithRangePorts(30000, 50000))
tcpPortName := port.GenName("TCP", port.WithRangePorts(10000, 11000))
udpPortName := port.GenName("UDP", port.WithRangePorts(12000, 13000))
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp-allowded-in-range"
@ -43,7 +43,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
name = "tcp-port-not-allowed"
type = "tcp"
localPort = {{ .%s }}
remotePort = 25001
remotePort = 11001
`, framework.TCPEchoServerPort)
clientConf += fmt.Sprintf(`
[[proxies]]
@ -64,7 +64,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
name = "udp-port-not-allowed"
type = "udp"
localPort = {{ .%s }}
remotePort = 25003
remotePort = 11003
`, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
@ -74,7 +74,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).Port(25001).ExpectError(true).Ensure()
framework.NewRequestExpect(f).Port(11001).ExpectError(true).Ensure()
// Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
@ -85,7 +85,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
// Not Allowed
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.UDP().Port(25003)
r.UDP().Port(11003)
}).ExpectError(true).Ensure()
})

View File

@ -32,7 +32,7 @@ var _ = ginkgo.Describe("[Feature: SSH Tunnel]", func() {
tc := ssh.NewTunnelClient(
fmt.Sprintf("127.0.0.1:%d", localPort),
fmt.Sprintf("127.0.0.1:%d", sshPort),
fmt.Sprintf("tcp --remote_port %d", remotePort),
fmt.Sprintf("tcp --remote-port %d", remotePort),
)
framework.ExpectNoError(tc.Start())
defer tc.Close()
@ -55,7 +55,7 @@ var _ = ginkgo.Describe("[Feature: SSH Tunnel]", func() {
tc := ssh.NewTunnelClient(
fmt.Sprintf("127.0.0.1:%d", localPort),
fmt.Sprintf("127.0.0.1:%d", sshPort),
"http --custom_domain test.example.com",
"http --custom-domain test.example.com",
)
framework.ExpectNoError(tc.Start())
defer tc.Close()
@ -83,7 +83,7 @@ var _ = ginkgo.Describe("[Feature: SSH Tunnel]", func() {
tc := ssh.NewTunnelClient(
fmt.Sprintf("127.0.0.1:%d", localPort),
fmt.Sprintf("127.0.0.1:%d", sshPort),
fmt.Sprintf("https --custom_domain %s", testDomain),
fmt.Sprintf("https --custom-domain %s", testDomain),
)
framework.ExpectNoError(tc.Start())
defer tc.Close()
@ -125,7 +125,7 @@ var _ = ginkgo.Describe("[Feature: SSH Tunnel]", func() {
tc := ssh.NewTunnelClient(
fmt.Sprintf("127.0.0.1:%d", localPort),
fmt.Sprintf("127.0.0.1:%d", sshPort),
fmt.Sprintf("tcpmux --mux=httpconnect --custom_domain %s", testDomain),
fmt.Sprintf("tcpmux --mux=httpconnect --custom-domain %s", testDomain),
)
framework.ExpectNoError(tc.Start())
defer tc.Close()
@ -179,7 +179,7 @@ var _ = ginkgo.Describe("[Feature: SSH Tunnel]", func() {
tc := ssh.NewTunnelClient(
fmt.Sprintf("127.0.0.1:%d", localPort),
fmt.Sprintf("127.0.0.1:%d", sshPort),
"stcp -n stcp-test --sk=abcdefg --allow_users=\"*\"",
"stcp -n stcp-test --sk=abcdefg --allow-users=\"*\"",
)
framework.ExpectNoError(tc.Start())
defer tc.Close()

View File

@ -1,6 +1,7 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {

View File

@ -3,11 +3,9 @@
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
declare module 'vue' {
export interface GlobalComponents {
ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']

View File

@ -1,6 +1,6 @@
{
"name": "-frpc-dashboard",
"version": "0.0.0",
"name": "frpc-dashboard",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite",
@ -11,25 +11,25 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"element-plus": "^2.3.3",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
"element-plus": "^2.5.3",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@rushstack/eslint-patch": "^1.7.2",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.21.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.14.3",
"unplugin-vue-components": "^0.24.0",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
"prettier": "^3.2.4",
"typescript": "~5.3.3",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.12",
"vue-tsc": "^1.8.27"
}
}

View File

@ -1,8 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}

View File

@ -1,16 +1,25 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"references": [
{
"path": "./tsconfig.config.json"
}
]
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
// Generated by 'unplugin-auto-import'
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {

View File

@ -1,11 +1,11 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
@ -13,6 +13,8 @@ declare module '@vue/runtime-core' {
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRow: typeof import('element-plus/es')['ElRow']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']

View File

@ -12,27 +12,27 @@
},
"dependencies": {
"@types/humanize-plus": "^1.8.0",
"echarts": "^5.4.1",
"element-plus": "^2.3.3",
"echarts": "^5.4.3",
"element-plus": "^2.5.3",
"humanize-plus": "^1.8.2",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@rushstack/eslint-patch": "^1.7.2",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.21.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.13.0",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.0.4",
"vue-tsc": "^1.0.12"
"prettier": "^3.2.4",
"typescript": "~5.3.3",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.12",
"vue-tsc": "^1.8.27"
}
}

View File

@ -1,5 +1,5 @@
<template>
<ProxyView :proxies="proxies" proxyType="http" />
<ProxyView :proxies="proxies" proxyType="http" @refresh="fetchData"/>
</template>
<script setup lang="ts">
@ -27,6 +27,7 @@ const fetchData = () => {
return res.json()
})
.then((json) => {
proxies.value = []
for (let proxyStats of json.proxies) {
proxies.value.push(
new HTTPProxy(proxyStats, vhostHTTPPort, subdomainHost)

View File

@ -1,5 +1,5 @@
<template>
<ProxyView :proxies="proxies" proxyType="https" />
<ProxyView :proxies="proxies" proxyType="https" @refresh="fetchData"/>
</template>
<script setup lang="ts">
@ -27,6 +27,7 @@ const fetchData = () => {
return res.json()
})
.then((json) => {
proxies.value = []
for (let proxyStats of json.proxies) {
proxies.value.push(
new HTTPSProxy(proxyStats, vhostHTTPSPort, subdomainHost)

View File

@ -1,5 +1,5 @@
<template>
<ProxyView :proxies="proxies" proxyType="stcp" />
<ProxyView :proxies="proxies" proxyType="stcp" @refresh="fetchData"/>
</template>
<script setup lang="ts">
@ -15,6 +15,7 @@ const fetchData = () => {
return res.json()
})
.then((json) => {
proxies.value = []
for (let proxyStats of json.proxies) {
proxies.value.push(new STCPProxy(proxyStats))
}

View File

@ -1,5 +1,5 @@
<template>
<ProxyView :proxies="proxies" proxyType="sudp" />
<ProxyView :proxies="proxies" proxyType="sudp" @refresh="fetchData"/>
</template>
<script setup lang="ts">
@ -15,6 +15,7 @@ const fetchData = () => {
return res.json()
})
.then((json) => {
proxies.value = []
for (let proxyStats of json.proxies) {
proxies.value.push(new SUDPProxy(proxyStats))
}

View File

@ -1,5 +1,5 @@
<template>
<ProxyView :proxies="proxies" proxyType="tcp" />
<ProxyView :proxies="proxies" proxyType="tcp" @refresh="fetchData" />
</template>
<script setup lang="ts">
@ -15,6 +15,7 @@ const fetchData = () => {
return res.json()
})
.then((json) => {
proxies.value = []
for (let proxyStats of json.proxies) {
proxies.value.push(new TCPProxy(proxyStats))
}

View File

@ -1,5 +1,5 @@
<template>
<ProxyView :proxies="proxies" proxyType="udp" />
<ProxyView :proxies="proxies" proxyType="udp" @refresh="fetchData"/>
</template>
<script setup lang="ts">
@ -15,12 +15,12 @@ const fetchData = () => {
return res.json()
})
.then((json) => {
proxies.value = []
for (let proxyStats of json.proxies) {
proxies.value.push(new UDPProxy(proxyStats))
}
})
}
fetchData()
</script>

View File

@ -1,5 +1,28 @@
<template>
<div>
<el-page-header
:icon="null"
style="width: 100%; margin-left: 30px; margin-bottom: 20px"
>
<template #title>
<span>{{ proxyType }}</span>
</template>
<template #content> </template>
<template #extra>
<div class="flex items-center" style="margin-right: 30px">
<el-popconfirm
title="Are you sure to clear all data of offline proxies?"
@confirm="clearOfflineProxies"
>
<template #reference>
<el-button>ClearOfflineProxies</el-button>
</template>
</el-popconfirm>
<el-button @click="$emit('refresh')">Refresh</el-button>
</div>
</template>
</el-page-header>
<el-table
:data="proxies"
:default-sort="{ prop: 'name', order: 'ascending' }"
@ -67,6 +90,7 @@
import * as Humanize from 'humanize-plus'
import type { TableColumnCtx } from 'element-plus'
import type { BaseProxy } from '../utils/proxy.js'
import { ElMessage } from 'element-plus'
import ProxyViewExpand from './ProxyViewExpand.vue'
defineProps<{
@ -74,6 +98,8 @@ defineProps<{
proxyType: string
}>()
const emit = defineEmits(['refresh'])
const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
return Humanize.fileSize(row.trafficIn)
}
@ -81,4 +107,37 @@ const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
const formatTrafficOut = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
return Humanize.fileSize(row.trafficOut)
}
const clearOfflineProxies = () => {
fetch('/api/proxies?status=offline', {
method: 'DELETE',
credentials: 'include',
})
.then((res) => {
if (res.ok) {
ElMessage({
message: 'Successfully cleared offline proxies',
type: 'success',
})
emit('refresh')
} else {
ElMessage({
message: 'Failed to clear offline proxies: ' + res.status + ' ' + res.statusText,
type: 'warning',
})
}
})
.catch((err) => {
ElMessage({
message: 'Failed to clear offline proxies: ' + err.message,
type: 'warning',
})
})
}
</script>
<style>
.el-page-header__title {
font-size: 20px;
}
</style>

View File

@ -17,10 +17,7 @@
<el-form-item label="KCP Bind Port" v-if="data.kcpBindPort != 0">
<span>{{ data.kcpBindPort }}</span>
</el-form-item>
<el-form-item
label="QUIC Bind Port"
v-if="data.quicBindPort != 0"
>
<el-form-item label="QUIC Bind Port" v-if="data.quicBindPort != 0">
<span>{{ data.quicBindPort }}</span>
</el-form-item>
<el-form-item label="Http Port" v-if="data.vhostHTTPPort != 0">

View File

@ -23,8 +23,10 @@ class BaseProxy {
this.type = ''
this.encryption = false
this.compression = false
this.encryption = (proxyStats.conf?.transport?.useEncryption) || this.encryption;
this.compression = (proxyStats.conf?.transport?.useCompression) || this.compression;
this.encryption =
proxyStats.conf?.transport?.useEncryption || this.encryption
this.compression =
proxyStats.conf?.transport?.useCompression || this.compression
this.conns = proxyStats.curConns
this.trafficIn = proxyStats.todayTrafficIn
this.trafficOut = proxyStats.todayTrafficOut
@ -76,12 +78,12 @@ class HTTPProxy extends BaseProxy {
this.type = 'http'
this.port = port
if (proxyStats.conf) {
this.customDomains = proxyStats.conf.customDomains || this.customDomains;
this.customDomains = proxyStats.conf.customDomains || this.customDomains
this.hostHeaderRewrite = proxyStats.conf.hostHeaderRewrite
this.locations = proxyStats.conf.locations
if (proxyStats.conf.subdomain) {
this.subdomain = `${proxyStats.conf.subdomain}.${subdomainHost}`
}
}
}
}
}
@ -92,7 +94,7 @@ class HTTPSProxy extends BaseProxy {
this.type = 'https'
this.port = port
if (proxyStats.conf != null) {
this.customDomains = proxyStats.conf.customDomains || this.customDomains;
this.customDomains = proxyStats.conf.customDomains || this.customDomains
if (proxyStats.conf.subdomain) {
this.subdomain = `${proxyStats.conf.subdomain}.${subdomainHost}`
}

View File

@ -1,8 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}

View File

@ -1,16 +1,25 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"references": [
{
"path": "./tsconfig.config.json"
}
]
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

File diff suppressed because it is too large Load Diff