@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 { User, Product, Order, Post } from '@stacksjs/orm'
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
- // Database may not exist yet - use fallback values
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), trend: '+12%', up: true, color: 'blue' },
45
- { label: 'Products', value: String(productCount), trend: '+8%', up: true, color: 'green' },
46
- { label: 'Revenue', value: `$${totalRevenue.toLocaleString()}`, trend: '+23%', up: true, color: 'orange' },
47
- { label: 'Orders', value: String(orderCount), trend: '+5%', up: true, color: 'red' },
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: userCount > 0 ? 'healthy' : 'offline', latency: userCount > 0 ? '12ms' : 'N/A' },
59
- { name: 'Cache', status: 'healthy', latency: '2ms' },
60
- { name: 'Queue', status: 'healthy', latency: '45ms' },
61
- { name: 'Storage', status: 'healthy', latency: '8ms' },
62
- { name: 'Email', status: 'warning', latency: '234ms' },
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.157",
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",