From 10025ffa66c011fd756af780772260d833460795 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Sun, 14 Sep 2025 01:22:42 +0200 Subject: [PATCH 1/4] Subscription --- sub/sub.go | 55 + sub/subController.go | 86 +- sub/subService.go | 203 +- web/assets/css/custom.min.css | 2836 +++++++++++++++++++++++++- web/assets/js/subscription.js | 125 ++ web/html/subscription.html | 272 +++ web/locale/locale.go | 22 +- web/translation/translate.ar_EG.toml | 14 + web/translation/translate.en_US.toml | 14 + web/translation/translate.es_ES.toml | 14 + web/translation/translate.fa_IR.toml | 14 + web/translation/translate.id_ID.toml | 14 + web/translation/translate.ja_JP.toml | 14 + web/translation/translate.pt_BR.toml | 14 + web/translation/translate.ru_RU.toml | 14 + web/translation/translate.tr_TR.toml | 14 + web/translation/translate.uk_UA.toml | 14 + web/translation/translate.vi_VN.toml | 14 + web/translation/translate.zh_CN.toml | 14 + web/translation/translate.zh_TW.toml | 14 + 20 files changed, 3722 insertions(+), 59 deletions(-) create mode 100644 web/assets/js/subscription.js create mode 100644 web/html/subscription.html diff --git a/sub/sub.go b/sub/sub.go index 4f8f5672..dce57243 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -6,11 +6,14 @@ import ( "io" "net" "net/http" + "os" + "path/filepath" "strconv" "x-ui/config" "x-ui/logger" "x-ui/util/common" + "x-ui/web/locale" "x-ui/web/middleware" "x-ui/web/network" "x-ui/web/service" @@ -57,6 +60,11 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine.Use(middleware.DomainValidatorMiddleware(subDomain)) } + // Provide base_path in context for templates + engine.Use(func(c *gin.Context) { + c.Set("base_path", "/") + }) + LinksPath, err := s.settingService.GetSubPath() if err != nil { return nil, err @@ -112,6 +120,29 @@ func (s *Server) initRouter() (*gin.Engine, error) { SubTitle = "" } + // init i18n for sub server using disk FS so templates can use {{ i18n }} + // Root FS is project root; translation files are under web/translation + if err := locale.InitLocalizerFS(os.DirFS("web"), &s.settingService); err != nil { + logger.Warning("sub: i18n init failed:", err) + } + // set per-request localizer from headers/cookies + engine.Use(locale.LocalizerMiddleware()) + + // load HTML templates needed for subscription page (common layout + page + component + subscription) + if files, err := s.getHtmlFiles(); err != nil { + logger.Warning("sub: getHtmlFiles failed:", err) + } else { + // register i18n function similar to web server + i18nWebFunc := func(key string, params ...string) string { + return locale.I18n(locale.Web, key, params...) + } + engine.SetFuncMap(map[string]any{"i18n": i18nWebFunc}) + engine.LoadHTMLFiles(files...) + } + + // serve assets from web/assets to use shared JS/CSS like other pages + engine.StaticFS("/assets", http.FS(os.DirFS("web/assets"))) + g := engine.Group("/") s.sub = NewSUBController( @@ -121,6 +152,30 @@ func (s *Server) initRouter() (*gin.Engine, error) { return engine, nil } +// getHtmlFiles loads templates from local folder (used in debug mode) +func (s *Server) getHtmlFiles() ([]string, error) { + dir, _ := os.Getwd() + files := []string{} + // common layout + common := filepath.Join(dir, "web", "html", "common", "page.html") + if _, err := os.Stat(common); err == nil { + files = append(files, common) + } + // components used + theme := filepath.Join(dir, "web", "html", "component", "aThemeSwitch.html") + if _, err := os.Stat(theme); err == nil { + files = append(files, theme) + } + // page itself + page := filepath.Join(dir, "web", "html", "subscription.html") + if _, err := os.Stat(page); err == nil { + files = append(files, page) + } else { + return nil, err + } + return files, nil +} + func (s *Server) Start() (err error) { // This is an anonymous function, no function name defer func() { diff --git a/sub/subController.go b/sub/subController.go index 3f053740..ac0c09a1 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -2,7 +2,6 @@ package sub import ( "encoding/base64" - "net" "strings" "github.com/gin-gonic/gin" @@ -58,21 +57,8 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) { func (a *SUBController) subs(c *gin.Context) { subId := c.Param("subid") - var host string - if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil { - host = h - } - if host == "" { - host = c.GetHeader("X-Real-IP") - } - if host == "" { - var err error - host, _, err = net.SplitHostPort(c.Request.Host) - if err != nil { - host = c.Request.Host - } - } - subs, header, err := a.subService.GetSubs(subId, host) + scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c) + subs, header, lastOnline, err := a.subService.GetSubs(subId, host) if err != nil || len(subs) == 0 { c.String(400, "Error!") } else { @@ -81,10 +67,38 @@ func (a *SUBController) subs(c *gin.Context) { result += sub + "\n" } - // Add headers - c.Writer.Header().Set("Subscription-Userinfo", header) - c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval) - c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle))) + // Add headers via service + a.subService.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) + a.subService.ApplyBase64ContentHeader(c, result) + + // If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here + accept := c.GetHeader("Accept") + if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") { + // Build page data in service + subURL, subJsonURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, subId) + page := a.subService.BuildPageData(subId, hostHeader, header, lastOnline, subs, subURL, subJsonURL) + c.HTML(200, "subscription.html", gin.H{ + "title": "subscription.title", + "host": page.Host, + "base_path": page.BasePath, + "sId": page.SId, + "download": page.Download, + "upload": page.Upload, + "total": page.Total, + "used": page.Used, + "remained": page.Remained, + "expire": page.Expire, + "lastOnline": page.LastOnline, + "datepicker": page.Datepicker, + "downloadByte": page.DownloadByte, + "uploadByte": page.UploadByte, + "totalByte": page.TotalByte, + "subUrl": page.SubUrl, + "subJsonUrl": page.SubJsonUrl, + "result": page.Result, + }) + return + } if a.subEncrypt { c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) @@ -96,41 +110,17 @@ func (a *SUBController) subs(c *gin.Context) { func (a *SUBController) subJsons(c *gin.Context) { subId := c.Param("subid") - var host string - if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil { - host = h - } - if host == "" { - host = c.GetHeader("X-Real-IP") - } - if host == "" { - var err error - host, _, err = net.SplitHostPort(c.Request.Host) - if err != nil { - host = c.Request.Host - } - } + _, host, _, _ := a.subService.ResolveRequest(c) jsonSub, header, err := a.subJsonService.GetJson(subId, host) if err != nil || len(jsonSub) == 0 { c.String(400, "Error!") } else { - // Add headers - c.Writer.Header().Set("Subscription-Userinfo", header) - c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval) - c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle))) + // Add headers via service + a.subService.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) c.String(200, jsonSub) } } -func getHostFromXFH(s string) (string, error) { - if strings.Contains(s, ":") { - realHost, _, err := net.SplitHostPort(s) - if err != nil { - return "", err - } - return realHost, nil - } - return s, nil -} +// Note: host parsing and page data preparation moved to SubService diff --git a/sub/subService.go b/sub/subService.go index e6e25e3a..485048fd 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -3,10 +3,15 @@ package sub import ( "encoding/base64" "fmt" + "net" "net/url" + "strconv" "strings" "time" + "github.com/gin-gonic/gin" + "github.com/goccy/go-json" + "x-ui/database" "x-ui/database/model" "x-ui/logger" @@ -14,8 +19,6 @@ import ( "x-ui/util/random" "x-ui/web/service" "x-ui/xray" - - "github.com/goccy/go-json" ) type SubService struct { @@ -34,19 +37,20 @@ func NewSubService(showInfo bool, remarkModel string) *SubService { } } -func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) { +func (s *SubService) GetSubs(subId string, host string) ([]string, string, int64, error) { s.address = host var result []string var header string var traffic xray.ClientTraffic + var lastOnline int64 var clientTraffics []xray.ClientTraffic inbounds, err := s.getInboundsBySubId(subId) if err != nil { - return nil, "", err + return nil, "", 0, err } if len(inbounds) == 0 { - return nil, "", common.NewError("No inbounds found with ", subId) + return nil, "", 0, common.NewError("No inbounds found with ", subId) } s.datepicker, err = s.settingService.GetDatepicker() @@ -73,7 +77,11 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error if client.Enable && client.SubID == subId { link := s.getLink(inbound, client.Email) result = append(result, link) - clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email)) + ct := s.getClientTraffics(inbound.ClientStats, client.Email) + clientTraffics = append(clientTraffics, ct) + if ct.LastOnline > lastOnline { + lastOnline = ct.LastOnline + } } } } @@ -101,7 +109,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error } } header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) - return result, header, nil + return result, header, lastOnline, nil } func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { @@ -1001,3 +1009,184 @@ func searchHost(headers any) string { return "" } + +// PageData is a view model for subscription.html +type PageData struct { + Host string + BasePath string + SId string + Download string + Upload string + Total string + Used string + Remained string + Expire int64 + LastOnline int64 + Datepicker string + DownloadByte int64 + UploadByte int64 + TotalByte int64 + SubUrl string + SubJsonUrl string + Result []string +} + +// ResolveRequest extracts scheme and host info from request/headers consistently. +func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string, hostWithPort string, hostHeader string) { + // scheme + scheme = "http" + if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") { + scheme = "https" + } + + // base host (no port) + if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil && h != "" { + host = h + } + if host == "" { + host = c.GetHeader("X-Real-IP") + } + if host == "" { + var err error + host, _, err = net.SplitHostPort(c.Request.Host) + if err != nil { + host = c.Request.Host + } + } + + // host:port for URLs + hostWithPort = c.GetHeader("X-Forwarded-Host") + if hostWithPort == "" { + hostWithPort = c.Request.Host + } + if hostWithPort == "" { + hostWithPort = host + } + + // header display host + hostHeader = c.GetHeader("X-Forwarded-Host") + if hostHeader == "" { + hostHeader = c.GetHeader("X-Real-IP") + } + if hostHeader == "" { + hostHeader = host + } + return +} + +// BuildURLs constructs absolute subscription and json URLs. +func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subId string) (subURL, subJsonURL string) { + if strings.HasSuffix(subPath, "/") { + subURL = scheme + "://" + hostWithPort + subPath + subId + } else { + subURL = scheme + "://" + hostWithPort + strings.TrimRight(subPath, "/") + "/" + subId + } + if strings.HasSuffix(subJsonPath, "/") { + subJsonURL = scheme + "://" + hostWithPort + subJsonPath + subId + } else { + subJsonURL = scheme + "://" + hostWithPort + strings.TrimRight(subJsonPath, "/") + "/" + subId + } + return +} + +// BuildPageData parses header and prepares the template view model. +func (s *SubService) BuildPageData(subId, hostHeader, header string, lastOnline int64, subs []string, subURL, subJsonURL string) PageData { + // Parse header values + var uploadByte, downloadByte, totalByte, expire int64 + parts := strings.Split(header, ";") + for _, p := range parts { + kv := strings.Split(strings.TrimSpace(p), "=") + if len(kv) != 2 { + continue + } + key := strings.ToLower(strings.TrimSpace(kv[0])) + val := strings.TrimSpace(kv[1]) + switch key { + case "upload": + if v, err := parseInt64(val); err == nil { + uploadByte = v + } + case "download": + if v, err := parseInt64(val); err == nil { + downloadByte = v + } + case "total": + if v, err := parseInt64(val); err == nil { + totalByte = v + } + case "expire": + if v, err := parseInt64(val); err == nil { + expire = v + } + } + } + + download := common.FormatTraffic(downloadByte) + upload := common.FormatTraffic(uploadByte) + total := "∞" + used := common.FormatTraffic(uploadByte + downloadByte) + remained := "" + if totalByte > 0 { + total = common.FormatTraffic(totalByte) + left := totalByte - (uploadByte + downloadByte) + if left < 0 { + left = 0 + } + remained = common.FormatTraffic(left) + } + + datepicker := s.datepicker + if datepicker == "" { + datepicker = "gregorian" + } + + return PageData{ + Host: hostHeader, + BasePath: "/", + SId: subId, + Download: download, + Upload: upload, + Total: total, + Used: used, + Remained: remained, + Expire: expire, + LastOnline: lastOnline, + Datepicker: datepicker, + DownloadByte: downloadByte, + UploadByte: uploadByte, + TotalByte: totalByte, + SubUrl: subURL, + SubJsonUrl: subJsonURL, + Result: subs, + } +} + +func getHostFromXFH(s string) (string, error) { + if strings.Contains(s, ":") { + realHost, _, err := net.SplitHostPort(s) + if err != nil { + return "", err + } + return realHost, nil + } + return s, nil +} + +func parseInt64(s string) (int64, error) { + // handle potential quotes + s = strings.Trim(s, "\"'") + n, err := strconv.ParseInt(s, 10, 64) + return n, err +} + +// ApplyCommonHeaders sets standard subscription headers on the response writer. +func (s *SubService) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) { + c.Writer.Header().Set("Subscription-Userinfo", header) + c.Writer.Header().Set("Profile-Update-Interval", updateInterval) + c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle))) +} + +// ApplyBase64ContentHeader adds the full subscription content as base64 header for convenience. +func (s *SubService) ApplyBase64ContentHeader(c *gin.Context, content string) { + c.Writer.Header().Set("Subscription-Content-Base64", base64.StdEncoding.EncodeToString([]byte(content))) +} diff --git a/web/assets/css/custom.min.css b/web/assets/css/custom.min.css index 0eb2b409..c078177a 100644 --- a/web/assets/css/custom.min.css +++ b/web/assets/css/custom.min.css @@ -1 +1,2835 @@ -:root{--color-primary-100:#008771;--dark-color-background:#0a1222;--dark-color-surface-100:#151f31;--dark-color-surface-200:#222d42;--dark-color-surface-300:#2c3950;--dark-color-surface-400:rgba(65,85,119,.5);--dark-color-surface-500:#2c3950;--dark-color-surface-600:#313f5a;--dark-color-surface-700:#111929;--dark-color-surface-700-rgb:17,25,41;--dark-color-table-hover:rgba(44,57,80,.2);--dark-color-text-primary:rgba(255,255,255,.75);--dark-color-stroke:#2c3950;--dark-color-btn-danger:#cd3838;--dark-color-btn-danger-border:transparent;--dark-color-btn-danger-hover:#e94b4b;--dark-color-tag-bg:rgba(255,255,255,.05);--dark-color-tag-border:rgba(255,255,255,.15);--dark-color-tag-color:rgba(255,255,255,.75);--dark-color-tag-green-bg:17,36,33;--dark-color-tag-green-border:25,81,65;--dark-color-tag-green-color:#3ad3ba;--dark-color-tag-purple-bg:#201425;--dark-color-tag-purple-border:#5a2969;--dark-color-tag-purple-color:#d988cd;--dark-color-tag-red-bg:#291515;--dark-color-tag-red-border:#5c2626;--dark-color-tag-red-color:#e04141;--dark-color-tag-orange-bg:#312313;--dark-color-tag-orange-border:#593914;--dark-color-tag-orange-color:#ffa031;--dark-color-tag-blue-bg:#111a2c;--dark-color-tag-blue-border:#1348ab;--dark-color-tag-blue-color:#529fff;--dark-color-codemirror-line-hover:rgba(0,135,113,.2);--dark-color-codemirror-line-selection:rgba(0,135,113,.3);--dark-color-login-background:var(--dark-color-background);--dark-color-login-wave:var(--dark-color-surface-200);--dark-color-tooltip:rgba(61,76,104,.9);--dark-color-back-top:rgba(61,76,104,.9);--dark-color-back-top-hover:rgba(61,76,104,1);--dark-color-scrollbar:#313f5a;--dark-color-scrollbar-webkit:#7484a0;--dark-color-scrollbar-webkit-hover:#90a4c7;--dark-color-table-ring:rgb(38 52 77);--dark-color-spin-container:#151f31}html[data-theme-animations='off']{.ant-menu,.ant-layout-sider,.ant-card,.ant-tag,.ant-progress-circle>*,.ant-input,.ant-table-row-expand-icon,.ant-switch,.ant-table-thead>tr>th,.ant-select-selection,.ant-btn,.ant-input-number,.ant-input-group-addon,.ant-checkbox-inner,.ant-progress-bg,.ant-progress-success-bg,.ant-radio-button-wrapper:not(:first-child):before,.ant-radio-button-wrapper,#login,.cm-s-xq.CodeMirror{transition:border 0s,background 0s!important}.ant-menu.ant-menu-inline .ant-menu-item:not(.ant-menu-sub .ant-menu-item),.ant-layout-sider-trigger,.ant-alert-close-icon .anticon-close,.ant-tabs-nav .ant-tabs-tab,.ant-input-number-input,.ant-collapse>.ant-collapse-item>.ant-collapse-header,.Line-Hover,.ant-menu-theme-switch,.ant-menu-submenu-title{transition:color 0s!important}.wave-btn-bg{transition:width 0s!important}}html[data-theme='ultra-dark']{--dark-color-background:#21242a;--dark-color-surface-100:#0c0e12;--dark-color-surface-200:#222327;--dark-color-surface-300:#32353b;--dark-color-surface-400:rgba(255,255,255,.1);--dark-color-surface-500:#3b404b;--dark-color-surface-600:#505663;--dark-color-surface-700:#101113;--dark-color-surface-700-rgb:16,17,19;--dark-color-table-hover:rgba(89,89,89,.15);--dark-color-text-primary:rgb(255 255 255 / 85%);--dark-color-stroke:#202025;--dark-color-tag-green-bg:17,36,33;--dark-color-tag-green-border:29,95,77;--dark-color-tag-green-color:#59cbac;--dark-color-tag-purple-bg:#241121;--dark-color-tag-purple-border:#5a2969;--dark-color-tag-purple-color:#d686ca;--dark-color-tag-red-bg:#2a1215;--dark-color-tag-red-border:#58181c;--dark-color-tag-red-color:#e84749;--dark-color-tag-orange-bg:#2b1d11;--dark-color-tag-orange-border:#593815;--dark-color-tag-orange-color:#e89a3c;--dark-color-tag-blue-bg:#111a2c;--dark-color-tag-blue-border:#0f367e;--dark-color-tag-blue-color:#3c89e8;--dark-color-codemirror-line-hover:rgba(82,84,94,.2);--dark-color-codemirror-line-selection:rgba(82,84,94,.3);--dark-color-login-background:#0a2227;--dark-color-login-wave:#0f2d32;--dark-color-tooltip:rgba(88,93,100,.9);--dark-color-back-top:rgba(88,93,100,.9);--dark-color-back-top-hover:rgba(88,93,100,1);--dark-color-scrollbar:rgb(107,107,107);--dark-color-scrollbar-webkit:#9f9f9f;--dark-color-scrollbar-webkit-hover:#d1d1d1;--dark-color-table-ring:rgb(37 39 42);--dark-color-spin-container:#1d1d1d;.ant-dropdown-menu-dark{background-color:var(--dark-color-surface-500)}.dark .ant-dropdown-menu-submenu-title:hover,.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled){background-color:rgb(0 93 78 / .3)}.dark .waves-header{background-color:#0a2227}.dark .ant-calendar-year-panel-year:hover,.dark .ant-calendar-month-panel-month:hover,.dark .ant-calendar-decade-panel-decade:hover{background-color:var(--dark-color-surface-600)}}html,body{height:100vh;width:100vw;margin:0;padding:0;overflow:hidden}body{color:rgb(0 0 0 / .65);font-size:14px;font-variant:tabular-nums;line-height:1.5;background-color:#fff;font-feature-settings:"tnum"}html{--antd-wave-shadow-color:var(--color-primary-100);line-height:1.15;text-size-adjust:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-moz-tap-highlight-color:#fff0;-webkit-tap-highlight-color:#fff0}@supports (scrollbar-width:auto) and (not selector(::-webkit-scrollbar)){:not(.dark){scrollbar-color:#9a9a9a #fff0;scrollbar-width:thin}.dark *{scrollbar-color:var(--dark-color-scrollbar) #fff0;scrollbar-width:thin}}::-webkit-scrollbar{width:10px;height:10px;background-color:#fff0}::-webkit-scrollbar-track{background-color:#fff0;margin-block:.5em}.ant-modal-wrap::-webkit-scrollbar-track{background-color:#fff;margin-block:0}::-webkit-scrollbar-thumb{border-radius:9999px;background-color:#9a9a9a;border:2px solid #fff0;background-clip:content-box}::-webkit-scrollbar-thumb:hover,::-webkit-scrollbar-thumb:active{background-color:#828282}.dark .ant-modal-wrap::-webkit-scrollbar-track{background-color:var(--dark-color-background)}.dark::-webkit-scrollbar-thumb{background-color:var(--dark-color-scrollbar-webkit)}.dark::-webkit-scrollbar-thumb:hover,.dark::-webkit-scrollbar-thumb:active{background-color:var(--dark-color-scrollbar-webkit-hover)}::-moz-selection{color:var(--color-primary-100);background-color:#cfe8e4}::selection{color:var(--color-primary-100);background-color:#cfe8e4}#app{height:100%;position:fixed;top:0;left:0;right:0;bottom:0;margin:0;padding:0;overflow:auto}.ant-layout,.ant-layout *{box-sizing:border-box}.ant-spin-container:after{border-radius:1.5rem}.dark .ant-spin-container:after{background:var(--dark-color-spin-container)}style attribute{text-align:center}.ant-table-thead>tr>th{padding:12px 8px}.ant-table-tbody>tr>td{padding:10px 8px}.ant-table-thead>tr>th{color:rgb(0 0 0 / .85);font-weight:500;text-align:left;border-bottom:1px solid #e8e8e8;transition:background .3s ease}.ant-table table{border-radius:1rem}.ant-table-bordered .ant-table-tbody:not(.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody)>tr:last-child>td:first-child{border-bottom-left-radius:1rem}.ant-table-bordered .ant-table-tbody:not(.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody)>tr:last-child>td:last-child{border-bottom-right-radius:1rem}.ant-table{box-sizing:border-box;margin:0;padding:0;color:rgb(0 0 0 / .65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;clear:both}.ant-table .ant-table-body:not(.ant-table-expanded-row .ant-table-body){overflow-x:auto!important}.ant-card-hoverable{cursor:auto;cursor:pointer}.ant-card{box-sizing:border-box;margin:0;padding:0;color:rgb(0 0 0 / .65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;position:relative;background-color:#fff;border-radius:2px;transition:all .3s}.ant-space{width:100%}.ant-layout-sider-zero-width-trigger{display:none}@media (max-width:768px){.ant-layout-sider{display:none}.ant-card,.ant-alert-error{margin:.5rem}.ant-tabs{margin:.5rem;padding:.5rem}.ant-modal-body{padding:20px}.ant-form-item-label{line-height:1.5;padding:8px 0 0}:not(.dark)::-webkit-scrollbar{width:8px;height:8px;background-color:#fff0}.dark::-webkit-scrollbar{width:8px;height:8px;background-color:#fff0}}.ant-layout-content{min-height:auto}.ant-card,.ant-tabs{border-radius:1.5rem}.ant-card-hoverable{cursor:auto}.ant-card+.ant-card{margin-top:20px}.drawer-handle{position:absolute;top:72px;width:41px;height:40px;cursor:pointer;z-index:0;text-align:center;line-height:40px;font-size:16px;display:flex;justify-content:center;align-items:center;background-color:#fff;right:-40px;box-shadow:2px 0 8px rgb(0 0 0 / .15);border-radius:0 4px 4px 0}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#006655!important;background-image:linear-gradient(270deg,#fff0 30%,#009980,#fff0 100%);background-repeat:no-repeat;animation:ma-bg-move linear 6.6s infinite;color:#fff;border-radius:.5rem}.ant-layout-sider-collapsed .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{border-radius:0}@-webkit-keyframes ma-bg-move{0%{background-position:-500px 0}100%{background-position:1000px 0}}@keyframes ma-bg-move{0%{background-position:-500px 0}50%{background-position:1000px 0}100%{background-position:1000px 0}}.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-title:hover,.ant-menu-item:active,.ant-menu-submenu-title:active{color:var(--color-primary-100);background-color:#e8f4f2}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title{border-radius:.5rem}.ant-menu-inline .ant-menu-item:after,.ant-menu{border-right-width:0}.ant-layout-sider-children,.ant-pagination ul{padding:.5rem}.ant-layout-sider-collapsed .ant-layout-sider-children{padding:.5rem 0}.ant-dropdown-menu,.ant-select-dropdown-menu{padding:.5rem}.ant-dropdown-menu-item,.ant-dropdown-menu-item:hover,.ant-select-dropdown-menu-item,.ant-select-dropdown-menu-item:hover,.ant-select-selection--multiple .ant-select-selection__choice{border-radius:.5rem}.ant-select-dropdown--multiple .ant-select-dropdown-menu .ant-select-dropdown-menu-item,.ant-select-dropdown--single .ant-select-dropdown-menu .ant-select-dropdown-menu-item-selected{margin-block:2px}@media (min-width:769px){.drawer-handle{display:none}.ant-tabs{padding:2rem}}.fade-in-enter,.fade-in-leave-active,.fade-in-linear-enter,.fade-in-linear-leave,.fade-in-linear-leave-active,.fade-in-linear-enter,.fade-in-linear-leave,.fade-in-linear-leave-active{opacity:0}.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.fade-in-enter-active,.fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.zoom-in-center-enter-active,.zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.zoom-in-center-enter,.zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.zoom-in-top-enter-active,.zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center top;transform-origin:center top}.zoom-in-top-enter,.zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.zoom-in-bottom-enter-active,.zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center bottom;transform-origin:center bottom}.zoom-in-bottom-enter,.zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.zoom-in-left-enter-active,.zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:top left;transform-origin:top left}.zoom-in-left-enter,.zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.list-enter-active,.list-leave-active{-webkit-transition:all .3s;transition:all .3s}.list-enter,.list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.ant-tooltip-inner{min-height:0;padding-inline:1rem}.ant-list-item-meta-title{font-size:14px}.ant-progress-inner{background-color:#ebeef5}.deactive-client .ant-collapse-header{color:#ffffff!important;background-color:#ff7f7f}.ant-table-expand-icon-th,.ant-table-row-expand-icon-cell{width:30px;min-width:30px}.ant-tabs{background-color:#fff}.ant-form-item{margin-bottom:0}.ant-setting-textarea{margin-top:1.5rem}.client-table-header{background-color:#f0f2f5}.client-table-odd-row{background-color:#fafafa}.ant-table-pagination.ant-pagination{float:left}.ant-tag{margin-right:0;margin-inline:2px;display:inline-flex;align-items:center;justify-content:space-evenly}.ant-tag:not(.qr-tag){column-gap:4px}#inbound-info-modal .ant-tag{margin-block:2px}.tr-info-table{display:inline-table;margin-block:10px;width:100%}#inbound-info-modal .tr-info-table .ant-tag{margin-block:0;margin-inline:0}.tr-info-row{display:flex;flex-direction:column;row-gap:2px;margin-block:10px}.tr-info-row a{margin-left:6px}.tr-info-row code{padding-inline:8px}.tr-info-tag{max-width:100%;text-wrap:balance;overflow:hidden;overflow-wrap:anywhere}.tr-info-title{display:inline-flex;align-items:center;justify-content:flex-start;column-gap:4px}.ant-tag-blue{background-color:#edf4fa;border-color:#a9c5e7;color:#0e49b5}.ant-tag-green{background-color:#eafff9;border-color:#76ccb4;color:#199270}.ant-tag-purple{background-color:#f2eaf1;border-color:#d5bed2;color:#7a316f}.ant-tag-orange,.ant-alert-warning{background-color:#ffeee1;border-color:#fec093;color:#f37b24}.ant-tag-red,.ant-alert-error{background-color:#ffe9e9;border-color:#ff9e9e;color:#cf3c3c}.ant-input::placeholder{opacity:.5}.ant-input:hover,.ant-input:focus{background-color:#e8f4f2}.ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){background-color:#e8f4f2}.delete-icon:hover{color:#e04141}.normal-icon:hover{color:var(--color-primary-100)}.dark::-moz-selection{color:#fff;background-color:var(--color-primary-100)}.dark::selection{color:#fff;background-color:var(--color-primary-100)}.dark .normal-icon:hover{color:#fff}.dark .ant-layout-sider,.dark .ant-drawer-content,.ant-menu-dark,.ant-menu-dark .ant-menu-sub,.dark .ant-card,.dark .ant-table,.dark .ant-collapse-content,.dark .ant-tabs{background-color:var(--dark-color-surface-100);color:var(--dark-color-text-primary)}.dark .ant-card-hoverable:hover,.dark .ant-space-item>.ant-tabs:hover{box-shadow:0 2px 8px #fff0}.dark>.ant-layout,.dark .drawer-handle,.dark .ant-table-thead>tr>th,.dark .ant-table-expanded-row,.dark .ant-table-expanded-row:hover,.dark .ant-table-expanded-row .ant-table-tbody,.dark .ant-calendar{background-color:var(--dark-color-background);color:var(--dark-color-text-primary)}.dark .ant-table-expanded-row .ant-table-thead>tr:first-child>th{border-radius:0}.dark .ant-calendar,.dark .ant-card-bordered{border-color:var(--dark-color-background)}.dark .ant-table-bordered,.dark .ant-table-bordered.ant-table-empty .ant-table-placeholder,.dark .ant-table-bordered .ant-table-body>table,.dark .ant-table-bordered .ant-table-fixed-left table,.dark .ant-table-bordered .ant-table-fixed-right table,.dark .ant-table-bordered .ant-table-header>table,.dark .ant-table-bordered .ant-table-thead>tr:not(:last-child)>th,.dark .ant-table-bordered .ant-table-tbody>tr>td,.dark .ant-table-bordered .ant-table-thead>tr>th{border-color:var(--dark-color-surface-400)}.dark .ant-table-tbody>tr>td,.dark .ant-table-thead>tr>th,.dark .ant-card-head,.dark .ant-modal-header,.dark .ant-collapse>.ant-collapse-item,.dark .ant-tabs-bar,.dark .ant-list-split .ant-list-item,.dark .ant-popover-title,.dark .ant-calendar-header,.dark .ant-calendar-input-wrap{border-bottom-color:var(--dark-color-surface-400)}.dark .ant-modal-footer,.dark .ant-collapse-content,.dark .ant-calendar-footer,.dark .ant-divider-horizontal.ant-divider-with-text-left:before,.dark .ant-divider-horizontal.ant-divider-with-text-left:after,.dark .ant-divider-horizontal.ant-divider-with-text-center:before,.dark .ant-divider-horizontal.ant-divider-with-text-center:after{border-top-color:var(--dark-color-surface-300)}.ant-divider-horizontal.ant-divider-with-text-left:before{width:10%}.dark .ant-progress-text,.dark .ant-card-head,.dark .ant-form,.dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,.dark .ant-modal-close-x,.dark .ant-form .anticon,.dark .ant-tabs-tab-arrow-show:not(.ant-tabs-tab-btn-disabled),.dark .anticon-close,.dark .ant-list-item-meta-title,.dark .ant-select-selection i,.dark .ant-modal-confirm-title,.dark .ant-modal-confirm-content,.dark .ant-popover-message,.dark .ant-modal,.dark .ant-divider-inner-text,.dark .ant-popover-title,.dark .ant-popover-inner-content,.dark h2,.dark .ant-modal-title,.dark .ant-form-item-label>label,.dark .ant-checkbox-wrapper,.dark .ant-form-item,.dark .ant-calendar-footer .ant-calendar-today-btn,.dark .ant-calendar-footer .ant-calendar-time-picker-btn,.dark .ant-calendar-day-select,.dark .ant-calendar-month-select,.dark .ant-calendar-year-select,.dark .ant-calendar-date,.dark .ant-calendar-year-panel-year,.dark .ant-calendar-month-panel-month,.dark .ant-calendar-decade-panel-decade{color:var(--dark-color-text-primary)}.dark .ant-pagination-options-size-changer .ant-select-arrow .anticon.anticon-down.ant-select-arrow-icon{color:rgb(255 255 255 / 35%)}.dark .ant-pagination-item a,.dark .ant-pagination-next a,.dark .ant-pagination-prev a{color:var(--dark-color-text-primary)}.dark .ant-pagination-item:focus a,.dark .ant-pagination-item:hover a,.dark .ant-pagination-item-active a,.dark .ant-pagination-next:hover .ant-pagination-item-link{color:var(--color-primary-100)}.dark .ant-pagination-item-active{background-color:#fff0}.dark .ant-list-item-meta-description{color:rgb(255 255 255 / .45)}.dark .ant-pagination-disabled i,.dark .ant-tabs-tab-btn-disabled{color:rgb(255 255 255 / .25)}.dark .ant-input,.dark .ant-input-group-addon,.dark .ant-collapse,.dark .ant-select-selection,.dark .ant-input-number,.dark .ant-input-number-handler-wrap,.dark .ant-table-placeholder,.dark .ant-empty-normal,.dark .ant-select-dropdown,.dark .ant-select-dropdown li,.dark .ant-select-dropdown-menu-item,.dark .client-table-header,.dark .ant-select-selection--multiple .ant-select-selection__choice{background-color:var(--dark-color-surface-200);border-color:var(--dark-color-surface-300);color:var(--dark-color-text-primary)}.dark .ant-select-dropdown--multiple .ant-select-dropdown-menu .ant-select-dropdown-menu-item.ant-select-dropdown-menu-item-selected:not(.ant-dropdown-menu-submenu-title:hover){background-color:var(--dark-color-surface-300)}.dark .ant-select-dropdown-menu-item.ant-select-dropdown-menu-item-selected{background-color:var(--dark-color-surface-300)}.dark .ant-calendar-time-picker-inner{background-color:var(--dark-color-background)}.dark .ant-select-selection:hover,.dark .ant-calendar-picker-clear,.dark .ant-input-number:hover,.dark .ant-input-number:focus,.dark .ant-input:hover,.dark .ant-input:focus{background-color:rgb(0 135 113 / .3);border-color:var(--color-primary-100)}.dark .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:var(--color-primary-100);background-color:rgb(0 135 113 / .3)}.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger){color:var(--dark-color-text-primary);background-color:rgb(10 117 87 / 30%);border:1px solid var(--color-primary-100)}.dark .ant-radio-button-wrapper,.dark .ant-radio-button-wrapper:before{color:var(--dark-color-text-primary);background-color:rgb(0 135 113 / .3);border-color:var(--color-primary-100)}.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger){background-color:#e8f4f2}.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger){color:#fff;background-color:rgb(10 117 87 / 50%);border-color:var(--color-primary-100)}.dark .ant-btn-primary[disabled],.dark .ant-btn-danger[disabled],.dark .ant-calendar-ok-btn-disabled{color:rgb(255 255 255 / 35%);background-color:var(--dark-color-surface-200);border-color:var(--dark-color-surface-300)}.dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.dark .client-table-odd-row{background-color:var(--dark-color-table-hover)}.dark .ant-table-row-expand-icon{color:#fff;background-color:#fff0;border-color:rgb(255 255 255 / 20%)}.dark .ant-table-row-expand-icon:hover{color:var(--color-primary-100);background-color:#fff0;border-color:var(--color-primary-100)}.dark .ant-switch:not(.ant-switch-checked),.dark .ant-progress-line .ant-progress-inner{background-color:var(--dark-color-surface-500)}.dark .ant-progress-circle-trail{stroke:var(--dark-color-stroke)!important}.dark .ant-popover-inner{background-color:var(--dark-color-surface-500)}.dark>.ant-popover-content>.ant-popover-arrow{border-color:var(--dark-color-surface-500)}@media (max-width:768px){.dark .ant-popover-inner{background-color:var(--dark-color-surface-200)}.dark>.ant-popover-content>.ant-popover-arrow{border-color:var(--dark-color-surface-200)}}.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,.dark .ant-select-dropdown-menu-item-selected,.dark .ant-calendar-time-picker-select-option-selected{background-color:var(--dark-color-surface-600)}.ant-menu-dark .ant-menu-item:hover,.ant-menu-dark .ant-menu-submenu-title:hover{background-color:var(--dark-color-surface-300)}.dark .ant-menu-item:active,.dark .ant-menu-submenu-title:active{color:#fff;background-color:var(--dark-color-surface-300)}.dark .ant-alert-message{color:rgb(255 255 255 / .85)}.dark .ant-tag{color:var(--dark-color-tag-color);background-color:var(--dark-color-tag-bg);border-color:var(--dark-color-tag-border)}.dark .ant-tag-blue{background-color:var(--dark-color-tag-blue-bg);border-color:var(--dark-color-tag-blue-border);color:var(--dark-color-tag-blue-color)}.dark .ant-tag-red,.dark .ant-alert-error{background-color:var(--dark-color-tag-red-bg);border-color:var(--dark-color-tag-red-border);color:var(--dark-color-tag-red-color)}.dark .ant-tag-orange,.dark .ant-alert-warning{background-color:var(--dark-color-tag-orange-bg);border-color:var(--dark-color-tag-orange-border);color:var(--dark-color-tag-orange-color)}.dark .ant-tag-green{background-color:rgb(var(--dark-color-tag-green-bg));border-color:rgb(var(--dark-color-tag-green-border));color:var(--dark-color-tag-green-color)}.dark .ant-tag-purple{background-color:var(--dark-color-tag-purple-bg);border-color:var(--dark-color-tag-purple-border);color:var(--dark-color-tag-purple-color)}.dark .ant-modal-content,.dark .ant-modal-header{background-color:var(--dark-color-surface-700)}.dark .ant-calendar-next-month-btn-day .ant-calendar-date,.dark .ant-calendar-last-month-cell .ant-calendar-date{color:var(--dark-color-surface-300)}.dark .ant-calendar-selected-day .ant-calendar-date{background-color:var(--color-primary-100)!important;color:#fff}.dark .ant-calendar-date:hover,.dark .ant-calendar-time-picker-select li:hover{background-color:var(--dark-color-surface-600);color:#fff}.dark .ant-calendar-header a:hover,.dark .ant-calendar-header a:hover::before,.dark .ant-calendar-header a:hover::after{border-color:#fff}.dark .ant-calendar-time-picker-select{border-right-color:var(--dark-color-surface-300)}.has-warning .ant-select-selection,.has-warning .ant-select-selection:hover,.has-warning .ant-input,.has-warning .ant-input:hover{background-color:#ffeee1;border-color:#fec093}.has-warning .ant-input::placeholder{color:#f37b24}.has-warning .ant-input:not([disabled]):hover{border-color:#fec093}.dark .has-warning .ant-select-selection,.dark .has-warning .ant-select-selection:hover,.dark .has-warning .ant-input,.dark .has-warning .ant-input:hover{border-color:#784e1d;background:#312313}.dark .has-warning .ant-input::placeholder{color:rgb(255 160 49 / 70%)}.dark .has-warning .anticon{color:#ffa031}.dark .has-success .anticon{color:var(--color-primary-100);animation-name:diffZoomIn1!important}.dark .anticon-close-circle{color:#e04141}.dark .ant-spin-nested-loading>div>.ant-spin .ant-spin-text{text-shadow:0 1px 2px #0007}.dark .ant-spin{color:#fff}.dark .ant-spin-dot-item{background-color:#fff}.ant-checkbox-wrapper,.ant-input-group-addon,.ant-tabs-tab,.ant-input::placeholder,.ant-collapse-header,.ant-menu,.ant-radio-button-wrapper{-webkit-user-select:none;user-select:none}.ant-calendar-date,.ant-calendar-year-panel-year,.ant-calendar-decade-panel-decade,.ant-calendar-month-panel-month{border-radius:4px}.ant-checkbox-inner,.ant-checkbox-checked:after,.ant-table-row-expand-icon{border-radius:6px}.ant-calendar-date:hover{background-color:#e8f4f2}.ant-calendar-date:active{background-color:#e8f4f2;color:rgb(0 0 0 / .65)}.ant-calendar-today .ant-calendar-date{color:var(--color-primary-100);font-weight:400;border-color:var(--color-primary-100)}.dark .ant-calendar-today .ant-calendar-date{color:#fff;border-color:var(--color-primary-100)}.ant-calendar-selected-day .ant-calendar-date{background:var(--color-primary-100);color:#fff}li.ant-select-dropdown-menu-item:empty:after{content:"None";font-weight:400;color:rgb(0 0 0 / .25)}.dark li.ant-select-dropdown-menu-item:empty:after{content:"None";font-weight:400;color:rgb(255 255 255 / .3)}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item:hover .ant-select-selected-icon{color:rgb(0 0 0 / .87)}.dark.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item:hover .ant-select-selected-icon{color:#fff}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected .ant-select-selected-icon,.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected:hover .ant-select-selected-icon{color:var(--color-primary-100)}.ant-select-selection:hover,.ant-input-number-focused,.ant-input-number:hover{background-color:#e8f4f2}.dark .ant-input-number-handler:active{background-color:var(--color-primary-100)}.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,.dark .ant-input-number-handler:hover .ant-input-number-handler-up-inner{color:#fff}.dark .ant-input-number-handler-down{border-top:1px solid rgb(217 217 217 / .3)}.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-century-select,.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-decade-select,.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-month-select,.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-year-select .dark .ant-calendar-month-panel-header .ant-calendar-month-panel-century-select,.dark .ant-calendar-month-panel-header .ant-calendar-month-panel-decade-select,.dark .ant-calendar-month-panel-header .ant-calendar-month-panel-month-select,.dark .ant-calendar-month-panel-header .ant-calendar-month-panel-year-select{color:rgb(255 255 255 / .85)}.dark .ant-calendar-year-panel-header{border-bottom:1px solid var(--dark-color-surface-200)}.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,.dark .ant-calendar-year-panel-next-decade-cell .ant-calendar-year-panel-year{color:rgb(255 255 255 / .35)}.dark .ant-divider:not(.ant-divider-with-text-center,.ant-divider-with-text-left,.ant-divider-with-text-right),.ant-dropdown-menu-dark,.dark .ant-calendar-year-panel-year:hover,.dark .ant-calendar-month-panel-month:hover,.dark .ant-calendar-decade-panel-decade:hover{background-color:var(--dark-color-surface-200)}.dark .ant-calendar-header a:hover{color:#fff}.dark .ant-calendar-month-panel-header{background-color:var(--dark-color-background);border-bottom:1px solid var(--dark-color-surface-200)}.dark .ant-calendar-year-panel,.dark .ant-calendar table{background-color:var(--dark-color-background)}.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year:hover,.dark .ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month,.dark .ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month:hover,.dark .ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade,.dark .ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade:hover{color:#fff;background-color:var(--color-primary-100)!important}.dark .ant-calendar-last-month-cell .ant-calendar-date,.dark .ant-calendar-last-month-cell .ant-calendar-date:hover,.dark .ant-calendar-next-month-btn-day .ant-calendar-date,.dark .ant-calendar-next-month-btn-day .ant-calendar-date:hover{color:rgb(255 255 255 / 25%);background:#fff0;border-color:#fff0}.dark .ant-calendar-today .ant-calendar-date:hover{color:#fff;border-color:var(--color-primary-100);background-color:var(--color-primary-100)}.dark .ant-calendar-decade-panel-last-century-cell .ant-calendar-decade-panel-decade,.dark .ant-calendar-decade-panel-next-century-cell .ant-calendar-decade-panel-decade{color:rgb(255 255 255 / 25%)}.dark .ant-calendar-decade-panel-header{border-bottom:1px solid var(--dark-color-surface-200);background-color:var(--dark-color-background)}.dark .ant-checkbox-inner{background-color:rgb(0 135 113 / .3);border-color:rgb(0 135 113 / .3)}.dark .ant-checkbox-checked .ant-checkbox-inner{background-color:var(--color-primary-100);border-color:var(--color-primary-100)}.dark .ant-calendar-input{background-color:var(--dark-color-background);color:var(--dark-color-text-primary)}.dark .ant-calendar-input::placeholder{color:rgb(255 255 255 / .25)}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child),.ant-input-number-handler,.ant-input-number-handler-wrap{border-radius:0}.ant-input-number{overflow:clip}.ant-modal-body,.ant-collapse-content>.ant-collapse-content-box{overflow-x:auto}.ant-modal-body{overflow-y:hidden}.ant-calendar-year-panel-year:hover,.ant-calendar-decade-panel-decade:hover,.ant-calendar-month-panel-month:hover,.ant-dropdown-menu-item:hover,.ant-dropdown-menu-submenu-title:hover,.ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),.ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),.ant-table-tbody>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-thead>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-thead>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td{background-color:#e8f4f2}.dark .ant-dropdown-menu-submenu-title:hover,.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled){background-color:rgb(0 93 78 / .3)}.ant-select-dropdown,.ant-popover-inner{overflow-x:hidden}.ant-popover-inner-content{max-height:450px;overflow-y:auto}@media (max-height:900px){.ant-popover-inner-content{max-height:400px}}@media (max-height:768px){.ant-popover-inner-content{max-height:300px}}@media (max-width:768px){.ant-popover-inner-content{max-height:300px}}.qr-modal{display:flex;align-items:flex-end;gap:10px;flex-direction:column;flex-wrap:wrap;row-gap:24px}.qr-box{width:220px}.qr-cv{width:100%;height:100%}.dark .qr-cv{filter:invert(1)}.qr-bg{background-color:#fff;display:flex;justify-content:center;align-content:center;padding:.8rem;border-radius:1rem;border:solid 1px #e8e8e8;height:220px;width:220px;transition:all .1s}.qr-bg:hover{border-color:#76ccb4;background-color:#eafff9}.qr-bg:hover:active{border-color:#76ccb4;background-color:rgb(197 241 228 / 70%)}.dark .qr-bg{background-color:var(--dark-color-surface-700);border-color:var(--dark-color-surface-300)}.dark .qr-bg:hover{background-color:rgb(var(--dark-color-tag-green-bg));border-color:rgb(var(--dark-color-tag-green-border))}.dark .qr-bg:hover:active{background-color:#17322e}@property --tr-rotate{syntax:'';initial-value:45deg;inherits:false}.qr-bg-sub{background-image:linear-gradient(var(--tr-rotate),#76ccb4,transparent,#d5bed2);display:flex;justify-content:center;align-content:center;padding:1px;border-radius:1rem;height:220px;width:220px}.dark .qr-bg-sub{background-image:linear-gradient(var(--tr-rotate),#195141,transparent,#5a2969)}.qr-bg-sub:hover{animation:tr-rotate-gradient 3.5s linear infinite}@keyframes tr-rotate-gradient{from{--tr-rotate:45deg}to{--tr-rotate:405deg}}.qr-bg-sub-inner{background-color:#fff;padding:.8rem;border-radius:1rem;transition:all .1s}.qr-bg-sub-inner:hover{background-color:rgb(255 255 255 / 60%);backdrop-filter:blur(25px)}.qr-bg-sub-inner:hover:active{background-color:rgb(255 255 255 / 30%)}.dark .qr-bg-sub-inner{background-color:rgb(var(--dark-color-surface-700-rgb))}.dark .qr-bg-sub-inner:hover{background-color:rgba(var(--dark-color-surface-700-rgb),.5);backdrop-filter:blur(25px)}.dark .qr-bg-sub-inner:hover:active{background-color:rgba(var(--dark-color-surface-700-rgb),.2)}.qr-tag{text-align:center;margin-bottom:10px;width:100%;overflow:hidden;margin-inline:0}@media (min-width:769px){.qr-modal{flex-direction:row;max-width:680px}}.tr-marquee{justify-content:flex-start}.tr-marquee span{padding-right:25%;white-space:nowrap;transform-origin:center}@keyframes move-ltr{0%{transform:translateX(0)}100%{transform:translateX(-100%)}}.ant-input-group-addon:not(:first-child):not(:last-child){border-radius:0 1rem 1rem 0}b,strong{font-weight:500}.ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:10px 16px 10px 40px}.dark .ant-message-notice-content{background-color:var(--dark-color-surface-200);border:1px solid var(--dark-color-surface-300);color:var(--dark-color-text-primary)}.ant-btn-danger{background-color:var(--dark-color-btn-danger);border-color:var(--dark-color-btn-danger-border)}.ant-btn-danger:focus,.ant-btn-danger:hover{background-color:var(--dark-color-btn-danger-hover);border-color:var(--dark-color-btn-danger-hover)}.dark .ant-alert-close-icon .anticon-close:hover{color:#fff}.ant-empty-small{margin:4px 0;background-color:transparent!important}.ant-empty-small .ant-empty-image{height:20px}.ant-menu-theme-switch,.ant-menu-theme-switch:hover{background-color:transparent!important;cursor:default!important}.dark .ant-tooltip-inner,.dark .ant-tooltip-arrow:before{background-color:var(--dark-color-tooltip)}.ant-select-sm .ant-select-selection__rendered{margin-left:10px}.ant-collapse{-moz-animation:collfade .3s ease;-webkit-animation:.3s collfade .3s ease;animation:collfade .3s ease}@-webkit-keyframes collfade{0%{transform:scaleY(.8);transform-origin:0 0%;opacity:0}100%{transform:scaleY(1);transform-origin:0 0%;opacity:1}}@keyframes collfade{0%{transform:scaleY(.8);transform-origin:0 0%;opacity:0}100%{transform:scaleY(1);transform-origin:0 0%;opacity:1}}.ant-table-tbody>tr>td{border-color:#f0f0f0}.ant-table-row-expand-icon{vertical-align:middle;margin-inline-end:8px;position:relative;transform:scale(.9411764705882353)}.ant-table-row-collapsed::before{transform:rotate(-180deg);top:7px;inset-inline-end:3px;inset-inline-start:3px;height:1px;position:absolute;background:currentcolor;transition:transform .3s ease-out;content:""}.ant-table-row-collapsed::after{transform:rotate(0deg);top:3px;bottom:3px;inset-inline-start:7px;width:1px;position:absolute;background:currentcolor;transition:transform .3s ease-out;content:""}.ant-table-row-expanded::before{top:7px;inset-inline-end:3px;inset-inline-start:3px;height:1px;position:absolute;background:currentcolor;transition:transform .3s ease-out;content:""}.ant-table-row-expanded::after{top:3px;bottom:3px;inset-inline-start:7px;width:1px;transform:rotate(90deg);position:absolute;background:currentcolor;transition:transform .3s ease-out;content:""}.ant-menu-theme-switch.ant-menu-item .ant-switch:not(.ant-switch-disabled):active:after,.ant-switch:not(.ant-switch-disabled):active:before{width:16px}.dark .ant-select-disabled .ant-select-selection{background:var(--dark-color-surface-100);border-color:var(--dark-color-surface-200);color:rgb(255 255 255 / .25)}.dark .ant-select-disabled .anticon{color:rgb(255 255 255 / .25)}.dark .ant-input-number-handler-down-disabled,.dark .ant-input-number-handler-up-disabled{background-color:rgb(0 0 0 / .1)}.dark .ant-input-number-handler-down-disabled .anticon,.dark .ant-input-number-handler-up-disabled .anticon,.dark .ant-input-number-handler-down:hover.ant-input-number-handler-down-disabled .anticon,.dark .ant-input-number-handler-up:hover.ant-input-number-handler-up-disabled .anticon{color:rgb(255 255 255 / .25)}.dark .ant-input-number-handler-down:active.ant-input-number-handler-down-disabled,.dark .ant-input-number-handler-up:active.ant-input-number-handler-up-disabled{background-color:rgb(0 0 0 / .2)}.ant-menu-dark .ant-menu-inline.ant-menu-sub{background:var(--dark-color-surface-100);box-shadow:none}.dark .ant-layout-sider-trigger{background:var(--dark-color-surface-100);color:rgb(255 255 255 / 65%)}.ant-layout-sider{overflow:auto}.dark .ant-back-top-content{background-color:var(--dark-color-back-top)}.dark .ant-back-top-content:hover{background-color:var(--dark-color-back-top-hover)}.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn{text-transform:capitalize}.ant-calendar{border-color:#fff0;border-width:0}.ant-calendar-time-picker-select li:focus,li.ant-calendar-time-picker-select-option-selected{color:rgb(0 0 0 / .65);font-weight:400;background-color:#e8f4f2}.dark li.ant-calendar-time-picker-select-option-selected{color:var(--dark-color-text-primary);font-weight:400}.dark .ant-calendar-time-picker-select li:focus{color:#fff;font-weight:400;background-color:var(--color-primary-100)}.ant-calendar-time-picker-select li:hover{background:#f5f5f5}.ant-calendar-date{transition:background .3s ease,color .3s ease}li.ant-calendar-time-picker-select-option-selected{margin-block:2px}.ant-calendar-time-picker-select{padding:4px}.ant-calendar-time-picker-select li{height:28px;line-height:28px;border-radius:4px}@media (min-width:769px){.ant-layout-content{margin:24px 16px}}.ant-card-dark h2{color:var(--dark-color-text-primary)}.ant-backup-list-item{gap:10px}.ant-version-list-item{--padding:12px;padding:var(--padding)!important;gap:var(--padding)}.dark .ant-version-list-item svg{color:var(--dark-color-text-primary)}.dark .ant-backup-list-item svg,.dark .ant-badge-status-text,.dark .ant-card-extra{color:var(--dark-color-text-primary)}.dark .ant-card-actions>li{color:rgba(255,255,255,.55)}.dark .ant-radio-inner{background-color:var(--dark-color-surface-100);border-color:var(--dark-color-surface-600)}.dark .ant-radio-checked .ant-radio-inner{border-color:var(--color-primary-100)}.dark .ant-backup-list,.dark .ant-version-list,.dark .ant-card-actions,.dark .ant-card-actions>li:not(:last-child){border-color:var(--dark-color-stroke)}.ant-card-actions{background:transparent}.ip-hidden{-webkit-user-select:none;-moz-user-select:none;user-select:none;filter:blur(10px)}.running-animation .ant-badge-status-dot{animation:runningAnimation 1.2s linear infinite}.running-animation .ant-badge-status-processing:after{border-color:var(--color-primary-100)}@keyframes runningAnimation{0%,50%,100%{transform:scale(1);opacity:1}10%{transform:scale(1.5);opacity:.2}}.mb-10{margin-bottom:10px!important}.mb-12{margin-bottom:12px!important}.mt-5{margin-top:5px!important}.mr-8{margin-right:8px!important}.ml-10{margin-left:10px!important}.mr-05{margin-right:.5rem!important}.fs-1rem{font-size:1rem!important}.w-100{width:100%!important}.w-70{width:70px!important}.w-95{width:95px!important}.text-center{text-align:center!important}.cursor-pointer{cursor:pointer!important}.float-right{float:right!important}.va-middle{vertical-align:middle!important}.d-flex{display:flex!important}.justify-end{justify-content:flex-end!important}.log-container{height:auto;max-height:500px;overflow:auto;margin-top:.5rem}.max-w-400{max-width:400px;display:inline-block}.card-placeholder{text-align:center;padding:30px 0;margin-top:10px;background:transparent;border:none}.ant-space.jc-center{justify-content:center}#app.login-app{overflow:hidden;margin:0!important;padding:0!important;min-height:100vh;width:100vw}#app.login-app *{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#app.login-app h1{text-align:center;height:110px}#app.login-app .ant-layout-content{margin:0!important;padding:0!important;width:100vw;max-width:100vw}#app.login-app.ant-layout,#app.login-app .ant-layout{margin:0!important;padding:0!important;width:100vw;max-width:100vw}.min-h-0{min-height:0!important}.min-h-100vh{min-height:100vh!important}.h-100{height:100%!important}.h-50px{height:50px!important}.overflow-auto{overflow:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-hidden-auto{overflow:hidden auto!important}.mt-1rem{margin-top:1rem!important}.my-3rem{margin-top:3rem!important;margin-bottom:3rem!important}#app.login-app #login{animation:charge .5s both;background-color:#fff;border-radius:2rem;padding:4rem 3rem;transition:all .3s;user-select:none;-webkit-user-select:none;-moz-user-select:none}#app.login-app #login:hover{box-shadow:0 2px 8px rgba(0,0,0,.09)}@keyframes charge{from{transform:translateY(5rem);opacity:0}to{transform:translateY(0);opacity:1}}#app.login-app .under{background-color:#c7ebe2;z-index:0}#app.login-app.dark .under{background-color:var(--dark-color-login-wave)}#app.login-app.dark #login{background-color:var(--dark-color-surface-100)}#app.login-app.dark h1{color:rgba(255,255,255)}#app.login-app #login .ant-form-item-children .ant-btn,#app.login-app #login .ant-input{height:50px;border-radius:30px}#app.login-app #login .ant-input-group-addon{border-radius:0 30px 30px 0;width:50px;font-size:18px}#app.login-app #login .ant-input-affix-wrapper .ant-input-prefix{left:23px}#app.login-app #login .ant-input-affix-wrapper .ant-input:not(:first-child){padding-left:50px}#app.login-app .centered{display:flex;text-align:center;align-items:center;justify-content:center;width:100%}#app.login-app .title{font-size:2rem;margin-block-end:2rem}#app.login-app .title b{font-weight:bold!important}.ant-btn-primary-login{width:100%}.ant-btn-primary-login:focus,.ant-btn-primary-login:hover{color:#fff;background-color:#006655;border-color:#006655;background-image:linear-gradient(270deg,rgba(123,199,77,0) 30%,#009980,rgba(123,199,77,0) 100%);background-repeat:no-repeat;animation:ma-bg-move ease-in-out 5s infinite;background-position-x:-500px;width:95%;animation-delay:-.5s;box-shadow:0 2px 0 rgba(0,0,0,.045)}.ant-btn-primary-login.active,.ant-btn-primary-login:active{color:#fff;background-color:#006655;border-color:#006655}.wave-btn-bg{position:relative;border-radius:25px;width:100%;transition:all .3s cubic-bezier(.645,.045,.355,1)}.dark .wave-btn-bg{color:#fff;position:relative;background-color:#0a7557;border:2px double transparent;background-origin:border-box;background-clip:padding-box,border-box;background-size:300%;width:100%;z-index:1}.dark .wave-btn-bg:hover{animation:wave-btn-tara 4s ease infinite}.dark .wave-btn-bg-cl{background-image:linear-gradient(rgba(13,14,33,0),rgba(13,14,33,0)),radial-gradient(circle at left top,#006655,#009980,#006655)!important;border-radius:3em}.dark .wave-btn-bg-cl:hover{width:95%}.dark .wave-btn-bg-cl:before{position:absolute;content:"";top:-5px;left:-5px;bottom:-5px;right:-5px;z-index:-1;background:inherit;background-size:inherit;border-radius:4em;opacity:0;transition:.5s}.dark .wave-btn-bg-cl:hover::before{opacity:1;filter:blur(20px);animation:wave-btn-tara 8s linear infinite}@keyframes wave-btn-tara{to{background-position:300%}}.waves-header{position:fixed;top:0;left:0;right:0;width:100%;text-align:center;background-color:#dbf5ed;color:white;z-index:-1}.dark .waves-header{background-color:var(--dark-color-login-background)}.waves-inner-header{height:50vh;width:100%;margin:0;padding:0}.waves{position:relative;width:100%;height:15vh;margin-bottom:-8px;min-height:100px;max-height:150px}.parallax>use{animation:move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite}.dark .parallax>use{fill:var(--dark-color-login-wave)}.parallax>use:nth-child(1){animation-delay:-2s;animation-duration:4s;opacity:.2}.parallax>use:nth-child(2){animation-delay:-3s;animation-duration:7s;opacity:.4}.parallax>use:nth-child(3){animation-delay:-4s;animation-duration:10s;opacity:.6}.parallax>use:nth-child(4){animation-delay:-5s;animation-duration:13s}@keyframes move-forever{0%{transform:translate3d(-90px,0,0)}100%{transform:translate3d(85px,0,0)}}@media (max-width:768px){#app.login-app .waves{height:40px;min-height:40px}}#app.login-app .words-wrapper{width:100%;display:inline-block;position:relative;text-align:center}#app.login-app .words-wrapper b{width:100%;display:inline-block;position:absolute;left:0;top:0}#app.login-app .words-wrapper b.is-visible{position:relative}#app.login-app .headline.zoom .words-wrapper{-webkit-perspective:300px;-moz-perspective:300px;perspective:300px}#app.login-app .headline{display:flex;justify-content:center;align-items:center}#app.login-app .headline.zoom b{opacity:0}#app.login-app .headline.zoom b.is-visible{opacity:1;-webkit-animation:zoom-in .8s;-moz-animation:zoom-in .8s;animation:cubic-bezier(.215,.61,.355,1) zoom-in .8s}#app.login-app .headline.zoom b.is-hidden{-webkit-animation:zoom-out .8s;-moz-animation:zoom-out .8s;animation:cubic-bezier(.215,.61,.355,1) zoom-out .4s}@-webkit-keyframes zoom-in{0%{opacity:0;-webkit-transform:translateZ(100px)}100%{opacity:1;-webkit-transform:translateZ(0)}}@-moz-keyframes zoom-in{0%{opacity:0;-moz-transform:translateZ(100px)}100%{opacity:1;-moz-transform:translateZ(0)}}@keyframes zoom-in{0%{opacity:0;-webkit-transform:translateZ(100px);-moz-transform:translateZ(100px);-ms-transform:translateZ(100px);-o-transform:translateZ(100px);transform:translateZ(100px)}100%{opacity:1;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}}.inbounds-page .ant-table:not(.ant-table-expanded-row .ant-table){outline:1px solid #f0f0f0;outline-offset:-1px;border-radius:1rem;overflow-x:hidden}.inbounds-page.dark .ant-table:not(.ant-table-expanded-row .ant-table){outline-color:var(--dark-color-table-ring)}.inbounds-page .ant-table .ant-table-content .ant-table-scroll .ant-table-body{overflow-y:hidden}.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper{margin:-10px 22px!important}.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table{border-bottom-left-radius:1rem;border-bottom-right-radius:1rem}.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child tr:last-child td{border-bottom-color:transparent}.inbounds-page .ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:first-child{border-bottom-left-radius:6px}.inbounds-page .ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:last-child{border-bottom-right-radius:6px}@media (max-width:768px){.inbounds-page .ant-card-body{padding:.5rem}.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper{margin:-10px 2px!important}}.inbounds-page.dark .ant-switch-small:not(.ant-switch-checked){background-color:var(--dark-color-surface-100)!important}.inbounds-page .ant-custom-popover-title{display:flex;align-items:center;gap:10px;margin:5px 0}.inbounds-page .ant-col-sm-24{margin:.5rem -2rem .5rem 2rem}.inbounds-page tr.hideExpandIcon .ant-table-row-expand-icon{display:none}.inbounds-page .infinite-tag{padding:0 5px;border-radius:2rem;min-width:50px;min-height:22px}.inbounds-page .infinite-bar .ant-progress-inner .ant-progress-bg{background-color:#F2EAF1;border:#D5BED2 solid 1px}.inbounds-page.dark .infinite-bar .ant-progress-inner .ant-progress-bg{background-color:#7a316f!important;border:#7a316f solid 1px}.inbounds-page .ant-collapse{margin:5px 0}.inbounds-page .info-large-tag{max-width:200px;overflow:hidden}.inbounds-page .client-comment{font-size:12px;opacity:.75;cursor:help}.inbounds-page .client-email{font-weight:500}.inbounds-page .client-popup-item{display:flex;align-items:center;gap:5px}.inbounds-page .online-animation .ant-badge-status-dot{animation:onlineAnimation 1.2s linear infinite}@keyframes onlineAnimation{0%,50%,100%{transform:scale(1);opacity:1}10%{transform:scale(1.5);opacity:.2}}.inbounds-page .tr-table-box{display:flex;gap:4px;justify-content:center;align-items:center}.inbounds-page .tr-table-rt{flex-basis:70px;min-width:70px;text-align:end}.inbounds-page .tr-table-lt{flex-basis:70px;min-width:70px;text-align:start}.inbounds-page .tr-table-bar{flex-basis:160px;min-width:60px}.inbounds-page .tr-infinity-ch{font-size:14pt;max-height:24px;display:inline-flex;align-items:center}.inbounds-page .ant-table-expanded-row .ant-table .ant-table-body{overflow-x:hidden}.inbounds-page .ant-table-expanded-row .ant-table-tbody>tr>td{padding:10px 2px}.inbounds-page .ant-table-expanded-row .ant-table-thead>tr>th{padding:12px 2px}@-webkit-keyframes zoom-out{0%{opacity:1;-webkit-transform:translateZ(0)}100%{opacity:0;-webkit-transform:translateZ(-100px)}}@-moz-keyframes zoom-out{0%{opacity:1;-moz-transform:translateZ(0)}100%{opacity:0;-moz-transform:translateZ(-100px)}}@keyframes zoom-out{0%{opacity:1;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}100%{opacity:0;-webkit-transform:translateZ(-100px);-moz-transform:translateZ(-100px);-ms-transform:translateZ(-100px);-o-transform:translateZ(-100px);transform:translateZ(-100px)}}.setting-section{position:absolute;top:0;right:0;padding:22px}.ant-space-item .ant-switch{margin:2px 0 4px}@media (min-width:769px){.settings-page .ant-layout-content{margin:24px 16px}}@media (max-width:768px){.settings-page .ant-tabs-nav .ant-tabs-tab{margin:0;padding:12px .5rem}}.settings-page .ant-tabs-bar{margin:0}.settings-page .ant-list-item{display:block}.settings-page .alert-msg{color:rgb(194,117,18);font-weight:normal;font-size:16px;padding:.5rem 1rem;text-align:center;background:rgb(255 145 0 / 15%);margin:1.5rem 2.5rem 0;border-radius:.5rem;transition:all .5s;animation:settings-signal 3s cubic-bezier(.18,.89,.32,1.28) infinite}.settings-page .alert-msg:hover{cursor:default;transition-duration:.3s;animation:settings-signal .9s ease infinite}@keyframes settings-signal{0%{box-shadow:0 0 0 0 rgba(194,118,18,.5)}50%{box-shadow:0 0 0 6px rgba(0,0,0,0)}100%{box-shadow:0 0 0 6px rgba(0,0,0,0)}}.settings-page .alert-msg>i{color:inherit;font-size:24px}.settings-page.dark .ant-input-password-icon{color:var(--dark-color-text-primary)}.settings-page .ant-collapse-content-box .ant-alert{margin-block-end:12px}@media (min-width:769px){.xray-page .ant-layout-content{margin:24px 16px}}@media (max-width:768px){.xray-page .ant-tabs-nav .ant-tabs-tab{margin:0;padding:12px .5rem}.xray-page .ant-table-thead>tr>th,.xray-page .ant-table-tbody>tr>td{padding:10px 0}}.xray-page .ant-tabs-bar{margin:0}.xray-page .ant-list-item{display:block}.xray-page .ant-list-item>li{padding:10px 20px!important}.xray-page .ant-collapse-content-box .ant-alert{margin-block-end:12px}#app.login-app.dark #login input.ant-input:-webkit-autofill,#app.login-app.dark #login input.ant-input:-webkit-autofill:hover,#app.login-app.dark #login input.ant-input:-webkit-autofill:focus,#app.login-app.dark #login .ant-input-password input:-webkit-autofill,#app.login-app.dark #login .ant-input-password input:-webkit-autofill:hover,#app.login-app.dark #login .ant-input-password input:-webkit-autofill:focus{-webkit-text-fill-color:var(--login-input-color,#d6dce6)!important;caret-color:var(--login-input-color,#d6dce6)!important;-webkit-box-shadow:0 0 0 1000px var(--login-input-bg,#1d2433) inset!important;box-shadow:0 0 0 1000px var(--login-input-bg,#1d2433) inset!important;transition:background-color 9999s ease-in-out 0s!important}#app.login-app.dark #login input.ant-input:-moz-autofill,#app.login-app.dark #login .ant-input-password input:-moz-autofill{-moz-text-fill-color:var(--login-input-color,#d6dce6)!important;caret-color:var(--login-input-color,#d6dce6)!important;box-shadow:0 0 0 1000px var(--login-input-bg,#1d2433) inset!important}#app.login-app.dark #login{--login-input-bg:var(--dark-color-surface-200);--login-input-color:var(--dark-color-text-primary)} \ No newline at end of file +:root { + --color-primary-100: #008771; + --dark-color-background: #0a1222; + --dark-color-surface-100: #151f31; + --dark-color-surface-200: #222d42; + --dark-color-surface-300: #2c3950; + --dark-color-surface-400: rgba(65, 85, 119, .5); + --dark-color-surface-500: #2c3950; + --dark-color-surface-600: #313f5a; + --dark-color-surface-700: #111929; + --dark-color-surface-700-rgb: 17, 25, 41; + --dark-color-table-hover: rgba(44, 57, 80, .2); + --dark-color-text-primary: rgba(255, 255, 255, .75); + --dark-color-stroke: #2c3950; + --dark-color-btn-danger: #cd3838; + --dark-color-btn-danger-border: transparent; + --dark-color-btn-danger-hover: #e94b4b; + --dark-color-tag-bg: rgba(255, 255, 255, .05); + --dark-color-tag-border: rgba(255, 255, 255, .15); + --dark-color-tag-color: rgba(255, 255, 255, .75); + --dark-color-tag-green-bg: 17, 36, 33; + --dark-color-tag-green-border: 25, 81, 65; + --dark-color-tag-green-color: #3ad3ba; + --dark-color-tag-purple-bg: #201425; + --dark-color-tag-purple-border: #5a2969; + --dark-color-tag-purple-color: #d988cd; + --dark-color-tag-red-bg: #291515; + --dark-color-tag-red-border: #5c2626; + --dark-color-tag-red-color: #e04141; + --dark-color-tag-orange-bg: #312313; + --dark-color-tag-orange-border: #593914; + --dark-color-tag-orange-color: #ffa031; + --dark-color-tag-blue-bg: #111a2c; + --dark-color-tag-blue-border: #1348ab; + --dark-color-tag-blue-color: #529fff; + --dark-color-codemirror-line-hover: rgba(0, 135, 113, .2); + --dark-color-codemirror-line-selection: rgba(0, 135, 113, .3); + --dark-color-login-background: var(--dark-color-background); + --dark-color-login-wave: var(--dark-color-surface-200); + --dark-color-tooltip: rgba(61, 76, 104, .9); + --dark-color-back-top: rgba(61, 76, 104, .9); + --dark-color-back-top-hover: rgba(61, 76, 104, 1); + --dark-color-scrollbar: #313f5a; + --dark-color-scrollbar-webkit: #7484a0; + --dark-color-scrollbar-webkit-hover: #90a4c7; + --dark-color-table-ring: rgb(38 52 77); + --dark-color-spin-container: #151f31 +} + +html[data-theme-animations='off'] { + + .ant-menu, + .ant-layout-sider, + .ant-card, + .ant-tag, + .ant-progress-circle>*, + .ant-input, + .ant-table-row-expand-icon, + .ant-switch, + .ant-table-thead>tr>th, + .ant-select-selection, + .ant-btn, + .ant-input-number, + .ant-input-group-addon, + .ant-checkbox-inner, + .ant-progress-bg, + .ant-progress-success-bg, + .ant-radio-button-wrapper:not(:first-child):before, + .ant-radio-button-wrapper, + #login, + .cm-s-xq.CodeMirror { + transition: border 0s, background 0s !important + } + + .ant-menu.ant-menu-inline .ant-menu-item:not(.ant-menu-sub .ant-menu-item), + .ant-layout-sider-trigger, + .ant-alert-close-icon .anticon-close, + .ant-tabs-nav .ant-tabs-tab, + .ant-input-number-input, + .ant-collapse>.ant-collapse-item>.ant-collapse-header, + .Line-Hover, + .ant-menu-theme-switch, + .ant-menu-submenu-title { + transition: color 0s !important + } + + .wave-btn-bg { + transition: width 0s !important + } +} + +html[data-theme='ultra-dark'] { + --dark-color-background: #21242a; + --dark-color-surface-100: #0c0e12; + --dark-color-surface-200: #222327; + --dark-color-surface-300: #32353b; + --dark-color-surface-400: rgba(255, 255, 255, .1); + --dark-color-surface-500: #3b404b; + --dark-color-surface-600: #505663; + --dark-color-surface-700: #101113; + --dark-color-surface-700-rgb: 16, 17, 19; + --dark-color-table-hover: rgba(89, 89, 89, .15); + --dark-color-text-primary: rgb(255 255 255 / 85%); + --dark-color-stroke: #202025; + --dark-color-tag-green-bg: 17, 36, 33; + --dark-color-tag-green-border: 29, 95, 77; + --dark-color-tag-green-color: #59cbac; + --dark-color-tag-purple-bg: #241121; + --dark-color-tag-purple-border: #5a2969; + --dark-color-tag-purple-color: #d686ca; + --dark-color-tag-red-bg: #2a1215; + --dark-color-tag-red-border: #58181c; + --dark-color-tag-red-color: #e84749; + --dark-color-tag-orange-bg: #2b1d11; + --dark-color-tag-orange-border: #593815; + --dark-color-tag-orange-color: #e89a3c; + --dark-color-tag-blue-bg: #111a2c; + --dark-color-tag-blue-border: #0f367e; + --dark-color-tag-blue-color: #3c89e8; + --dark-color-codemirror-line-hover: rgba(82, 84, 94, .2); + --dark-color-codemirror-line-selection: rgba(82, 84, 94, .3); + --dark-color-login-background: #0a2227; + --dark-color-login-wave: #0f2d32; + --dark-color-tooltip: rgba(88, 93, 100, .9); + --dark-color-back-top: rgba(88, 93, 100, .9); + --dark-color-back-top-hover: rgba(88, 93, 100, 1); + --dark-color-scrollbar: rgb(107, 107, 107); + --dark-color-scrollbar-webkit: #9f9f9f; + --dark-color-scrollbar-webkit-hover: #d1d1d1; + --dark-color-table-ring: rgb(37 39 42); + --dark-color-spin-container: #1d1d1d; + + .ant-dropdown-menu-dark { + background-color: var(--dark-color-surface-500) + } + + .dark .ant-dropdown-menu-submenu-title:hover, + .dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled), + .dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) { + background-color: rgb(0 93 78 / .3) + } + + .dark .waves-header { + background-color: #0a2227 + } + + .dark .ant-calendar-year-panel-year:hover, + .dark .ant-calendar-month-panel-month:hover, + .dark .ant-calendar-decade-panel-decade:hover { + background-color: var(--dark-color-surface-600) + } +} + +html, +body { + height: 100vh; + width: 100vw; + margin: 0; + padding: 0; + overflow: hidden +} + +body { + color: rgb(0 0 0 / .65); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + background-color: #fff; + font-feature-settings: "tnum" +} + +html { + --antd-wave-shadow-color: var(--color-primary-100); + line-height: 1.15; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -moz-tap-highlight-color: #fff0; + -webkit-tap-highlight-color: #fff0 +} + +@supports (scrollbar-width:auto) and (not selector(::-webkit-scrollbar)) { + :not(.dark) { + scrollbar-color: #9a9a9a #fff0; + scrollbar-width: thin + } + + .dark * { + scrollbar-color: var(--dark-color-scrollbar) #fff0; + scrollbar-width: thin + } +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: #fff0 +} + +::-webkit-scrollbar-track { + background-color: #fff0; + margin-block: .5em +} + +.ant-modal-wrap::-webkit-scrollbar-track { + background-color: #fff; + margin-block: 0 +} + +::-webkit-scrollbar-thumb { + border-radius: 9999px; + background-color: #9a9a9a; + border: 2px solid #fff0; + background-clip: content-box +} + +::-webkit-scrollbar-thumb:hover, +::-webkit-scrollbar-thumb:active { + background-color: #828282 +} + +.dark .ant-modal-wrap::-webkit-scrollbar-track { + background-color: var(--dark-color-background) +} + +.dark::-webkit-scrollbar-thumb { + background-color: var(--dark-color-scrollbar-webkit) +} + +.dark::-webkit-scrollbar-thumb:hover, +.dark::-webkit-scrollbar-thumb:active { + background-color: var(--dark-color-scrollbar-webkit-hover) +} + +::-moz-selection { + color: var(--color-primary-100); + background-color: #cfe8e4 +} + +::selection { + color: var(--color-primary-100); + background-color: #cfe8e4 +} + +#app { + height: 100%; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: 0; + padding: 0; + overflow: auto +} + +.ant-layout, +.ant-layout * { + box-sizing: border-box +} + +.ant-spin-container:after { + border-radius: 1.5rem +} + +.dark .ant-spin-container:after { + background: var(--dark-color-spin-container) +} + +style attribute { + text-align: center +} + +.ant-table-thead>tr>th { + padding: 12px 8px +} + +.ant-table-tbody>tr>td { + padding: 10px 8px +} + +.ant-table-thead>tr>th { + color: rgb(0 0 0 / .85); + font-weight: 500; + text-align: left; + border-bottom: 1px solid #e8e8e8; + transition: background .3s ease +} + +.ant-table table { + border-radius: 1rem +} + +.ant-table-bordered .ant-table-tbody:not(.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody)>tr:last-child>td:first-child { + border-bottom-left-radius: 1rem +} + +.ant-table-bordered .ant-table-tbody:not(.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody)>tr:last-child>td:last-child { + border-bottom-right-radius: 1rem +} + +.ant-table { + box-sizing: border-box; + margin: 0; + padding: 0; + color: rgb(0 0 0 / .65); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + list-style: none; + font-feature-settings: "tnum"; + position: relative; + clear: both +} + +.ant-table .ant-table-body:not(.ant-table-expanded-row .ant-table-body) { + overflow-x: auto !important +} + +.ant-card-hoverable { + cursor: auto; + cursor: pointer +} + +.ant-card { + box-sizing: border-box; + margin: 0; + padding: 0; + color: rgb(0 0 0 / .65); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + list-style: none; + position: relative; + background-color: #fff; + border-radius: 2px; + transition: all .3s +} + +.ant-space { + width: 100% +} + +.ant-layout-sider-zero-width-trigger { + display: none +} + +@media (max-width:768px) { + .ant-layout-sider { + display: none + } + + .ant-card, + .ant-alert-error { + margin: .5rem + } + + .ant-tabs { + margin: .5rem; + padding: .5rem + } + + .ant-modal-body { + padding: 20px + } + + .ant-form-item-label { + line-height: 1.5; + padding: 8px 0 0 + } + + :not(.dark)::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: #fff0 + } + + .dark::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: #fff0 + } +} + +.ant-layout-content { + min-height: auto +} + +.ant-card, +.ant-tabs { + border-radius: 1.5rem +} + +.ant-card-hoverable { + cursor: auto +} + +.ant-card+.ant-card { + margin-top: 20px +} + +.drawer-handle { + position: absolute; + top: 72px; + width: 41px; + height: 40px; + cursor: pointer; + z-index: 0; + text-align: center; + line-height: 40px; + font-size: 16px; + display: flex; + justify-content: center; + align-items: center; + background-color: #fff; + right: -40px; + box-shadow: 2px 0 8px rgb(0 0 0 / .15); + border-radius: 0 4px 4px 0 +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { + background-color: #006655 !important; + background-image: linear-gradient(270deg, #fff0 30%, #009980, #fff0 100%); + background-repeat: no-repeat; + animation: ma-bg-move linear 6.6s infinite; + color: #fff; + border-radius: .5rem +} + +.ant-layout-sider-collapsed .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { + border-radius: 0 +} + +@-webkit-keyframes ma-bg-move { + 0% { + background-position: -500px 0 + } + + 100% { + background-position: 1000px 0 + } +} + +@keyframes ma-bg-move { + 0% { + background-position: -500px 0 + } + + 50% { + background-position: 1000px 0 + } + + 100% { + background-position: 1000px 0 + } +} + +.ant-menu-item-active, +.ant-menu-item:hover, +.ant-menu-submenu-title:hover, +.ant-menu-item:active, +.ant-menu-submenu-title:active { + color: var(--color-primary-100); + background-color: #e8f4f2 +} + +.ant-menu-inline .ant-menu-item, +.ant-menu-inline .ant-menu-submenu-title { + border-radius: .5rem +} + +.ant-menu-inline .ant-menu-item:after, +.ant-menu { + border-right-width: 0 +} + +.ant-layout-sider-children, +.ant-pagination ul { + padding: .5rem +} + +.ant-layout-sider-collapsed .ant-layout-sider-children { + padding: .5rem 0 +} + +.ant-dropdown-menu, +.ant-select-dropdown-menu { + padding: .5rem +} + +.ant-dropdown-menu-item, +.ant-dropdown-menu-item:hover, +.ant-select-dropdown-menu-item, +.ant-select-dropdown-menu-item:hover, +.ant-select-selection--multiple .ant-select-selection__choice { + border-radius: .5rem +} + +.ant-select-dropdown--multiple .ant-select-dropdown-menu .ant-select-dropdown-menu-item, +.ant-select-dropdown--single .ant-select-dropdown-menu .ant-select-dropdown-menu-item-selected { + margin-block: 2px +} + +@media (min-width:769px) { + .drawer-handle { + display: none + } + + .ant-tabs { + padding: 2rem + } +} + +.fade-in-enter, +.fade-in-leave-active, +.fade-in-linear-enter, +.fade-in-linear-leave, +.fade-in-linear-leave-active, +.fade-in-linear-enter, +.fade-in-linear-leave, +.fade-in-linear-leave-active { + opacity: 0 +} + +.fade-in-linear-enter-active, +.fade-in-linear-leave-active { + -webkit-transition: opacity .2s linear; + transition: opacity .2s linear +} + +.fade-in-linear-enter-active, +.fade-in-linear-leave-active { + -webkit-transition: opacity .2s linear; + transition: opacity .2s linear +} + +.fade-in-enter-active, +.fade-in-leave-active { + -webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1); + transition: all .3s cubic-bezier(.55, 0, .1, 1) +} + +.zoom-in-center-enter-active, +.zoom-in-center-leave-active { + -webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1); + transition: all .3s cubic-bezier(.55, 0, .1, 1) +} + +.zoom-in-center-enter, +.zoom-in-center-leave-active { + opacity: 0; + -webkit-transform: scaleX(0); + transform: scaleX(0) +} + +.zoom-in-top-enter-active, +.zoom-in-top-leave-active { + opacity: 1; + -webkit-transform: scaleY(1); + transform: scaleY(1); + -webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1); + transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + -webkit-transform-origin: center top; + transform-origin: center top +} + +.zoom-in-top-enter, +.zoom-in-top-leave-active { + opacity: 0; + -webkit-transform: scaleY(0); + transform: scaleY(0) +} + +.zoom-in-bottom-enter-active, +.zoom-in-bottom-leave-active { + opacity: 1; + -webkit-transform: scaleY(1); + transform: scaleY(1); + -webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1); + transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + -webkit-transform-origin: center bottom; + transform-origin: center bottom +} + +.zoom-in-bottom-enter, +.zoom-in-bottom-leave-active { + opacity: 0; + -webkit-transform: scaleY(0); + transform: scaleY(0) +} + +.zoom-in-left-enter-active, +.zoom-in-left-leave-active { + opacity: 1; + -webkit-transform: scale(1, 1); + transform: scale(1, 1); + -webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1); + transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); + -webkit-transform-origin: top left; + transform-origin: top left +} + +.zoom-in-left-enter, +.zoom-in-left-leave-active { + opacity: 0; + -webkit-transform: scale(.45, .45); + transform: scale(.45, .45) +} + +.list-enter-active, +.list-leave-active { + -webkit-transition: all .3s; + transition: all .3s +} + +.list-enter, +.list-leave-active { + opacity: 0; + -webkit-transform: translateY(-30px); + transform: translateY(-30px) +} + +.ant-tooltip-inner { + min-height: 0; + padding-inline: 1rem +} + +.ant-list-item-meta-title { + font-size: 14px +} + +.ant-progress-inner { + background-color: #ebeef5 +} + +.deactive-client .ant-collapse-header { + color: #ffffff !important; + background-color: #ff7f7f +} + +.ant-table-expand-icon-th, +.ant-table-row-expand-icon-cell { + width: 30px; + min-width: 30px +} + +.ant-tabs { + background-color: #fff +} + +.ant-form-item { + margin-bottom: 0 +} + +.ant-setting-textarea { + margin-top: 1.5rem +} + +.client-table-header { + background-color: #f0f2f5 +} + +.client-table-odd-row { + background-color: #fafafa +} + +.ant-table-pagination.ant-pagination { + float: left +} + +.ant-tag { + margin-right: 0; + margin-inline: 2px; + display: inline-flex; + align-items: center; + justify-content: space-evenly +} + +.ant-tag:not(.qr-tag) { + column-gap: 4px +} + +#inbound-info-modal .ant-tag { + margin-block: 2px +} + +.tr-info-table { + display: inline-table; + margin-block: 10px; + width: 100% +} + +#inbound-info-modal .tr-info-table .ant-tag { + margin-block: 0; + margin-inline: 0 +} + +.tr-info-row { + display: flex; + flex-direction: column; + row-gap: 2px; + margin-block: 10px +} + +.tr-info-row a { + margin-left: 6px +} + +.tr-info-row code { + padding-inline: 8px +} + +.tr-info-tag { + max-width: 100%; + text-wrap: balance; + overflow: hidden; + overflow-wrap: anywhere +} + +.tr-info-title { + display: inline-flex; + align-items: center; + justify-content: flex-start; + column-gap: 4px +} + +.ant-tag-blue { + background-color: #edf4fa; + border-color: #a9c5e7; + color: #0e49b5 +} + +.ant-tag-green { + background-color: #eafff9; + border-color: #76ccb4; + color: #199270 +} + +.ant-tag-purple { + background-color: #f2eaf1; + border-color: #d5bed2; + color: #7a316f +} + +.ant-tag-orange, +.ant-alert-warning { + background-color: #ffeee1; + border-color: #fec093; + color: #f37b24 +} + +.ant-tag-red, +.ant-alert-error { + background-color: #ffe9e9; + border-color: #ff9e9e; + color: #cf3c3c +} + +.ant-input::placeholder { + opacity: .5 +} + +.ant-input:hover, +.ant-input:focus { + background-color: #e8f4f2 +} + +.ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled) { + background-color: #e8f4f2 +} + +.delete-icon:hover { + color: #e04141 +} + +.normal-icon:hover { + color: var(--color-primary-100) +} + +.dark::-moz-selection { + color: #fff; + background-color: var(--color-primary-100) +} + +.dark::selection { + color: #fff; + background-color: var(--color-primary-100) +} + +.dark .normal-icon:hover { + color: #fff +} + +.dark .ant-layout-sider, +.dark .ant-drawer-content, +.ant-menu-dark, +.ant-menu-dark .ant-menu-sub, +.dark .ant-card, +.dark .ant-table, +.dark .ant-collapse-content, +.dark .ant-tabs { + background-color: var(--dark-color-surface-100); + color: var(--dark-color-text-primary) +} + +.dark .ant-card-hoverable:hover, +.dark .ant-space-item>.ant-tabs:hover { + box-shadow: 0 2px 8px #fff0 +} + +.dark>.ant-layout, +.dark .drawer-handle, +.dark .ant-table-thead>tr>th, +.dark .ant-table-expanded-row, +.dark .ant-table-expanded-row:hover, +.dark .ant-table-expanded-row .ant-table-tbody, +.dark .ant-calendar { + background-color: var(--dark-color-background); + color: var(--dark-color-text-primary) +} + +.dark .ant-table-expanded-row .ant-table-thead>tr:first-child>th { + border-radius: 0 +} + +.dark .ant-calendar, +.dark .ant-card-bordered { + border-color: var(--dark-color-background) +} + +.dark .ant-table-bordered, +.dark .ant-table-bordered.ant-table-empty .ant-table-placeholder, +.dark .ant-table-bordered .ant-table-body>table, +.dark .ant-table-bordered .ant-table-fixed-left table, +.dark .ant-table-bordered .ant-table-fixed-right table, +.dark .ant-table-bordered .ant-table-header>table, +.dark .ant-table-bordered .ant-table-thead>tr:not(:last-child)>th, +.dark .ant-table-bordered .ant-table-tbody>tr>td, +.dark .ant-table-bordered .ant-table-thead>tr>th { + border-color: var(--dark-color-surface-400) +} + +.dark .ant-table-tbody>tr>td, +.dark .ant-table-thead>tr>th, +.dark .ant-card-head, +.dark .ant-modal-header, +.dark .ant-collapse>.ant-collapse-item, +.dark .ant-tabs-bar, +.dark .ant-list-split .ant-list-item, +.dark .ant-popover-title, +.dark .ant-calendar-header, +.dark .ant-calendar-input-wrap { + border-bottom-color: var(--dark-color-surface-400) +} + +.dark .ant-modal-footer, +.dark .ant-collapse-content, +.dark .ant-calendar-footer, +.dark .ant-divider-horizontal.ant-divider-with-text-left:before, +.dark .ant-divider-horizontal.ant-divider-with-text-left:after, +.dark .ant-divider-horizontal.ant-divider-with-text-center:before, +.dark .ant-divider-horizontal.ant-divider-with-text-center:after { + border-top-color: var(--dark-color-surface-300) +} + +.ant-divider-horizontal.ant-divider-with-text-left:before { + width: 10% +} + +.dark .ant-progress-text, +.dark .ant-card-head, +.dark .ant-form, +.dark .ant-collapse>.ant-collapse-item>.ant-collapse-header, +.dark .ant-modal-close-x, +.dark .ant-form .anticon, +.dark .ant-tabs-tab-arrow-show:not(.ant-tabs-tab-btn-disabled), +.dark .anticon-close, +.dark .ant-list-item-meta-title, +.dark .ant-select-selection i, +.dark .ant-modal-confirm-title, +.dark .ant-modal-confirm-content, +.dark .ant-popover-message, +.dark .ant-modal, +.dark .ant-divider-inner-text, +.dark .ant-popover-title, +.dark .ant-popover-inner-content, +.dark h2, +.dark .ant-modal-title, +.dark .ant-form-item-label>label, +.dark .ant-checkbox-wrapper, +.dark .ant-form-item, +.dark .ant-calendar-footer .ant-calendar-today-btn, +.dark .ant-calendar-footer .ant-calendar-time-picker-btn, +.dark .ant-calendar-day-select, +.dark .ant-calendar-month-select, +.dark .ant-calendar-year-select, +.dark .ant-calendar-date, +.dark .ant-calendar-year-panel-year, +.dark .ant-calendar-month-panel-month, +.dark .ant-calendar-decade-panel-decade { + color: var(--dark-color-text-primary) +} + +.dark .ant-pagination-options-size-changer .ant-select-arrow .anticon.anticon-down.ant-select-arrow-icon { + color: rgb(255 255 255 / 35%) +} + +.dark .ant-pagination-item a, +.dark .ant-pagination-next a, +.dark .ant-pagination-prev a { + color: var(--dark-color-text-primary) +} + +.dark .ant-pagination-item:focus a, +.dark .ant-pagination-item:hover a, +.dark .ant-pagination-item-active a, +.dark .ant-pagination-next:hover .ant-pagination-item-link { + color: var(--color-primary-100) +} + +.dark .ant-pagination-item-active { + background-color: #fff0 +} + +.dark .ant-list-item-meta-description { + color: rgb(255 255 255 / .45) +} + +.dark .ant-pagination-disabled i, +.dark .ant-tabs-tab-btn-disabled { + color: rgb(255 255 255 / .25) +} + +.dark .ant-input, +.dark .ant-input-group-addon, +.dark .ant-collapse, +.dark .ant-select-selection, +.dark .ant-input-number, +.dark .ant-input-number-handler-wrap, +.dark .ant-table-placeholder, +.dark .ant-empty-normal, +.dark .ant-select-dropdown, +.dark .ant-select-dropdown li, +.dark .ant-select-dropdown-menu-item, +.dark .client-table-header, +.dark .ant-select-selection--multiple .ant-select-selection__choice { + background-color: var(--dark-color-surface-200); + border-color: var(--dark-color-surface-300); + color: var(--dark-color-text-primary) +} + +.dark .ant-select-dropdown--multiple .ant-select-dropdown-menu .ant-select-dropdown-menu-item.ant-select-dropdown-menu-item-selected:not(.ant-dropdown-menu-submenu-title:hover) { + background-color: var(--dark-color-surface-300) +} + +.dark .ant-select-dropdown-menu-item.ant-select-dropdown-menu-item-selected { + background-color: var(--dark-color-surface-300) +} + +.dark .ant-calendar-time-picker-inner { + background-color: var(--dark-color-background) +} + +.dark .ant-select-selection:hover, +.dark .ant-calendar-picker-clear, +.dark .ant-input-number:hover, +.dark .ant-input-number:focus, +.dark .ant-input:hover, +.dark .ant-input:focus { + background-color: rgb(0 135 113 / .3); + border-color: var(--color-primary-100) +} + +.dark .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled) { + border-color: var(--color-primary-100); + background-color: rgb(0 135 113 / .3) +} + +.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) { + color: var(--dark-color-text-primary); + background-color: rgb(10 117 87 / 30%); + border: 1px solid var(--color-primary-100) +} + +.dark .ant-radio-button-wrapper, +.dark .ant-radio-button-wrapper:before { + color: var(--dark-color-text-primary); + background-color: rgb(0 135 113 / .3); + border-color: var(--color-primary-100) +} + +.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger), +.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) { + background-color: #e8f4f2 +} + +.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger), +.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) { + color: #fff; + background-color: rgb(10 117 87 / 50%); + border-color: var(--color-primary-100) +} + +.dark .ant-btn-primary[disabled], +.dark .ant-btn-danger[disabled], +.dark .ant-calendar-ok-btn-disabled { + color: rgb(255 255 255 / 35%); + background-color: var(--dark-color-surface-200); + border-color: var(--dark-color-surface-300) +} + +.dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, +.dark .client-table-odd-row { + background-color: var(--dark-color-table-hover) +} + +.dark .ant-table-row-expand-icon { + color: #fff; + background-color: #fff0; + border-color: rgb(255 255 255 / 20%) +} + +.dark .ant-table-row-expand-icon:hover { + color: var(--color-primary-100); + background-color: #fff0; + border-color: var(--color-primary-100) +} + +.dark .ant-switch:not(.ant-switch-checked), +.dark .ant-progress-line .ant-progress-inner { + background-color: var(--dark-color-surface-500) +} + +.dark .ant-progress-circle-trail { + stroke: var(--dark-color-stroke) !important +} + +.dark .ant-popover-inner { + background-color: var(--dark-color-surface-500) +} + +.dark>.ant-popover-content>.ant-popover-arrow { + border-color: var(--dark-color-surface-500) +} + +@media (max-width:768px) { + .dark .ant-popover-inner { + background-color: var(--dark-color-surface-200) + } + + .dark>.ant-popover-content>.ant-popover-arrow { + border-color: var(--dark-color-surface-200) + } +} + +.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover, +.dark .ant-select-dropdown-menu-item-selected, +.dark .ant-calendar-time-picker-select-option-selected { + background-color: var(--dark-color-surface-600) +} + +.ant-menu-dark .ant-menu-item:hover, +.ant-menu-dark .ant-menu-submenu-title:hover { + background-color: var(--dark-color-surface-300) +} + +.dark .ant-menu-item:active, +.dark .ant-menu-submenu-title:active { + color: #fff; + background-color: var(--dark-color-surface-300) +} + +.dark .ant-alert-message { + color: rgb(255 255 255 / .85) +} + +.dark .ant-tag { + color: var(--dark-color-tag-color); + background-color: var(--dark-color-tag-bg); + border-color: var(--dark-color-tag-border) +} + +.dark .ant-tag-blue { + background-color: var(--dark-color-tag-blue-bg); + border-color: var(--dark-color-tag-blue-border); + color: var(--dark-color-tag-blue-color) +} + +.dark .ant-tag-red, +.dark .ant-alert-error { + background-color: var(--dark-color-tag-red-bg); + border-color: var(--dark-color-tag-red-border); + color: var(--dark-color-tag-red-color) +} + +.dark .ant-tag-orange, +.dark .ant-alert-warning { + background-color: var(--dark-color-tag-orange-bg); + border-color: var(--dark-color-tag-orange-border); + color: var(--dark-color-tag-orange-color) +} + +.dark .ant-tag-green { + background-color: rgb(var(--dark-color-tag-green-bg)); + border-color: rgb(var(--dark-color-tag-green-border)); + color: var(--dark-color-tag-green-color) +} + +.dark .ant-tag-purple { + background-color: var(--dark-color-tag-purple-bg); + border-color: var(--dark-color-tag-purple-border); + color: var(--dark-color-tag-purple-color) +} + +.dark .ant-modal-content, +.dark .ant-modal-header { + background-color: var(--dark-color-surface-700) +} + +.dark .ant-calendar-next-month-btn-day .ant-calendar-date, +.dark .ant-calendar-last-month-cell .ant-calendar-date { + color: var(--dark-color-surface-300) +} + +.dark .ant-calendar-selected-day .ant-calendar-date { + background-color: var(--color-primary-100) !important; + color: #fff +} + +.dark .ant-calendar-date:hover, +.dark .ant-calendar-time-picker-select li:hover { + background-color: var(--dark-color-surface-600); + color: #fff +} + +.dark .ant-calendar-header a:hover, +.dark .ant-calendar-header a:hover::before, +.dark .ant-calendar-header a:hover::after { + border-color: #fff +} + +.dark .ant-calendar-time-picker-select { + border-right-color: var(--dark-color-surface-300) +} + +.has-warning .ant-select-selection, +.has-warning .ant-select-selection:hover, +.has-warning .ant-input, +.has-warning .ant-input:hover { + background-color: #ffeee1; + border-color: #fec093 +} + +.has-warning .ant-input::placeholder { + color: #f37b24 +} + +.has-warning .ant-input:not([disabled]):hover { + border-color: #fec093 +} + +.dark .has-warning .ant-select-selection, +.dark .has-warning .ant-select-selection:hover, +.dark .has-warning .ant-input, +.dark .has-warning .ant-input:hover { + border-color: #784e1d; + background: #312313 +} + +.dark .has-warning .ant-input::placeholder { + color: rgb(255 160 49 / 70%) +} + +.dark .has-warning .anticon { + color: #ffa031 +} + +.dark .has-success .anticon { + color: var(--color-primary-100); + animation-name: diffZoomIn1 !important +} + +.dark .anticon-close-circle { + color: #e04141 +} + +.dark .ant-spin-nested-loading>div>.ant-spin .ant-spin-text { + text-shadow: 0 1px 2px #0007 +} + +.dark .ant-spin { + color: #fff +} + +.dark .ant-spin-dot-item { + background-color: #fff +} + +.ant-checkbox-wrapper, +.ant-input-group-addon, +.ant-tabs-tab, +.ant-input::placeholder, +.ant-collapse-header, +.ant-menu, +.ant-radio-button-wrapper { + -webkit-user-select: none; + user-select: none +} + +.ant-calendar-date, +.ant-calendar-year-panel-year, +.ant-calendar-decade-panel-decade, +.ant-calendar-month-panel-month { + border-radius: 4px +} + +.ant-checkbox-inner, +.ant-checkbox-checked:after, +.ant-table-row-expand-icon { + border-radius: 6px +} + +.ant-calendar-date:hover { + background-color: #e8f4f2 +} + +.ant-calendar-date:active { + background-color: #e8f4f2; + color: rgb(0 0 0 / .65) +} + +.ant-calendar-today .ant-calendar-date { + color: var(--color-primary-100); + font-weight: 400; + border-color: var(--color-primary-100) +} + +.dark .ant-calendar-today .ant-calendar-date { + color: #fff; + border-color: var(--color-primary-100) +} + +.ant-calendar-selected-day .ant-calendar-date { + background: var(--color-primary-100); + color: #fff +} + +li.ant-select-dropdown-menu-item:empty:after { + content: "None"; + font-weight: 400; + color: rgb(0 0 0 / .25) +} + +.dark li.ant-select-dropdown-menu-item:empty:after { + content: "None"; + font-weight: 400; + color: rgb(255 255 255 / .3) +} + +.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item:hover .ant-select-selected-icon { + color: rgb(0 0 0 / .87) +} + +.dark.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item:hover .ant-select-selected-icon { + color: #fff +} + +.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected .ant-select-selected-icon, +.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected:hover .ant-select-selected-icon { + color: var(--color-primary-100) +} + +.ant-select-selection:hover, +.ant-input-number-focused, +.ant-input-number:hover { + background-color: #e8f4f2 +} + +.dark .ant-input-number-handler:active { + background-color: var(--color-primary-100) +} + +.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner, +.dark .ant-input-number-handler:hover .ant-input-number-handler-up-inner { + color: #fff +} + +.dark .ant-input-number-handler-down { + border-top: 1px solid rgb(217 217 217 / .3) +} + +.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-century-select, +.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-decade-select, +.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-month-select, +.dark .ant-calendar-year-panel-header .ant-calendar-year-panel-year-select .dark .ant-calendar-month-panel-header .ant-calendar-month-panel-century-select, +.dark .ant-calendar-month-panel-header .ant-calendar-month-panel-decade-select, +.dark .ant-calendar-month-panel-header .ant-calendar-month-panel-month-select, +.dark .ant-calendar-month-panel-header .ant-calendar-month-panel-year-select { + color: rgb(255 255 255 / .85) +} + +.dark .ant-calendar-year-panel-header { + border-bottom: 1px solid var(--dark-color-surface-200) +} + +.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year, +.dark .ant-calendar-year-panel-next-decade-cell .ant-calendar-year-panel-year { + color: rgb(255 255 255 / .35) +} + +.dark .ant-divider:not(.ant-divider-with-text-center, .ant-divider-with-text-left, .ant-divider-with-text-right), +.ant-dropdown-menu-dark, +.dark .ant-calendar-year-panel-year:hover, +.dark .ant-calendar-month-panel-month:hover, +.dark .ant-calendar-decade-panel-decade:hover { + background-color: var(--dark-color-surface-200) +} + +.dark .ant-calendar-header a:hover { + color: #fff +} + +.dark .ant-calendar-month-panel-header { + background-color: var(--dark-color-background); + border-bottom: 1px solid var(--dark-color-surface-200) +} + +.dark .ant-calendar-year-panel, +.dark .ant-calendar table { + background-color: var(--dark-color-background) +} + +.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year, +.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year:hover, +.dark .ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month, +.dark .ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month:hover, +.dark .ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade, +.dark .ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade:hover { + color: #fff; + background-color: var(--color-primary-100) !important +} + +.dark .ant-calendar-last-month-cell .ant-calendar-date, +.dark .ant-calendar-last-month-cell .ant-calendar-date:hover, +.dark .ant-calendar-next-month-btn-day .ant-calendar-date, +.dark .ant-calendar-next-month-btn-day .ant-calendar-date:hover { + color: rgb(255 255 255 / 25%); + background: #fff0; + border-color: #fff0 +} + +.dark .ant-calendar-today .ant-calendar-date:hover { + color: #fff; + border-color: var(--color-primary-100); + background-color: var(--color-primary-100) +} + +.dark .ant-calendar-decade-panel-last-century-cell .ant-calendar-decade-panel-decade, +.dark .ant-calendar-decade-panel-next-century-cell .ant-calendar-decade-panel-decade { + color: rgb(255 255 255 / 25%) +} + +.dark .ant-calendar-decade-panel-header { + border-bottom: 1px solid var(--dark-color-surface-200); + background-color: var(--dark-color-background) +} + +.dark .ant-checkbox-inner { + background-color: rgb(0 135 113 / .3); + border-color: rgb(0 135 113 / .3) +} + +.dark .ant-checkbox-checked .ant-checkbox-inner { + background-color: var(--color-primary-100); + border-color: var(--color-primary-100) +} + +.dark .ant-calendar-input { + background-color: var(--dark-color-background); + color: var(--dark-color-text-primary) +} + +.dark .ant-calendar-input::placeholder { + color: rgb(255 255 255 / .25) +} + +.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child), +.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child), +.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child), +.ant-input-number-handler, +.ant-input-number-handler-wrap { + border-radius: 0 +} + +.ant-input-number { + overflow: clip +} + +.ant-modal-body, +.ant-collapse-content>.ant-collapse-content-box { + overflow-x: auto +} + +.ant-modal-body { + overflow-y: hidden +} + +.ant-calendar-year-panel-year:hover, +.ant-calendar-decade-panel-decade:hover, +.ant-calendar-month-panel-month:hover, +.ant-dropdown-menu-item:hover, +.ant-dropdown-menu-submenu-title:hover, +.ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled), +.ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled), +.ant-table-tbody>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, +.ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, +.ant-table-thead>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, +.ant-table-thead>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td { + background-color: #e8f4f2 +} + +.dark .ant-dropdown-menu-submenu-title:hover, +.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled), +.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) { + background-color: rgb(0 93 78 / .3) +} + +.ant-select-dropdown, +.ant-popover-inner { + overflow-x: hidden +} + +.ant-popover-inner-content { + max-height: 450px; + overflow-y: auto +} + +@media (max-height:900px) { + .ant-popover-inner-content { + max-height: 400px + } +} + +@media (max-height:768px) { + .ant-popover-inner-content { + max-height: 300px + } +} + +@media (max-width:768px) { + .ant-popover-inner-content { + max-height: 300px + } +} + +.qr-modal { + display: flex; + align-items: flex-end; + gap: 10px; + flex-direction: column; + flex-wrap: wrap; + row-gap: 24px +} + +.qr-box { + width: 220px +} + +.qr-cv { + width: 100%; + height: 100% +} + +.dark .qr-cv { + filter: invert(1) +} + +.qr-bg { + background-color: #fff; + display: flex; + justify-content: center; + align-content: center; + padding: .8rem; + border-radius: 1rem; + border: solid 1px #e8e8e8; + height: 220px; + width: 220px; + transition: all .1s +} + +.qr-bg:hover { + border-color: #76ccb4; + background-color: #eafff9 +} + +.qr-bg:hover:active { + border-color: #76ccb4; + background-color: rgb(197 241 228 / 70%) +} + +.dark .qr-bg { + background-color: var(--dark-color-surface-700); + border-color: var(--dark-color-surface-300) +} + +.dark .qr-bg:hover { + background-color: rgb(var(--dark-color-tag-green-bg)); + border-color: rgb(var(--dark-color-tag-green-border)) +} + +.dark .qr-bg:hover:active { + background-color: #17322e +} + +@property --tr-rotate { + syntax: ''; + initial-value: 45deg; + inherits: false +} + +.qr-bg-sub { + background-image: linear-gradient(var(--tr-rotate), #76ccb4, transparent, #d5bed2); + display: flex; + justify-content: center; + align-content: center; + padding: 1px; + border-radius: 1rem; + height: 220px; + width: 220px +} + +.dark .qr-bg-sub { + background-image: linear-gradient(var(--tr-rotate), #195141, transparent, #5a2969) +} + +.qr-bg-sub:hover { + animation: tr-rotate-gradient 3.5s linear infinite +} + +@keyframes tr-rotate-gradient { + from { + --tr-rotate: 45deg + } + + to { + --tr-rotate: 405deg + } +} + +.qr-bg-sub-inner { + background-color: #fff; + padding: .8rem; + border-radius: 1rem; + transition: all .1s +} + +.qr-bg-sub-inner:hover { + background-color: rgb(255 255 255 / 60%); + backdrop-filter: blur(25px) +} + +.qr-bg-sub-inner:hover:active { + background-color: rgb(255 255 255 / 30%) +} + +.dark .qr-bg-sub-inner { + background-color: rgb(var(--dark-color-surface-700-rgb)) +} + +.dark .qr-bg-sub-inner:hover { + background-color: rgba(var(--dark-color-surface-700-rgb), .5); + backdrop-filter: blur(25px) +} + +.dark .qr-bg-sub-inner:hover:active { + background-color: rgba(var(--dark-color-surface-700-rgb), .2) +} + +/* Subscription long links styling */ +.mono-link .ant-input { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; + direction: ltr !important; + unicode-bidi: plaintext !important; + white-space: nowrap !important; + overflow-x: auto !important; + text-overflow: clip !important; +} +.mono-link.single-line .ant-input { + height: 38px; +} + +.qr-tag { + text-align: center; + margin-bottom: 10px; + width: 100%; + overflow: hidden; + margin-inline: 0 +} + +@media (min-width:769px) { + .qr-modal { + flex-direction: row; + max-width: 680px + } +} + +.tr-marquee { + justify-content: flex-start +} + +.tr-marquee span { + padding-right: 25%; + white-space: nowrap; + transform-origin: center +} + +@keyframes move-ltr { + 0% { + transform: translateX(0) + } + + 100% { + transform: translateX(-100%) + } +} + +.ant-input-group-addon:not(:first-child):not(:last-child) { + border-radius: 0 1rem 1rem 0 +} + +b, +strong { + font-weight: 500 +} + +.ant-collapse>.ant-collapse-item>.ant-collapse-header { + padding: 10px 16px 10px 40px +} + +.dark .ant-message-notice-content { + background-color: var(--dark-color-surface-200); + border: 1px solid var(--dark-color-surface-300); + color: var(--dark-color-text-primary) +} + +.ant-btn-danger { + background-color: var(--dark-color-btn-danger); + border-color: var(--dark-color-btn-danger-border) +} + +.ant-btn-danger:focus, +.ant-btn-danger:hover { + background-color: var(--dark-color-btn-danger-hover); + border-color: var(--dark-color-btn-danger-hover) +} + +.dark .ant-alert-close-icon .anticon-close:hover { + color: #fff +} + +.ant-empty-small { + margin: 4px 0; + background-color: transparent !important +} + +.ant-empty-small .ant-empty-image { + height: 20px +} + +.ant-menu-theme-switch, +.ant-menu-theme-switch:hover { + background-color: transparent !important; + cursor: default !important +} + +.dark .ant-tooltip-inner, +.dark .ant-tooltip-arrow:before { + background-color: var(--dark-color-tooltip) +} + +.ant-select-sm .ant-select-selection__rendered { + margin-left: 10px +} + +.ant-collapse { + -moz-animation: collfade .3s ease; + -webkit-animation: .3s collfade .3s ease; + animation: collfade .3s ease +} + +@-webkit-keyframes collfade { + 0% { + transform: scaleY(.8); + transform-origin: 0 0%; + opacity: 0 + } + + 100% { + transform: scaleY(1); + transform-origin: 0 0%; + opacity: 1 + } +} + +@keyframes collfade { + 0% { + transform: scaleY(.8); + transform-origin: 0 0%; + opacity: 0 + } + + 100% { + transform: scaleY(1); + transform-origin: 0 0%; + opacity: 1 + } +} + +.ant-table-tbody>tr>td { + border-color: #f0f0f0 +} + +.ant-table-row-expand-icon { + vertical-align: middle; + margin-inline-end: 8px; + position: relative; + transform: scale(.9411764705882353) +} + +.ant-table-row-collapsed::before { + transform: rotate(-180deg); + top: 7px; + inset-inline-end: 3px; + inset-inline-start: 3px; + height: 1px; + position: absolute; + background: currentcolor; + transition: transform .3s ease-out; + content: "" +} + +.ant-table-row-collapsed::after { + transform: rotate(0deg); + top: 3px; + bottom: 3px; + inset-inline-start: 7px; + width: 1px; + position: absolute; + background: currentcolor; + transition: transform .3s ease-out; + content: "" +} + +.ant-table-row-expanded::before { + top: 7px; + inset-inline-end: 3px; + inset-inline-start: 3px; + height: 1px; + position: absolute; + background: currentcolor; + transition: transform .3s ease-out; + content: "" +} + +.ant-table-row-expanded::after { + top: 3px; + bottom: 3px; + inset-inline-start: 7px; + width: 1px; + transform: rotate(90deg); + position: absolute; + background: currentcolor; + transition: transform .3s ease-out; + content: "" +} + +.ant-menu-theme-switch.ant-menu-item .ant-switch:not(.ant-switch-disabled):active:after, +.ant-switch:not(.ant-switch-disabled):active:before { + width: 16px +} + +.dark .ant-select-disabled .ant-select-selection { + background: var(--dark-color-surface-100); + border-color: var(--dark-color-surface-200); + color: rgb(255 255 255 / .25) +} + +.dark .ant-select-disabled .anticon { + color: rgb(255 255 255 / .25) +} + +.dark .ant-input-number-handler-down-disabled, +.dark .ant-input-number-handler-up-disabled { + background-color: rgb(0 0 0 / .1) +} + +.dark .ant-input-number-handler-down-disabled .anticon, +.dark .ant-input-number-handler-up-disabled .anticon, +.dark .ant-input-number-handler-down:hover.ant-input-number-handler-down-disabled .anticon, +.dark .ant-input-number-handler-up:hover.ant-input-number-handler-up-disabled .anticon { + color: rgb(255 255 255 / .25) +} + +.dark .ant-input-number-handler-down:active.ant-input-number-handler-down-disabled, +.dark .ant-input-number-handler-up:active.ant-input-number-handler-up-disabled { + background-color: rgb(0 0 0 / .2) +} + +.ant-menu-dark .ant-menu-inline.ant-menu-sub { + background: var(--dark-color-surface-100); + box-shadow: none +} + +.dark .ant-layout-sider-trigger { + background: var(--dark-color-surface-100); + color: rgb(255 255 255 / 65%) +} + +.ant-layout-sider { + overflow: auto +} + +.dark .ant-back-top-content { + background-color: var(--dark-color-back-top) +} + +.dark .ant-back-top-content:hover { + background-color: var(--dark-color-back-top-hover) +} + +.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn { + text-transform: capitalize +} + +.ant-calendar { + border-color: #fff0; + border-width: 0 +} + +.ant-calendar-time-picker-select li:focus, +li.ant-calendar-time-picker-select-option-selected { + color: rgb(0 0 0 / .65); + font-weight: 400; + background-color: #e8f4f2 +} + +.dark li.ant-calendar-time-picker-select-option-selected { + color: var(--dark-color-text-primary); + font-weight: 400 +} + +.dark .ant-calendar-time-picker-select li:focus { + color: #fff; + font-weight: 400; + background-color: var(--color-primary-100) +} + +.ant-calendar-time-picker-select li:hover { + background: #f5f5f5 +} + +.ant-calendar-date { + transition: background .3s ease, color .3s ease +} + +li.ant-calendar-time-picker-select-option-selected { + margin-block: 2px +} + +.ant-calendar-time-picker-select { + padding: 4px +} + +.ant-calendar-time-picker-select li { + height: 28px; + line-height: 28px; + border-radius: 4px +} + +@media (min-width:769px) { + .ant-layout-content { + margin: 24px 16px + } +} + +.ant-card-dark h2 { + color: var(--dark-color-text-primary) +} + +.ant-backup-list-item { + gap: 10px +} + +.ant-version-list-item { + --padding: 12px; + padding: var(--padding) !important; + gap: var(--padding) +} + +.dark .ant-version-list-item svg { + color: var(--dark-color-text-primary) +} + +.dark .ant-backup-list-item svg, +.dark .ant-badge-status-text, +.dark .ant-card-extra { + color: var(--dark-color-text-primary) +} + +.dark .ant-card-actions>li { + color: rgba(255, 255, 255, .55) +} + +.dark .ant-radio-inner { + background-color: var(--dark-color-surface-100); + border-color: var(--dark-color-surface-600) +} + +.dark .ant-radio-checked .ant-radio-inner { + border-color: var(--color-primary-100) +} + +.dark .ant-backup-list, +.dark .ant-version-list, +.dark .ant-card-actions, +.dark .ant-card-actions>li:not(:last-child) { + border-color: var(--dark-color-stroke) +} + +.ant-card-actions { + background: transparent +} + +.ip-hidden { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + filter: blur(10px) +} + +.running-animation .ant-badge-status-dot { + animation: runningAnimation 1.2s linear infinite +} + +.running-animation .ant-badge-status-processing:after { + border-color: var(--color-primary-100) +} + +@keyframes runningAnimation { + + 0%, + 50%, + 100% { + transform: scale(1); + opacity: 1 + } + + 10% { + transform: scale(1.5); + opacity: .2 + } +} + +.mb-10 { + margin-bottom: 10px !important +} + +.mb-12 { + margin-bottom: 12px !important +} + +.mt-5 { + margin-top: 5px !important +} + +.mr-8 { + margin-right: 8px !important +} + +.ml-10 { + margin-left: 10px !important +} + +.mr-05 { + margin-right: .5rem !important +} + +.fs-1rem { + font-size: 1rem !important +} + +.w-100 { + width: 100% !important +} + +.w-70 { + width: 70px !important +} + +.w-95 { + width: 95px !important +} + +.text-center { + text-align: center !important +} + +.cursor-pointer { + cursor: pointer !important +} + +.float-right { + float: right !important +} + +.va-middle { + vertical-align: middle !important +} + +.d-flex { + display: flex !important +} + +.justify-end { + justify-content: flex-end !important +} + +.log-container { + height: auto; + max-height: 500px; + overflow: auto; + margin-top: .5rem +} + +.max-w-400 { + max-width: 400px; + display: inline-block +} + +.card-placeholder { + text-align: center; + padding: 30px 0; + margin-top: 10px; + background: transparent; + border: none +} + +.ant-space.jc-center { + justify-content: center +} + +#app.login-app { + overflow: hidden; + margin: 0 !important; + padding: 0 !important; + min-height: 100vh; + width: 100vw +} + +#app.login-app * { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +#app.login-app h1 { + text-align: center; + height: 110px +} + +#app.login-app .ant-layout-content { + margin: 0 !important; + padding: 0 !important; + width: 100vw; + max-width: 100vw +} + +#app.login-app.ant-layout, +#app.login-app .ant-layout { + margin: 0 !important; + padding: 0 !important; + width: 100vw; + max-width: 100vw +} + +.min-h-0 { + min-height: 0 !important +} + +.min-h-100vh { + min-height: 100vh !important +} + +.h-100 { + height: 100% !important +} + +.h-50px { + height: 50px !important +} + +.overflow-auto { + overflow: auto !important +} + +.overflow-x-hidden { + overflow-x: hidden !important +} + +.overflow-hidden-auto { + overflow: hidden auto !important +} + +.mt-1rem { + margin-top: 1rem !important +} + +.my-3rem { + margin-top: 3rem !important; + margin-bottom: 3rem !important +} + +#app.login-app #login { + animation: charge .5s both; + background-color: #fff; + border-radius: 2rem; + padding: 4rem 3rem; + transition: all .3s; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none +} + +#app.login-app #login:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, .09) +} + +@keyframes charge { + from { + transform: translateY(5rem); + opacity: 0 + } + + to { + transform: translateY(0); + opacity: 1 + } +} + +#app.login-app .under { + background-color: #c7ebe2; + z-index: 0 +} + +#app.login-app.dark .under { + background-color: var(--dark-color-login-wave) +} + +#app.login-app.dark #login { + background-color: var(--dark-color-surface-100) +} + +#app.login-app.dark h1 { + color: rgba(255, 255, 255) +} + +#app.login-app #login .ant-form-item-children .ant-btn, +#app.login-app #login .ant-input { + height: 50px; + border-radius: 30px +} + +#app.login-app #login .ant-input-group-addon { + border-radius: 0 30px 30px 0; + width: 50px; + font-size: 18px +} + +#app.login-app #login .ant-input-affix-wrapper .ant-input-prefix { + left: 23px +} + +#app.login-app #login .ant-input-affix-wrapper .ant-input:not(:first-child) { + padding-left: 50px +} + +#app.login-app .centered { + display: flex; + text-align: center; + align-items: center; + justify-content: center; + width: 100% +} + +#app.login-app .title { + font-size: 2rem; + margin-block-end: 2rem +} + +#app.login-app .title b { + font-weight: bold !important +} + +.ant-btn-primary-login { + width: 100% +} + +.ant-btn-primary-login:focus, +.ant-btn-primary-login:hover { + color: #fff; + background-color: #006655; + border-color: #006655; + background-image: linear-gradient(270deg, rgba(123, 199, 77, 0) 30%, #009980, rgba(123, 199, 77, 0) 100%); + background-repeat: no-repeat; + animation: ma-bg-move ease-in-out 5s infinite; + background-position-x: -500px; + width: 95%; + animation-delay: -.5s; + box-shadow: 0 2px 0 rgba(0, 0, 0, .045) +} + +.ant-btn-primary-login.active, +.ant-btn-primary-login:active { + color: #fff; + background-color: #006655; + border-color: #006655 +} + +.wave-btn-bg { + position: relative; + border-radius: 25px; + width: 100%; + transition: all .3s cubic-bezier(.645, .045, .355, 1) +} + +.dark .wave-btn-bg { + color: #fff; + position: relative; + background-color: #0a7557; + border: 2px double transparent; + background-origin: border-box; + background-clip: padding-box, border-box; + background-size: 300%; + width: 100%; + z-index: 1 +} + +.dark .wave-btn-bg:hover { + animation: wave-btn-tara 4s ease infinite +} + +.dark .wave-btn-bg-cl { + background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)), radial-gradient(circle at left top, #006655, #009980, #006655) !important; + border-radius: 3em +} + +.dark .wave-btn-bg-cl:hover { + width: 95% +} + +.dark .wave-btn-bg-cl:before { + position: absolute; + content: ""; + top: -5px; + left: -5px; + bottom: -5px; + right: -5px; + z-index: -1; + background: inherit; + background-size: inherit; + border-radius: 4em; + opacity: 0; + transition: .5s +} + +.dark .wave-btn-bg-cl:hover::before { + opacity: 1; + filter: blur(20px); + animation: wave-btn-tara 8s linear infinite +} + +@keyframes wave-btn-tara { + to { + background-position: 300% + } +} + +.waves-header { + position: fixed; + top: 0; + left: 0; + right: 0; + width: 100%; + text-align: center; + background-color: #dbf5ed; + color: white; + z-index: -1 +} + +.dark .waves-header { + background-color: var(--dark-color-login-background) +} + +.waves-inner-header { + height: 50vh; + width: 100%; + margin: 0; + padding: 0 +} + +.waves { + position: relative; + width: 100%; + height: 15vh; + margin-bottom: -8px; + min-height: 100px; + max-height: 150px +} + +.parallax>use { + animation: move-forever 25s cubic-bezier(.55, .5, .45, .5) infinite +} + +.dark .parallax>use { + fill: var(--dark-color-login-wave) +} + +.parallax>use:nth-child(1) { + animation-delay: -2s; + animation-duration: 4s; + opacity: .2 +} + +.parallax>use:nth-child(2) { + animation-delay: -3s; + animation-duration: 7s; + opacity: .4 +} + +.parallax>use:nth-child(3) { + animation-delay: -4s; + animation-duration: 10s; + opacity: .6 +} + +.parallax>use:nth-child(4) { + animation-delay: -5s; + animation-duration: 13s +} + +@keyframes move-forever { + 0% { + transform: translate3d(-90px, 0, 0) + } + + 100% { + transform: translate3d(85px, 0, 0) + } +} + +@media (max-width:768px) { + #app.login-app .waves { + height: 40px; + min-height: 40px + } +} + +#app.login-app .words-wrapper { + width: 100%; + display: inline-block; + position: relative; + text-align: center +} + +#app.login-app .words-wrapper b { + width: 100%; + display: inline-block; + position: absolute; + left: 0; + top: 0 +} + +#app.login-app .words-wrapper b.is-visible { + position: relative +} + +#app.login-app .headline.zoom .words-wrapper { + -webkit-perspective: 300px; + -moz-perspective: 300px; + perspective: 300px +} + +#app.login-app .headline { + display: flex; + justify-content: center; + align-items: center +} + +#app.login-app .headline.zoom b { + opacity: 0 +} + +#app.login-app .headline.zoom b.is-visible { + opacity: 1; + -webkit-animation: zoom-in .8s; + -moz-animation: zoom-in .8s; + animation: cubic-bezier(.215, .61, .355, 1) zoom-in .8s +} + +#app.login-app .headline.zoom b.is-hidden { + -webkit-animation: zoom-out .8s; + -moz-animation: zoom-out .8s; + animation: cubic-bezier(.215, .61, .355, 1) zoom-out .4s +} + +@-webkit-keyframes zoom-in { + 0% { + opacity: 0; + -webkit-transform: translateZ(100px) + } + + 100% { + opacity: 1; + -webkit-transform: translateZ(0) + } +} + +@-moz-keyframes zoom-in { + 0% { + opacity: 0; + -moz-transform: translateZ(100px) + } + + 100% { + opacity: 1; + -moz-transform: translateZ(0) + } +} + +@keyframes zoom-in { + 0% { + opacity: 0; + -webkit-transform: translateZ(100px); + -moz-transform: translateZ(100px); + -ms-transform: translateZ(100px); + -o-transform: translateZ(100px); + transform: translateZ(100px) + } + + 100% { + opacity: 1; + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0) + } +} + +.inbounds-page .ant-table:not(.ant-table-expanded-row .ant-table) { + outline: 1px solid #f0f0f0; + outline-offset: -1px; + border-radius: 1rem; + overflow-x: hidden +} + +.inbounds-page.dark .ant-table:not(.ant-table-expanded-row .ant-table) { + outline-color: var(--dark-color-table-ring) +} + +.inbounds-page .ant-table .ant-table-content .ant-table-scroll .ant-table-body { + overflow-y: hidden +} + +.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper { + margin: -10px 22px !important +} + +.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table { + border-bottom-left-radius: 1rem; + border-bottom-right-radius: 1rem +} + +.inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child tr:last-child td { + border-bottom-color: transparent +} + +.inbounds-page .ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:first-child { + border-bottom-left-radius: 6px +} + +.inbounds-page .ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:last-child { + border-bottom-right-radius: 6px +} + +@media (max-width:768px) { + .inbounds-page .ant-card-body { + padding: .5rem + } + + .inbounds-page .ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper { + margin: -10px 2px !important + } +} + +.inbounds-page.dark .ant-switch-small:not(.ant-switch-checked) { + background-color: var(--dark-color-surface-100) !important +} + +.inbounds-page .ant-custom-popover-title { + display: flex; + align-items: center; + gap: 10px; + margin: 5px 0 +} + +.inbounds-page .ant-col-sm-24 { + margin: .5rem -2rem .5rem 2rem +} + +.inbounds-page tr.hideExpandIcon .ant-table-row-expand-icon { + display: none +} + +.inbounds-page .infinite-tag { + padding: 0 5px; + border-radius: 2rem; + min-width: 50px; + min-height: 22px +} + +.inbounds-page .infinite-bar .ant-progress-inner .ant-progress-bg { + background-color: #F2EAF1; + border: #D5BED2 solid 1px +} + +.inbounds-page.dark .infinite-bar .ant-progress-inner .ant-progress-bg { + background-color: #7a316f !important; + border: #7a316f solid 1px +} + +.inbounds-page .ant-collapse { + margin: 5px 0 +} + +.inbounds-page .info-large-tag { + max-width: 200px; + overflow: hidden +} + +.inbounds-page .client-comment { + font-size: 12px; + opacity: .75; + cursor: help +} + +.inbounds-page .client-email { + font-weight: 500 +} + +.inbounds-page .client-popup-item { + display: flex; + align-items: center; + gap: 5px +} + +.inbounds-page .online-animation .ant-badge-status-dot { + animation: onlineAnimation 1.2s linear infinite +} + +@keyframes onlineAnimation { + + 0%, + 50%, + 100% { + transform: scale(1); + opacity: 1 + } + + 10% { + transform: scale(1.5); + opacity: .2 + } +} + +.inbounds-page .tr-table-box { + display: flex; + gap: 4px; + justify-content: center; + align-items: center +} + +.inbounds-page .tr-table-rt { + flex-basis: 70px; + min-width: 70px; + text-align: end +} + +.inbounds-page .tr-table-lt { + flex-basis: 70px; + min-width: 70px; + text-align: start +} + +.inbounds-page .tr-table-bar { + flex-basis: 160px; + min-width: 60px +} + +.inbounds-page .tr-infinity-ch { + font-size: 14pt; + max-height: 24px; + display: inline-flex; + align-items: center +} + +.inbounds-page .ant-table-expanded-row .ant-table .ant-table-body { + overflow-x: hidden +} + +.inbounds-page .ant-table-expanded-row .ant-table-tbody>tr>td { + padding: 10px 2px +} + +.inbounds-page .ant-table-expanded-row .ant-table-thead>tr>th { + padding: 12px 2px +} + +@-webkit-keyframes zoom-out { + 0% { + opacity: 1; + -webkit-transform: translateZ(0) + } + + 100% { + opacity: 0; + -webkit-transform: translateZ(-100px) + } +} + +@-moz-keyframes zoom-out { + 0% { + opacity: 1; + -moz-transform: translateZ(0) + } + + 100% { + opacity: 0; + -moz-transform: translateZ(-100px) + } +} + +@keyframes zoom-out { + 0% { + opacity: 1; + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0) + } + + 100% { + opacity: 0; + -webkit-transform: translateZ(-100px); + -moz-transform: translateZ(-100px); + -ms-transform: translateZ(-100px); + -o-transform: translateZ(-100px); + transform: translateZ(-100px) + } +} + +.setting-section { + position: absolute; + top: 0; + right: 0; + padding: 22px +} + +.ant-space-item .ant-switch { + margin: 2px 0 4px +} + +@media (min-width:769px) { + .settings-page .ant-layout-content { + margin: 24px 16px + } +} + +@media (max-width:768px) { + .settings-page .ant-tabs-nav .ant-tabs-tab { + margin: 0; + padding: 12px .5rem + } +} + +.settings-page .ant-tabs-bar { + margin: 0 +} + +.settings-page .ant-list-item { + display: block +} + +.settings-page .alert-msg { + color: rgb(194, 117, 18); + font-weight: normal; + font-size: 16px; + padding: .5rem 1rem; + text-align: center; + background: rgb(255 145 0 / 15%); + margin: 1.5rem 2.5rem 0; + border-radius: .5rem; + transition: all .5s; + animation: settings-signal 3s cubic-bezier(.18, .89, .32, 1.28) infinite +} + +.settings-page .alert-msg:hover { + cursor: default; + transition-duration: .3s; + animation: settings-signal .9s ease infinite +} + +@keyframes settings-signal { + 0% { + box-shadow: 0 0 0 0 rgba(194, 118, 18, .5) + } + + 50% { + box-shadow: 0 0 0 6px rgba(0, 0, 0, 0) + } + + 100% { + box-shadow: 0 0 0 6px rgba(0, 0, 0, 0) + } +} + +.settings-page .alert-msg>i { + color: inherit; + font-size: 24px +} + +.settings-page.dark .ant-input-password-icon { + color: var(--dark-color-text-primary) +} + +.settings-page .ant-collapse-content-box .ant-alert { + margin-block-end: 12px +} + +@media (min-width:769px) { + .xray-page .ant-layout-content { + margin: 24px 16px + } +} + +@media (max-width:768px) { + .xray-page .ant-tabs-nav .ant-tabs-tab { + margin: 0; + padding: 12px .5rem + } + + .xray-page .ant-table-thead>tr>th, + .xray-page .ant-table-tbody>tr>td { + padding: 10px 0 + } +} + +.xray-page .ant-tabs-bar { + margin: 0 +} + +.xray-page .ant-list-item { + display: block +} + +.xray-page .ant-list-item>li { + padding: 10px 20px !important +} + +.xray-page .ant-collapse-content-box .ant-alert { + margin-block-end: 12px +} + +#app.login-app.dark #login input.ant-input:-webkit-autofill, +#app.login-app.dark #login input.ant-input:-webkit-autofill:hover, +#app.login-app.dark #login input.ant-input:-webkit-autofill:focus, +#app.login-app.dark #login .ant-input-password input:-webkit-autofill, +#app.login-app.dark #login .ant-input-password input:-webkit-autofill:hover, +#app.login-app.dark #login .ant-input-password input:-webkit-autofill:focus { + -webkit-text-fill-color: var(--login-input-color, #d6dce6) !important; + caret-color: var(--login-input-color, #d6dce6) !important; + -webkit-box-shadow: 0 0 0 1000px var(--login-input-bg, #1d2433) inset !important; + box-shadow: 0 0 0 1000px var(--login-input-bg, #1d2433) inset !important; + transition: background-color 9999s ease-in-out 0s !important +} + +#app.login-app.dark #login input.ant-input:-moz-autofill, +#app.login-app.dark #login .ant-input-password input:-moz-autofill { + -moz-text-fill-color: var(--login-input-color, #d6dce6) !important; + caret-color: var(--login-input-color, #d6dce6) !important; + box-shadow: 0 0 0 1000px var(--login-input-bg, #1d2433) inset !important +} + +#app.login-app.dark #login { + --login-input-bg: var(--dark-color-surface-200); + --login-input-color: var(--dark-color-text-primary) +} + +/* Subscription page - dark theme fixes */ +.dark .ant-descriptions-bordered .ant-descriptions-view { + background-color: transparent; +} +.dark .ant-descriptions-bordered .ant-descriptions-item-label, +.dark .ant-descriptions-bordered .ant-descriptions-item-content { + background-color: transparent; + border-color: var(--dark-color-surface-400); +} +.dark .ant-descriptions-bordered .ant-descriptions-item-label { + color: #a9c2d6; /* softer label color */ +} +.dark .ant-descriptions-bordered .ant-descriptions-item-content { + color: var(--dark-color-text-primary); +} +.dark .ant-input[readonly], +.dark .ant-input-textarea .ant-input[readonly] { + background-color: var(--dark-color-surface-200); + color: var(--dark-color-text-primary); +} \ No newline at end of file diff --git a/web/assets/js/subscription.js b/web/assets/js/subscription.js new file mode 100644 index 00000000..625d55b3 --- /dev/null +++ b/web/assets/js/subscription.js @@ -0,0 +1,125 @@ +(function () { + // Vue app for Subscription page + const el = document.getElementById('subscription-data'); + if (!el) return; + const textarea = document.getElementById('subscription-links'); + const rawLinks = (textarea?.value || '').split('\n').filter(Boolean); + + const data = { + sId: el.getAttribute('data-sid') || '', + subUrl: el.getAttribute('data-sub-url') || '', + subJsonUrl: el.getAttribute('data-subjson-url') || '', + download: el.getAttribute('data-download') || '', + upload: el.getAttribute('data-upload') || '', + used: el.getAttribute('data-used') || '', + total: el.getAttribute('data-total') || '', + remained: el.getAttribute('data-remained') || '', + expireMs: (parseInt(el.getAttribute('data-expire') || '0', 10) || 0) * 1000, + lastOnlineMs: (parseInt(el.getAttribute('data-lastonline') || '0', 10) || 0), + downloadByte: parseInt(el.getAttribute('data-downloadbyte') || '0', 10) || 0, + uploadByte: parseInt(el.getAttribute('data-uploadbyte') || '0', 10) || 0, + totalByte: parseInt(el.getAttribute('data-totalbyte') || '0', 10) || 0, + datepicker: el.getAttribute('data-datepicker') || 'gregorian', + }; + + // Normalize lastOnline to milliseconds if it looks like seconds + if (data.lastOnlineMs && data.lastOnlineMs < 10_000_000_000) { + data.lastOnlineMs *= 1000; + } + + function renderLink(item) { + return ( + Vue.h('a-list-item', {}, [ + Vue.h('a-space', { props: { size: 'small' } }, [ + Vue.h('a-button', { props: { size: 'small' }, on: { click: () => copy(item) } }, [Vue.h('a-icon', { props: { type: 'copy' } })]), + Vue.h('span', { class: 'break-all' }, item) + ]) + ]) + ); + } + + function copy(text) { + ClipboardManager.copyText(text).then(ok => { + const messageType = ok ? 'success' : 'error'; + Vue.prototype.$message[messageType](ok ? 'Copied' : 'Copy failed'); + }); + } + + function open(url) { + window.location.href = url; + } + + function drawQR(value) { + try { new QRious({ element: document.getElementById('qrcode'), value, size: 220 }); } catch (e) { console.warn(e); } + } + + // Try to extract a human label (email/ps) from different link types + function linkName(link, idx) { + try { + if (link.startsWith('vmess://')) { + const json = JSON.parse(atob(link.replace('vmess://', ''))); + if (json.ps) return json.ps; + if (json.add && json.id) return json.add; // fallback host + } else if (link.startsWith('vless://') || link.startsWith('trojan://')) { + // vless://@host:port?...#name + const hashIdx = link.indexOf('#'); + if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1)); + // email sometimes in query params like sni or remark + const qIdx = link.indexOf('?'); + if (qIdx !== -1) { + const qs = new URL('http://x/?' + link.substring(qIdx + 1, hashIdx !== -1 ? hashIdx : undefined)).searchParams; + if (qs.get('remark')) return qs.get('remark'); + if (qs.get('email')) return qs.get('email'); + } + // else take user@host + const at = link.indexOf('@'); + const protSep = link.indexOf('://'); + if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at); + } else if (link.startsWith('ss://')) { + // shadowsocks: label often after # + const hashIdx = link.indexOf('#'); + if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1)); + } + } catch (e) { /* ignore and fallback */ } + return 'Link ' + (idx + 1); + } + + const app = new Vue({ + delimiters: ['[[', ']]'], + el: '#app', + data: { + themeSwitcher, + app: data, + links: rawLinks, + lang: '', + viewportWidth: (typeof window !== 'undefined' ? window.innerWidth : 1024), + }, + async mounted() { + this.lang = LanguageManager.getLanguage(); + // Discover subJsonUrl if provided via template bootstrap + const tpl = document.getElementById('subscription-data'); + const sj = tpl ? tpl.getAttribute('data-subjson-url') : ''; + if (sj) this.app.subJsonUrl = sj; + drawQR(this.app.subUrl); + // Draw second QR if available + try { new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 }); } catch (e) { /* ignore */ } + // Track viewport width for responsive behavior + this._onResize = () => { this.viewportWidth = window.innerWidth; }; + window.addEventListener('resize', this._onResize); + }, + beforeDestroy() { + if (this._onResize) window.removeEventListener('resize', this._onResize); + }, + computed: { + isMobile() { return this.viewportWidth < 576; }, + isUnlimited() { return !this.app.totalByte; }, + isActive() { + const now = Date.now(); + const expiryOk = !this.app.expireMs || this.app.expireMs >= now; + const trafficOk = !this.app.totalByte || (this.app.uploadByte + this.app.downloadByte) <= this.app.totalByte; + return expiryOk && trafficOk; + }, + }, + methods: { renderLink, copy, open, linkName, i18nLabel(key) { return '{{ i18n "' + key + '" }}'; } }, + }); +})(); diff --git a/web/html/subscription.html b/web/html/subscription.html new file mode 100644 index 00000000..adcc97b1 --- /dev/null +++ b/web/html/subscription.html @@ -0,0 +1,272 @@ +{{ template "page/head_start" .}} +{{ template "page/head_end" .}} + +{{ template "page/body_start" .}} + + + + + + + + + + + + + + + + {{ i18n + "pages.settings.subSettings"}} + + + + + + + + + + + + {{ i18n + "pages.settings.subSettings"}} + Json + + + + + + + + + + + + + + + [[ + app.sId + ]] + + + + + [[ + app.download + ]] + [[ + app.upload + ]] + [[ app.used + ]] + [[ + app.total + ]] + [[ + app.remained ]] + + + + + + + + + + + + +
+ + +
+ [[ linkName(link, idx) + ]] +
+
+
+
+ + + + + + + + + Android + + + V2Box + V2RayNG + Sing-box + V2RayTun + NPV + Tunnel + + + + + + + + iOS + + + Shadowrocket + V2Box + Streisand + V2RayTun + NPV + Tunnel + + + + + + +
+
+
+
+
+ + + + + +{{template "page/body_scripts" .}} + + +{{template "component/aThemeSwitch" .}} + +{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/locale/locale.go b/web/locale/locale.go index cdfe5394..8d4179ae 100644 --- a/web/locale/locale.go +++ b/web/locale/locale.go @@ -48,6 +48,22 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error { return nil } +// InitLocalizerFS allows initializing i18n from any fs.FS (e.g., disk), rooted at a directory containing a "translation" folder +func InitLocalizerFS(fsys fs.FS, settingService SettingService) error { + // set default bundle to english + i18nBundle = i18n.NewBundle(language.MustParse("en-US")) + i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + + if err := parseTranslationFiles(fsys, i18nBundle); err != nil { + return err + } + + if err := initTGBotLocalizer(settingService); err != nil { + return err + } + return nil +} + func createTemplateData(params []string, seperator ...string) map[string]any { var sep string = "==" if len(seperator) > 0 { @@ -118,8 +134,8 @@ func LocalizerMiddleware() gin.HandlerFunc { } } -func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error { - err := fs.WalkDir(i18nFS, "translation", +func parseTranslationFiles(fsys fs.FS, i18nBundle *i18n.Bundle) error { + err := fs.WalkDir(fsys, "translation", func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -129,7 +145,7 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error { return nil } - data, err := i18nFS.ReadFile(path) + data, err := fs.ReadFile(fsys, path) if err != nil { return err } diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml index 9f11a30c..036fbeb9 100644 --- a/web/translation/translate.ar_EG.toml +++ b/web/translation/translate.ar_EG.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "مفيش بروكسي عكسي مضاف." "somethingWentWrong" = "حدث خطأ ما" +[subscription] +"title" = "معلومات الاشتراك" +"subId" = "معرّف الاشتراك" +"status" = "الحالة" +"downloaded" = "التنزيل" +"uploaded" = "الرفع" +"expiry" = "تاريخ الانتهاء" +"totalQuota" = "الحصة الإجمالية" +"individualLinks" = "روابط فردية" +"active" = "نشط" +"inactive" = "غير نشط" +"unlimited" = "غير محدود" +"noExpiry" = "بدون انتهاء" + [menu] "theme" = "الثيم" "dark" = "داكن" diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index f802dd6d..19ac810c 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "No added reverse proxies." "somethingWentWrong" = "Something went wrong" +[subscription] +"title" = "Subscription info" +"subId" = "Subscription ID" +"status" = "Status" +"downloaded" = "Downloaded" +"uploaded" = "Uploaded" +"expiry" = "Expiry" +"totalQuota" = "Total quota" +"individualLinks" = "Individual links" +"active" = "Active" +"inactive" = "Inactive" +"unlimited" = "Unlimited" +"noExpiry" = "No expiry" + [menu] "theme" = "Theme" "dark" = "Dark" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 80ddd98a..89226b32 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "No hay proxies inversos añadidos." "somethingWentWrong" = "Algo salió mal" +[subscription] +"title" = "Información de suscripción" +"subId" = "ID de suscripción" +"status" = "Estado" +"downloaded" = "Descargado" +"uploaded" = "Subido" +"expiry" = "Caducidad" +"totalQuota" = "Cuota total" +"individualLinks" = "Enlaces individuales" +"active" = "Activo" +"inactive" = "Inactivo" +"unlimited" = "Ilimitado" +"noExpiry" = "Sin caducidad" + [menu] "theme" = "Tema" "dark" = "Oscuro" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 778dd528..2bbcb39e 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "هیچ پروکسی معکوس اضافه نشده است." "somethingWentWrong" = "مشکلی پیش آمد" +[subscription] +"title" = "اطلاعات سابسکریپشن" +"subId" = "شناسه اشتراک" +"status" = "وضعیت" +"downloaded" = "دانلود" +"uploaded" = "آپلود" +"expiry" = "تاریخ پایان" +"totalQuota" = "حجم کلی" +"individualLinks" = "لینک‌های تکی" +"active" = "فعال" +"inactive" = "غیرفعال" +"unlimited" = "نامحدود" +"noExpiry" = "بدون انقضا" + [menu] "theme" = "تم" "dark" = "تیره" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index 2a5b568b..0977c6e6 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "Tidak ada proxy terbalik yang ditambahkan." "somethingWentWrong" = "Terjadi kesalahan" +[subscription] +"title" = "Info langganan" +"subId" = "ID langganan" +"status" = "Status" +"downloaded" = "Diunduh" +"uploaded" = "Diunggah" +"expiry" = "Kedaluwarsa" +"totalQuota" = "Kuota total" +"individualLinks" = "Tautan individual" +"active" = "Aktif" +"inactive" = "Nonaktif" +"unlimited" = "Tanpa batas" +"noExpiry" = "Tanpa kedaluwarsa" + [menu] "theme" = "Tema" "dark" = "Gelap" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml index 7af0a339..e807ee03 100644 --- a/web/translation/translate.ja_JP.toml +++ b/web/translation/translate.ja_JP.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "追加されたリバースプロキシはありません。" "somethingWentWrong" = "エラーが発生しました" +[subscription] +"title" = "サブスクリプション情報" +"subId" = "サブスクリプションID" +"status" = "ステータス" +"downloaded" = "ダウンロード" +"uploaded" = "アップロード" +"expiry" = "有効期限" +"totalQuota" = "合計クォータ" +"individualLinks" = "個別リンク" +"active" = "有効" +"inactive" = "無効" +"unlimited" = "無制限" +"noExpiry" = "期限なし" + [menu] "theme" = "テーマ" "dark" = "ダーク" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml index f61f8995..5640f4ff 100644 --- a/web/translation/translate.pt_BR.toml +++ b/web/translation/translate.pt_BR.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "Nenhum proxy reverso adicionado." "somethingWentWrong" = "Algo deu errado" +[subscription] +"title" = "Informações da assinatura" +"subId" = "ID da assinatura" +"status" = "Status" +"downloaded" = "Baixado" +"uploaded" = "Enviado" +"expiry" = "Validade" +"totalQuota" = "Cota total" +"individualLinks" = "Links individuais" +"active" = "Ativo" +"inactive" = "Inativo" +"unlimited" = "Ilimitado" +"noExpiry" = "Sem validade" + [menu] "theme" = "Tema" "dark" = "Escuro" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 060b7334..c3f0579e 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "Нет добавленных реверс-прокси." "somethingWentWrong" = "Что-то пошло не так" +[subscription] +"title" = "Информация о подписке" +"subId" = "ID подписки" +"status" = "Статус" +"downloaded" = "Загружено" +"uploaded" = "Отправлено" +"expiry" = "Срок действия" +"totalQuota" = "Общий лимит" +"individualLinks" = "Индивидуальные ссылки" +"active" = "Активна" +"inactive" = "Неактивна" +"unlimited" = "Безлимит" +"noExpiry" = "Без срока" + [menu] "theme" = "Тема" "dark" = "Темная" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml index ac10bf65..452e4b7d 100644 --- a/web/translation/translate.tr_TR.toml +++ b/web/translation/translate.tr_TR.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "Eklenmiş ters proxy yok." "somethingWentWrong" = "Bir şeyler yanlış gitti" +[subscription] +"title" = "Abonelik Bilgisi" +"subId" = "Abonelik Kimliği" +"status" = "Durum" +"downloaded" = "İndirilen" +"uploaded" = "Yüklenen" +"expiry" = "Son Kullanma" +"totalQuota" = "Toplam Kota" +"individualLinks" = "Bireysel Bağlantılar" +"active" = "Aktif" +"inactive" = "Pasif" +"unlimited" = "Sınırsız" +"noExpiry" = "Süresiz" + [menu] "theme" = "Tema" "dark" = "Koyu" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index 4005f14f..826ced0d 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "Немає доданих зворотних проксі." "somethingWentWrong" = "Щось пішло не так" +[subscription] +"title" = "Інформація про підписку" +"subId" = "ID підписки" +"status" = "Статус" +"downloaded" = "Завантажено" +"uploaded" = "Відвантажено" +"expiry" = "Термін дії" +"totalQuota" = "Загальна квота" +"individualLinks" = "Окремі посилання" +"active" = "Активна" +"inactive" = "Неактивна" +"unlimited" = "Безліміт" +"noExpiry" = "Без строку" + [menu] "theme" = "Тема" "dark" = "Темна" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 66ed38ce..7e3869a0 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "Không có proxy ngược nào được thêm." "somethingWentWrong" = "Đã xảy ra lỗi" +[subscription] +"title" = "Thông tin đăng ký" +"subId" = "ID đăng ký" +"status" = "Trạng thái" +"downloaded" = "Đã tải xuống" +"uploaded" = "Đã tải lên" +"expiry" = "Hết hạn" +"totalQuota" = "Tổng hạn mức" +"individualLinks" = "Liên kết riêng lẻ" +"active" = "Hoạt động" +"inactive" = "Không hoạt động" +"unlimited" = "Không giới hạn" +"noExpiry" = "Không hết hạn" + [menu] "theme" = "Chủ đề" "dark" = "Tối" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index 5875add9..18a8c97c 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "未添加反向代理。" "somethingWentWrong" = "出了点问题" +[subscription] +"title" = "订阅信息" +"subId" = "订阅 ID" +"status" = "状态" +"downloaded" = "已下载" +"uploaded" = "已上传" +"expiry" = "到期" +"totalQuota" = "总配额" +"individualLinks" = "单独链接" +"active" = "启用" +"inactive" = "停用" +"unlimited" = "无限制" +"noExpiry" = "无到期" + [menu] "theme" = "主题" "dark" = "暗色" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml index 6843e9ee..758781e7 100644 --- a/web/translation/translate.zh_TW.toml +++ b/web/translation/translate.zh_TW.toml @@ -72,6 +72,20 @@ "emptyReverseDesc" = "未添加反向代理。" "somethingWentWrong" = "發生錯誤" +[subscription] +"title" = "訂閱資訊" +"subId" = "訂閱 ID" +"status" = "狀態" +"downloaded" = "已下載" +"uploaded" = "已上傳" +"expiry" = "到期" +"totalQuota" = "總配額" +"individualLinks" = "個別連結" +"active" = "啟用" +"inactive" = "停用" +"unlimited" = "無限制" +"noExpiry" = "無到期" + [menu] "theme" = "主題" "dark" = "深色" From 3ac1d7f5460b4e89d8057c27d7772bd34519269d Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Sun, 14 Sep 2025 19:44:26 +0200 Subject: [PATCH 2/4] enhancements --- sub/subController.go | 19 ++++++++++++------- sub/subService.go | 12 ------------ web/html/subscription.html | 12 +++++++----- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/sub/subController.go b/sub/subController.go index ac0c09a1..c37ff5a9 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -3,6 +3,7 @@ package sub import ( "encoding/base64" "strings" + "x-ui/config" "github.com/gin-gonic/gin" ) @@ -67,10 +68,6 @@ func (a *SUBController) subs(c *gin.Context) { result += sub + "\n" } - // Add headers via service - a.subService.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) - a.subService.ApplyBase64ContentHeader(c, result) - // If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here accept := c.GetHeader("Accept") if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") { @@ -79,6 +76,7 @@ func (a *SUBController) subs(c *gin.Context) { page := a.subService.BuildPageData(subId, hostHeader, header, lastOnline, subs, subURL, subJsonURL) c.HTML(200, "subscription.html", gin.H{ "title": "subscription.title", + "cur_ver": config.GetVersion(), "host": page.Host, "base_path": page.BasePath, "sId": page.SId, @@ -100,6 +98,9 @@ func (a *SUBController) subs(c *gin.Context) { return } + // Add headers + a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) + if a.subEncrypt { c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) } else { @@ -116,11 +117,15 @@ func (a *SUBController) subJsons(c *gin.Context) { c.String(400, "Error!") } else { - // Add headers via service - a.subService.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) + // Add headers + a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) c.String(200, jsonSub) } } -// Note: host parsing and page data preparation moved to SubService +func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) { + c.Writer.Header().Set("Subscription-Userinfo", header) + c.Writer.Header().Set("Profile-Update-Interval", updateInterval) + c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle))) +} diff --git a/sub/subService.go b/sub/subService.go index 485048fd..a47cb6be 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -1178,15 +1178,3 @@ func parseInt64(s string) (int64, error) { n, err := strconv.ParseInt(s, 10, 64) return n, err } - -// ApplyCommonHeaders sets standard subscription headers on the response writer. -func (s *SubService) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) { - c.Writer.Header().Set("Subscription-Userinfo", header) - c.Writer.Header().Set("Profile-Update-Interval", updateInterval) - c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle))) -} - -// ApplyBase64ContentHeader adds the full subscription content as base64 header for convenience. -func (s *SubService) ApplyBase64ContentHeader(c *gin.Context, content string) { - c.Writer.Header().Set("Subscription-Content-Base64", base64.StdEncoding.EncodeToString([]byte(content))) -} diff --git a/web/html/subscription.html b/web/html/subscription.html index adcc97b1..710bfe43 100644 --- a/web/html/subscription.html +++ b/web/html/subscription.html @@ -1,4 +1,10 @@ {{ template "page/head_start" .}} + + + + + + {{ template "page/head_end" .}} {{ template "page/body_start" .}} @@ -262,11 +268,7 @@ style="display:none">{{ range .result }}{{ . }} {{ end }} -{{template "page/body_scripts" .}} - - {{template "component/aThemeSwitch" .}} + {{ template "page/body_end" .}} \ No newline at end of file From ed96fa090bc936fcf134e9337a185150a8d4dcf3 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Sun, 14 Sep 2025 19:51:57 +0200 Subject: [PATCH 3/4] tgbot: subscription,qrcode, link --- go.mod | 1 + go.sum | 2 + web/service/tgbot.go | 343 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+) diff --git a/go.mod b/go.mod index 26f45c61..78b6f09c 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v4 v4.25.8 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/valyala/fasthttp v1.65.0 github.com/xlzd/gotp v0.1.0 github.com/xtls/xray-core v1.250911.0 diff --git a/go.sum b/go.sum index e45b4986..7cf8d118 100644 --- a/go.sum +++ b/go.sum @@ -142,6 +142,8 @@ github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1 github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/web/service/tgbot.go b/web/service/tgbot.go index e0acebb7..aab0d3e8 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -7,8 +7,10 @@ import ( "encoding/base64" "errors" "fmt" + "io" "math/big" "net" + "net/http" "net/url" "os" "regexp" @@ -29,6 +31,7 @@ import ( "github.com/mymmrac/telego" th "github.com/mymmrac/telego/telegohandler" tu "github.com/mymmrac/telego/telegoutil" + "github.com/skip2/go-qrcode" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpproxy" ) @@ -1355,6 +1358,73 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool case "client_commands": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands")) t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands")) + case "client_sub_links": + // show user's own clients to choose one for sub links + tgUserID := callbackQuery.From.ID + traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID) + if err != nil { + // fallback to message + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + if len(traffics) == 0 { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10))) + return + } + var buttons []telego.InlineKeyboardButton + for _, tr := range traffics { + buttons = append(buttons, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_sub_links "+tr.Email))) + } + cols := 1 + if len(buttons) >= 6 { + cols = 2 + } + keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...)) + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard) + case "client_individual_links": + // show user's clients to choose for individual links + tgUserID := callbackQuery.From.ID + traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + if len(traffics) == 0 { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10))) + return + } + var buttons2 []telego.InlineKeyboardButton + for _, tr := range traffics { + buttons2 = append(buttons2, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_individual_links "+tr.Email))) + } + cols2 := 1 + if len(buttons2) >= 6 { + cols2 = 2 + } + keyboard2 := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols2, buttons2...)) + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard2) + case "client_qr_links": + // show user's clients to choose for QR codes + tgUserID := callbackQuery.From.ID + traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOccurred")+"\r\n"+err.Error()) + return + } + if len(traffics) == 0 { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10))) + return + } + var buttons3 []telego.InlineKeyboardButton + for _, tr := range traffics { + buttons3 = append(buttons3, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_qr_links "+tr.Email))) + } + cols3 := 1 + if len(buttons3) >= 6 { + cols3 = 2 + } + keyboard3 := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols3, buttons3...)) + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard3) case "onlines": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.onlines")) t.onlineClients(chatId) @@ -1439,6 +1509,23 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool ) prompt_message := t.I18nBot("tgbot.messages.comment_prompt", "ClientComment=="+client_Comment) t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup) + default: + // dynamic callbacks + if strings.HasPrefix(callbackQuery.Data, "client_sub_links ") { + email := strings.TrimPrefix(callbackQuery.Data, "client_sub_links ") + t.sendClientSubLinks(chatId, email) + return + } + if strings.HasPrefix(callbackQuery.Data, "client_individual_links ") { + email := strings.TrimPrefix(callbackQuery.Data, "client_individual_links ") + t.sendClientIndividualLinks(chatId, email) + return + } + if strings.HasPrefix(callbackQuery.Data, "client_qr_links ") { + email := strings.TrimPrefix(callbackQuery.Data, "client_qr_links ") + t.sendClientQRLinks(chatId, email) + return + } case "add_client_ch_default_traffic": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( @@ -1847,6 +1934,13 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clientUsage")).WithCallbackData(t.encodeQuery("client_traffic")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("client_commands")), ), + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("pages.settings.subSettings")).WithCallbackData(t.encodeQuery("client_sub_links")), + tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links")), + ), + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("client_qr_links")), + ), ) var ReplyMarkup telego.ReplyMarkup @@ -1908,6 +2002,255 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R } } +// buildSubscriptionURLs builds the HTML sub page URL and JSON subscription URL for a client email +func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) { + // Resolve subId from client email + traffic, client, err := t.inboundService.GetClientByEmail(email) + _ = traffic + if err != nil || client == nil { + return "", "", errors.New("client not found") + } + + // Gather settings to construct absolute URLs + subDomain, _ := t.settingService.GetSubDomain() + subPort, _ := t.settingService.GetSubPort() + subPath, _ := t.settingService.GetSubPath() + subJsonPath, _ := t.settingService.GetSubJsonPath() + subKeyFile, _ := t.settingService.GetSubKeyFile() + subCertFile, _ := t.settingService.GetSubCertFile() + + tls := (subKeyFile != "" && subCertFile != "") + scheme := "http" + if tls { + scheme = "https" + } + + // Fallbacks + if subDomain == "" { + // try panel domain, otherwise OS hostname + if d, err := t.settingService.GetWebDomain(); err == nil && d != "" { + subDomain = d + } else if hostname != "" { + subDomain = hostname + } else { + subDomain = "localhost" + } + } + + host := subDomain + if (subPort == 443 && tls) || (subPort == 80 && !tls) { + // standard ports: no port in host + } else { + host = fmt.Sprintf("%s:%d", subDomain, subPort) + } + + // Ensure paths + if !strings.HasPrefix(subPath, "/") { + subPath = "/" + subPath + } + if !strings.HasSuffix(subPath, "/") { + subPath = subPath + "/" + } + if !strings.HasPrefix(subJsonPath, "/") { + subJsonPath = "/" + subJsonPath + } + if !strings.HasSuffix(subJsonPath, "/") { + subJsonPath = subJsonPath + "/" + } + + subURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subPath, client.SubID) + subJsonURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID) + return subURL, subJsonURL, nil +} + +func (t *Tgbot) sendClientSubLinks(chatId int64, email string) { + subURL, subJsonURL, err := t.buildSubscriptionURLs(email) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + msg := "Subscription URL:\r\n" + subURL + "\r\n\r\n" + + "JSON URL:\r\n" + subJsonURL + "" + inlineKeyboard := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links " + email)), + ), + ) + t.SendMsgToTgbot(chatId, msg, inlineKeyboard) +} + +// sendClientIndividualLinks fetches the subscription content (individual links) and sends it to the user +func (t *Tgbot) sendClientIndividualLinks(chatId int64, email string) { + // Build the HTML sub page URL; we'll call it with header Accept to get raw content + subURL, _, err := t.buildSubscriptionURLs(email) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + + // Try to fetch raw subscription links. Prefer plain text response. + req, err := http.NewRequest("GET", subURL, nil) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + // Force plain text to avoid HTML page; controller respects Accept header + req.Header.Set("Accept", "text/plain, */*;q=0.1") + + // Use default client with reasonable timeout via context + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + req = req.WithContext(ctx) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + + // If service is configured to encode (Base64), decode it + encoded, _ := t.settingService.GetSubEncrypt() + var content string + if encoded { + decoded, err := base64.StdEncoding.DecodeString(string(bodyBytes)) + if err != nil { + // fallback to raw text + content = string(bodyBytes) + } else { + content = string(decoded) + } + } else { + content = string(bodyBytes) + } + + // Normalize line endings and trim + lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n") + var cleaned []string + for _, l := range lines { + l = strings.TrimSpace(l) + if l != "" { + cleaned = append(cleaned, l) + } + } + if len(cleaned) == 0 { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult")) + return + } + + // Send in chunks to respect message length; use monospace formatting + const maxPerMessage = 50 + for i := 0; i < len(cleaned); i += maxPerMessage { + j := i + maxPerMessage + if j > len(cleaned) { + j = len(cleaned) + } + chunk := cleaned[i:j] + msg := t.I18nBot("subscription.individualLinks") + ":\r\n" + for _, link := range chunk { + // wrap each link in + msg += "" + link + "\r\n" + } + t.SendMsgToTgbot(chatId, msg) + } +} + +// sendClientQRLinks generates QR images for subscription URL, JSON URL, and a few individual links, then sends them +func (t *Tgbot) sendClientQRLinks(chatId int64, email string) { + subURL, subJsonURL, err := t.buildSubscriptionURLs(email) + if err != nil { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + return + } + + // Helper to create QR PNG bytes from content + createQR := func(content string, size int) ([]byte, error) { + if size <= 0 { + size = 256 + } + return qrcode.Encode(content, qrcode.Medium, size) + } + + // Inform user + t.SendMsgToTgbot(chatId, "QRCode"+":") + + // Send sub URL QR (filename: sub.png) + if png, err := createQR(subURL, 320); err == nil { + document := tu.Document( + tu.ID(chatId), + tu.FileFromBytes(png, "sub.png"), + ) + _, _ = bot.SendDocument(context.Background(), document) + } else { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + } + + // Send JSON URL QR (filename: subjson.png) + if png, err := createQR(subJsonURL, 320); err == nil { + document := tu.Document( + tu.ID(chatId), + tu.FileFromBytes(png, "subjson.png"), + ) + _, _ = bot.SendDocument(context.Background(), document) + } else { + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error()) + } + + // Also generate a few individual links' QRs (first up to 5) + subPageURL := subURL + req, err := http.NewRequest("GET", subPageURL, nil) + if err == nil { + req.Header.Set("Accept", "text/plain, */*;q=0.1") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + req = req.WithContext(ctx) + if resp, err := http.DefaultClient.Do(req); err == nil { + body, _ := io.ReadAll(resp.Body) + _ = resp.Body.Close() + encoded, _ := t.settingService.GetSubEncrypt() + var content string + if encoded { + if dec, err := base64.StdEncoding.DecodeString(string(body)); err == nil { + content = string(dec) + } else { + content = string(body) + } + } else { + content = string(body) + } + lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n") + var cleaned []string + for _, l := range lines { + l = strings.TrimSpace(l) + if l != "" { + cleaned = append(cleaned, l) + } + } + if len(cleaned) > 0 { + max := min(len(cleaned), 5) + for i := range max { + if png, err := createQR(cleaned[i], 320); err == nil { + // Use the email as filename for individual link QR + filename := email + ".png" + document := tu.Document( + tu.ID(chatId), + tu.FileFromBytes(png, filename), + ) + _, _ = bot.SendDocument(context.Background(), document) + time.Sleep(200 * time.Millisecond) + } + } + } + } + } +} + func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) { if len(replyMarkup) > 0 { for _, adminId := range adminIds { From bf9d2e6aeb47e980305e10be2a0d8545113013e6 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Sun, 14 Sep 2025 19:53:05 +0200 Subject: [PATCH 4/4] rule: Vless Route --- web/html/modals/xray_rule_modal.html | 26 ++++++++++++++++++++------ web/html/settings/xray/routing.html | 12 ++++++++---- web/html/xray.html | 5 +++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/web/html/modals/xray_rule_modal.html b/web/html/modals/xray_rule_modal.html index d367600b..487bf197 100644 --- a/web/html/modals/xray_rule_modal.html +++ b/web/html/modals/xray_rule_modal.html @@ -9,7 +9,7 @@ Source IPs - + Source Port - + + + + + @@ -52,7 +62,7 @@ IP - + Domain - + User - + Port - + @@ -122,6 +132,7 @@ ip: "", port: "", sourcePort: "", + vlessRoute: "", network: "", sourceIP: "", user: "", @@ -155,6 +166,7 @@ this.rule.ip = rule.ip ? rule.ip.join(',') : []; this.rule.port = rule.port; this.rule.sourcePort = rule.sourcePort; + this.rule.vlessRoute = rule.vlessRoute; this.rule.network = rule.network; this.rule.sourceIP = rule.sourceIP ? rule.sourceIP.join(',') : []; this.rule.user = rule.user ? rule.user.join(',') : []; @@ -169,6 +181,7 @@ ip: "", port: "", sourcePort: "", + vlessRoute: "", network: "", sourceIP: "", user: "", @@ -210,6 +223,7 @@ rule.ip = value.ip.length > 0 ? value.ip.split(',') : []; rule.port = value.port; rule.sourcePort = value.sourcePort; + rule.vlessRoute = value.vlessRoute; rule.network = value.network; rule.sourceIP = value.sourceIP.length > 0 ? value.sourceIP.split(',') : []; rule.user = value.user.length > 0 ? value.user.split(',') : []; diff --git a/web/html/settings/xray/routing.html b/web/html/settings/xray/routing.html index e5b9b6c6..487f7963 100644 --- a/web/html/settings/xray/routing.html +++ b/web/html/settings/xray/routing.html @@ -67,18 +67,22 @@