package services import ( "context" "encoding/json" teaconst "github.com/TeaOSLab/EdgeAPI/internal/const" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models/authority" "github.com/TeaOSLab/EdgeAPI/internal/db/models/stats" "github.com/TeaOSLab/EdgeAPI/internal/errors" rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" "github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/types" timeutil "github.com/iwind/TeaGo/utils/time" "time" ) // AdminService 管理员相关服务 type AdminService struct { BaseService debug bool } // LoginAdmin 登录 func (this *AdminService) LoginAdmin(ctx context.Context, req *pb.LoginAdminRequest) (*pb.LoginAdminResponse, error) { _, _, _, err := rpcutils.ValidateRequest(ctx) if err != nil { return nil, err } if len(req.Username) == 0 || len(req.Password) == 0 { return &pb.LoginAdminResponse{ AdminId: 0, IsOk: false, Message: "请输入正确的用户名密码", }, nil } tx := this.NullTx() adminId, err := models.SharedAdminDAO.CheckAdminPassword(tx, req.Username, req.Password) if err != nil { utils.PrintError(err) return nil, err } if adminId <= 0 { return &pb.LoginAdminResponse{ AdminId: 0, IsOk: false, Message: "请输入正确的用户名密码", }, nil } return &pb.LoginAdminResponse{ AdminId: adminId, IsOk: true, }, nil } // CheckAdminExists 检查管理员是否存在 func (this *AdminService) CheckAdminExists(ctx context.Context, req *pb.CheckAdminExistsRequest) (*pb.CheckAdminExistsResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } if req.AdminId <= 0 { return &pb.CheckAdminExistsResponse{ IsOk: false, }, nil } tx := this.NullTx() ok, err := models.SharedAdminDAO.ExistEnabledAdmin(tx, req.AdminId) if err != nil { return nil, err } return &pb.CheckAdminExistsResponse{ IsOk: ok, }, nil } // CheckAdminUsername 检查用户名是否存在 func (this *AdminService) CheckAdminUsername(ctx context.Context, req *pb.CheckAdminUsernameRequest) (*pb.CheckAdminUsernameResponse, error) { // 校验请求 _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } tx := this.NullTx() exists, err := models.SharedAdminDAO.CheckAdminUsername(tx, req.AdminId, req.Username) if err != nil { return nil, err } return &pb.CheckAdminUsernameResponse{Exists: exists}, nil } // FindAdminFullname 获取管理员名称 func (this *AdminService) FindAdminFullname(ctx context.Context, req *pb.FindAdminFullnameRequest) (*pb.FindAdminFullnameResponse, error) { // 校验请求 _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } tx := this.NullTx() fullname, err := models.SharedAdminDAO.FindAdminFullname(tx, req.AdminId) if err != nil { utils.PrintError(err) return nil, err } return &pb.FindAdminFullnameResponse{ Fullname: fullname, }, nil } // FindEnabledAdmin 获取管理员信息 func (this *AdminService) FindEnabledAdmin(ctx context.Context, req *pb.FindEnabledAdminRequest) (*pb.FindEnabledAdminResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() admin, err := models.SharedAdminDAO.FindEnabledAdmin(tx, req.AdminId) if err != nil { return nil, err } if admin == nil { return &pb.FindEnabledAdminResponse{Admin: nil}, nil } pbModules := []*pb.AdminModule{} modules := []*systemconfigs.AdminModule{} if len(admin.Modules) > 0 && admin.Modules != "null" { err = json.Unmarshal([]byte(admin.Modules), &modules) if err != nil { return nil, err } for _, module := range modules { pbModules = append(pbModules, &pb.AdminModule{ AllowAll: module.AllowAll, Code: module.Code, Actions: module.Actions, }) } } // OTP认证 var pbOtpAuth *pb.Login = nil { adminAuth, err := models.SharedLoginDAO.FindEnabledLoginWithAdminId(tx, int64(admin.Id), models.LoginTypeOTP) if err != nil { return nil, err } if adminAuth != nil { pbOtpAuth = &pb.Login{ Id: int64(adminAuth.Id), Type: adminAuth.Type, ParamsJSON: []byte(adminAuth.Params), IsOn: adminAuth.IsOn == 1, } } } result := &pb.Admin{ Id: int64(admin.Id), Fullname: admin.Fullname, Username: admin.Username, IsOn: admin.IsOn == 1, IsSuper: admin.IsSuper == 1, Modules: pbModules, OtpLogin: pbOtpAuth, CanLogin: admin.CanLogin == 1, } return &pb.FindEnabledAdminResponse{Admin: result}, nil } // CreateOrUpdateAdmin 创建或修改管理员 func (this *AdminService) CreateOrUpdateAdmin(ctx context.Context, req *pb.CreateOrUpdateAdminRequest) (*pb.CreateOrUpdateAdminResponse, error) { // 校验请求 _, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin, rpcutils.UserTypeAPI) if err != nil { return nil, err } tx := this.NullTx() adminId, err := models.SharedAdminDAO.FindAdminIdWithUsername(tx, req.Username) if err != nil { return nil, err } if adminId > 0 { err = models.SharedAdminDAO.UpdateAdminPassword(tx, adminId, req.Password) if err != nil { return nil, err } return &pb.CreateOrUpdateAdminResponse{AdminId: adminId}, nil } adminId, err = models.SharedAdminDAO.CreateAdmin(tx, req.Username, true, req.Password, "管理员", true, nil) if err != nil { return nil, err } return &pb.CreateOrUpdateAdminResponse{AdminId: adminId}, nil } // UpdateAdminInfo 修改管理员信息 func (this *AdminService) UpdateAdminInfo(ctx context.Context, req *pb.UpdateAdminInfoRequest) (*pb.RPCSuccess, error) { // 校验请求 _, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin, rpcutils.UserTypeAPI) if err != nil { return nil, err } tx := this.NullTx() err = models.SharedAdminDAO.UpdateAdminInfo(tx, req.AdminId, req.Fullname) if err != nil { return nil, err } return this.Success() } // UpdateAdminLogin 修改管理员登录信息 func (this *AdminService) UpdateAdminLogin(ctx context.Context, req *pb.UpdateAdminLoginRequest) (*pb.RPCSuccess, error) { // 校验请求 _, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin, rpcutils.UserTypeAPI) if err != nil { return nil, err } tx := this.NullTx() exists, err := models.SharedAdminDAO.CheckAdminUsername(tx, req.AdminId, req.Username) if err != nil { return nil, err } if exists { return nil, errors.New("username already been token") } err = models.SharedAdminDAO.UpdateAdminLogin(tx, req.AdminId, req.Username, req.Password) if err != nil { return nil, err } return this.Success() } // FindAllAdminModules 获取所有管理员的权限列表 func (this *AdminService) FindAllAdminModules(ctx context.Context, req *pb.FindAllAdminModulesRequest) (*pb.FindAllAdminModulesResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() admins, err := models.SharedAdminDAO.FindAllAdminModules(tx) if err != nil { return nil, err } result := []*pb.AdminModuleList{} for _, admin := range admins { modules := []*systemconfigs.AdminModule{} if len(admin.Modules) > 0 && admin.Modules != "null" { err = json.Unmarshal([]byte(admin.Modules), &modules) if err != nil { return nil, err } } pbModules := []*pb.AdminModule{} for _, module := range modules { pbModules = append(pbModules, &pb.AdminModule{ AllowAll: module.AllowAll, Code: module.Code, Actions: module.Actions, }) } list := &pb.AdminModuleList{ AdminId: int64(admin.Id), IsSuper: admin.IsSuper == 1, Fullname: admin.Fullname, Theme: admin.Theme, Modules: pbModules, } result = append(result, list) } return &pb.FindAllAdminModulesResponse{AdminModules: result}, nil } // CreateAdmin 创建管理员 func (this *AdminService) CreateAdmin(ctx context.Context, req *pb.CreateAdminRequest) (*pb.CreateAdminResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() adminId, err := models.SharedAdminDAO.CreateAdmin(tx, req.Username, req.CanLogin, req.Password, req.Fullname, req.IsSuper, req.ModulesJSON) if err != nil { return nil, err } return &pb.CreateAdminResponse{AdminId: adminId}, nil } // UpdateAdmin 修改管理员 func (this *AdminService) UpdateAdmin(ctx context.Context, req *pb.UpdateAdminRequest) (*pb.RPCSuccess, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() err = models.SharedAdminDAO.UpdateAdmin(tx, req.AdminId, req.Username, req.CanLogin, req.Password, req.Fullname, req.IsSuper, req.ModulesJSON, req.IsOn) if err != nil { return nil, err } return this.Success() } // CountAllEnabledAdmins 计算管理员数量 func (this *AdminService) CountAllEnabledAdmins(ctx context.Context, req *pb.CountAllEnabledAdminsRequest) (*pb.RPCCountResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() count, err := models.SharedAdminDAO.CountAllEnabledAdmins(tx) if err != nil { return nil, err } return this.SuccessCount(count) } // ListEnabledAdmins 列出单页的管理员 func (this *AdminService) ListEnabledAdmins(ctx context.Context, req *pb.ListEnabledAdminsRequest) (*pb.ListEnabledAdminsResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() admins, err := models.SharedAdminDAO.ListEnabledAdmins(tx, req.Offset, req.Size) if err != nil { return nil, err } result := []*pb.Admin{} for _, admin := range admins { var pbOtpAuth *pb.Login = nil { adminAuth, err := models.SharedLoginDAO.FindEnabledLoginWithAdminId(tx, int64(admin.Id), models.LoginTypeOTP) if err != nil { return nil, err } if adminAuth != nil { pbOtpAuth = &pb.Login{ Id: int64(adminAuth.Id), Type: adminAuth.Type, ParamsJSON: []byte(adminAuth.Params), IsOn: adminAuth.IsOn == 1, } } } result = append(result, &pb.Admin{ Id: int64(admin.Id), Fullname: admin.Fullname, Username: admin.Username, IsOn: admin.IsOn == 1, IsSuper: admin.IsSuper == 1, CreatedAt: int64(admin.CreatedAt), OtpLogin: pbOtpAuth, CanLogin: admin.CanLogin == 1, }) } return &pb.ListEnabledAdminsResponse{Admins: result}, nil } // DeleteAdmin 删除管理员 func (this *AdminService) DeleteAdmin(ctx context.Context, req *pb.DeleteAdminRequest) (*pb.RPCSuccess, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } // TODO 检查权限 tx := this.NullTx() // TODO 超级管理员用户是不能删除的,或者要至少留一个超级管理员用户 _, err = models.SharedAdminDAO.DisableAdmin(tx, req.AdminId) if err != nil { return nil, err } return this.Success() } // CheckAdminOTPWithUsername 检查是否需要输入OTP func (this *AdminService) CheckAdminOTPWithUsername(ctx context.Context, req *pb.CheckAdminOTPWithUsernameRequest) (*pb.CheckAdminOTPWithUsernameResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } if len(req.Username) == 0 { return &pb.CheckAdminOTPWithUsernameResponse{RequireOTP: false}, nil } tx := this.NullTx() adminId, err := models.SharedAdminDAO.FindAdminIdWithUsername(tx, req.Username) if err != nil { return nil, err } if adminId <= 0 { return &pb.CheckAdminOTPWithUsernameResponse{RequireOTP: false}, nil } otpIsOn, err := models.SharedLoginDAO.CheckLoginIsOn(tx, adminId, "otp") if err != nil { return nil, err } return &pb.CheckAdminOTPWithUsernameResponse{RequireOTP: otpIsOn}, nil } // ComposeAdminDashboard 取得管理员Dashboard数据 func (this *AdminService) ComposeAdminDashboard(ctx context.Context, req *pb.ComposeAdminDashboardRequest) (*pb.ComposeAdminDashboardResponse, error) { _, err := this.ValidateAdmin(ctx, 0) if err != nil { return nil, err } result := &pb.ComposeAdminDashboardResponse{} var tx = this.NullTx() // 默认集群 nodeClusters, err := models.SharedNodeClusterDAO.ListEnabledClusters(tx, "", 0, 1) if err != nil { return nil, err } if len(nodeClusters) > 0 { result.DefaultNodeClusterId = int64(nodeClusters[0].Id) } // 集群数 countClusters, err := models.SharedNodeClusterDAO.CountAllEnabledClusters(tx, "") if err != nil { return nil, err } result.CountNodeClusters = countClusters // 节点数 countNodes, err := models.SharedNodeDAO.CountAllEnabledNodes(tx) if err != nil { return nil, err } result.CountNodes = countNodes // 离线节点 countOfflineNodes, err := models.SharedNodeDAO.CountAllEnabledOfflineNodes(tx) if err != nil { return nil, err } result.CountOfflineNodes = countOfflineNodes // 服务数 countServers, err := models.SharedServerDAO.CountAllEnabledServers(tx) if err != nil { return nil, err } result.CountServers = countServers // 用户数 countUsers, err := models.SharedUserDAO.CountAllEnabledUsers(tx, 0, "") if err != nil { return nil, err } result.CountUsers = countUsers // API节点数 countAPINodes, err := models.SharedAPINodeDAO.CountAllEnabledAndOnAPINodes(tx) if err != nil { return nil, err } result.CountAPINodes = countAPINodes // 离线API节点 countOfflineAPINodes, err := models.SharedAPINodeDAO.CountAllEnabledAndOnOfflineAPINodes(tx) if err != nil { return nil, err } result.CountOfflineAPINodes = countOfflineAPINodes // 数据库节点数 countDBNodes, err := models.SharedDBNodeDAO.CountAllEnabledNodes(tx) if err != nil { return nil, err } result.CountDBNodes = countDBNodes // 用户节点数 countUserNodes, err := models.SharedUserNodeDAO.CountAllEnabledAndOnUserNodes(tx) if err != nil { return nil, err } result.CountUserNodes = countUserNodes // 离线用户节点数 countOfflineUserNodes, err := models.SharedUserNodeDAO.CountAllEnabledAndOnOfflineNodes(tx) if err != nil { return nil, err } result.CountOfflineUserNodes = countOfflineUserNodes // 按日流量统计 dayFrom := timeutil.Format("Ymd", time.Now().AddDate(0, 0, -14)) dailyTrafficStats, err := stats.SharedTrafficDailyStatDAO.FindDailyStats(tx, dayFrom, timeutil.Format("Ymd")) if err != nil { return nil, err } for _, stat := range dailyTrafficStats { result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeAdminDashboardResponse_DailyTrafficStat{ Day: stat.Day, Bytes: int64(stat.Bytes), CachedBytes: int64(stat.CachedBytes), CountRequests: int64(stat.CountRequests), CountCachedRequests: int64(stat.CountCachedRequests), CountAttackRequests: int64(stat.CountAttackRequests), AttackBytes: int64(stat.AttackBytes), }) } // 小时流量统计 hourFrom := timeutil.Format("YmdH", time.Now().Add(-23*time.Hour)) hourTo := timeutil.Format("YmdH") hourlyTrafficStats, err := stats.SharedTrafficHourlyStatDAO.FindHourlyStats(tx, hourFrom, hourTo) if err != nil { return nil, err } for _, stat := range hourlyTrafficStats { result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeAdminDashboardResponse_HourlyTrafficStat{ Hour: stat.Hour, Bytes: int64(stat.Bytes), CachedBytes: int64(stat.CachedBytes), CountRequests: int64(stat.CountRequests), CountCachedRequests: int64(stat.CountCachedRequests), CountAttackRequests: int64(stat.CountAttackRequests), AttackBytes: int64(stat.AttackBytes), }) } // 是否是商业版 isPlus, err := authority.SharedAuthorityKeyDAO.IsPlus(tx) if err != nil { return nil, err } // 边缘节点升级信息 { upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: teaconst.NodeVersion, } countNodes, err := models.SharedNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.NodeUpgradeInfo = upgradeInfo } // 监控节点升级信息 if isPlus { upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: teaconst.MonitorNodeVersion, } countNodes, err := models.SharedMonitorNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.MonitorNodeUpgradeInfo = upgradeInfo } // 认证节点升级信息 if isPlus { upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: teaconst.AuthorityNodeVersion, } countNodes, err := authority.SharedAuthorityNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.AuthorityNodeUpgradeInfo = upgradeInfo } // 用户节点升级信息 if isPlus { upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: teaconst.UserNodeVersion, } countNodes, err := models.SharedUserNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.UserNodeUpgradeInfo = upgradeInfo } // API节点升级信息 { var apiVersion = req.ApiVersion if len(apiVersion) == 0 { apiVersion = teaconst.Version } upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: apiVersion, } countNodes, err := models.SharedAPINodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.ApiNodeUpgradeInfo = upgradeInfo } // DNS节点升级信息 if isPlus { upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: teaconst.DNSNodeVersion, } countNodes, err := models.SharedNSNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.NsNodeUpgradeInfo = upgradeInfo } // Report节点升级信息 if isPlus { upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{ NewVersion: teaconst.ReportNodeVersion, } countNodes, err := models.SharedReportNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion) if err != nil { return nil, err } upgradeInfo.CountNodes = countNodes result.ReportNodeUpgradeInfo = upgradeInfo } // 域名排行 topDomainStats, err := stats.SharedServerDomainHourlyStatDAO.FindTopDomainStats(tx, hourFrom, hourTo, 10) if err != nil { return nil, err } for _, stat := range topDomainStats { result.TopDomainStats = append(result.TopDomainStats, &pb.ComposeAdminDashboardResponse_DomainStat{ ServerId: int64(stat.ServerId), Domain: stat.Domain, CountRequests: int64(stat.CountRequests), Bytes: int64(stat.Bytes), }) } // 节点排行 if isPlus { topNodeStats, err := stats.SharedNodeTrafficHourlyStatDAO.FindTopNodeStats(tx, "node", hourFrom, hourTo, 10) if err != nil { return nil, err } for _, stat := range topNodeStats { nodeName, err := models.SharedNodeDAO.FindNodeName(tx, int64(stat.NodeId)) if err != nil { return nil, err } if len(nodeName) == 0 { continue } result.TopNodeStats = append(result.TopNodeStats, &pb.ComposeAdminDashboardResponse_NodeStat{ NodeId: int64(stat.NodeId), NodeName: nodeName, CountRequests: int64(stat.CountRequests), Bytes: int64(stat.Bytes), }) } } // 指标数据 pbCharts, err := this.findMetricDataCharts(tx) if err != nil { return nil, err } result.MetricDataCharts = pbCharts return result, nil } // UpdateAdminTheme 修改管理员使用的界面风格 func (this *AdminService) UpdateAdminTheme(ctx context.Context, req *pb.UpdateAdminThemeRequest) (*pb.RPCSuccess, error) { _, err := this.ValidateAdmin(ctx, req.AdminId) if err != nil { return nil, err } var tx = this.NullTx() err = models.SharedAdminDAO.UpdateAdminTheme(tx, req.AdminId, req.Theme) if err != nil { return nil, err } return this.Success() } // 查找集群、节点和服务的指标数据 func (this *AdminService) findMetricDataCharts(tx *dbs.Tx) (result []*pb.MetricDataChart, err error) { // 集群指标 items, err := models.SharedMetricItemDAO.FindAllPublicItems(tx, serverconfigs.MetricItemCategoryHTTP) if err != nil { return nil, err } var pbMetricCharts = []*pb.MetricDataChart{} for _, item := range items { var itemId = int64(item.Id) charts, err := models.SharedMetricChartDAO.FindAllEnabledCharts(tx, itemId) if err != nil { return nil, err } for _, chart := range charts { if chart.IsOn == 0 { continue } var pbChart = &pb.MetricChart{ Id: int64(chart.Id), Name: chart.Name, Type: chart.Type, WidthDiv: chart.WidthDiv, ParamsJSON: nil, IsOn: chart.IsOn == 1, MaxItems: types.Int32(chart.MaxItems), MetricItem: &pb.MetricItem{ Id: itemId, PeriodUnit: item.PeriodUnit, Period: types.Int32(item.Period), Name: item.Name, Value: item.Value, Category: item.Category, Keys: item.DecodeKeys(), Code: item.Code, IsOn: item.IsOn == 1, }, } var pbStats = []*pb.MetricStat{} switch chart.Type { case serverconfigs.MetricChartTypeTimeLine: itemStats, err := models.SharedMetricStatDAO.FindLatestItemStats(tx, itemId, chart.IgnoreEmptyKeys == 1, chart.DecodeIgnoredKeys(), types.Int32(item.Version), 10) if err != nil { return nil, err } for _, stat := range itemStats { // 当前时间总和 count, total, err := models.SharedMetricSumStatDAO.FindSumAtTime(tx, stat.Time, itemId, types.Int32(item.Version)) if err != nil { return nil, err } pbStats = append(pbStats, &pb.MetricStat{ Id: int64(stat.Id), Hash: stat.Hash, ServerId: 0, ItemId: 0, Keys: stat.DecodeKeys(), Value: types.Float32(stat.Value), Time: stat.Time, Version: 0, NodeCluster: nil, Node: nil, Server: nil, SumCount: count, SumTotal: total, }) } default: itemStats, err := models.SharedMetricStatDAO.FindItemStatsAtLastTime(tx, itemId, chart.IgnoreEmptyKeys == 1, chart.DecodeIgnoredKeys(), types.Int32(item.Version), 10) if err != nil { return nil, err } for _, stat := range itemStats { count, total, err := models.SharedMetricSumStatDAO.FindSumAtTime(tx, stat.Time, itemId, types.Int32(item.Version)) if err != nil { return nil, err } pbStats = append(pbStats, &pb.MetricStat{ Id: int64(stat.Id), Hash: stat.Hash, ServerId: 0, ItemId: 0, Keys: stat.DecodeKeys(), Value: types.Float32(stat.Value), Time: stat.Time, Version: 0, NodeCluster: nil, Node: nil, Server: nil, SumCount: count, SumTotal: total, }) } } pbMetricCharts = append(pbMetricCharts, &pb.MetricDataChart{ MetricChart: pbChart, MetricStats: pbStats, }) } } return pbMetricCharts, nil }