diff --git a/CMakeLists.txt b/CMakeLists.txt index aed08c4..8c7d38a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,8 @@ set(PROJECT_SOURCES src/sys/AutoRun.cpp src/sys/Process.cpp include/ui/mainwindow_interface.h + include/stats/connections/connectionLister.hpp + src/stats/connectionLister/connectionLister.cpp ) # Qt exe diff --git a/core/cmd/nekobox_core/grpc_box.go b/core/cmd/nekobox_core/grpc_box.go index 7d70562..171996c 100644 --- a/core/cmd/nekobox_core/grpc_box.go +++ b/core/cmd/nekobox_core/grpc_box.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/sagernet/sing-box/common/settings" + "github.com/sagernet/sing-box/experimental/clashapi" "github.com/sagernet/sing/common/metadata" "nekobox_core/internal/boxbox" grpc_server "nekobox_core/server" @@ -148,7 +149,6 @@ func (s *server) StopTest(ctx context.Context, in *gen.EmptyReq) (*gen.EmptyResp func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) { out = &gen.QueryStatsResp{} - if instance != nil && instance.Router().V2RayServer() != nil { if ss, ok := instance.Router().V2RayServer().(*boxapi.SbV2rayServer); ok { out.Traffic = ss.QueryStats(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", in.Tag, in.Direct)) @@ -159,10 +159,13 @@ func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *ge } func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.ListConnectionsResp, error) { + if instance == nil { + return &gen.ListConnectionsResp{}, nil + } if instance.Router().ClashServer() == nil { return nil, errors.New("no clash server found") } - clash, ok := instance.Router().ClashServer().(*boxapi.SbClashServer) + clash, ok := instance.Router().ClashServer().(*clashapi.Server) if !ok { return nil, errors.New("invalid state, should not be here") } @@ -170,6 +173,11 @@ func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.Li res := make([]*gen.ConnectionMetaData, 0) for _, c := range connections { + process := "" + if c.Metadata.ProcessInfo != nil { + spl := strings.Split(c.Metadata.ProcessInfo.ProcessPath, string(os.PathSeparator)) + process = spl[len(spl)-1] + } r := &gen.ConnectionMetaData{ Id: c.ID.String(), CreatedAt: c.CreatedAt.UnixMilli(), @@ -180,6 +188,7 @@ func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.Li Dest: c.Metadata.Destination.String(), Protocol: c.Metadata.Protocol, Domain: c.Metadata.Domain, + Process: process, } res = append(res, r) } diff --git a/core/cmd/nekobox_core/internal/boxapi/clash_server.go b/core/cmd/nekobox_core/internal/boxapi/clash_server.go deleted file mode 100644 index daf09e5..0000000 --- a/core/cmd/nekobox_core/internal/boxapi/clash_server.go +++ /dev/null @@ -1,359 +0,0 @@ -package boxapi - -import ( - "bytes" - "context" - "net" - "net/http" - "os" - "strings" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental" - "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/json" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/filemanager" - "github.com/sagernet/ws" - "github.com/sagernet/ws/wsutil" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" -) - -func init() { - experimental.RegisterClashServerConstructor(NewServer) -} - -var _ adapter.ClashServer = (*SbClashServer)(nil) - -type SbClashServer struct { - ctx context.Context - router adapter.Router - logger log.Logger - httpServer *http.Server - trafficManager *trafficontrol.Manager - urlTestHistory *urltest.HistoryStorage - mode string - modeList []string - modeUpdateHook chan<- struct{} - - externalController bool - externalUI string - externalUIDownloadURL string - externalUIDownloadDetour string -} - -func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { - trafficManager := trafficontrol.NewManager() - chiRouter := chi.NewRouter() - server := &SbClashServer{ - ctx: ctx, - router: router, - logger: logFactory.NewLogger("clash-api"), - httpServer: &http.Server{ - Addr: options.ExternalController, - Handler: chiRouter, - }, - trafficManager: trafficManager, - modeList: options.ModeList, - externalController: options.ExternalController != "", - externalUIDownloadURL: options.ExternalUIDownloadURL, - externalUIDownloadDetour: options.ExternalUIDownloadDetour, - } - server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) - if server.urlTestHistory == nil { - server.urlTestHistory = urltest.NewHistoryStorage() - } - defaultMode := "Rule" - if options.DefaultMode != "" { - defaultMode = options.DefaultMode - } - if !common.Contains(server.modeList, defaultMode) { - server.modeList = append([]string{defaultMode}, server.modeList...) - } - server.mode = defaultMode - //goland:noinspection GoDeprecation - //nolint:staticcheck - if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" { - return nil, E.New("cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.") - } - allowedOrigins := options.AccessControlAllowOrigin - if len(allowedOrigins) == 0 { - allowedOrigins = []string{"*"} - } - if options.ExternalUI != "" { - server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) - chiRouter.Group(func(r chi.Router) { - r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP) - r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(server.externalUI)))) - }) - } - return server, nil -} - -func (s *SbClashServer) PreStart() error { - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - mode := cacheFile.LoadMode() - if common.Any(s.modeList, func(it string) bool { - return strings.EqualFold(it, mode) - }) { - s.mode = mode - } - } - return nil -} - -func (s *SbClashServer) Start() error { - return nil -} - -func (s *SbClashServer) Close() error { - return common.Close( - common.PtrOrNil(s.httpServer), - s.trafficManager, - s.urlTestHistory, - ) -} - -func (s *SbClashServer) Mode() string { - return s.mode -} - -func (s *SbClashServer) ModeList() []string { - return s.modeList -} - -func (s *SbClashServer) SetModeUpdateHook(hook chan<- struct{}) { - s.modeUpdateHook = hook -} - -func (s *SbClashServer) SetMode(newMode string) { - if !common.Contains(s.modeList, newMode) { - newMode = common.Find(s.modeList, func(it string) bool { - return strings.EqualFold(it, newMode) - }) - } - if !common.Contains(s.modeList, newMode) { - return - } - if newMode == s.mode { - return - } - s.mode = newMode - if s.modeUpdateHook != nil { - select { - case s.modeUpdateHook <- struct{}{}: - default: - } - } - s.router.ClearDNSCache() - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - err := cacheFile.StoreMode(newMode) - if err != nil { - s.logger.Error(E.Cause(err, "save mode")) - } - } - s.logger.Info("updated mode: ", newMode) -} - -func (s *SbClashServer) HistoryStorage() *urltest.HistoryStorage { - return s.urlTestHistory -} - -func (s *SbClashServer) TrafficManager() *trafficontrol.Manager { - return s.trafficManager -} - -func (s *SbClashServer) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) - return tracker, tracker -} - -func (s *SbClashServer) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) - return tracker, tracker -} - -func authentication(serverSecret string) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - if serverSecret == "" { - next.ServeHTTP(w, r) - return - } - - // Browser websocket not support custom header - if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" { - token := r.URL.Query().Get("token") - if token != serverSecret { - render.Status(r, http.StatusUnauthorized) - return - } - next.ServeHTTP(w, r) - return - } - - header := r.Header.Get("Authorization") - bearer, token, found := strings.Cut(header, " ") - - hasInvalidHeader := bearer != "Bearer" - hasInvalidSecret := !found || token != serverSecret - if hasInvalidHeader || hasInvalidSecret { - render.Status(r, http.StatusUnauthorized) - return - } - next.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) - } -} - -func hello(redirect bool) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - contentType := r.Header.Get("Content-Type") - if !redirect || contentType == "application/json" { - render.JSON(w, r, render.M{"hello": "clash"}) - } else { - http.Redirect(w, r, "/ui/", http.StatusTemporaryRedirect) - } - } -} - -type Traffic struct { - Up int64 `json:"up"` - Down int64 `json:"down"` -} - -func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - var conn net.Conn - if r.Header.Get("Upgrade") == "websocket" { - var err error - conn, _, _, err = ws.UpgradeHTTP(r, w) - if err != nil { - return - } - defer conn.Close() - } - - if conn == nil { - w.Header().Set("Content-Type", "application/json") - render.Status(r, http.StatusOK) - } - - tick := time.NewTicker(time.Second) - defer tick.Stop() - buf := &bytes.Buffer{} - uploadTotal, downloadTotal := trafficManager.Total() - for range tick.C { - buf.Reset() - uploadTotalNew, downloadTotalNew := trafficManager.Total() - err := json.NewEncoder(buf).Encode(Traffic{ - Up: uploadTotalNew - uploadTotal, - Down: downloadTotalNew - downloadTotal, - }) - if err != nil { - break - } - if conn == nil { - _, err = w.Write(buf.Bytes()) - w.(http.Flusher).Flush() - } else { - err = wsutil.WriteServerText(conn, buf.Bytes()) - } - if err != nil { - break - } - - uploadTotal = uploadTotalNew - downloadTotal = downloadTotalNew - } - } -} - -type Log struct { - Type string `json:"type"` - Payload string `json:"payload"` -} - -func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - levelText := r.URL.Query().Get("level") - if levelText == "" { - levelText = "info" - } - - level, ok := log.ParseLevel(levelText) - if ok != nil { - render.Status(r, http.StatusBadRequest) - return - } - - subscription, done, err := logFactory.Subscribe() - if err != nil { - render.Status(r, http.StatusNoContent) - return - } - defer logFactory.UnSubscribe(subscription) - - var conn net.Conn - if r.Header.Get("Upgrade") == "websocket" { - conn, _, _, err = ws.UpgradeHTTP(r, w) - if err != nil { - return - } - defer conn.Close() - } - - if conn == nil { - w.Header().Set("Content-Type", "application/json") - render.Status(r, http.StatusOK) - } - - buf := &bytes.Buffer{} - var logEntry log.Entry - for { - select { - case <-done: - return - case logEntry = <-subscription: - } - if logEntry.Level > level { - continue - } - buf.Reset() - err = json.NewEncoder(buf).Encode(Log{ - Type: log.FormatLevel(logEntry.Level), - Payload: logEntry.Message, - }) - if err != nil { - break - } - if conn == nil { - _, err = w.Write(buf.Bytes()) - w.(http.Flusher).Flush() - } else { - err = wsutil.WriteServerText(conn, buf.Bytes()) - } - - if err != nil { - break - } - } - } -} - -func version(w http.ResponseWriter, r *http.Request) { - render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true, "meta": true}) -} diff --git a/core/cmd/nekobox_core/server/gen/libcore.pb.go b/core/cmd/nekobox_core/server/gen/libcore.pb.go index 4f028a9..0a7d12a 100644 --- a/core/cmd/nekobox_core/server/gen/libcore.pb.go +++ b/core/cmd/nekobox_core/server/gen/libcore.pb.go @@ -762,6 +762,7 @@ type ConnectionMetaData struct { Dest string `protobuf:"bytes,7,opt,name=dest,proto3" json:"dest,omitempty"` Protocol string `protobuf:"bytes,8,opt,name=protocol,proto3" json:"protocol,omitempty"` Domain string `protobuf:"bytes,9,opt,name=domain,proto3" json:"domain,omitempty"` + Process string `protobuf:"bytes,10,opt,name=process,proto3" json:"process,omitempty"` } func (x *ConnectionMetaData) Reset() { @@ -859,6 +860,13 @@ func (x *ConnectionMetaData) GetDomain() string { return "" } +func (x *ConnectionMetaData) GetProcess() string { + if x != nil { + return x.Process + } + return "" +} + type GetGeoIPListResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1358,7 +1366,7 @@ var file_libcore_proto_rawDesc = []byte{ 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8f, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, @@ -1373,104 +1381,106 @@ var file_libcore_proto_rawDesc = []byte{ 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, - 0x2c, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, - 0x16, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x24, 0x0a, - 0x0e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x22, 0x42, 0x0a, 0x18, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, - 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65, 0x74, + 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x47, 0x65, + 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x24, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x42, 0x0a, + 0x18, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x22, 0x44, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, + 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x44, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, 0x69, - 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x49, 0x0a, - 0x15, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x60, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x53, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, - 0x63, 0x6c, 0x65, 0x61, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x5f, 0x64, 0x68, 0x63, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x73, 0x65, 0x74, 0x44, 0x68, 0x63, 0x70, 0x22, 0x49, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x17, 0x0a, 0x07, - 0x69, 0x73, 0x5f, 0x64, 0x68, 0x63, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, - 0x73, 0x44, 0x68, 0x63, 0x70, 0x2a, 0x27, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0xc1, - 0x07, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x1a, 0x13, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, - 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, - 0x2d, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, - 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x12, 0x3f, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, - 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, - 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, - 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4a, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x49, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x53, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x22, 0x60, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, + 0x4e, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x65, + 0x61, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x12, + 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x74, + 0x5f, 0x64, 0x68, 0x63, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x74, + 0x44, 0x68, 0x63, 0x70, 0x22, 0x49, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x64, 0x68, 0x63, + 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x44, 0x68, 0x63, 0x70, 0x2a, + 0x27, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x09, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0xc1, 0x07, 0x0a, 0x0e, 0x4c, 0x69, 0x62, + 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, + 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6c, 0x69, 0x62, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, + 0x00, 0x12, 0x35, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, + 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x54, 0x65, 0x73, + 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, + 0x54, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3f, 0x0a, 0x0a, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, - 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, - 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, - 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, - 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, - 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, - 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x44, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x53, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x1e, 0x2e, 0x6c, 0x69, 0x62, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, - 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, - 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x12, 0x11, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, + 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, + 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69, + 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, + 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x53, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x40, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, - 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, - 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, + 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, + 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x12, 0x44, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, + 0x72, 0x6f, 0x78, 0x79, 0x12, 0x1e, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, + 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, + 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, + 0x4e, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x53, 0x65, + 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x62, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, + 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x42, 0x11, 0x5a, 0x0f, + 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/core/cmd/nekobox_core/server/gen/libcore.proto b/core/cmd/nekobox_core/server/gen/libcore.proto index f8ec07f..b36cf67 100644 --- a/core/cmd/nekobox_core/server/gen/libcore.proto +++ b/core/cmd/nekobox_core/server/gen/libcore.proto @@ -101,6 +101,7 @@ message ConnectionMetaData { string dest = 7; string protocol = 8; string domain = 9; + string process = 10; } message GetGeoIPListResponse { diff --git a/include/api/gRPC.h b/include/api/gRPC.h index d09a090..6eef17d 100644 --- a/include/api/gRPC.h +++ b/include/api/gRPC.h @@ -42,6 +42,8 @@ namespace NekoGui_rpc { QString SetSystemDNS(bool *rpcOK, const QStringList& servers, bool dhcp, bool clear) const; + libcore::ListConnectionsResp ListConnections(bool *rpcOK) const; + private: std::function()> make_grpc_channel; std::unique_ptr default_grpc_channel; diff --git a/include/global/NekoGui_DataStore.hpp b/include/global/NekoGui_DataStore.hpp index 4e55a0e..1004a6f 100644 --- a/include/global/NekoGui_DataStore.hpp +++ b/include/global/NekoGui_DataStore.hpp @@ -92,6 +92,7 @@ namespace NekoGui { bool start_minimal = false; int max_log_line = 200; QString splitter_state = ""; + bool enable_stats = true; // Subscription QString user_agent = ""; // set at main.cpp diff --git a/include/stats/connections/connectionLister.hpp b/include/stats/connections/connectionLister.hpp new file mode 100644 index 0000000..b5f9892 --- /dev/null +++ b/include/stats/connections/connectionLister.hpp @@ -0,0 +1,62 @@ +#pragma once +#include +#include + +namespace NekoGui_traffic +{ + constexpr int IDKEY = 242315; + + enum ConnectionSort + { + Default, + ByDownload, + ByUpload, + ByDomain + }; + + class ConnectionMetadata + { + public: + QString id; + long long createdAtMs; + long long upload; + long long download; + QString outbound; + QString network; + QString dest; + QString protocol; + QString domain; + QString process; + }; + + class ConnectionLister + { + public: + ConnectionLister(); + + bool suspend = true; + + void Loop(); + + void ForceUpdate(); + + void stopLoop(); + + void setSort(ConnectionSort newSort); + + private: + void update(); + + QMutex mu; + + bool stop = false; + + std::shared_ptr> state; + + ConnectionSort sort = Default; + + bool asc = false; + }; + + extern ConnectionLister* connection_lister; +} diff --git a/include/ui/mainwindow.h b/include/ui/mainwindow.h index 5686399..497eaab 100644 --- a/include/ui/mainwindow.h +++ b/include/ui/mainwindow.h @@ -3,6 +3,7 @@ #include #include "include/global/NekoGui.hpp" +#include "include/stats/connections/connectionLister.hpp" #ifndef MW_INTERFACE @@ -73,6 +74,10 @@ public: void DownloadAssets(const QString &geoipUrl, const QString &geositeUrl); + void UpdateConnectionList(const QMap& toUpdate, const QMap& toAdd); + + void UpdateConnectionListWithRecreate(const QList& connections); + signals: void profile_selected(int id); @@ -215,6 +220,8 @@ private: void CheckUpdate(); + void setupConnectionList(); + protected: bool eventFilter(QObject *obj, QEvent *event) override; diff --git a/include/ui/mainwindow.ui b/include/ui/mainwindow.ui index 7da0097..474ce46 100644 --- a/include/ui/mainwindow.ui +++ b/include/ui/mainwindow.ui @@ -173,6 +173,9 @@ 0 + + true + true @@ -182,13 +185,13 @@ - 0 + 1 0 - 0 + 1 0 @@ -279,13 +282,149 @@ 0 - - - Qt::ContextMenuPolicy::CustomContextMenu + + + true - + + QTabWidget::TabPosition::North + + + QTabWidget::TabShape::Rounded + + + 0 + + + Qt::TextElideMode::ElideNone + + + true + + + true + + false + + false + + + false + + + + Connections + + + + 0 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + 1 + + + 0 + + + 1 + + + 0 + + + + + false + + + + Destination + + + Click To Disable Sorting + + + + + Domain + + + Click To Sort By Domain + + + + + Network + + + + + Protocol + + + + + Download + + + Click To Sort By Download + + + + + Upload + + + Click To Sort By Upload + + + + + + + + + true + + + Logs + + + + 0 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + 1 + + + 0 + + + 1 + + + 0 + + + + + Qt::ContextMenuPolicy::CustomContextMenu + + + false + + + + + diff --git a/src/api/gRPC.cpp b/src/api/gRPC.cpp index ec4df0a..946b32d 100644 --- a/src/api/gRPC.cpp +++ b/src/api/gRPC.cpp @@ -418,4 +418,20 @@ namespace NekoGui_rpc { } } + libcore::ListConnectionsResp Client::ListConnections(bool* rpcOK) const + { + libcore::EmptyReq req; + libcore::ListConnectionsResp resp; + auto status = default_grpc_channel->Call("ListConnections", req, &resp); + if (status == QNetworkReply::NoError) { + *rpcOK = true; + return resp; + } else { + NOT_OK + MW_show_log(QString("Failed to list connections: " + qt_error_string(status))); + return {}; + } + } + + } // namespace NekoGui_rpc diff --git a/src/configs/ConfigBuilder.cpp b/src/configs/ConfigBuilder.cpp index df7e59a..34bbde8 100644 --- a/src/configs/ConfigBuilder.cpp +++ b/src/configs/ConfigBuilder.cpp @@ -710,13 +710,19 @@ namespace NekoGui { // experimental QJsonObject experimentalObj; + QJsonObject clash_api = { + {"default_mode", "Rule"} // dummy to make sure it is created + }; - if (!status->forTest && dataStore->core_box_clash_api > 0) { - QJsonObject clash_api = { + if (!status->forTest) + { + if (dataStore->core_box_clash_api > 0){ + clash_api = { {"external_controller", NekoGui::dataStore->core_box_clash_listen_addr + ":" + Int2String(dataStore->core_box_clash_api)}, {"secret", dataStore->core_box_clash_api_secret}, {"external_ui", "dashboard"}, - }; + }; + } experimentalObj["clash_api"] = clash_api; } diff --git a/src/global/NekoGui.cpp b/src/global/NekoGui.cpp index 9242827..b79f30e 100644 --- a/src/global/NekoGui.cpp +++ b/src/global/NekoGui.cpp @@ -309,6 +309,7 @@ namespace NekoGui { _add(new configItem("is_dhcp", &is_dhcp, itemType::boolean)); _add(new configItem("system_dns_servers", &system_dns_servers, itemType::stringList)); _add(new configItem("windows_set_admin", &windows_set_admin, itemType::boolean)); + _add(new configItem("enable_stats", &enable_stats, itemType::boolean)); } void DataStore::UpdateStartedId(int id) { diff --git a/src/stats/connectionLister/connectionLister.cpp b/src/stats/connectionLister/connectionLister.cpp new file mode 100644 index 0000000..53b7ff3 --- /dev/null +++ b/src/stats/connectionLister/connectionLister.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include "include/ui/mainwindow_interface.h" +#include + +namespace NekoGui_traffic +{ + ConnectionLister* connection_lister = new ConnectionLister(); + + ConnectionLister::ConnectionLister() + { + state = std::make_shared>(); + } + + void ConnectionLister::ForceUpdate() + { + mu.lock(); + update(); + mu.unlock(); + } + + + void ConnectionLister::Loop() + { + while (true) + { + if (stop) return; + auto sleep_ms = NekoGui::dataStore->traffic_loop_interval; + QThread::msleep(sleep_ms); + + if (suspend || !NekoGui::dataStore->enable_stats) continue; + + mu.lock(); + update(); + mu.unlock(); + } + } + + void ConnectionLister::update() + { + bool ok; + libcore::ListConnectionsResp resp = NekoGui_rpc::defaultClient->ListConnections(&ok); + if (!ok) + { + return; + } + + QMap toUpdate; + QMap toAdd; + QSet newState; + QList sorted; + auto conns = resp.connections(); + for (auto conn : conns) + { + auto c = ConnectionMetadata(); + c.id = QString(conn.mutable_id()->c_str()); + c.createdAtMs = conn.created_at(); + c.dest = QString(conn.mutable_dest()->c_str()); + c.upload = conn.upload(); + c.download = conn.download(); + c.domain = QString(conn.mutable_domain()->c_str()); + c.network = QString(conn.mutable_network()->c_str()); + c.outbound = QString(conn.mutable_outbound()->c_str()); + c.process = QString(conn.mutable_process()->c_str()); + c.protocol = QString(conn.mutable_protocol()->c_str()); + if (sort == Default) + { + if (state->contains(c.id)) + { + toUpdate[c.id] = c; + } else + { + toAdd[c.id] = c; + } + } else + { + sorted.append(c); + } + newState.insert(c.id); + } + + state->clear(); + for (const auto& id : newState) state->insert(id); + + if (sort == Default) + { + runOnUiThread([=] { + auto m = GetMainWindow(); + m->UpdateConnectionList(toUpdate, toAdd); + }); + } else + { + if (sort == ByDownload) + { + std::sort(sorted.begin(), sorted.end(), [=](const ConnectionMetadata& a, const ConnectionMetadata& b) + { + if (a.download == b.download) return asc ? a.id > b.id : a.id < b.id; + return asc ? a.download < b.download : a.download > b.download; + }); + } + if (sort == ByUpload) + { + std::sort(sorted.begin(), sorted.end(), [=](const ConnectionMetadata& a, const ConnectionMetadata& b) + { + if (a.upload == b.upload) return asc ? a.id > b.id : a.id < b.id; + return asc ? a.upload < b.upload : a.upload > b.upload; + }); + } + if (sort == ByDomain) + { + std::sort(sorted.begin(), sorted.end(), [=](const ConnectionMetadata& a, const ConnectionMetadata& b) + { + if (a.domain == b.domain) return asc ? a.id > b.id : a.id < b.id; + return asc ? a.domain > b.domain : a.domain < b.domain; + }); + } + runOnUiThread([=] { + auto m = GetMainWindow(); + m->UpdateConnectionListWithRecreate(sorted); + }); + } + } + + void ConnectionLister::stopLoop() + { + stop = true; + } + + void ConnectionLister::setSort(const ConnectionSort newSort) + { + if (sort == newSort) asc = !asc; + else + { + sort = newSort; + asc = false; + } + } + +} diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index ffee1d6..56a9a14 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -154,6 +154,38 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi runOnUiThread([=] { show_log_impl(log); }); }; + // setup connection UI + setupConnectionList(); + connect(ui->connections->horizontalHeader(), &QHeaderView::sectionClicked, this, [=](int index) + { + // TODO this is a very bad idea to hardcode it like this, need to refactor it later + if (index == 0) + { + NekoGui_traffic::connection_lister->setSort(NekoGui_traffic::Default); + NekoGui_traffic::connection_lister->ForceUpdate(); + } + if (index == 2 || index == 3) + { + // ignore + return; + } + if (index == 1) + { + NekoGui_traffic::connection_lister->setSort(NekoGui_traffic::ByDomain); + NekoGui_traffic::connection_lister->ForceUpdate(); + } + if (index == 4) + { + NekoGui_traffic::connection_lister->setSort(NekoGui_traffic::ByDownload); + NekoGui_traffic::connection_lister->ForceUpdate(); + } + if (index == 5) + { + NekoGui_traffic::connection_lister->setSort(NekoGui_traffic::ByUpload); + NekoGui_traffic::connection_lister->ForceUpdate(); + } + }); + // table UI ui->proxyListTable->callback_save_order = [=] { auto group = NekoGui::profileManager->CurrentGroup(); @@ -814,6 +846,139 @@ void MainWindow::neko_set_spmode_vpn(bool enable, bool save) { if (NekoGui::dataStore->started_id >= 0) neko_start(NekoGui::dataStore->started_id); } +void MainWindow::setupConnectionList() +{ + ui->connections->horizontalHeader()->setHighlightSections(false); + ui->connections->setSelectionMode(QAbstractItemView::NoSelection); + ui->connections->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->connections->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->connections->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + ui->connections->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + ui->connections->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents); + ui->connections->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents); + ui->connections->verticalHeader()->hide(); +} + +void MainWindow::UpdateConnectionList(const QMap& toUpdate, const QMap& toAdd) +{ + ui->connections->setUpdatesEnabled(false); + for (int row=0;rowconnections->rowCount();row++) + { + auto key = ui->connections->item(row, 0)->data(NekoGui_traffic::IDKEY).toString(); + if (!toUpdate.contains(key)) + { + ui->connections->removeRow(row); + row--; + continue; + } + + auto conn = toUpdate[key]; + // C0: Dest + ui->connections->item(row, 0)->setText(conn.dest); + + // C1: Domain + ui->connections->item(row, 1)->setText(conn.domain); + + // C2: Network + ui->connections->item(row, 2)->setText(conn.network); + + // C3: Protocol + ui->connections->item(row, 3)->setText(conn.protocol); + + // C4: Download + ui->connections->item(row, 4)->setText(ReadableSize(conn.download)); + + // C5: Upload + ui->connections->item(row, 5)->setText(ReadableSize(conn.upload)); + } + int row = ui->connections->rowCount(); + for (const auto& conn : toAdd) + { + ui->connections->insertRow(row); + auto f0 = std::make_unique(); + f0->setData(NekoGui_traffic::IDKEY, conn.id); + + // C0: Dest + auto f = f0->clone(); + f->setText(conn.dest); + ui->connections->setItem(row, 0, f); + + // C1: Domain + f = f0->clone(); + f->setText(conn.domain); + ui->connections->setItem(row, 1, f); + + // C2: Network + f = f0->clone(); + f->setText(conn.network); + ui->connections->setItem(row, 2, f); + + // C3: Protocol + f = f0->clone(); + f->setText(conn.protocol); + ui->connections->setItem(row, 3, f); + + // C4: Download + f = f0->clone(); + f->setText(ReadableSize(conn.download)); + ui->connections->setItem(row, 4, f); + + // C5: Upload + f = f0->clone(); + f->setText(ReadableSize(conn.upload)); + ui->connections->setItem(row, 5, f); + + row++; + } + ui->connections->setUpdatesEnabled(true); +} + +void MainWindow::UpdateConnectionListWithRecreate(const QList& connections) +{ + ui->connections->setUpdatesEnabled(false); + ui->connections->setRowCount(0); + int row=0; + for (const auto& conn : connections) + { + ui->connections->insertRow(row); + auto f0 = std::make_unique(); + f0->setData(NekoGui_traffic::IDKEY, conn.id); + + // C0: Dest + auto f = f0->clone(); + f->setText(conn.dest); + ui->connections->setItem(row, 0, f); + + // C1: Domain + f = f0->clone(); + f->setText(conn.domain); + ui->connections->setItem(row, 1, f); + + // C2: Network + f = f0->clone(); + f->setText(conn.network); + ui->connections->setItem(row, 2, f); + + // C3: Protocol + f = f0->clone(); + f->setText(conn.protocol); + ui->connections->setItem(row, 3, f); + + // C4: Download + f = f0->clone(); + f->setText(ReadableSize(conn.download)); + ui->connections->setItem(row, 4, f); + + // C5: Upload + f = f0->clone(); + f->setText(ReadableSize(conn.upload)); + ui->connections->setItem(row, 5, f); + + row++; + } + ui->connections->setUpdatesEnabled(true); +} + void MainWindow::refresh_status(const QString &traffic_update) { auto refresh_speed_label = [=] { if (NekoGui::dataStore->disable_traffic_stats) { @@ -955,6 +1120,7 @@ void MainWindow::refresh_proxy_list(const int &id) { } void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSortAction) { + ui->proxyListTable->setUpdatesEnabled(false); // id < 0 重绘 if (id < 0) { // 清空数据 @@ -975,7 +1141,11 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor switch (groupSortAction.method) { case GroupSortMethod::Raw: { auto group = NekoGui::profileManager->CurrentGroup(); - if (group == nullptr) return; + if (group == nullptr) + { + ui->proxyListTable->setUpdatesEnabled(true); + return; + } ui->proxyListTable->order = group->order; break; } @@ -1043,11 +1213,13 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor } void MainWindow::refresh_proxy_list_impl_refresh_data(const int &id, bool stopping) { + ui->proxyListTable->setUpdatesEnabled(false); if (id >= 0) { if (ui->proxyListTable->id2Row.count(id) == 0) { qDebug("Invalid proxy list id, data might be corrupted"); + ui->proxyListTable->setUpdatesEnabled(true); return; } auto rowID = ui->proxyListTable->id2Row[id]; @@ -1061,6 +1233,7 @@ void MainWindow::refresh_proxy_list_impl_refresh_data(const int &id, bool stoppi refresh_table_item(row, profile, stopping); } } + ui->proxyListTable->setUpdatesEnabled(true); } void MainWindow::refresh_table_item(const int row, const std::shared_ptr& profile, bool stopping) diff --git a/src/ui/mainwindow_grpc.cpp b/src/ui/mainwindow_grpc.cpp index 8f38282..9279962 100644 --- a/src/ui/mainwindow_grpc.cpp +++ b/src/ui/mainwindow_grpc.cpp @@ -26,6 +26,7 @@ void MainWindow::setup_grpc() { // Looper runOnNewThread([=] { NekoGui_traffic::trafficLooper->Loop(); }); + runOnNewThread([=] {NekoGui_traffic::connection_lister->Loop(); }); } void MainWindow::RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap& tag2entID, int entID) { @@ -279,6 +280,7 @@ void MainWindow::neko_start(int _id) { NekoGui_traffic::trafficLooper->items = result->outboundStats; NekoGui::dataStore->ignoreConnTag = result->ignoreConnTag; NekoGui_traffic::trafficLooper->loop_enabled = true; + NekoGui_traffic::connection_lister->suspend = false; NekoGui::dataStore->UpdateStartedId(ent->id); running = ent; @@ -403,6 +405,7 @@ void MainWindow::neko_stop(bool crash, bool sem, bool manual) { auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000); NekoGui_traffic::trafficLooper->loop_enabled = false; + NekoGui_traffic::connection_lister->suspend = true; NekoGui_traffic::trafficLooper->loop_mutex.lock(); if (NekoGui::dataStore->traffic_loop_interval != 0) { NekoGui_traffic::trafficLooper->UpdateAll(); diff --git a/src/ui/setting/dialog_basic_settings.cpp b/src/ui/setting/dialog_basic_settings.cpp index 13ee86f..7c3c3c1 100644 --- a/src/ui/setting/dialog_basic_settings.cpp +++ b/src/ui/setting/dialog_basic_settings.cpp @@ -38,7 +38,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) }); // Style - ui->connection_statistics_box->setDisabled(true); + ui->connection_statistics->setChecked(NekoGui::dataStore->enable_stats); // D_LOAD_BOOL(start_minimal) D_LOAD_INT(max_log_line) @@ -175,6 +175,7 @@ void DialogBasicSettings::accept() { // Style + NekoGui::dataStore->enable_stats = ui->connection_statistics->isChecked(); NekoGui::dataStore->language = ui->language->currentIndex(); D_SAVE_BOOL(start_minimal) D_SAVE_INT(max_log_line) @@ -271,7 +272,6 @@ void DialogBasicSettings::on_core_settings_clicked() { w->setLayout(layout); // auto line = -1; - QCheckBox *core_box_enable_clash_api; MyLineEdit *core_box_clash_api; MyLineEdit *core_box_clash_api_secret; MyLineEdit *core_box_underlying_dns; @@ -288,12 +288,6 @@ void DialogBasicSettings::on_core_settings_clicked() { layout->addWidget(core_box_underlying_dns_l, ++line, 0); layout->addWidget(core_box_underlying_dns, line, 1); // - auto core_box_enable_clash_api_l = new QLabel("Enable Clash API"); - core_box_enable_clash_api = new QCheckBox; - core_box_enable_clash_api->setChecked(NekoGui::dataStore->core_box_clash_api > 0); - layout->addWidget(core_box_enable_clash_api_l, ++line, 0); - layout->addWidget(core_box_enable_clash_api, line, 1); - // auto core_box_clash_listen_addr_l = new QLabel("Clash Api Listen Address"); core_box_clash_listen_addr = new MyLineEdit; core_box_clash_listen_addr->setText(NekoGui::dataStore->core_box_clash_listen_addr); @@ -302,7 +296,7 @@ void DialogBasicSettings::on_core_settings_clicked() { // auto core_box_clash_api_l = new QLabel("Clash API Listen Port"); core_box_clash_api = new MyLineEdit; - core_box_clash_api->setText(Int2String(std::abs(NekoGui::dataStore->core_box_clash_api))); + core_box_clash_api->setText(NekoGui::dataStore->core_box_clash_api > 0 ? Int2String(NekoGui::dataStore->core_box_clash_api) : ""); layout->addWidget(core_box_clash_api_l, ++line, 0); layout->addWidget(core_box_clash_api, line, 1); // @@ -317,7 +311,7 @@ void DialogBasicSettings::on_core_settings_clicked() { box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); connect(box, &QDialogButtonBox::accepted, w, [=] { NekoGui::dataStore->core_box_underlying_dns = core_box_underlying_dns->text(); - NekoGui::dataStore->core_box_clash_api = core_box_clash_api->text().toInt() * (core_box_enable_clash_api->isChecked() ? 1 : -1); + NekoGui::dataStore->core_box_clash_api = core_box_clash_api->text().toInt(); NekoGui::dataStore->core_box_clash_listen_addr = core_box_clash_listen_addr->text(); NekoGui::dataStore->core_box_clash_api_secret = core_box_clash_api_secret->text(); MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore");