@stacksjs/defaults 0.70.157 → 0.70.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { summarizeHttpRequests } from './DashboardHomeAction'
|
|
3
|
+
|
|
4
|
+
describe('dashboard HTTP metrics', () => {
|
|
5
|
+
it('summarizes captured requests without fake values', () => {
|
|
6
|
+
const metrics = summarizeHttpRequests(12_345, [
|
|
7
|
+
{ duration: 10, status: 200 },
|
|
8
|
+
{ duration: 20, status: 302 },
|
|
9
|
+
{ duration: 30, status: 404 },
|
|
10
|
+
{ duration: 40, status: 500 },
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
expect(metrics.map(metric => metric.value)).toEqual(['12,345', '25ms', '50.0%', '50.0%'])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns explicit empty-state metrics', () => {
|
|
17
|
+
expect(summarizeHttpRequests(0, []).map(metric => metric.value)).toEqual(['0', '0ms', '100%', '0%'])
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
-
import {
|
|
2
|
+
import { Order, Post, Product, Request, User } from '@stacksjs/orm'
|
|
3
|
+
|
|
4
|
+
interface HttpRequestSample {
|
|
5
|
+
duration: number
|
|
6
|
+
status: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function summarizeHttpRequests(total: number, requests: HttpRequestSample[]) {
|
|
10
|
+
const successful = requests.filter(request => request.status >= 200 && request.status < 400).length
|
|
11
|
+
const failed = requests.filter(request => request.status >= 400).length
|
|
12
|
+
const averageDuration = requests.length > 0
|
|
13
|
+
? Math.round(requests.reduce((sum, request) => sum + request.duration, 0) / requests.length)
|
|
14
|
+
: 0
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
{ title: 'HTTP Requests', value: total.toLocaleString(), detail: 'All captured requests', icon: 'i-hugeicons-global' },
|
|
18
|
+
{ title: 'Average Response', value: `${averageDuration}ms`, detail: `Latest ${requests.length.toLocaleString()} requests`, icon: 'i-hugeicons-clock-01' },
|
|
19
|
+
{ title: 'Success Rate', value: requests.length > 0 ? `${((successful / requests.length) * 100).toFixed(1)}%` : '100%', detail: '2xx and 3xx responses', icon: 'i-hugeicons-checkmark-circle-02' },
|
|
20
|
+
{ title: 'Error Rate', value: requests.length > 0 ? `${((failed / requests.length) * 100).toFixed(1)}%` : '0%', detail: '4xx and 5xx responses', icon: 'i-hugeicons-alert-02' },
|
|
21
|
+
]
|
|
22
|
+
}
|
|
3
23
|
|
|
4
24
|
export default new Action({
|
|
5
25
|
name: 'DashboardHomeAction',
|
|
@@ -14,6 +34,8 @@ export default new Action({
|
|
|
14
34
|
let totalRevenue = 0
|
|
15
35
|
let recentOrderRows: any[] = []
|
|
16
36
|
let recentUserRows: any[] = []
|
|
37
|
+
let databaseStatus = 'offline'
|
|
38
|
+
let httpMetrics = summarizeHttpRequests(0, [])
|
|
17
39
|
|
|
18
40
|
try {
|
|
19
41
|
const results = await Promise.all([
|
|
@@ -35,16 +57,31 @@ export default new Action({
|
|
|
35
57
|
// Calculate total revenue from all orders
|
|
36
58
|
const allOrders = await Order.all()
|
|
37
59
|
totalRevenue = allOrders.reduce((sum, o) => sum + (Number(o.get('total_amount')) || 0), 0)
|
|
60
|
+
databaseStatus = 'healthy'
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// A new project may not have run its migrations yet.
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const [requestCount, recentRequests] = await Promise.all([
|
|
68
|
+
Request.count(),
|
|
69
|
+
Request.orderBy('created_at', 'desc').limit(1000).get(),
|
|
70
|
+
])
|
|
71
|
+
httpMetrics = summarizeHttpRequests(requestCount, recentRequests.map(request => ({
|
|
72
|
+
duration: Number(request.get('duration_ms')) || 0,
|
|
73
|
+
status: Number(request.get('status_code')) || 500,
|
|
74
|
+
})))
|
|
38
75
|
}
|
|
39
76
|
catch {
|
|
40
|
-
//
|
|
77
|
+
// Request capture is optional, so its empty state is valid.
|
|
41
78
|
}
|
|
42
79
|
|
|
43
80
|
const stats = [
|
|
44
|
-
{ label: 'Total Users', value: String(userCount),
|
|
45
|
-
{ label: 'Products', value: String(productCount),
|
|
46
|
-
{ label: 'Revenue', value: `$${totalRevenue.toLocaleString()}`,
|
|
47
|
-
{ label: 'Orders', value: String(orderCount),
|
|
81
|
+
{ label: 'Total Users', value: String(userCount), color: 'blue' },
|
|
82
|
+
{ label: 'Products', value: String(productCount), color: 'green' },
|
|
83
|
+
{ label: 'Revenue', value: `$${totalRevenue.toLocaleString()}`, color: 'orange' },
|
|
84
|
+
{ label: 'Orders', value: String(orderCount), color: 'red' },
|
|
48
85
|
]
|
|
49
86
|
|
|
50
87
|
const quickLinks = [
|
|
@@ -55,11 +92,11 @@ export default new Action({
|
|
|
55
92
|
]
|
|
56
93
|
|
|
57
94
|
const services = [
|
|
58
|
-
{ name: 'Database', status:
|
|
59
|
-
{ name: 'Cache', status: '
|
|
60
|
-
{ name: 'Queue', status: '
|
|
61
|
-
{ name: 'Storage', status: '
|
|
62
|
-
{ name: 'Email', status: '
|
|
95
|
+
{ name: 'Database', status: databaseStatus, latency: 'N/A' },
|
|
96
|
+
{ name: 'Cache', status: 'configured', latency: 'N/A' },
|
|
97
|
+
{ name: 'Queue', status: 'configured', latency: 'N/A' },
|
|
98
|
+
{ name: 'Storage', status: 'configured', latency: 'N/A' },
|
|
99
|
+
{ name: 'Email', status: 'configured', latency: 'N/A' },
|
|
63
100
|
]
|
|
64
101
|
|
|
65
102
|
// Build activities from real data
|
|
@@ -78,6 +115,6 @@ export default new Action({
|
|
|
78
115
|
})),
|
|
79
116
|
].slice(0, 5)
|
|
80
117
|
|
|
81
|
-
return { stats, quickLinks, services, activities }
|
|
118
|
+
return { stats, httpMetrics, quickLinks, services, activities }
|
|
82
119
|
},
|
|
83
120
|
})
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.160",
|
|
6
6
|
"description": "Default Stacks scaffold resources (views, layouts, components, preloader) — shipped so apps consuming the framework from node_modules get the same fallback resources a vendored checkout provides. Source of truth: storage/framework/defaults.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|