完成target资产统计count

This commit is contained in:
yyhuni
2026-01-13 16:55:37 +08:00
parent a5c48fe4d4
commit 00dfad60b8
5 changed files with 188 additions and 2 deletions

View File

@@ -33,6 +33,8 @@ export interface TargetDetail extends Target {
websites: number
endpoints: number
ips: number
directories: number
screenshots: number
vulnerabilities: {
total: number
critical: number

View File

@@ -69,3 +69,34 @@ type BulkDeleteRequest struct {
type BulkDeleteResponse struct {
DeletedCount int64 `json:"deletedCount"`
}
// TargetDetailResponse represents target detail response with summary
type TargetDetailResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
CreatedAt time.Time `json:"createdAt"`
LastScannedAt *time.Time `json:"lastScannedAt"`
Organizations []OrganizationBrief `json:"organizations,omitempty"`
Summary *TargetSummary `json:"summary"`
}
// TargetSummary represents target asset statistics
type TargetSummary struct {
Subdomains int64 `json:"subdomains"`
Websites int64 `json:"websites"`
Endpoints int64 `json:"endpoints"`
IPs int64 `json:"ips"`
Directories int64 `json:"directories"`
Screenshots int64 `json:"screenshots"`
Vulnerabilities *VulnerabilitySummary `json:"vulnerabilities"`
}
// VulnerabilitySummary represents vulnerability statistics by severity
type VulnerabilitySummary struct {
Total int64 `json:"total"`
Critical int64 `json:"critical"`
High int64 `json:"high"`
Medium int64 `json:"medium"`
Low int64 `json:"low"`
}

View File

@@ -97,7 +97,7 @@ func (h *TargetHandler) GetByID(c *gin.Context) {
return
}
target, err := h.svc.GetByID(id)
target, summary, err := h.svc.GetDetailByID(id)
if err != nil {
if errors.Is(err, service.ErrTargetNotFound) {
dto.NotFound(c, "Target not found")
@@ -107,12 +107,13 @@ func (h *TargetHandler) GetByID(c *gin.Context) {
return
}
dto.Success(c, dto.TargetResponse{
dto.Success(c, dto.TargetDetailResponse{
ID: target.ID,
Name: target.Name,
Type: target.Type,
CreatedAt: target.CreatedAt,
LastScannedAt: target.LastScannedAt,
Summary: summary,
})
}

View File

@@ -141,3 +141,114 @@ func (r *TargetRepository) FindByNames(names []string) ([]model.Target, error) {
Find(&targets).Error
return targets, err
}
// TargetAssetCounts holds asset count statistics for a target
type TargetAssetCounts struct {
Subdomains int64
Websites int64
Endpoints int64
IPs int64
Directories int64
Screenshots int64
}
// VulnerabilityCounts holds vulnerability count statistics by severity
type VulnerabilityCounts struct {
Total int64
Critical int64
High int64
Medium int64
Low int64
}
// GetAssetCounts returns asset counts for a target
func (r *TargetRepository) GetAssetCounts(targetID int) (*TargetAssetCounts, error) {
counts := &TargetAssetCounts{}
// Count subdomains
if err := r.db.Table("subdomain").
Where("target_id = ?", targetID).
Count(&counts.Subdomains).Error; err != nil {
return nil, err
}
// Count websites
if err := r.db.Table("website").
Where("target_id = ?", targetID).
Count(&counts.Websites).Error; err != nil {
return nil, err
}
// Count endpoints
if err := r.db.Table("endpoint").
Where("target_id = ?", targetID).
Count(&counts.Endpoints).Error; err != nil {
return nil, err
}
// Count distinct IPs from host_port_mapping
if err := r.db.Table("host_port_mapping").
Where("target_id = ?", targetID).
Select("COUNT(DISTINCT ip)").
Scan(&counts.IPs).Error; err != nil {
return nil, err
}
// Count directories
if err := r.db.Table("directory").
Where("target_id = ?", targetID).
Count(&counts.Directories).Error; err != nil {
return nil, err
}
// Count screenshots
if err := r.db.Table("screenshot").
Where("target_id = ?", targetID).
Count(&counts.Screenshots).Error; err != nil {
return nil, err
}
return counts, nil
}
// GetVulnerabilityCounts returns vulnerability counts by severity for a target
func (r *TargetRepository) GetVulnerabilityCounts(targetID int) (*VulnerabilityCounts, error) {
counts := &VulnerabilityCounts{}
// Count total vulnerabilities
if err := r.db.Table("vulnerability").
Where("target_id = ?", targetID).
Count(&counts.Total).Error; err != nil {
return nil, err
}
// Count by severity
type severityCount struct {
Severity string
Count int64
}
var severityCounts []severityCount
if err := r.db.Table("vulnerability").
Select("severity, COUNT(*) as count").
Where("target_id = ?", targetID).
Group("severity").
Scan(&severityCounts).Error; err != nil {
return nil, err
}
for _, sc := range severityCounts {
switch sc.Severity {
case "critical":
counts.Critical = sc.Count
case "high":
counts.High = sc.Count
case "medium":
counts.Medium = sc.Count
case "low":
counts.Low = sc.Count
}
}
return counts, nil
}

View File

@@ -73,6 +73,47 @@ func (s *TargetService) GetByID(id int) (*model.Target, error) {
return target, nil
}
// GetDetailByID returns a target with asset summary by ID
func (s *TargetService) GetDetailByID(id int) (*model.Target, *dto.TargetSummary, error) {
target, err := s.repo.FindByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, ErrTargetNotFound
}
return nil, nil, err
}
// Get asset counts
assetCounts, err := s.repo.GetAssetCounts(id)
if err != nil {
return nil, nil, err
}
// Get vulnerability counts
vulnCounts, err := s.repo.GetVulnerabilityCounts(id)
if err != nil {
return nil, nil, err
}
summary := &dto.TargetSummary{
Subdomains: assetCounts.Subdomains,
Websites: assetCounts.Websites,
Endpoints: assetCounts.Endpoints,
IPs: assetCounts.IPs,
Directories: assetCounts.Directories,
Screenshots: assetCounts.Screenshots,
Vulnerabilities: &dto.VulnerabilitySummary{
Total: vulnCounts.Total,
Critical: vulnCounts.Critical,
High: vulnCounts.High,
Medium: vulnCounts.Medium,
Low: vulnCounts.Low,
},
}
return target, summary, nil
}
// Update updates a target
func (s *TargetService) Update(id int, req *dto.UpdateTargetRequest) (*model.Target, error) {
target, err := s.repo.FindByID(id)