feat(web): add zone-based website management

以稳定 Zone ID 替换旧托管域名详情页:列表展示根域与域名计数,详情提供
概览/域名/路由/证书/设置 Tabs,并补齐 ZoneService 与 vitest 行为测试。
Zone 列表 API 返回 domain_count;同步 Swagger 与重构计划进度。
This commit is contained in:
ryan
2026-07-12 15:23:43 +08:00
parent d4d9bad74d
commit 350b433cc1
30 changed files with 2369 additions and 1178 deletions
+45 -4
View File
@@ -8,6 +8,7 @@ import (
"context"
"errors"
"strings"
"time"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
@@ -34,6 +35,16 @@ type Overview struct {
Domains []model.ZoneDomain `json:"domains"`
}
// ListItem is a Zone list row with denormalized domain count for the UI.
type ListItem struct {
ID uint `json:"id"`
Domain string `json:"domain"`
Remark string `json:"remark"`
DomainCount int64 `json:"domain_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func zoneRoot(domain string) (string, error) {
return publicsuffix.EffectiveTLDPlusOne(strings.ToLower(strings.TrimSpace(domain)))
}
@@ -99,11 +110,41 @@ func Update(ctx context.Context, id uint, input Input) (*model.Zone, error) {
return &zone, nil
}
// List returns all Zones in stable domain order.
func List(ctx context.Context) ([]model.Zone, error) {
// List returns all Zones in stable domain order, with domain counts for list cards.
func List(ctx context.Context) ([]ListItem, error) {
var zones []model.Zone
err := db.DB(ctx).Order("domain asc").Find(&zones).Error
return zones, err
if err := db.DB(ctx).Order("domain asc").Find(&zones).Error; err != nil {
return nil, err
}
type countRow struct {
ZoneID uint `gorm:"column:zone_id"`
Count int64 `gorm:"column:count"`
}
var rows []countRow
if err := db.DB(ctx).Model(&model.ZoneDomain{}).
Select("zone_id, count(*) as count").
Group("zone_id").
Scan(&rows).Error; err != nil {
return nil, err
}
counts := make(map[uint]int64, len(rows))
for _, row := range rows {
counts[row.ZoneID] = row.Count
}
items := make([]ListItem, 0, len(zones))
for _, zone := range zones {
items = append(items, ListItem{
ID: zone.ID,
Domain: zone.Domain,
Remark: zone.Remark,
DomainCount: counts[zone.ID],
CreatedAt: zone.CreatedAt,
UpdatedAt: zone.UpdatedAt,
})
}
return items, nil
}
// GetOverview returns a Zone and its domains.
+1 -1
View File
@@ -33,7 +33,7 @@ func abort(c *gin.Context, err error, missing string) bool {
// @Tags openflare-zone
// @Produce json
// @Security SessionCookie
// @Success 200 {object} response.Any{data=[]model.Zone}
// @Success 200 {object} response.Any{data=[]zone.ListItem}
// @Router /api/v1/d/zones [get]
func ListHandler(c *gin.Context) {
items, err := List(c.Request.Context())