feat: add superadmin health center
This commit is contained in:
75
backend/app/http/super/v1/dto/super_health.go
Normal file
75
backend/app/http/super/v1/dto/super_health.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
// SuperHealthOverviewFilter 超管健康概览查询条件。
|
||||||
|
type SuperHealthOverviewFilter struct {
|
||||||
|
// TenantID 租户ID(不传代表全平台)。
|
||||||
|
TenantID *int64 `query:"tenant_id"`
|
||||||
|
// StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。
|
||||||
|
StartAt *string `query:"start_at"`
|
||||||
|
// EndAt 统计结束时间(RFC3339,可选;默认当前时间)。
|
||||||
|
EndAt *string `query:"end_at"`
|
||||||
|
// UploadStuckHours 上传处理超时时长(小时,可选;默认 24 小时)。
|
||||||
|
UploadStuckHours *int64 `query:"upload_stuck_hours"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperHealthOverviewResponse 超管健康概览响应。
|
||||||
|
type SuperHealthOverviewResponse struct {
|
||||||
|
// TenantID 当前统计的租户ID(0 代表全平台)。
|
||||||
|
TenantID int64 `json:"tenant_id"`
|
||||||
|
// StartAt 统计开始时间(RFC3339)。
|
||||||
|
StartAt string `json:"start_at"`
|
||||||
|
// EndAt 统计结束时间(RFC3339)。
|
||||||
|
EndAt string `json:"end_at"`
|
||||||
|
// UploadStuckHours 上传处理超时时长(小时)。
|
||||||
|
UploadStuckHours int64 `json:"upload_stuck_hours"`
|
||||||
|
// TenantTotal 租户总数。
|
||||||
|
TenantTotal int64 `json:"tenant_total"`
|
||||||
|
// TenantWarningCount 健康预警租户数。
|
||||||
|
TenantWarningCount int64 `json:"tenant_warning_count"`
|
||||||
|
// TenantRiskCount 健康风险租户数。
|
||||||
|
TenantRiskCount int64 `json:"tenant_risk_count"`
|
||||||
|
// OrderTotal 订单总数(统计区间内)。
|
||||||
|
OrderTotal int64 `json:"order_total"`
|
||||||
|
// OrderFailed 失败订单数(统计区间内)。
|
||||||
|
OrderFailed int64 `json:"order_failed"`
|
||||||
|
// OrderFailedRate 失败率(失败订单数 / 总订单数)。
|
||||||
|
OrderFailedRate float64 `json:"order_failed_rate"`
|
||||||
|
// UploadTotal 上传资产总数(统计区间内)。
|
||||||
|
UploadTotal int64 `json:"upload_total"`
|
||||||
|
// UploadFailed 上传失败资产数(统计区间内)。
|
||||||
|
UploadFailed int64 `json:"upload_failed"`
|
||||||
|
// UploadFailedRate 上传失败率(失败资产数 / 总资产数)。
|
||||||
|
UploadFailedRate float64 `json:"upload_failed_rate"`
|
||||||
|
// UploadProcessingStuck 处理超时资产数(processing 且超时)。
|
||||||
|
UploadProcessingStuck int64 `json:"upload_processing_stuck"`
|
||||||
|
// UploadUploadedStuck 已上传但未进入处理的超时资产数(uploaded 且超时)。
|
||||||
|
UploadUploadedStuck int64 `json:"upload_uploaded_stuck"`
|
||||||
|
// StorageTotalCount 资产总量(全量)。
|
||||||
|
StorageTotalCount int64 `json:"storage_total_count"`
|
||||||
|
// StorageTotalSize 资产总大小(字节,全量)。
|
||||||
|
StorageTotalSize int64 `json:"storage_total_size"`
|
||||||
|
// Alerts 健康告警列表。
|
||||||
|
Alerts []SuperHealthAlertItem `json:"alerts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperHealthAlertItem 健康告警条目。
|
||||||
|
type SuperHealthAlertItem struct {
|
||||||
|
// Level 告警级别(warning/risk)。
|
||||||
|
Level string `json:"level"`
|
||||||
|
// Kind 告警类型(order_error_rate/upload_error_rate/upload_stuck/tenant_health)。
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
// TenantID 关联租户ID(无则为 0)。
|
||||||
|
TenantID int64 `json:"tenant_id"`
|
||||||
|
// TenantCode 关联租户编码。
|
||||||
|
TenantCode string `json:"tenant_code"`
|
||||||
|
// TenantName 关联租户名称。
|
||||||
|
TenantName string `json:"tenant_name"`
|
||||||
|
// Title 告警标题。
|
||||||
|
Title string `json:"title"`
|
||||||
|
// Detail 告警详情说明。
|
||||||
|
Detail string `json:"detail"`
|
||||||
|
// Count 关联数量(例如失败数、超时数)。
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
// UpdatedAt 告警更新时间(RFC3339)。
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
29
backend/app/http/super/v1/healths.go
Normal file
29
backend/app/http/super/v1/healths.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
dto "quyun/v2/app/http/super/v1/dto"
|
||||||
|
"quyun/v2/app/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @provider
|
||||||
|
type healths struct{}
|
||||||
|
|
||||||
|
// Health overview
|
||||||
|
//
|
||||||
|
// @Router /super/v1/health/overview [get]
|
||||||
|
// @Summary Health overview
|
||||||
|
// @Description Platform health overview
|
||||||
|
// @Tags Health
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param tenant_id query int false "Tenant ID"
|
||||||
|
// @Param start_at query string false "Start time"
|
||||||
|
// @Param end_at query string false "End time"
|
||||||
|
// @Param upload_stuck_hours query int false "Upload stuck hours"
|
||||||
|
// @Success 200 {object} dto.SuperHealthOverviewResponse
|
||||||
|
// @Bind filter query
|
||||||
|
func (c *healths) Overview(ctx fiber.Ctx, filter *dto.SuperHealthOverviewFilter) (*dto.SuperHealthOverviewResponse, error) {
|
||||||
|
return services.Super.HealthOverview(ctx, filter)
|
||||||
|
}
|
||||||
@@ -73,6 +73,13 @@ func Provide(opts ...opt.Option) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := container.Container.Provide(func() (*healths, error) {
|
||||||
|
obj := &healths{}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := container.Container.Provide(func() (*notifications, error) {
|
if err := container.Container.Provide(func() (*notifications, error) {
|
||||||
obj := ¬ifications{}
|
obj := ¬ifications{}
|
||||||
|
|
||||||
@@ -111,6 +118,7 @@ func Provide(opts ...opt.Option) error {
|
|||||||
creatorApplications *creatorApplications,
|
creatorApplications *creatorApplications,
|
||||||
creators *creators,
|
creators *creators,
|
||||||
finance *finance,
|
finance *finance,
|
||||||
|
healths *healths,
|
||||||
middlewares *middlewares.Middlewares,
|
middlewares *middlewares.Middlewares,
|
||||||
notifications *notifications,
|
notifications *notifications,
|
||||||
orders *orders,
|
orders *orders,
|
||||||
@@ -131,6 +139,7 @@ func Provide(opts ...opt.Option) error {
|
|||||||
creatorApplications: creatorApplications,
|
creatorApplications: creatorApplications,
|
||||||
creators: creators,
|
creators: creators,
|
||||||
finance: finance,
|
finance: finance,
|
||||||
|
healths: healths,
|
||||||
middlewares: middlewares,
|
middlewares: middlewares,
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
orders: orders,
|
orders: orders,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type Routes struct {
|
|||||||
creatorApplications *creatorApplications
|
creatorApplications *creatorApplications
|
||||||
creators *creators
|
creators *creators
|
||||||
finance *finance
|
finance *finance
|
||||||
|
healths *healths
|
||||||
notifications *notifications
|
notifications *notifications
|
||||||
orders *orders
|
orders *orders
|
||||||
payoutAccounts *payoutAccounts
|
payoutAccounts *payoutAccounts
|
||||||
@@ -243,6 +244,12 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
r.finance.ListLedgers,
|
r.finance.ListLedgers,
|
||||||
Query[dto.SuperLedgerListFilter]("filter"),
|
Query[dto.SuperLedgerListFilter]("filter"),
|
||||||
))
|
))
|
||||||
|
// Register routes for controller: healths
|
||||||
|
r.log.Debugf("Registering route: Get /super/v1/health/overview -> healths.Overview")
|
||||||
|
router.Get("/super/v1/health/overview"[len(r.Path()):], DataFunc1(
|
||||||
|
r.healths.Overview,
|
||||||
|
Query[dto.SuperHealthOverviewFilter]("filter"),
|
||||||
|
))
|
||||||
// Register routes for controller: notifications
|
// Register routes for controller: notifications
|
||||||
r.log.Debugf("Registering route: Get /super/v1/notifications -> notifications.List")
|
r.log.Debugf("Registering route: Get /super/v1/notifications -> notifications.List")
|
||||||
router.Get("/super/v1/notifications"[len(r.Path()):], DataFunc1(
|
router.Get("/super/v1/notifications"[len(r.Path()):], DataFunc1(
|
||||||
|
|||||||
@@ -1153,6 +1153,55 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/health/overview": {
|
||||||
|
"get": {
|
||||||
|
"description": "Platform health overview",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Health"
|
||||||
|
],
|
||||||
|
"summary": "Health overview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Tenant ID",
|
||||||
|
"name": "tenant_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Start time",
|
||||||
|
"name": "start_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "End time",
|
||||||
|
"name": "end_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Upload stuck hours",
|
||||||
|
"name": "upload_stuck_hours",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.SuperHealthOverviewResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/notifications": {
|
"/super/v1/notifications": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List notifications across tenants",
|
"description": "List notifications across tenants",
|
||||||
@@ -8669,6 +8718,127 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperHealthAlertItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"description": "Count 关联数量(例如失败数、超时数)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"description": "Detail 告警详情说明。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"description": "Kind 告警类型(order_error_rate/upload_error_rate/upload_stuck/tenant_health)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"description": "Level 告警级别(warning/risk)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_code": {
|
||||||
|
"description": "TenantCode 关联租户编码。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"description": "TenantID 关联租户ID(无则为 0)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_name": {
|
||||||
|
"description": "TenantName 关联租户名称。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "Title 告警标题。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"description": "UpdatedAt 告警更新时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.SuperHealthOverviewResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"alerts": {
|
||||||
|
"description": "Alerts 健康告警列表。",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.SuperHealthAlertItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end_at": {
|
||||||
|
"description": "EndAt 统计结束时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order_failed": {
|
||||||
|
"description": "OrderFailed 失败订单数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"order_failed_rate": {
|
||||||
|
"description": "OrderFailedRate 失败率(失败订单数 / 总订单数)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"order_total": {
|
||||||
|
"description": "OrderTotal 订单总数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"start_at": {
|
||||||
|
"description": "StartAt 统计开始时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"storage_total_count": {
|
||||||
|
"description": "StorageTotalCount 资产总量(全量)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"storage_total_size": {
|
||||||
|
"description": "StorageTotalSize 资产总大小(字节,全量)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"description": "TenantID 当前统计的租户ID(0 代表全平台)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_risk_count": {
|
||||||
|
"description": "TenantRiskCount 健康风险租户数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_total": {
|
||||||
|
"description": "TenantTotal 租户总数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_warning_count": {
|
||||||
|
"description": "TenantWarningCount 健康预警租户数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_failed": {
|
||||||
|
"description": "UploadFailed 上传失败资产数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_failed_rate": {
|
||||||
|
"description": "UploadFailedRate 上传失败率(失败资产数 / 总资产数)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"upload_processing_stuck": {
|
||||||
|
"description": "UploadProcessingStuck 处理超时资产数(processing 且超时)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_stuck_hours": {
|
||||||
|
"description": "UploadStuckHours 上传处理超时时长(小时)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_total": {
|
||||||
|
"description": "UploadTotal 上传资产总数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_uploaded_stuck": {
|
||||||
|
"description": "UploadUploadedStuck 已上传但未进入处理的超时资产数(uploaded 且超时)。",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperLedgerItem": {
|
"dto.SuperLedgerItem": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1147,6 +1147,55 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/health/overview": {
|
||||||
|
"get": {
|
||||||
|
"description": "Platform health overview",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Health"
|
||||||
|
],
|
||||||
|
"summary": "Health overview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Tenant ID",
|
||||||
|
"name": "tenant_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Start time",
|
||||||
|
"name": "start_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "End time",
|
||||||
|
"name": "end_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Upload stuck hours",
|
||||||
|
"name": "upload_stuck_hours",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.SuperHealthOverviewResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/notifications": {
|
"/super/v1/notifications": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List notifications across tenants",
|
"description": "List notifications across tenants",
|
||||||
@@ -8663,6 +8712,127 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperHealthAlertItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"description": "Count 关联数量(例如失败数、超时数)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"description": "Detail 告警详情说明。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"description": "Kind 告警类型(order_error_rate/upload_error_rate/upload_stuck/tenant_health)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"description": "Level 告警级别(warning/risk)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_code": {
|
||||||
|
"description": "TenantCode 关联租户编码。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"description": "TenantID 关联租户ID(无则为 0)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_name": {
|
||||||
|
"description": "TenantName 关联租户名称。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "Title 告警标题。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"description": "UpdatedAt 告警更新时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.SuperHealthOverviewResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"alerts": {
|
||||||
|
"description": "Alerts 健康告警列表。",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.SuperHealthAlertItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end_at": {
|
||||||
|
"description": "EndAt 统计结束时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order_failed": {
|
||||||
|
"description": "OrderFailed 失败订单数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"order_failed_rate": {
|
||||||
|
"description": "OrderFailedRate 失败率(失败订单数 / 总订单数)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"order_total": {
|
||||||
|
"description": "OrderTotal 订单总数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"start_at": {
|
||||||
|
"description": "StartAt 统计开始时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"storage_total_count": {
|
||||||
|
"description": "StorageTotalCount 资产总量(全量)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"storage_total_size": {
|
||||||
|
"description": "StorageTotalSize 资产总大小(字节,全量)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"description": "TenantID 当前统计的租户ID(0 代表全平台)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_risk_count": {
|
||||||
|
"description": "TenantRiskCount 健康风险租户数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_total": {
|
||||||
|
"description": "TenantTotal 租户总数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_warning_count": {
|
||||||
|
"description": "TenantWarningCount 健康预警租户数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_failed": {
|
||||||
|
"description": "UploadFailed 上传失败资产数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_failed_rate": {
|
||||||
|
"description": "UploadFailedRate 上传失败率(失败资产数 / 总资产数)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"upload_processing_stuck": {
|
||||||
|
"description": "UploadProcessingStuck 处理超时资产数(processing 且超时)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_stuck_hours": {
|
||||||
|
"description": "UploadStuckHours 上传处理超时时长(小时)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_total": {
|
||||||
|
"description": "UploadTotal 上传资产总数(统计区间内)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_uploaded_stuck": {
|
||||||
|
"description": "UploadUploadedStuck 已上传但未进入处理的超时资产数(uploaded 且超时)。",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperLedgerItem": {
|
"dto.SuperLedgerItem": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1762,6 +1762,95 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- action
|
- action
|
||||||
type: object
|
type: object
|
||||||
|
dto.SuperHealthAlertItem:
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
description: Count 关联数量(例如失败数、超时数)。
|
||||||
|
type: integer
|
||||||
|
detail:
|
||||||
|
description: Detail 告警详情说明。
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: Kind 告警类型(order_error_rate/upload_error_rate/upload_stuck/tenant_health)。
|
||||||
|
type: string
|
||||||
|
level:
|
||||||
|
description: Level 告警级别(warning/risk)。
|
||||||
|
type: string
|
||||||
|
tenant_code:
|
||||||
|
description: TenantCode 关联租户编码。
|
||||||
|
type: string
|
||||||
|
tenant_id:
|
||||||
|
description: TenantID 关联租户ID(无则为 0)。
|
||||||
|
type: integer
|
||||||
|
tenant_name:
|
||||||
|
description: TenantName 关联租户名称。
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: Title 告警标题。
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
description: UpdatedAt 告警更新时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.SuperHealthOverviewResponse:
|
||||||
|
properties:
|
||||||
|
alerts:
|
||||||
|
description: Alerts 健康告警列表。
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/dto.SuperHealthAlertItem'
|
||||||
|
type: array
|
||||||
|
end_at:
|
||||||
|
description: EndAt 统计结束时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
order_failed:
|
||||||
|
description: OrderFailed 失败订单数(统计区间内)。
|
||||||
|
type: integer
|
||||||
|
order_failed_rate:
|
||||||
|
description: OrderFailedRate 失败率(失败订单数 / 总订单数)。
|
||||||
|
type: number
|
||||||
|
order_total:
|
||||||
|
description: OrderTotal 订单总数(统计区间内)。
|
||||||
|
type: integer
|
||||||
|
start_at:
|
||||||
|
description: StartAt 统计开始时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
storage_total_count:
|
||||||
|
description: StorageTotalCount 资产总量(全量)。
|
||||||
|
type: integer
|
||||||
|
storage_total_size:
|
||||||
|
description: StorageTotalSize 资产总大小(字节,全量)。
|
||||||
|
type: integer
|
||||||
|
tenant_id:
|
||||||
|
description: TenantID 当前统计的租户ID(0 代表全平台)。
|
||||||
|
type: integer
|
||||||
|
tenant_risk_count:
|
||||||
|
description: TenantRiskCount 健康风险租户数。
|
||||||
|
type: integer
|
||||||
|
tenant_total:
|
||||||
|
description: TenantTotal 租户总数。
|
||||||
|
type: integer
|
||||||
|
tenant_warning_count:
|
||||||
|
description: TenantWarningCount 健康预警租户数。
|
||||||
|
type: integer
|
||||||
|
upload_failed:
|
||||||
|
description: UploadFailed 上传失败资产数(统计区间内)。
|
||||||
|
type: integer
|
||||||
|
upload_failed_rate:
|
||||||
|
description: UploadFailedRate 上传失败率(失败资产数 / 总资产数)。
|
||||||
|
type: number
|
||||||
|
upload_processing_stuck:
|
||||||
|
description: UploadProcessingStuck 处理超时资产数(processing 且超时)。
|
||||||
|
type: integer
|
||||||
|
upload_stuck_hours:
|
||||||
|
description: UploadStuckHours 上传处理超时时长(小时)。
|
||||||
|
type: integer
|
||||||
|
upload_total:
|
||||||
|
description: UploadTotal 上传资产总数(统计区间内)。
|
||||||
|
type: integer
|
||||||
|
upload_uploaded_stuck:
|
||||||
|
description: UploadUploadedStuck 已上传但未进入处理的超时资产数(uploaded 且超时)。
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
dto.SuperLedgerItem:
|
dto.SuperLedgerItem:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
@@ -4064,6 +4153,38 @@ paths:
|
|||||||
summary: List ledgers
|
summary: List ledgers
|
||||||
tags:
|
tags:
|
||||||
- Finance
|
- Finance
|
||||||
|
/super/v1/health/overview:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Platform health overview
|
||||||
|
parameters:
|
||||||
|
- description: Tenant ID
|
||||||
|
in: query
|
||||||
|
name: tenant_id
|
||||||
|
type: integer
|
||||||
|
- description: Start time
|
||||||
|
in: query
|
||||||
|
name: start_at
|
||||||
|
type: string
|
||||||
|
- description: End time
|
||||||
|
in: query
|
||||||
|
name: end_at
|
||||||
|
type: string
|
||||||
|
- description: Upload stuck hours
|
||||||
|
in: query
|
||||||
|
name: upload_stuck_hours
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.SuperHealthOverviewResponse'
|
||||||
|
summary: Health overview
|
||||||
|
tags:
|
||||||
|
- Health
|
||||||
/super/v1/notifications:
|
/super/v1/notifications:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -87,6 +87,11 @@
|
|||||||
- **接口**:用户侧 `GET /t/:tenantCode/v1/me/notifications`。
|
- **接口**:用户侧 `GET /t/:tenantCode/v1/me/notifications`。
|
||||||
- **缺口**:超管发送与批量触达接口、通知模板管理接口。
|
- **缺口**:超管发送与批量触达接口、通知模板管理接口。
|
||||||
|
|
||||||
|
### 2.15 健康与告警 `/superadmin/health`
|
||||||
|
- **功能**:平台健康总览、失败率/超时/风险租户聚合、告警列表。
|
||||||
|
- **接口**:`GET /super/v1/health/overview`、`GET /super/v1/tenants/health`(租户级健康明细)。
|
||||||
|
- **缺口**:后续可补充告警订阅与自动化处理规则。
|
||||||
|
|
||||||
## 3) 导航与便捷性设计(建议)
|
## 3) 导航与便捷性设计(建议)
|
||||||
|
|
||||||
- 统一筛选器:租户/用户/时间/状态为默认筛选维度。
|
- 统一筛选器:租户/用户/时间/状态为默认筛选维度。
|
||||||
@@ -97,4 +102,4 @@
|
|||||||
|
|
||||||
- **P0(已有接口即可落地)**:登录、概览、租户管理、用户管理、内容治理、订单退款。
|
- **P0(已有接口即可落地)**:登录、概览、租户管理、用户管理、内容治理、订单退款。
|
||||||
- **P1(需补超管接口)**:创作者审核、优惠券、提现/钱包、报表导出。
|
- **P1(需补超管接口)**:创作者审核、优惠券、提现/钱包、报表导出。
|
||||||
- **P2(扩展增强)**:资产/上传、通知中心、审计与系统配置。
|
- **P2(扩展增强)**:资产/上传、通知中心、审计与系统配置、健康与告警。
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 1) 总体结论
|
## 1) 总体结论
|
||||||
|
|
||||||
- **已落地**:登录、租户/用户/订单/内容基础管理、内容审核(含批量)、平台概览(内容趋势/退款率/漏斗)、提现审核、钱包流水与异常排查、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录、互动(收藏/点赞/关注)与内容消费明细视图、创作者申请/成员审核/邀请、优惠券创建/编辑/发放/冻结/发放记录/异常核查、资产治理(列表/用量/清理)、通知中心(列表/群发/模板)、审计日志与系统配置、评论治理。
|
- **已落地**:登录、租户/用户/订单/内容基础管理、内容审核(含批量)、平台概览(内容趋势/退款率/漏斗)、提现审核、钱包流水与异常排查、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录、互动(收藏/点赞/关注)与内容消费明细视图、创作者申请/成员审核/邀请、优惠券创建/编辑/发放/冻结/发放记录/异常核查、资产治理(列表/用量/清理)、通知中心(列表/群发/模板)、健康与告警中心、审计日志与系统配置、评论治理。
|
||||||
- **部分落地**:暂无。
|
- **部分落地**:暂无。
|
||||||
- **未落地**:暂无。
|
- **未落地**:暂无。
|
||||||
|
|
||||||
@@ -80,14 +80,19 @@
|
|||||||
- 已有:通知列表、批量发送、模板管理。
|
- 已有:通知列表、批量发送、模板管理。
|
||||||
- 缺口:无显著功能缺口。
|
- 缺口:无显著功能缺口。
|
||||||
|
|
||||||
### 2.15 审计与系统配置 `/superadmin/audit-logs` `/superadmin/system-configs`
|
### 2.15 健康与告警 `/superadmin/health`
|
||||||
|
- 状态:**已完成**
|
||||||
|
- 已有:平台健康概览、失败率/超时统计、风险租户告警列表。
|
||||||
|
- 缺口:无显著功能缺口。
|
||||||
|
|
||||||
|
### 2.16 审计与系统配置 `/superadmin/audit-logs` `/superadmin/system-configs`
|
||||||
- 状态:**已完成**
|
- 状态:**已完成**
|
||||||
- 已有:跨租户审计日志查询、系统配置列表/创建/更新。
|
- 已有:跨租户审计日志查询、系统配置列表/创建/更新。
|
||||||
- 缺口:无显著功能缺口。
|
- 缺口:无显著功能缺口。
|
||||||
|
|
||||||
## 3) `/super/v1` 接口覆盖度概览
|
## 3) `/super/v1` 接口覆盖度概览
|
||||||
|
|
||||||
- **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包/通知/优惠券/实名/互动/内容消费)、Contents(含评论治理)、Orders、Withdrawals、Finance(流水/异常)、Reports、Coupons(列表/创建/编辑/发放/冻结/记录)、Creators(列表/申请/成员审核)、Payout Accounts(列表/删除)、Assets(列表/用量/删除)、Notifications(列表/群发/模板)。
|
- **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包/通知/优惠券/实名/互动/内容消费)、Contents(含评论治理)、Orders、Withdrawals、Finance(流水/异常)、Reports、Coupons(列表/创建/编辑/发放/冻结/记录)、Creators(列表/申请/成员审核)、Payout Accounts(列表/删除)、Assets(列表/用量/删除)、Notifications(列表/群发/模板)、Health(平台健康概览)。
|
||||||
- **缺失/待补**:无。
|
- **缺失/待补**:无。
|
||||||
|
|
||||||
## 4) 建议的下一步(按优先级)
|
## 4) 建议的下一步(按优先级)
|
||||||
|
|||||||
@@ -32,12 +32,13 @@
|
|||||||
- `/superadmin/reports`:报表与导出(待补超管接口)
|
- `/superadmin/reports`:报表与导出(待补超管接口)
|
||||||
- `/superadmin/assets`:资产与上传(待补超管接口)
|
- `/superadmin/assets`:资产与上传(待补超管接口)
|
||||||
- `/superadmin/notifications`:通知与消息(待补超管接口)
|
- `/superadmin/notifications`:通知与消息(待补超管接口)
|
||||||
|
- `/superadmin/health`:健康与告警中心
|
||||||
|
|
||||||
## 1.1 迭代路线(按接口可用性)
|
## 1.1 迭代路线(按接口可用性)
|
||||||
|
|
||||||
- **P0(已有接口可落地)**:登录、概览、租户管理/详情、用户管理/详情、订单列表/详情/退款、内容治理。
|
- **P0(已有接口可落地)**:登录、概览、租户管理/详情、用户管理/详情、订单列表/详情/退款、内容治理。
|
||||||
- **P1(需补超管接口)**:创作者审核、优惠券、财务/提现、报表导出。
|
- **P1(需补超管接口)**:创作者审核、优惠券、财务/提现、报表导出。
|
||||||
- **P2(扩展增强)**:资产/上传治理、通知中心、审计日志。
|
- **P2(扩展增强)**:资产/上传治理、通知中心、审计日志、健康与告警。
|
||||||
|
|
||||||
## 2. 页面规格(页面 → 功能 → API)
|
## 2. 页面规格(页面 → 功能 → API)
|
||||||
|
|
||||||
@@ -188,6 +189,13 @@
|
|||||||
- `GET /super/v1/notifications/templates`
|
- `GET /super/v1/notifications/templates`
|
||||||
- `POST /super/v1/notifications/templates`
|
- `POST /super/v1/notifications/templates`
|
||||||
|
|
||||||
|
### 2.16 健康与告警 `/superadmin/health`
|
||||||
|
|
||||||
|
- 平台健康总览、失败率/超时统计、风险租户告警列表。
|
||||||
|
- API:
|
||||||
|
- `GET /super/v1/health/overview`
|
||||||
|
- `GET /super/v1/tenants/health`
|
||||||
|
|
||||||
## 3. 枚举与数据结构(UI 需要)
|
## 3. 枚举与数据结构(UI 需要)
|
||||||
|
|
||||||
- 租户状态:`consts.TenantStatus`(`pending_verify` / `verified` / `banned`),推荐用 `GET /super/v1/tenants/statuses` 驱动展示 label
|
- 租户状态:`consts.TenantStatus`(`pending_verify` / `verified` / `banned`),推荐用 `GET /super/v1/tenants/statuses` 驱动展示 label
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const model = ref([
|
|||||||
label: 'Super Admin',
|
label: 'Super Admin',
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Tenants', icon: 'pi pi-fw pi-building', to: '/superadmin/tenants' },
|
{ label: 'Tenants', icon: 'pi pi-fw pi-building', to: '/superadmin/tenants' },
|
||||||
|
{ label: 'Health Center', icon: 'pi pi-fw pi-heart', to: '/superadmin/health' },
|
||||||
{ label: 'Users', icon: 'pi pi-fw pi-users', to: '/superadmin/users' },
|
{ label: 'Users', icon: 'pi pi-fw pi-users', to: '/superadmin/users' },
|
||||||
{ label: 'Orders', icon: 'pi pi-fw pi-shopping-cart', to: '/superadmin/orders' },
|
{ label: 'Orders', icon: 'pi pi-fw pi-shopping-cart', to: '/superadmin/orders' },
|
||||||
{ label: 'Contents', icon: 'pi pi-fw pi-file', to: '/superadmin/contents' },
|
{ label: 'Contents', icon: 'pi pi-fw pi-file', to: '/superadmin/contents' },
|
||||||
|
|||||||
@@ -119,6 +119,11 @@ const router = createRouter({
|
|||||||
name: 'superadmin-tenants',
|
name: 'superadmin-tenants',
|
||||||
component: () => import('@/views/superadmin/Tenants.vue')
|
component: () => import('@/views/superadmin/Tenants.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/health',
|
||||||
|
name: 'superadmin-health',
|
||||||
|
component: () => import('@/views/superadmin/HealthOverview.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/superadmin/tenants/:tenantID',
|
path: '/superadmin/tenants/:tenantID',
|
||||||
name: 'superadmin-tenant-detail',
|
name: 'superadmin-tenant-detail',
|
||||||
|
|||||||
21
frontend/superadmin/src/service/HealthService.js
Normal file
21
frontend/superadmin/src/service/HealthService.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { requestJson } from './apiClient';
|
||||||
|
|
||||||
|
export const HealthService = {
|
||||||
|
async getOverview({ tenant_id, start_at, end_at, upload_stuck_hours } = {}) {
|
||||||
|
const iso = (d) => {
|
||||||
|
if (!d) return undefined;
|
||||||
|
const date = d instanceof Date ? d : new Date(d);
|
||||||
|
if (Number.isNaN(date.getTime())) return undefined;
|
||||||
|
return date.toISOString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
tenant_id,
|
||||||
|
start_at: iso(start_at),
|
||||||
|
end_at: iso(end_at),
|
||||||
|
upload_stuck_hours
|
||||||
|
};
|
||||||
|
|
||||||
|
return requestJson('/super/v1/health/overview', { query });
|
||||||
|
}
|
||||||
|
};
|
||||||
239
frontend/superadmin/src/views/superadmin/HealthOverview.vue
Normal file
239
frontend/superadmin/src/views/superadmin/HealthOverview.vue
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<script setup>
|
||||||
|
import SearchField from '@/components/SearchField.vue';
|
||||||
|
import SearchPanel from '@/components/SearchPanel.vue';
|
||||||
|
import StatisticsStrip from '@/components/StatisticsStrip.vue';
|
||||||
|
import { HealthService } from '@/service/HealthService';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const overview = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const tenantID = ref(null);
|
||||||
|
const startAt = ref(null);
|
||||||
|
const endAt = ref(null);
|
||||||
|
const uploadStuckHours = ref(24);
|
||||||
|
|
||||||
|
const defaultRangeDays = 7;
|
||||||
|
|
||||||
|
function formatDate(value) {
|
||||||
|
if (!value) return '-';
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return String(value);
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPercent(value) {
|
||||||
|
const rate = Number(value);
|
||||||
|
if (!Number.isFinite(rate)) return '-';
|
||||||
|
return `${(rate * 100).toFixed(2)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
const value = Number(bytes);
|
||||||
|
if (!Number.isFinite(value)) return '-';
|
||||||
|
if (value < 1024) return `${value} B`;
|
||||||
|
const units = ['KB', 'MB', 'GB', 'TB'];
|
||||||
|
let size = value;
|
||||||
|
let idx = -1;
|
||||||
|
while (size >= 1024 && idx < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
return `${size.toFixed(1)} ${units[idx]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlertSeverity(level) {
|
||||||
|
switch (level) {
|
||||||
|
case 'risk':
|
||||||
|
return 'danger';
|
||||||
|
case 'warning':
|
||||||
|
return 'warn';
|
||||||
|
default:
|
||||||
|
return 'secondary';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAlertLevel(level) {
|
||||||
|
switch (level) {
|
||||||
|
case 'risk':
|
||||||
|
return '风险';
|
||||||
|
case 'warning':
|
||||||
|
return '预警';
|
||||||
|
default:
|
||||||
|
return level || '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAlertKind(kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case 'order_error_rate':
|
||||||
|
return '订单失败率';
|
||||||
|
case 'upload_error_rate':
|
||||||
|
return '上传失败率';
|
||||||
|
case 'upload_stuck':
|
||||||
|
return '上传超时';
|
||||||
|
case 'tenant_health':
|
||||||
|
return '租户健康';
|
||||||
|
default:
|
||||||
|
return kind || '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeText = computed(() => {
|
||||||
|
if (!overview.value) return '';
|
||||||
|
return `统计区间:${formatDate(overview.value.start_at)} ~ ${formatDate(overview.value.end_at)}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const coreItems = computed(() => {
|
||||||
|
const data = overview.value || {};
|
||||||
|
const warningCount = Number(data.tenant_warning_count ?? 0);
|
||||||
|
const riskCount = Number(data.tenant_risk_count ?? 0);
|
||||||
|
const orderFailedRate = Number(data.order_failed_rate ?? 0);
|
||||||
|
const failedRateClass = orderFailedRate >= 0.2 ? 'text-red-500' : orderFailedRate >= 0.1 ? 'text-orange-500' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ key: 'tenants-total', label: '租户总数:', value: loading.value ? '-' : (data.tenant_total ?? 0), icon: 'pi-building' },
|
||||||
|
{ key: 'tenants-warning', label: '预警租户:', value: loading.value ? '-' : warningCount, icon: 'pi-exclamation-triangle', valueClass: warningCount ? 'text-orange-500' : '' },
|
||||||
|
{ key: 'tenants-risk', label: '风险租户:', value: loading.value ? '-' : riskCount, icon: 'pi-times-circle', valueClass: riskCount ? 'text-red-500' : '' },
|
||||||
|
{ key: 'orders-total', label: '订单总数:', value: loading.value ? '-' : (data.order_total ?? 0), icon: 'pi-shopping-cart' },
|
||||||
|
{ key: 'orders-failed', label: '失败订单:', value: loading.value ? '-' : (data.order_failed ?? 0), icon: 'pi-ban', valueClass: data.order_failed ? 'text-red-500' : '' },
|
||||||
|
{ key: 'orders-failed-rate', label: '失败率:', value: loading.value ? '-' : formatPercent(data.order_failed_rate), icon: 'pi-percentage', valueClass: failedRateClass }
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadItems = computed(() => {
|
||||||
|
const data = overview.value || {};
|
||||||
|
const uploadFailedRate = Number(data.upload_failed_rate ?? 0);
|
||||||
|
const failedRateClass = uploadFailedRate >= 0.2 ? 'text-red-500' : uploadFailedRate >= 0.1 ? 'text-orange-500' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ key: 'uploads-total', label: '上传总量:', value: loading.value ? '-' : (data.upload_total ?? 0), icon: 'pi-upload' },
|
||||||
|
{ key: 'uploads-failed', label: '失败上传:', value: loading.value ? '-' : (data.upload_failed ?? 0), icon: 'pi-times', valueClass: data.upload_failed ? 'text-red-500' : '' },
|
||||||
|
{ key: 'uploads-failed-rate', label: '上传失败率:', value: loading.value ? '-' : formatPercent(data.upload_failed_rate), icon: 'pi-percentage', valueClass: failedRateClass },
|
||||||
|
{ key: 'uploads-stuck-processing', label: '处理超时:', value: loading.value ? '-' : (data.upload_processing_stuck ?? 0), icon: 'pi-clock', valueClass: data.upload_processing_stuck ? 'text-orange-500' : '' },
|
||||||
|
{ key: 'uploads-stuck-uploaded', label: '入库滞留:', value: loading.value ? '-' : (data.upload_uploaded_stuck ?? 0), icon: 'pi-exclamation-circle', valueClass: data.upload_uploaded_stuck ? 'text-orange-500' : '' },
|
||||||
|
{ key: 'storage-total', label: '资产总量:', value: loading.value ? '-' : (data.storage_total_count ?? 0), icon: 'pi-images' },
|
||||||
|
{ key: 'storage-size', label: '资产大小:', value: loading.value ? '-' : formatBytes(data.storage_total_size ?? 0), icon: 'pi-database' }
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const alertItems = computed(() => overview.value?.alerts || []);
|
||||||
|
|
||||||
|
async function loadOverview() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
overview.value = await HealthService.getOverview({
|
||||||
|
tenant_id: tenantID.value || undefined,
|
||||||
|
start_at: startAt.value || undefined,
|
||||||
|
end_at: endAt.value || undefined,
|
||||||
|
upload_stuck_hours: uploadStuckHours.value || undefined
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载健康概览', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date(end);
|
||||||
|
start.setDate(start.getDate() - defaultRangeDays);
|
||||||
|
startAt.value = start;
|
||||||
|
endAt.value = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSearch() {
|
||||||
|
loadOverview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReset() {
|
||||||
|
tenantID.value = null;
|
||||||
|
uploadStuckHours.value = 24;
|
||||||
|
applyDefaultRange();
|
||||||
|
loadOverview();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
applyDefaultRange();
|
||||||
|
loadOverview();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="flex items-start justify-between gap-3 mb-4">
|
||||||
|
<div>
|
||||||
|
<h4 class="m-0">平台健康与告警</h4>
|
||||||
|
<p class="text-sm text-muted-color mt-2">{{ rangeText }}</p>
|
||||||
|
</div>
|
||||||
|
<Button label="刷新" icon="pi pi-refresh" severity="secondary" :loading="loading" @click="loadOverview" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SearchPanel :loading="loading" @search="onSearch" @reset="onReset">
|
||||||
|
<SearchField label="TenantID">
|
||||||
|
<InputNumber v-model="tenantID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="统计开始">
|
||||||
|
<DatePicker v-model="startAt" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="统计结束">
|
||||||
|
<DatePicker v-model="endAt" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="上传超时(小时)">
|
||||||
|
<InputNumber v-model="uploadStuckHours" :min="1" :max="168" placeholder="例如 24" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
</SearchPanel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StatisticsStrip :items="coreItems" containerClass="card mb-4" />
|
||||||
|
<StatisticsStrip :items="uploadItems" containerClass="card mb-4" />
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h5 class="m-0">健康告警</h5>
|
||||||
|
<Tag :value="`共 ${alertItems.length} 条`" severity="secondary" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable :value="alertItems" :loading="loading">
|
||||||
|
<template #empty>
|
||||||
|
<span class="text-muted-color">暂无告警</span>
|
||||||
|
</template>
|
||||||
|
<Column header="级别" style="min-width: 8rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Tag :value="formatAlertLevel(data.level)" :severity="getAlertSeverity(data.level)" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="类型" style="min-width: 10rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatAlertKind(data.kind) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="租户" style="min-width: 14rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="font-medium">{{ data.tenant_name || '-' }}</span>
|
||||||
|
<span class="text-sm text-muted-color">{{ data.tenant_code || (data.tenant_id ? `ID:${data.tenant_id}` : '-') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="title" header="标题" style="min-width: 14rem" />
|
||||||
|
<Column header="详情" style="min-width: 18rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<span class="block max-w-[360px] truncate" :title="data.detail">{{ data.detail || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="count" header="数量" style="min-width: 8rem" />
|
||||||
|
<Column field="updated_at" header="更新时间" style="min-width: 14rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatDate(data.updated_at) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user