@stacksjs/defaults 0.75.242 → 0.75.244
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.
- package/ai/skills/stacks-actions/SKILL.md +1 -1
- package/ai/skills/stacks-native/CATALOG.md +6 -6
- package/app/Actions/Dashboard/DashboardHomeAction.ts +7 -1
- package/app/Actions/Dashboard/hq-read.ts +144 -0
- package/app/Models/QueryLog.ts +10 -0
- package/functions/dashboard.ts +10 -1
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +58 -61
- package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +59 -61
- package/resources/components/Dashboard/Commerce/ProductWaitlistTable.stx +49 -61
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistTable.stx +66 -71
- package/resources/components/Dashboard/Home/DashboardHome.stx +20 -2
- package/resources/components/Dashboard/Library/DependenciesDashboard.stx +68 -14
- package/resources/components/Dashboard/Realtime/RealtimeDashboard.stx +25 -26
- package/resources/components/Dashboard/UI/Table.stx +244 -9
- package/resources/functions/dashboard/toggles.ts +47 -11
- package/stx-components-plugin.ts +8 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-actions
|
|
3
|
-
description: Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the
|
|
3
|
+
description: Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 673 default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/app/Actions/.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -11,7 +11,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
11
11
|
| Package | Task guidance | Source entry | Retained tests |
|
|
12
12
|
|---|---|---|---|
|
|
13
13
|
| `@stacksjs/action-runner` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/action-runner/src/index.ts) | 1 |
|
|
14
|
-
| `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) |
|
|
14
|
+
| `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 75 |
|
|
15
15
|
| `@stacksjs/ai` | [`stacks-ai`](../stacks-ai/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/ai/src/index.ts) | 17 |
|
|
16
16
|
| `@stacksjs/alias` | [`stacks-alias`](../stacks-alias/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/alias/src/index.ts) | 1 |
|
|
17
17
|
| `@stacksjs/analytics` | [`stacks-analytics`](../stacks-analytics/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/analytics/src/index.ts) | 6 |
|
|
@@ -21,7 +21,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
21
21
|
| `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 119 |
|
|
22
22
|
| `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 8 |
|
|
23
23
|
| `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 7 |
|
|
24
|
-
| `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) |
|
|
24
|
+
| `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 152 |
|
|
25
25
|
| `@stacksjs/build` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/build/src/index.ts) | 2 |
|
|
26
26
|
| `@stacksjs/cache` | [`stacks-cache`](../stacks-cache/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cache/src/index.ts) | 11 |
|
|
27
27
|
| `@stacksjs/calendar-api` | [`stacks-calendar`](../stacks-calendar/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/calendar-api/src/index.ts) | 2 |
|
|
@@ -65,7 +65,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
65
65
|
| `@stacksjs/newsletter` | [`stacks-newsletter`](../stacks-newsletter/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/newsletter/src/index.ts) | 7 |
|
|
66
66
|
| `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 16 |
|
|
67
67
|
| `@stacksjs/objects` | [`stacks-objects`](../stacks-objects/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/objects/src/index.ts) | 4 |
|
|
68
|
-
| `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) |
|
|
68
|
+
| `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 64 |
|
|
69
69
|
| `@stacksjs/pagination` | [`stacks-pagination`](../stacks-pagination/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/pagination/src/index.ts) | 0 |
|
|
70
70
|
| `@stacksjs/path` | [`stacks-path`](../stacks-path/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/path/src/index.ts) | 11 |
|
|
71
71
|
| `@stacksjs/payments` | [`stacks-payments`](../stacks-payments/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/payments/src/index.ts) | 28 |
|
|
@@ -119,7 +119,7 @@ Declared runtime exports: `BlogAdminError`, `DEFAULT_IOS_DEPENDENCIES`, `DEFAULT
|
|
|
119
119
|
|
|
120
120
|
Local source modules: [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/action.ts), [src/add.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/add.ts), [src/blog-admin.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/blog-admin.ts), [src/build/ios-toolchain.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/ios-toolchain.ts), [src/commit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/commit.ts), [src/dev/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/dev/index.ts), [src/discover-packages.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/discover-packages.ts), [src/generate/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/generate/index.ts), [src/github-release.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/github-release.ts), [src/helpers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/helpers/index.ts), [src/library/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/library/index.ts), [src/lint/lint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/lint.ts), [src/lint/stx-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/stx-gate.ts), [src/make-command.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-command.ts), [src/make-job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-job.ts), [src/make-policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-policy.ts), [src/make-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-resource.ts), [src/make.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make.ts), [src/scaffold-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/scaffold-crud.ts), [src/setup/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/setup/index.ts), [src/stacks.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stacks.ts), [src/upgrade/package-project.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/package-project.ts), [src/upgrade/route-actions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/route-actions.ts).
|
|
121
121
|
|
|
122
|
-
Evidence directory: [
|
|
122
|
+
Evidence directory: [75 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
123
123
|
|
|
124
124
|
### @stacksjs/ai
|
|
125
125
|
|
|
@@ -215,7 +215,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
|
|
|
215
215
|
|
|
216
216
|
Local source modules: [src/commands/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/commands/index.ts).
|
|
217
217
|
|
|
218
|
-
Evidence directory: [
|
|
218
|
+
Evidence directory: [152 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
219
219
|
|
|
220
220
|
### @stacksjs/build
|
|
221
221
|
|
|
@@ -633,7 +633,7 @@ Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`
|
|
|
633
633
|
|
|
634
634
|
Local source modules: [src/auto-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/auto-crud.ts), [src/batch-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/batch-loader.ts), [src/db.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/db.ts), [src/define-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/define-model.ts), [src/extend-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/extend-model.ts), [src/gdpr/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/gdpr/index.ts), [src/generate-database-schema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/generate-database-schema.ts), [src/locked-row.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/locked-row.ts), [src/messaging.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/messaging.ts), [src/model-types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/model-types.ts), [src/ownership.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/ownership.ts), [src/paginator-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/paginator-request.ts), [src/row-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/row-token.ts), [src/subquery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/subquery.ts), [src/traits/audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/audit.ts), [src/traits/billable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/billable.ts), [src/traits/categorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/categorizable.ts), [src/traits/commentable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/commentable.ts), [src/traits/likeable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/likeable.ts), [src/traits/taggable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/taggable.ts), [src/transaction.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/transaction.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/types.ts), [src/utils/encrypted.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/encrypted.ts), [src/utils/prunable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/prunable.ts).
|
|
635
635
|
|
|
636
|
-
Evidence directory: [
|
|
636
|
+
Evidence directory: [64 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
637
637
|
|
|
638
638
|
### @stacksjs/pagination
|
|
639
639
|
|
|
@@ -193,6 +193,12 @@ export default new Action({
|
|
|
193
193
|
})
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
// Which feature-backed sections exist, so the home page does not offer a
|
|
197
|
+
// card for a section the sidebar has hidden and whose pages cannot load.
|
|
198
|
+
// `feature()` is server-side only, and this component runs on the client.
|
|
199
|
+
// See stacksjs/stacks#2928.
|
|
200
|
+
const sections = { commerce, content: feature('cms'), marketing: feature('marketing') }
|
|
201
|
+
|
|
202
|
+
return { stats, httpMetrics, services, activities, issues, sections }
|
|
197
203
|
},
|
|
198
204
|
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { DashboardHqDriverOptions } from '@stacksjs/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one way a dashboard section talks to a hosted HQ product.
|
|
5
|
+
*
|
|
6
|
+
* `dashboard-provider.ts` decides *which* provider a section reads from and
|
|
7
|
+
* guarantees that a remote one cannot take the page down. This module is the
|
|
8
|
+
* other half: how a remote read is actually performed, so that each domain
|
|
9
|
+
* driver is a path and a shape rather than its own copy of base-URL joining,
|
|
10
|
+
* header handling and failure translation.
|
|
11
|
+
*
|
|
12
|
+
* Everything here exists to make one of `readThroughProvider`'s three degrade
|
|
13
|
+
* paths real:
|
|
14
|
+
*
|
|
15
|
+
* it rejects — an unreachable host, a non-2xx answer, or a body that
|
|
16
|
+
* is not JSON, all raised as {@link HqReadError}
|
|
17
|
+
* it never answers — a hung connection, aborted at `timeoutMs`. Nothing
|
|
18
|
+
* else in the seam imposes a deadline, so without this
|
|
19
|
+
* the "never answers" case was documented rather than
|
|
20
|
+
* handled: the request simply hung and took the
|
|
21
|
+
* section's request with it
|
|
22
|
+
* the wrong shape — left to the caller, which knows what its section
|
|
23
|
+
* renders; this module parses JSON and no more
|
|
24
|
+
*
|
|
25
|
+
* The read key only ever travels in `Authorization`. A URL is logged by
|
|
26
|
+
* proxies, kept in browser history and attached to error reports, and a
|
|
27
|
+
* read-scoped key is still a credential, so it never becomes a query
|
|
28
|
+
* parameter even though several hosted APIs would accept one.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** How long a hosted read may take before it is treated as unreachable. */
|
|
32
|
+
export const HQ_DEFAULT_TIMEOUT_MS = 10_000
|
|
33
|
+
|
|
34
|
+
/** The upper bound on a configured timeout, so one cannot hold a section open. */
|
|
35
|
+
export const HQ_MAX_TIMEOUT_MS = 60_000
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A hosted read that did not produce a payload.
|
|
39
|
+
*
|
|
40
|
+
* Carries `status` when the product answered and the answer was not a 2xx, so
|
|
41
|
+
* an operator reading the log can tell a misconfigured key (401/403) from a
|
|
42
|
+
* wrong project id (404) from the product being unwell (5xx). `status` is
|
|
43
|
+
* absent when no answer arrived at all.
|
|
44
|
+
*/
|
|
45
|
+
export class HqReadError extends Error {
|
|
46
|
+
readonly status?: number
|
|
47
|
+
readonly url: string
|
|
48
|
+
|
|
49
|
+
constructor(message: string, url: string, status?: number) {
|
|
50
|
+
super(message)
|
|
51
|
+
this.name = 'HqReadError'
|
|
52
|
+
this.url = url
|
|
53
|
+
this.status = status
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Whether the options carry enough to attempt a read at all. */
|
|
58
|
+
export function hqConfigured(options: DashboardHqDriverOptions | null | undefined): boolean {
|
|
59
|
+
return Boolean(options?.baseUrl && options?.token)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `base` and `path` joined into one URL, with exactly one slash between them.
|
|
64
|
+
*
|
|
65
|
+
* A base URL is hand-written in a config file, so it arrives with or without
|
|
66
|
+
* a trailing slash, and a driver's path is written as a literal that reads
|
|
67
|
+
* best with a leading one. Both spellings have to land on the same address.
|
|
68
|
+
*/
|
|
69
|
+
export function hqUrl(base: string, path: string, query?: Record<string, string | number | undefined>): string {
|
|
70
|
+
const url = new URL(path.replace(/^\/+/, ''), base.endsWith('/') ? base : `${base}/`)
|
|
71
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
72
|
+
if (value !== undefined && value !== '')
|
|
73
|
+
url.searchParams.set(key, String(value))
|
|
74
|
+
}
|
|
75
|
+
return url.toString()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A configured timeout, clamped so neither a typo nor a zero disables it. */
|
|
79
|
+
export function hqTimeout(options: DashboardHqDriverOptions | null | undefined): number {
|
|
80
|
+
const asked = Number(options?.timeoutMs)
|
|
81
|
+
if (!Number.isFinite(asked) || asked <= 0)
|
|
82
|
+
return HQ_DEFAULT_TIMEOUT_MS
|
|
83
|
+
return Math.min(HQ_MAX_TIMEOUT_MS, asked)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* One read from a hosted HQ product, as JSON.
|
|
88
|
+
*
|
|
89
|
+
* Throws {@link HqReadError} for every failure, which is what the caller
|
|
90
|
+
* wants: `readThroughProvider` turns a rejection into the section's empty
|
|
91
|
+
* payload with a reason, so a driver never has to handle a failure itself.
|
|
92
|
+
*
|
|
93
|
+
* `fetchImpl` exists for tests. Nothing in the application passes it.
|
|
94
|
+
*/
|
|
95
|
+
export async function hqRead<T = unknown>(
|
|
96
|
+
options: DashboardHqDriverOptions | null | undefined,
|
|
97
|
+
path: string,
|
|
98
|
+
query?: Record<string, string | number | undefined>,
|
|
99
|
+
fetchImpl: typeof fetch = fetch,
|
|
100
|
+
): Promise<T> {
|
|
101
|
+
if (!hqConfigured(options)) {
|
|
102
|
+
// Not a network failure, and worth saying so differently: a section that
|
|
103
|
+
// reads empty because nobody filled in a base URL is a configuration
|
|
104
|
+
// mistake an operator can fix, not an outage to wait out.
|
|
105
|
+
throw new HqReadError('The hosted provider has no baseUrl and token configured.', '')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const url = hqUrl(options!.baseUrl!, path, query)
|
|
109
|
+
let answer: Response
|
|
110
|
+
try {
|
|
111
|
+
answer = await fetchImpl(url, {
|
|
112
|
+
headers: {
|
|
113
|
+
// The read key, and only here. See the note at the top of this file.
|
|
114
|
+
authorization: `Bearer ${options!.token}`,
|
|
115
|
+
accept: 'application/json',
|
|
116
|
+
},
|
|
117
|
+
signal: AbortSignal.timeout(hqTimeout(options)),
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
catch (cause) {
|
|
121
|
+
// An abort and a refused connection are the same thing to a section: no
|
|
122
|
+
// data arrived. They are told apart in the message so the log is useful.
|
|
123
|
+
const aborted = (cause as { name?: string })?.name === 'TimeoutError'
|
|
124
|
+
|| (cause as { name?: string })?.name === 'AbortError'
|
|
125
|
+
throw new HqReadError(
|
|
126
|
+
aborted
|
|
127
|
+
? `The hosted provider did not answer within ${hqTimeout(options)}ms.`
|
|
128
|
+
: `The hosted provider could not be reached: ${(cause as Error)?.message ?? String(cause)}`,
|
|
129
|
+
url,
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!answer.ok)
|
|
134
|
+
throw new HqReadError(`The hosted provider answered ${answer.status}.`, url, answer.status)
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
return await answer.json() as T
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// A 2xx that is not JSON is usually a sign-in page or a proxy's error
|
|
141
|
+
// document, which is a configuration problem wearing a success code.
|
|
142
|
+
throw new HqReadError('The hosted provider answered with something that is not JSON.', url, answer.status)
|
|
143
|
+
}
|
|
144
|
+
}
|
package/app/Models/QueryLog.ts
CHANGED
|
@@ -142,38 +142,48 @@ export default defineModel({
|
|
|
142
142
|
rule: schema.string().max(255),
|
|
143
143
|
},
|
|
144
144
|
},
|
|
145
|
+
// `text`, all of these: an EXPLAIN plan, the tables a query touched and the
|
|
146
|
+
// suggestions made about it run past the varchar(255) Postgres enforces, and
|
|
147
|
+
// a log row that does not fit is dropped ("value too long for type character
|
|
148
|
+
// varying(255)") - losing exactly the slow queries this table exists for.
|
|
145
149
|
tags: {
|
|
146
150
|
fillable: true,
|
|
151
|
+
type: 'text',
|
|
147
152
|
validation: {
|
|
148
153
|
rule: schema.string(),
|
|
149
154
|
},
|
|
150
155
|
},
|
|
151
156
|
affected_tables: {
|
|
152
157
|
fillable: true,
|
|
158
|
+
type: 'text',
|
|
153
159
|
validation: {
|
|
154
160
|
rule: schema.string(),
|
|
155
161
|
},
|
|
156
162
|
},
|
|
157
163
|
indexes_used: {
|
|
158
164
|
fillable: true,
|
|
165
|
+
type: 'text',
|
|
159
166
|
validation: {
|
|
160
167
|
rule: schema.string(),
|
|
161
168
|
},
|
|
162
169
|
},
|
|
163
170
|
missing_indexes: {
|
|
164
171
|
fillable: true,
|
|
172
|
+
type: 'text',
|
|
165
173
|
validation: {
|
|
166
174
|
rule: schema.string(),
|
|
167
175
|
},
|
|
168
176
|
},
|
|
169
177
|
explain_plan: {
|
|
170
178
|
fillable: true,
|
|
179
|
+
type: 'text',
|
|
171
180
|
validation: {
|
|
172
181
|
rule: schema.string(),
|
|
173
182
|
},
|
|
174
183
|
},
|
|
175
184
|
optimization_suggestions: {
|
|
176
185
|
fillable: true,
|
|
186
|
+
type: 'text',
|
|
177
187
|
validation: {
|
|
178
188
|
rule: schema.string(),
|
|
179
189
|
},
|
package/functions/dashboard.ts
CHANGED
|
@@ -39,6 +39,11 @@ export interface DashboardIssue {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
interface DashboardHomeResponse {
|
|
42
|
+
/**
|
|
43
|
+
* Which feature-backed sections the server has available, so the home page
|
|
44
|
+
* only offers cards for sections that exist. See stacksjs/stacks#2928.
|
|
45
|
+
*/
|
|
46
|
+
sections?: Record<string, boolean>
|
|
42
47
|
stats?: Array<{ label: string, value: string }>
|
|
43
48
|
httpMetrics?: DashboardHttpMetric[]
|
|
44
49
|
services?: SystemHealthItem[]
|
|
@@ -98,6 +103,7 @@ export async function fetchDashboardHome(): Promise<DashboardHomeResponse> {
|
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
export function useDashboard() {
|
|
106
|
+
const sections = ref<Record<string, boolean>>({})
|
|
101
107
|
const stats = ref<DashboardStats[]>([])
|
|
102
108
|
const httpMetrics = ref<DashboardHttpMetric[]>([])
|
|
103
109
|
const recentActivity = ref<ActivityItem[]>([])
|
|
@@ -109,6 +115,7 @@ export function useDashboard() {
|
|
|
109
115
|
async function fetchDashboardStats(timeRange = '7d') {
|
|
110
116
|
void timeRange
|
|
111
117
|
const data = await fetchDashboardHome()
|
|
118
|
+
sections.value = data.sections && typeof data.sections === 'object' ? data.sections : {}
|
|
112
119
|
stats.value = normalizeStats(data.stats)
|
|
113
120
|
issues.value = Array.isArray(data.issues) ? data.issues : []
|
|
114
121
|
}
|
|
@@ -132,7 +139,8 @@ export function useDashboard() {
|
|
|
132
139
|
|
|
133
140
|
try {
|
|
134
141
|
const data = await fetchDashboardHome()
|
|
135
|
-
|
|
142
|
+
sections.value = data.sections && typeof data.sections === 'object' ? data.sections : {}
|
|
143
|
+
stats.value = normalizeStats(data.stats)
|
|
136
144
|
httpMetrics.value = Array.isArray(data.httpMetrics) ? data.httpMetrics : []
|
|
137
145
|
recentActivity.value = normalizeActivities(data.activities)
|
|
138
146
|
systemHealth.value = Array.isArray(data.services) ? data.services : []
|
|
@@ -157,6 +165,7 @@ export function useDashboard() {
|
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
return {
|
|
168
|
+
sections,
|
|
160
169
|
stats,
|
|
161
170
|
httpMetrics,
|
|
162
171
|
recentActivity,
|
package/ide/vscode/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"publisher": "Stacks",
|
|
3
3
|
"name": "vscode-stacks",
|
|
4
4
|
"displayName": "Stacks",
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.244",
|
|
6
6
|
"description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.244",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@iconify-json/f7": "^1.2.2",
|
|
46
46
|
"@iconify-json/hugeicons": "^1.2.35",
|
|
47
|
-
"@stacksjs/mobile": "0.75.
|
|
47
|
+
"@stacksjs/mobile": "0.75.244",
|
|
48
48
|
"@stacksjs/sanitizer": "^0.2.534",
|
|
49
49
|
"@xterm/addon-fit": "^0.11.0",
|
|
50
50
|
"@xterm/xterm": "^6.0.0",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import('../UI/Table')
|
|
2
|
+
|
|
1
3
|
<script client>
|
|
2
4
|
import type { ManufacturerRecord } from '../../../../app/Actions/Dashboard/Commerce/manufacturer-records'
|
|
3
5
|
|
|
@@ -5,67 +7,62 @@ const records = useReactiveProp('records', [] as ManufacturerRecord[])
|
|
|
5
7
|
const loading = useReactiveProp('loading', false)
|
|
6
8
|
const emit = defineEmits()
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `featured` becomes a flag badge beside the name rather than the starred line
|
|
12
|
+
* it used to be under it. The information is the same and it reads as a table
|
|
13
|
+
* column; the star is dropped because a `cell` slot cannot carry markup yet
|
|
14
|
+
* (stacksjs/stx#2095, stacksjs/stx#2096).
|
|
15
|
+
*
|
|
16
|
+
* `createdAt` is a `date` column, and `UI/Table` now normalises SQLite's
|
|
17
|
+
* space-separated timestamps the way this component used to do for itself.
|
|
18
|
+
*/
|
|
19
|
+
const COLUMNS = [
|
|
20
|
+
{ key: 'name', label: 'Manufacturer', sortable: true, emptyText: 'Unnamed manufacturer', class: 'min-w-48 font-medium text-gray-900 dark:text-white' },
|
|
21
|
+
{
|
|
22
|
+
key: 'featuredFlag',
|
|
23
|
+
label: '',
|
|
24
|
+
type: 'badge' as const,
|
|
25
|
+
emptyText: '',
|
|
26
|
+
badgeClasses: { featured: 'text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40' },
|
|
27
|
+
badgeLabels: { featured: 'Featured' },
|
|
28
|
+
searchable: false,
|
|
29
|
+
},
|
|
30
|
+
{ key: 'description', label: 'Description', emptyText: 'No description recorded.', class: 'max-w-md min-w-64 line-clamp-2 text-gray-500 dark:text-neutral-400' },
|
|
31
|
+
{ key: 'country', label: 'Country', sortable: true, emptyText: 'Not recorded', class: 'whitespace-nowrap' },
|
|
32
|
+
{ key: 'productCount', label: 'Products', type: 'number' as const, sortable: true, align: 'right' as const, class: 'font-mono' },
|
|
33
|
+
{ key: 'createdAt', label: 'Created', type: 'date' as const, sortable: true, class: 'whitespace-nowrap' },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const ROW_ACTIONS = [
|
|
37
|
+
{ action: 'edit', label: 'Edit', icon: 'i-hugeicons-edit-01' },
|
|
38
|
+
{ action: 'delete', label: 'Delete', icon: 'i-hugeicons-waste', class: 'text-red-500' },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
/** A badge column maps a row field, so the flag needs one. */
|
|
42
|
+
const rows = derived(() => records().map(record => ({
|
|
43
|
+
...record,
|
|
44
|
+
featuredFlag: record.featured ? 'featured' : '',
|
|
45
|
+
})))
|
|
46
|
+
|
|
47
|
+
function handleRowAction(payload: { action: string, row: ManufacturerRecord }): void {
|
|
48
|
+
emit(payload.action, payload.row)
|
|
18
49
|
}
|
|
19
50
|
</script>
|
|
20
51
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<th scope="col" class="px-4 py-3 font-semibold text-gray-600 text-left text-xs uppercase dark:text-neutral-400">Created</th>
|
|
39
|
-
<th scope="col" class="relative px-4 py-3"><span class="sr-only">Actions</span></th>
|
|
40
|
-
</tr>
|
|
41
|
-
</thead>
|
|
42
|
-
<tbody class="divide-gray-100 divide-y dark:divide-neutral-700">
|
|
43
|
-
<template :for="record in records()" :key="record.id">
|
|
44
|
-
<tr class="hover:bg-gray-50/60 dark:hover:bg-neutral-700/40">
|
|
45
|
-
<td class="px-4 py-4 min-w-48">
|
|
46
|
-
<p class="font-medium text-gray-900 text-sm dark:text-white">{{ record.name || 'Unnamed manufacturer' }}</p>
|
|
47
|
-
<span :if="record.featured" class="inline-flex items-center mt-1 font-medium text-blue-700 text-xs dark:text-blue-300">
|
|
48
|
-
<span class="mr-1 h-3.5 w-3.5 i-hugeicons-star"></span>
|
|
49
|
-
Featured
|
|
50
|
-
</span>
|
|
51
|
-
</td>
|
|
52
|
-
<td class="px-4 py-4 max-w-md min-w-64 text-gray-500 text-sm dark:text-neutral-400">
|
|
53
|
-
<p class="line-clamp-2">{{ record.description || 'No description recorded.' }}</p>
|
|
54
|
-
</td>
|
|
55
|
-
<td class="px-4 py-4 text-gray-700 text-sm whitespace-nowrap dark:text-neutral-200">{{ record.country || 'Not recorded' }}</td>
|
|
56
|
-
<td class="px-4 py-4 font-mono text-gray-700 text-right text-sm dark:text-neutral-200">{{ record.productCount }}</td>
|
|
57
|
-
<td class="px-4 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ formatDate(record.createdAt) }}</td>
|
|
58
|
-
<td class="px-4 py-3 text-right whitespace-nowrap">
|
|
59
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'Edit ' + record.name" @click="emit('edit', record)">
|
|
60
|
-
<span class="block h-4 w-4 text-blue-500 i-hugeicons-edit-01"></span>
|
|
61
|
-
</Button>
|
|
62
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'Delete ' + record.name" @click="emit('delete', record)">
|
|
63
|
-
<span class="block h-4 w-4 text-red-500 i-hugeicons-waste"></span>
|
|
64
|
-
</Button>
|
|
65
|
-
</td>
|
|
66
|
-
</tr>
|
|
67
|
-
</template>
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
52
|
+
{{--
|
|
53
|
+
No `searchable` or `filterable`: the parent dashboard already owns the
|
|
54
|
+
search box and the filter selects, and a second set inside the table would
|
|
55
|
+
duplicate them. Paging is what was missing, and Table brings it.
|
|
56
|
+
--}}
|
|
57
|
+
<Table
|
|
58
|
+
:columns="COLUMNS"
|
|
59
|
+
:data="rows()"
|
|
60
|
+
:loading="loading()"
|
|
61
|
+
:row-actions="ROW_ACTIONS"
|
|
62
|
+
action-label-key="name"
|
|
63
|
+
paginated
|
|
64
|
+
:page-size="25"
|
|
65
|
+
empty-title="No manufacturers found"
|
|
66
|
+
empty-description="Add a manufacturer or adjust the current filters."
|
|
67
|
+
@row-action="handleRowAction"
|
|
68
|
+
/>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import('../UI/Table')
|
|
2
|
+
|
|
1
3
|
<script client>
|
|
2
4
|
import type { ProductUnitRecord } from '../../../../app/Actions/Dashboard/Commerce/product-unit-records'
|
|
3
5
|
|
|
@@ -5,67 +7,63 @@ const records = useReactiveProp('records', [] as ProductUnitRecord[])
|
|
|
5
7
|
const loading = useReactiveProp('loading', false)
|
|
6
8
|
const emit = defineEmits()
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* No slots. `UI/Table`'s `cell` and `actions` slots cannot be used yet -
|
|
12
|
+
* slot content loses its `{{ … }}` (stacksjs/stx#2095) and `stx typecheck`
|
|
13
|
+
* rejects every scoped slot prop (stacksjs/stx#2096) - so the two custom
|
|
14
|
+
* cells here are expressed as column options instead: `subtitleKey` for the
|
|
15
|
+
* abbreviation under the name, and a flag `badge` column for the default
|
|
16
|
+
* marker.
|
|
17
|
+
*/
|
|
18
|
+
const COLUMNS = [
|
|
19
|
+
{ key: 'name', label: 'Unit', sortable: true, subtitleKey: 'abbreviation', class: 'font-medium text-gray-900 dark:text-white' },
|
|
20
|
+
{
|
|
21
|
+
key: 'defaultFlag',
|
|
22
|
+
label: '',
|
|
23
|
+
type: 'badge' as const,
|
|
24
|
+
// A flag rather than a status: only the default unit is marked, and the
|
|
25
|
+
// rest of the column stays blank.
|
|
26
|
+
emptyText: '',
|
|
27
|
+
badgeClasses: { default: 'text-emerald-700 dark:text-emerald-300 bg-emerald-50 dark:bg-emerald-950/40' },
|
|
28
|
+
badgeLabels: { default: 'Default' },
|
|
29
|
+
searchable: false,
|
|
30
|
+
},
|
|
31
|
+
{ key: 'type', label: 'Type', sortable: true, class: 'whitespace-nowrap' },
|
|
32
|
+
{ key: 'productName', label: 'Product', sortable: true, emptyText: 'Unassigned' },
|
|
33
|
+
{ key: 'description', label: 'Description', class: 'max-w-sm', emptyText: 'No description' },
|
|
34
|
+
{ key: 'createdAt', label: 'Created', type: 'datetime' as const, sortable: true, class: 'whitespace-nowrap' },
|
|
35
|
+
]
|
|
17
36
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</div>
|
|
37
|
+
const ROW_ACTIONS = [
|
|
38
|
+
{ action: 'edit', label: 'Edit', icon: 'i-hugeicons-edit-01' },
|
|
39
|
+
{ action: 'delete', label: 'Delete', icon: 'i-hugeicons-delete-02', class: 'text-red-500' },
|
|
40
|
+
]
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
/** `defaultFlag` carries the badge value, since a badge column maps a row field. */
|
|
43
|
+
const rows = derived(() => records().map(record => ({
|
|
44
|
+
...record,
|
|
45
|
+
defaultFlag: record.isDefault ? 'default' : '',
|
|
46
|
+
})))
|
|
47
|
+
|
|
48
|
+
function handleRowAction(payload: { action: string, row: ProductUnitRecord }): void {
|
|
49
|
+
emit(payload.action, payload.row)
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
29
52
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<p class="font-medium text-gray-900 text-sm dark:text-white">{{ record.name }}</p>
|
|
48
|
-
<span :if="record.isDefault" class="px-2 py-0.5 font-medium text-emerald-700 text-xs dark:text-emerald-300 bg-emerald-50 dark:bg-emerald-950/40 rounded-full">Default</span>
|
|
49
|
-
</div>
|
|
50
|
-
<p class="mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ record.abbreviation }}</p>
|
|
51
|
-
</td>
|
|
52
|
-
<td class="px-4 py-4 text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ record.type }}</td>
|
|
53
|
-
<td class="px-4 py-4 text-gray-600 text-sm dark:text-neutral-300">{{ record.productName || 'Unassigned' }}</td>
|
|
54
|
-
<td class="px-4 py-4 max-w-sm text-gray-600 text-sm dark:text-neutral-300">{{ record.description || 'No description' }}</td>
|
|
55
|
-
<td class="px-4 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ formatDate(record.createdAt) }}</td>
|
|
56
|
-
<td class="px-4 py-4">
|
|
57
|
-
<div class="flex gap-1 justify-end">
|
|
58
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'Edit ' + record.name" @click="emit('edit', record)">
|
|
59
|
-
<span class="block h-4 w-4 text-blue-500 i-hugeicons-edit-01"></span>
|
|
60
|
-
</Button>
|
|
61
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'Delete ' + record.name" @click="emit('delete', record)">
|
|
62
|
-
<span class="block h-4 w-4 text-red-500 i-hugeicons-delete-02"></span>
|
|
63
|
-
</Button>
|
|
64
|
-
</div>
|
|
65
|
-
</td>
|
|
66
|
-
</tr>
|
|
67
|
-
</template>
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
53
|
+
{{--
|
|
54
|
+
No `searchable` or `filterable`: the parent dashboard already owns the
|
|
55
|
+
search box and the filter selects, and a second set inside the table would
|
|
56
|
+
duplicate them. Paging is what was missing, and Table brings it.
|
|
57
|
+
--}}
|
|
58
|
+
<Table
|
|
59
|
+
:columns="COLUMNS"
|
|
60
|
+
:data="rows()"
|
|
61
|
+
:loading="loading()"
|
|
62
|
+
:row-actions="ROW_ACTIONS"
|
|
63
|
+
action-label-key="name"
|
|
64
|
+
paginated
|
|
65
|
+
:page-size="25"
|
|
66
|
+
empty-title="No product units found"
|
|
67
|
+
empty-description="Adjust the filters or add a ProductUnit record."
|
|
68
|
+
@row-action="handleRowAction"
|
|
69
|
+
/>
|