From 7a61da098b0cbd9972272a424c9f81c9a418c3c9 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Sat, 20 Jun 2026 00:45:53 +0800 Subject: [PATCH] fix(service): filter service stats based on user permissions --- .../controller/permission_matrix_test.go | 42 ++++++++++++------- cmd/dashboard/controller/service.go | 18 +++++++- service/singleton/servicesentinel.go | 5 --- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/cmd/dashboard/controller/permission_matrix_test.go b/cmd/dashboard/controller/permission_matrix_test.go index e70361e9..84fcbbcf 100644 --- a/cmd/dashboard/controller/permission_matrix_test.go +++ b/cmd/dashboard/controller/permission_matrix_test.go @@ -268,7 +268,10 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) { serviceSentinel, err := singleton.NewServiceSentinel(make(chan *model.Service, 2)) assert.NoError(t, err) singleton.ServiceSentinelShared = serviceSentinel - t.Cleanup(func() { singleton.ServiceSentinelShared = originalServiceSentinel }) + t.Cleanup(func() { + serviceSentinel.Close() + singleton.ServiceSentinelShared = originalServiceSentinel + }) singleton.AlertsLock.Lock() originalCycleTransferStats := singleton.AlertsCycleTransferStatsStore @@ -288,23 +291,27 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) { }) tests := []struct { - name string - viewer *model.User - wantNames map[uint64]string + name string + viewer *model.User + wantServices []uint64 + wantNames map[uint64]string }{ { - name: "guest sees public servers only", - wantNames: map[uint64]string{1: "public server"}, + name: "guest sees public servers only", + wantServices: []uint64{10}, + wantNames: map[uint64]string{1: "public server"}, }, { - name: "member sees public and owned hidden servers", - viewer: &model.User{Common: model.Common{ID: 200}, Role: model.RoleMember}, - wantNames: map[uint64]string{1: "public server", 3: "hidden member server"}, + name: "member sees public and owned hidden servers", + viewer: &model.User{Common: model.Common{ID: 200}, Role: model.RoleMember}, + wantServices: []uint64{10}, + wantNames: map[uint64]string{1: "public server", 3: "hidden member server"}, }, { - name: "admin sees every server", - viewer: &model.User{Common: model.Common{ID: 1}, Role: model.RoleAdmin}, - wantNames: map[uint64]string{1: "public server", 2: "hidden admin server", 3: "hidden member server"}, + name: "admin sees every server", + viewer: &model.User{Common: model.Common{ID: 1}, Role: model.RoleAdmin}, + wantServices: []uint64{10, 11}, + wantNames: map[uint64]string{1: "public server", 2: "hidden admin server", 3: "hidden member server"}, }, } @@ -317,8 +324,7 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) { got, err := showService(ctx) assert.NoError(t, err) - assert.Contains(t, got.Services, uint64(10)) - assert.NotContains(t, got.Services, uint64(11)) + assert.ElementsMatch(t, tc.wantServices, serviceResponseIDs(got.Services)) if assert.Contains(t, got.CycleTransferStats, uint64(7)) { cycleStats := got.CycleTransferStats[7] assert.Equal(t, tc.wantNames, cycleStats.ServerName) @@ -335,6 +341,14 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) { } } +func serviceResponseIDs(stats map[uint64]model.ServiceResponseItem) []uint64 { + ids := make([]uint64, 0, len(stats)) + for id := range stats { + ids = append(ids, id) + } + return ids +} + func decodeIDs[T ~uint64](t *testing.T, body []byte) []T { t.Helper() var resp struct { diff --git a/cmd/dashboard/controller/service.go b/cmd/dashboard/controller/service.go index ce5937d7..3cd54464 100644 --- a/cmd/dashboard/controller/service.go +++ b/cmd/dashboard/controller/service.go @@ -29,7 +29,7 @@ func showService(c *gin.Context) (*model.ServiceResponse, error) { res, err, _ := requestGroup.Do(serviceResponseCacheKey(c), func() (any, error) { singleton.AlertsLock.RLock() defer singleton.AlertsLock.RUnlock() - stats := singleton.ServiceSentinelShared.CopyStats() + stats := filterServiceStatsForViewer(c, singleton.ServiceSentinelShared.CopyStats()) var cycleTransferStats map[uint64]model.CycleTransferStats copier.Copy(&cycleTransferStats, singleton.AlertsCycleTransferStatsStore) return []any{ @@ -46,6 +46,22 @@ func showService(c *gin.Context) (*model.ServiceResponse, error) { }, nil } +func filterServiceStatsForViewer(c *gin.Context, stats map[uint64]model.ServiceResponseItem) map[uint64]model.ServiceResponseItem { + if len(stats) == 0 { + return stats + } + services := singleton.ServiceSentinelShared.GetList() + filteredStats := make(map[uint64]model.ServiceResponseItem, len(stats)) + for serviceID, stat := range stats { + service, ok := services[serviceID] + if !ok || !userCanViewService(c, service) { + continue + } + filteredStats[serviceID] = stat + } + return filteredStats +} + func serviceResponseCacheKey(c *gin.Context) string { auth, ok := c.Get(model.CtxKeyAuthorizedUser) if !ok { diff --git a/service/singleton/servicesentinel.go b/service/singleton/servicesentinel.go index dbe00cdf..a8300176 100644 --- a/service/singleton/servicesentinel.go +++ b/service/singleton/servicesentinel.go @@ -438,11 +438,6 @@ func (ss *ServiceSentinel) CopyStats() map[uint64]model.ServiceResponseItem { sri := make(map[uint64]model.ServiceResponseItem) for k, service := range stats { - if service.service.HideForGuest { - delete(stats, k) - continue - } - service.ServiceName = service.service.Name sri[k] = service.ServiceResponseItem }