diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 544969a..b369b5d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,7 +8,9 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
- "vue": "^3.5.39"
+ "chart.js": "^4.5.1",
+ "vue": "^3.5.39",
+ "vue-chartjs": "^5.3.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.3",
@@ -149,6 +151,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
@@ -952,6 +960,18 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -1652,6 +1672,16 @@
}
}
},
+ "node_modules/vue-chartjs": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.4.tgz",
+ "integrity": "sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "vue": "^3.0.0-0 || ^2.7.0"
+ }
+ },
"node_modules/vue-router": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 65f9009..5c08693 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,7 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
- "vue": "^3.5.39"
+ "chart.js": "^4.5.1",
+ "vue": "^3.5.39",
+ "vue-chartjs": "^5.3.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.3",
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 0e5894a..a8dfc21 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -7,7 +7,8 @@
Nucleus
diff --git a/frontend/src/components/Dashboard.vue b/frontend/src/components/Dashboard.vue
new file mode 100644
index 0000000..b7d9c32
--- /dev/null
+++ b/frontend/src/components/Dashboard.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
Total Targets
+
{{ stats.total_targets || 0 }}
+
+
+
Total Scans
+
{{ stats.total_scans || 0 }}
+
+
+
Critical Findings
+
{{ stats.severity?.critical || 0 }}
+
+
+
+
+
+
+
Severity Distribution
+
+
+
+
+
+
Top Vulnerable Hosts
+
+
+ -
+ {{ h.host }}
+ {{ h.count }} findings
+
+
+
+ No hosts with findings.
+
+
+
+
+
+
+
+
Recent Discovery Trends (Last 7 Days)
+
+
+
No trends available
+
+
+
+
+
+
diff --git a/frontend/src/router.js b/frontend/src/router.js
index 052cab1..f07cab7 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router.js
@@ -3,10 +3,12 @@ import TargetManager from './components/TargetManager.vue'
import ScansHistory from './components/ScansHistory.vue'
import FindingsInspector from './components/FindingsInspector.vue'
import Login from './components/Login.vue'
+import Dashboard from './components/Dashboard.vue'
const routes = [
{ path: '/login', component: Login },
- { path: '/', component: TargetManager, meta: { requiresAuth: true } },
+ { path: '/', component: Dashboard, meta: { requiresAuth: true } },
+ { path: '/targets', component: TargetManager, meta: { requiresAuth: true } },
{ path: '/scans', component: ScansHistory, meta: { requiresAuth: true } },
{ path: '/scans/:id', component: FindingsInspector, props: true, meta: { requiresAuth: true } }
]
diff --git a/internal/api/handlers.go b/internal/api/handlers.go
index d71a0c9..a5f4590 100644
--- a/internal/api/handlers.go
+++ b/internal/api/handlers.go
@@ -15,6 +15,8 @@ func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /api/auth/login", LoginHandler)
mux.HandleFunc("POST /api/auth/logout", LogoutHandler)
mux.HandleFunc("GET /api/auth/status", StatusHandler)
+
+ mux.HandleFunc("GET /api/dashboard/stats", AuthMiddleware(getDashboardStats))
mux.HandleFunc("GET /api/targets", AuthMiddleware(getTargets))
mux.HandleFunc("POST /api/targets", AuthMiddleware(createTarget))
@@ -195,3 +197,59 @@ func getFindings(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(findings)
}
+
+func getDashboardStats(w http.ResponseWriter, r *http.Request) {
+ var stats models.DashboardStats
+ stats.Severity = map[string]int{"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
+ stats.TopHosts = []models.HostCount{}
+ stats.RecentTrends = []models.TrendPoint{}
+
+ // Total Targets
+ db.DB.QueryRow("SELECT COUNT(*) FROM targets").Scan(&stats.TotalTargets)
+
+ // Total Scans
+ db.DB.QueryRow("SELECT COUNT(*) FROM scans").Scan(&stats.TotalScans)
+
+ // Severity Counts
+ rows, err := db.DB.Query("SELECT severity, COUNT(*) FROM findings GROUP BY severity")
+ if err == nil {
+ for rows.Next() {
+ var sev string
+ var count int
+ rows.Scan(&sev, &count)
+ stats.Severity[sev] = count
+ }
+ rows.Close()
+ }
+
+ // Top Hosts
+ hRows, err := db.DB.Query("SELECT host, COUNT(*) as c FROM findings GROUP BY host ORDER BY c DESC LIMIT 5")
+ if err == nil {
+ for hRows.Next() {
+ var h models.HostCount
+ hRows.Scan(&h.Host, &h.Count)
+ stats.TopHosts = append(stats.TopHosts, h)
+ }
+ hRows.Close()
+ }
+
+ // Recent Trends (Last 7 days)
+ tRows, err := db.DB.Query(`
+ SELECT DATE(detected_at) as d, COUNT(*)
+ FROM findings
+ WHERE detected_at >= date('now', '-7 days')
+ GROUP BY d
+ ORDER BY d ASC
+ `)
+ if err == nil {
+ for tRows.Next() {
+ var t models.TrendPoint
+ tRows.Scan(&t.Date, &t.Count)
+ stats.RecentTrends = append(stats.RecentTrends, t)
+ }
+ tRows.Close()
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(stats)
+}
diff --git a/internal/models/models.go b/internal/models/models.go
index fcfe808..199da59 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -45,3 +45,21 @@ type NucleiFinding struct {
MatchedAt string `json:"matched-at"`
Timestamp string `json:"timestamp"`
}
+
+type TrendPoint struct {
+ Date string `json:"date"`
+ Count int `json:"count"`
+}
+
+type HostCount struct {
+ Host string `json:"host"`
+ Count int `json:"count"`
+}
+
+type DashboardStats struct {
+ TotalTargets int `json:"total_targets"`
+ TotalScans int `json:"total_scans"`
+ Severity map[string]int `json:"severity"`
+ TopHosts []HostCount `json:"top_hosts"`
+ RecentTrends []TrendPoint `json:"recent_trends"`
+}