Add Connection list &&

Improve QTable performace
This commit is contained in:
Nova 2025-01-03 23:37:45 +03:30
parent 76bddde9a4
commit ccee10b972
No known key found for this signature in database
GPG Key ID: 389787EC83F5D73A
17 changed files with 682 additions and 475 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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})
}

View File

@ -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 (

View File

@ -101,6 +101,7 @@ message ConnectionMetaData {
string dest = 7;
string protocol = 8;
string domain = 9;
string process = 10;
}
message GetGeoIPListResponse {

View File

@ -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<std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate>()> make_grpc_channel;
std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate> default_grpc_channel;

View File

@ -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

View File

@ -0,0 +1,62 @@
#pragma once
#include <QMutex>
#include <QString>
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<QSet<QString>> state;
ConnectionSort sort = Default;
bool asc = false;
};
extern ConnectionLister* connection_lister;
}

View File

@ -3,6 +3,7 @@
#include <QMainWindow>
#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<QString, NekoGui_traffic::ConnectionMetadata>& toUpdate, const QMap<QString, NekoGui_traffic::ConnectionMetadata>& toAdd);
void UpdateConnectionListWithRecreate(const QList<NekoGui_traffic::ConnectionMetadata>& connections);
signals:
void profile_selected(int id);
@ -215,6 +220,8 @@ private:
void CheckUpdate();
void setupConnectionList();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;

View File

@ -173,6 +173,9 @@
<property name="currentIndex">
<number>0</number>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
<property name="movable">
<bool>true</bool>
</property>
@ -182,13 +185,13 @@
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
<number>1</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
<number>1</number>
</property>
<property name="bottomMargin">
<number>0</number>
@ -279,13 +282,149 @@
<number>0</number>
</property>
<item>
<widget class="QTextBrowser" name="masterLogBrowser">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
<widget class="QTabWidget" name="stats_widget">
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="openLinks">
<property name="tabPosition">
<enum>QTabWidget::TabPosition::North</enum>
</property>
<property name="tabShape">
<enum>QTabWidget::TabShape::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="elideMode">
<enum>Qt::TextElideMode::ElideNone</enum>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="tabBarAutoHide">
<bool>false</bool>
</property>
<widget class="QWidget" name="connections_tab">
<attribute name="title">
<string>Connections</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableWidget" name="connections">
<property name="autoFillBackground">
<bool>false</bool>
</property>
<column>
<property name="text">
<string>Destination</string>
</property>
<property name="toolTip">
<string>Click To Disable Sorting</string>
</property>
</column>
<column>
<property name="text">
<string>Domain</string>
</property>
<property name="toolTip">
<string>Click To Sort By Domain</string>
</property>
</column>
<column>
<property name="text">
<string>Network</string>
</property>
</column>
<column>
<property name="text">
<string>Protocol</string>
</property>
</column>
<column>
<property name="text">
<string>Download</string>
</property>
<property name="toolTip">
<string>Click To Sort By Download</string>
</property>
</column>
<column>
<property name="text">
<string>Upload</string>
</property>
<property name="toolTip">
<string>Click To Sort By Upload</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="Logs">
<property name="enabled">
<bool>true</bool>
</property>
<attribute name="title">
<string>Logs</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTextBrowser" name="masterLogBrowser">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="openLinks">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -0,0 +1,140 @@
#include <QThread>
#include <core/cmd/nekobox_core/server/gen/libcore.pb.h>
#include <include/api/gRPC.h>
#include "include/ui/mainwindow_interface.h"
#include <include/stats/connections/connectionLister.hpp>
namespace NekoGui_traffic
{
ConnectionLister* connection_lister = new ConnectionLister();
ConnectionLister::ConnectionLister()
{
state = std::make_shared<QSet<QString>>();
}
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<QString, ConnectionMetadata> toUpdate;
QMap<QString, ConnectionMetadata> toAdd;
QSet<QString> newState;
QList<ConnectionMetadata> 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;
}
}
}

View File

@ -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<QString, NekoGui_traffic::ConnectionMetadata>& toUpdate, const QMap<QString, NekoGui_traffic::ConnectionMetadata>& toAdd)
{
ui->connections->setUpdatesEnabled(false);
for (int row=0;row<ui->connections->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<QTableWidgetItem>();
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<NekoGui_traffic::ConnectionMetadata>& 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<QTableWidgetItem>();
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<NekoGui::ProxyEntity>& profile, bool stopping)

View File

@ -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<QString, int>& 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();

View File

@ -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");