@stacksjs/defaults 0.70.362 → 0.70.365
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-browse/SKILL.md +2 -1
- package/ai/skills/stacks-browse/scripts/browse.ts +20 -2
- package/ai/skills/stacks-dashboard/SKILL.md +4 -0
- package/app/Actions/Auth/LogoutAction.ts +11 -4
- package/app/Actions/Auth/LogoutAllAction.ts +8 -4
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
- package/app/Actions/Dashboard/Models/model-write.ts +5 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Buttons/Counter.stx +1 -1
- package/resources/components/Buttons/ToggleDark.stx +1 -1
- package/resources/components/Dashboard/App/RequestDetailsDialog.stx +42 -0
- package/resources/components/Dashboard/App/RequestsOverview.stx +1 -21
- package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +155 -0
- package/resources/components/Dashboard/Auth/AccessTokens.stx +41 -129
- package/resources/components/Dashboard/Ci/CiDashboard.stx +1 -157
- package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +127 -0
- package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +22 -17
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +25 -19
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +14 -11
- package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +24 -16
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +14 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +15 -10
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +30 -25
- package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +27 -20
- package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +14 -17
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DriverDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DriverDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +10 -11
- package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +13 -10
- package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +96 -105
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +92 -95
- package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +94 -0
- package/resources/components/Dashboard/Content/AuthorsDashboard.stx +39 -31
- package/resources/components/Dashboard/Content/BlogDashboard.stx +87 -176
- package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +195 -0
- package/resources/components/Dashboard/Content/CommentsDashboard.stx +14 -4
- package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +40 -55
- package/resources/components/Dashboard/Content/CreateFolderDialog.stx +45 -0
- package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +31 -0
- package/resources/components/Dashboard/Content/FileManagerDashboard.stx +47 -107
- package/resources/components/Dashboard/Content/FileUploadDialog.stx +89 -0
- package/resources/components/Dashboard/Content/PageEditorDialog.stx +93 -0
- package/resources/components/Dashboard/Content/PagesDashboard.stx +36 -26
- package/resources/components/Dashboard/Content/PostEditorDialog.stx +176 -0
- package/resources/components/Dashboard/Content/PostsDashboard.stx +40 -64
- package/resources/components/Dashboard/Content/SeoDashboard.stx +18 -84
- package/resources/components/Dashboard/Content/SeoEditorDialog.stx +108 -0
- package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +106 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +72 -83
- package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
- package/resources/components/Dashboard/GlobalSearch.stx +3 -9
- package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +2 -16
- package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +4 -16
- package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +4 -19
- package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +6 -16
- package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +105 -0
- package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -59
- package/resources/components/Dashboard/Jobs/JobHistory.stx +1 -11
- package/resources/components/Dashboard/Library/ComponentsDashboard.stx +11 -60
- package/resources/components/Dashboard/Library/FunctionsDashboard.stx +11 -15
- package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +95 -0
- package/resources/components/Dashboard/Management/AssignmentDrawer.stx +93 -0
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +47 -129
- package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
- package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +17 -16
- package/resources/components/Dashboard/Models/EditRecordModal.stx +81 -0
- package/resources/components/Dashboard/Models/ModelRecordActions.stx +30 -0
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +42 -94
- package/resources/components/Dashboard/Navbar.stx +5 -5
- package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +40 -60
- package/resources/components/Dashboard/UI/Button.stx +4 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +4 -0
- package/resources/components/Dashboard/UI/Drawer.stx +114 -0
- package/resources/components/Dashboard/UI/Modal.stx +19 -12
- package/resources/components/Docs/Demo/ComboboxDemo.stx +4 -5
- package/resources/components/Docs/Demo/DropdownDemo.stx +12 -5
- package/resources/components/Docs/Demo/ListboxDemo.stx +14 -6
- package/resources/components/Docs/Demo/PopoverDemo.stx +10 -5
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +14 -6
- package/resources/components/Docs/Demo/StepperDemo.stx +4 -5
- package/resources/components/Docs/TeamMember.stx +16 -9
- package/resources/components/Forum/ForumReplyForm.stx +7 -7
- package/resources/components/Forum/ForumSidebar.stx +10 -6
- package/resources/components/Marketing/Faqs.stx +5 -5
- package/resources/components/Marketing/Feature.stx +11 -6
- package/resources/components/Marketing/Layout.stx +12 -8
- package/resources/components/Marketing/Pricing.stx +11 -5
- package/resources/components/Marketing/PrimaryFeatures.stx +4 -5
- package/resources/components/Marketing/PrimaryFeatures2.stx +6 -5
- package/resources/components/Marketing/SecondaryFeatures.stx +10 -5
- package/resources/components/Marketing/Testimonials.stx +8 -5
- package/resources/views/index.stx +4 -4
- package/views/system-tray/index.stx +1 -1
|
@@ -101,6 +101,7 @@ Extracts headings, links (`text -> href`), buttons, forms (action + field count)
|
|
|
101
101
|
bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts scenario <url> \
|
|
102
102
|
--step '{"action":"click","selector":"button[data-open]"}' \
|
|
103
103
|
--step '{"action":"focus","selector":"input[name=name]"}' \
|
|
104
|
+
--step '{"action":"evaluate","expression":"window.__stxDevtools?.stats()"}' \
|
|
104
105
|
--step '{"action":"fill","selector":"input[name=name]","value":"Example"}' \
|
|
105
106
|
--step '{"action":"click","selector":"button[type=submit]"}' \
|
|
106
107
|
--step '{"action":"assert","selector":"main","text":"Saved"}' \
|
|
@@ -108,7 +109,7 @@ bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts scenari
|
|
|
108
109
|
--out storage/framework/runtime/shots/scenario.png
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
Steps run in order within one isolated browser page, preserving reactive STX state and SPA navigation. Supported actions are `click`, `fill`, `focus`, `press`, `wait`, and `assert`. Each step is a JSON object passed through a repeatable `--step` flag. Click and focus steps may include `text` to select the matching control from their CSS selector. The command reports every completed step, the final URL and page text, console messages, console exceptions, failed requests, and an optional screenshot. It exits nonzero when the scenario assertion, browser console, or network fails.
|
|
112
|
+
Steps run in order within one isolated browser page, preserving reactive STX state and SPA navigation. Supported actions are `click`, `fill`, `focus`, `press`, `wait`, `evaluate`, and `assert`. Each step is a JSON object passed through a repeatable `--step` flag. Click and focus steps may include `text` to select the matching control from their CSS selector. They scroll matched controls into view before interacting, including controls inside horizontal overflow regions. Evaluate steps run a JavaScript expression in the isolated page and return its serialized value, which is useful for inspecting STX signals and DevTools state between interactions. The command reports every completed step, the final URL and page text, console messages, console exceptions, failed requests, and an optional screenshot. It exits nonzero when the scenario assertion, evaluation, browser console, or network fails.
|
|
112
113
|
Use `{"action":"assert","selector":"...","absent":true}` to verify that an element is missing or hidden after an interaction.
|
|
113
114
|
Use `{"action":"assert","selector":"button","text":"Save","focused":true}` to verify keyboard focus and focus restoration.
|
|
114
115
|
|
|
@@ -523,7 +523,7 @@ function parseViewport(value: string | undefined): { w: number, h: number } {
|
|
|
523
523
|
return { w, h }
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
type ScenarioAction = 'assert' | 'click' | 'fill' | 'focus' | 'press' | 'wait'
|
|
526
|
+
type ScenarioAction = 'assert' | 'click' | 'evaluate' | 'fill' | 'focus' | 'press' | 'wait'
|
|
527
527
|
|
|
528
528
|
interface ScenarioStep {
|
|
529
529
|
action: ScenarioAction
|
|
@@ -531,6 +531,7 @@ interface ScenarioStep {
|
|
|
531
531
|
text?: string
|
|
532
532
|
absent?: boolean
|
|
533
533
|
focused?: boolean
|
|
534
|
+
expression?: string
|
|
534
535
|
value?: string
|
|
535
536
|
key?: string
|
|
536
537
|
ms?: number
|
|
@@ -550,7 +551,7 @@ function parseScenarioStep(value: string): ScenarioStep {
|
|
|
550
551
|
throw new TypeError('Each scenario step must be a JSON object.')
|
|
551
552
|
|
|
552
553
|
const step = parsed as Record<string, unknown>
|
|
553
|
-
if (!['assert', 'click', 'fill', 'focus', 'press', 'wait'].includes(String(step.action)))
|
|
554
|
+
if (!['assert', 'click', 'evaluate', 'fill', 'focus', 'press', 'wait'].includes(String(step.action)))
|
|
554
555
|
throw new TypeError(`Unsupported scenario action: ${String(step.action)}`)
|
|
555
556
|
|
|
556
557
|
const action = step.action as ScenarioAction
|
|
@@ -558,6 +559,8 @@ function parseScenarioStep(value: string): ScenarioStep {
|
|
|
558
559
|
throw new TypeError(`Scenario action "${action}" requires a selector.`)
|
|
559
560
|
if (action === 'fill' && typeof step.value !== 'string')
|
|
560
561
|
throw new TypeError('Scenario action "fill" requires a string value.')
|
|
562
|
+
if (action === 'evaluate' && typeof step.expression !== 'string')
|
|
563
|
+
throw new TypeError('Scenario action "evaluate" requires an expression.')
|
|
561
564
|
if (step.absent !== undefined && (action !== 'assert' || typeof step.absent !== 'boolean'))
|
|
562
565
|
throw new TypeError('Scenario "absent" is a boolean supported only by assert actions.')
|
|
563
566
|
if (step.focused !== undefined && (action !== 'assert' || typeof step.focused !== 'boolean'))
|
|
@@ -584,6 +587,17 @@ async function runScenarioStep(cdp: Cdp, step: ScenarioStep): Promise<Record<str
|
|
|
584
587
|
return { action: step.action, key, ok: true }
|
|
585
588
|
}
|
|
586
589
|
|
|
590
|
+
if (step.action === 'evaluate') {
|
|
591
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
592
|
+
expression: step.expression,
|
|
593
|
+
returnByValue: true,
|
|
594
|
+
awaitPromise: true,
|
|
595
|
+
})
|
|
596
|
+
if (result.exceptionDetails)
|
|
597
|
+
throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text || 'Evaluation failed')
|
|
598
|
+
return { action: step.action, value: result.result?.value, ok: true }
|
|
599
|
+
}
|
|
600
|
+
|
|
587
601
|
const result = await cdp.send('Runtime.evaluate', {
|
|
588
602
|
expression: `(() => {
|
|
589
603
|
const step = ${JSON.stringify(step)}
|
|
@@ -598,6 +612,8 @@ async function runScenarioStep(cdp: Cdp, step: ScenarioStep): Promise<Record<str
|
|
|
598
612
|
if (!element)
|
|
599
613
|
return { ok: false, error: 'Element not found', selector: step.selector }
|
|
600
614
|
|
|
615
|
+
if (step.action === 'click' || step.action === 'focus')
|
|
616
|
+
element.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' })
|
|
601
617
|
const rect = element.getBoundingClientRect()
|
|
602
618
|
const style = getComputedStyle(element)
|
|
603
619
|
const visible = rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none'
|
|
@@ -612,6 +628,8 @@ async function runScenarioStep(cdp: Cdp, step: ScenarioStep): Promise<Record<str
|
|
|
612
628
|
if (step.action === 'click') {
|
|
613
629
|
if (element.disabled || element.getAttribute('aria-disabled') === 'true')
|
|
614
630
|
return { ok: false, error: 'Element is disabled', selector: step.selector }
|
|
631
|
+
if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= innerWidth || rect.top >= innerHeight)
|
|
632
|
+
return { ok: false, error: 'Element could not be scrolled into the viewport', selector: step.selector }
|
|
615
633
|
}
|
|
616
634
|
else if (step.action === 'focus') {
|
|
617
635
|
if (typeof element.focus !== 'function')
|
|
@@ -153,6 +153,9 @@ is hidden. Do not create separate `/deployments/scripts` or
|
|
|
153
153
|
- Use `tag="a"` whenever `href` is reactive, for example
|
|
154
154
|
`<Button tag="a" :href="detailsPath()">Open details</Button>`. Server rendering
|
|
155
155
|
cannot infer an anchor from a client-only reactive URL.
|
|
156
|
+
- When a submit action lives in a shared Modal footer, give the form a stable
|
|
157
|
+
`id` and associate the action with `<Button type="submit" form="form-id">`.
|
|
158
|
+
Do not duplicate the footer inside the form or use script-driven submission.
|
|
156
159
|
- Prefer component events and named slots over string callback props or
|
|
157
160
|
`data-action` markers. A `data-action` attribute is only valid when an active
|
|
158
161
|
host integration consumes that exact action.
|
|
@@ -325,6 +328,7 @@ the canonical primary action style across the dashboard.
|
|
|
325
328
|
</Button>
|
|
326
329
|
|
|
327
330
|
<Button :loading="saving()" type="submit">Save changes</Button>
|
|
331
|
+
<Button :loading="saving()" type="submit" form="settings-form">Save from modal footer</Button>
|
|
328
332
|
<Button variant="secondary" @click="close">Cancel</Button>
|
|
329
333
|
<Button :loading="deleting()" variant="danger" @click="destroy">Delete</Button>
|
|
330
334
|
<Button tag="a" :href="exportHref()" :download="exportFilename()">Export</Button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
-
import { Auth } from '@stacksjs/auth'
|
|
2
|
+
import { Auth, clearAuthCookie } from '@stacksjs/auth'
|
|
3
3
|
import { response } from '@stacksjs/router'
|
|
4
4
|
|
|
5
5
|
export default new Action({
|
|
@@ -7,10 +7,17 @@ export default new Action({
|
|
|
7
7
|
description: 'Logout from the application',
|
|
8
8
|
method: 'POST',
|
|
9
9
|
async handle() {
|
|
10
|
+
// `Auth.logout()` resolves the token from the Authorization header or the
|
|
11
|
+
// auth cookie, so this revokes the session either way.
|
|
10
12
|
await Auth.logout()
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
// Clearing is separate from revoking, and both are needed. Revoking alone
|
|
15
|
+
// leaves the browser sending a dead cookie on every request; clearing alone
|
|
16
|
+
// leaves a copied cookie valid for the token's whole lifetime, which would
|
|
17
|
+
// make "log out on a shared computer" mean only "hide the key".
|
|
18
|
+
return response.json(
|
|
19
|
+
{ message: 'Successfully logged out' },
|
|
20
|
+
{ headers: { 'Set-Cookie': clearAuthCookie() } },
|
|
21
|
+
)
|
|
15
22
|
},
|
|
16
23
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
-
import { revokeAllTokens, sessionDestroyAll } from '@stacksjs/auth'
|
|
2
|
+
import { clearAuthCookie, revokeAllTokens, sessionDestroyAll } from '@stacksjs/auth'
|
|
3
3
|
import { response } from '@stacksjs/router'
|
|
4
4
|
|
|
5
5
|
export default new Action({
|
|
@@ -21,8 +21,12 @@ export default new Action({
|
|
|
21
21
|
await revokeAllTokens(Number(user.id))
|
|
22
22
|
await sessionDestroyAll(Number(user.id))
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// This device included: its cookie carries one of the tokens just revoked,
|
|
25
|
+
// so leaving it in place means the browser keeps presenting a dead
|
|
26
|
+
// credential on every request.
|
|
27
|
+
return response.json(
|
|
28
|
+
{ message: 'Successfully logged out from all devices' },
|
|
29
|
+
{ headers: { 'Set-Cookie': clearAuthCookie() } },
|
|
30
|
+
)
|
|
27
31
|
},
|
|
28
32
|
})
|
|
@@ -3,7 +3,7 @@ import { Action } from '@stacksjs/actions'
|
|
|
3
3
|
import { response as routerResponse } from '@stacksjs/router'
|
|
4
4
|
import { loadModelIfExists, safeGet } from '../../../../resources/functions/dashboard/data'
|
|
5
5
|
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
|
-
import { isValidModelSlug, type ModelCreateField, type ModelWriteCapabilities, modelCreateFields, modelSchemaColumns, modelWriteCapabilities, slugToPascal } from './model-write'
|
|
6
|
+
import { isValidModelSlug, type ModelCreateField, type ModelWriteCapabilities, modelCreateFields, modelSchemaColumns, modelWritableColumns, modelWriteCapabilities, slugToPascal } from './model-write'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* `GET /api/dashboard/models/{slug}` (stacksjs/stacks#1838).
|
|
@@ -53,6 +53,7 @@ interface ResponseShape {
|
|
|
53
53
|
writable: boolean
|
|
54
54
|
writeCapabilities: ModelWriteCapabilities
|
|
55
55
|
createFields: ModelCreateField[]
|
|
56
|
+
updateColumns: string[]
|
|
56
57
|
error: string | null
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -214,6 +215,7 @@ export default new Action({
|
|
|
214
215
|
writable: false,
|
|
215
216
|
writeCapabilities: { create: false, update: false, destroy: false },
|
|
216
217
|
createFields: [],
|
|
218
|
+
updateColumns: [],
|
|
217
219
|
error: null,
|
|
218
220
|
}
|
|
219
221
|
|
|
@@ -296,6 +298,7 @@ export default new Action({
|
|
|
296
298
|
response.writeCapabilities = modelWriteCapabilities(Model)
|
|
297
299
|
response.writable = Object.values(response.writeCapabilities).some(Boolean)
|
|
298
300
|
response.createFields = response.writeCapabilities.create ? modelCreateFields(Model) : []
|
|
301
|
+
response.updateColumns = response.writeCapabilities.update ? modelWritableColumns(Model) : []
|
|
299
302
|
response.displayColumns = response.columns.filter(col => !col.startsWith('_'))
|
|
300
303
|
response.columnMeta = response.displayColumns.map(name => ({
|
|
301
304
|
name,
|
|
@@ -204,6 +204,11 @@ export function modelCreateFields(Model: any): ModelCreateField[] {
|
|
|
204
204
|
})
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/** Physical database columns accepted by the model's generic update API. */
|
|
208
|
+
export function modelWritableColumns(Model: any): string[] {
|
|
209
|
+
return modelCreateFields(Model).map(field => snakeCase(field.name))
|
|
210
|
+
}
|
|
211
|
+
|
|
207
212
|
function normalizeModelValue(type: ModelCreateField['type'], value: unknown): unknown {
|
|
208
213
|
if (type === 'number') {
|
|
209
214
|
if (value === '' || value === null || value === undefined)
|
package/ide/vscode/package.json
CHANGED
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.365",
|
|
6
6
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface RequestRow {
|
|
3
|
+
id: number
|
|
4
|
+
method: string
|
|
5
|
+
path: string
|
|
6
|
+
status: number
|
|
7
|
+
durationMs: number
|
|
8
|
+
ipAddress: string
|
|
9
|
+
memoryUsageMb: number
|
|
10
|
+
userAgent: string
|
|
11
|
+
errorMessage: string
|
|
12
|
+
createdAt: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const request = useReactiveProp('request', null as RequestRow | null)
|
|
16
|
+
const emit = defineEmits()
|
|
17
|
+
|
|
18
|
+
function formatDate(value: string | undefined): string {
|
|
19
|
+
if (!value)
|
|
20
|
+
return 'Unknown'
|
|
21
|
+
const date = new Date(value)
|
|
22
|
+
return Number.isNaN(date.getTime()) ? 'Unknown' : date.toLocaleString()
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Modal
|
|
27
|
+
:isOpen="Boolean(request())"
|
|
28
|
+
:title="request() ? 'Request #' + request()?.id : 'Request details'"
|
|
29
|
+
size="lg"
|
|
30
|
+
@close="emit('close')"
|
|
31
|
+
>
|
|
32
|
+
<p class="mb-5 break-all font-mono text-neutral-500 text-sm dark:text-neutral-400">{{ request()?.method }} {{ request()?.path }}</p>
|
|
33
|
+
<dl class="grid gap-4 grid-cols-1 sm:grid-cols-2 text-sm">
|
|
34
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Status</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.status }}</dd></div>
|
|
35
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Duration</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.durationMs?.toFixed(1) || '0.0' }} ms</dd></div>
|
|
36
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">IP address</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.ipAddress || 'Not recorded' }}</dd></div>
|
|
37
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Memory usage</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.memoryUsageMb || 0 }} MB</dd></div>
|
|
38
|
+
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Recorded</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ formatDate(request()?.createdAt) }}</dd></div>
|
|
39
|
+
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">User agent</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ request()?.userAgent || 'Not recorded' }}</dd></div>
|
|
40
|
+
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Error</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ request()?.errorMessage || 'No error recorded' }}</dd></div>
|
|
41
|
+
</dl>
|
|
42
|
+
</Modal>
|
|
@@ -229,25 +229,5 @@ onMount(() => {
|
|
|
229
229
|
</div>
|
|
230
230
|
</Card>
|
|
231
231
|
|
|
232
|
-
<
|
|
233
|
-
<div role="dialog" aria-modal="true" aria-labelledby="request-detail-title" class="p-6 max-w-2xl w-full bg-white dark:bg-neutral-900 border border-neutral-200 rounded-xl dark:border-neutral-700 shadow-xl">
|
|
234
|
-
<div class="flex gap-4 items-start justify-between">
|
|
235
|
-
<div>
|
|
236
|
-
<h2 id="request-detail-title" class="font-semibold text-lg text-neutral-900 dark:text-white">Request #{{ selectedRequest()?.id }}</h2>
|
|
237
|
-
<p class="mt-1 break-all font-mono text-neutral-500 text-sm dark:text-neutral-400">{{ selectedRequest()?.method }} {{ selectedRequest()?.path }}</p>
|
|
238
|
-
</div>
|
|
239
|
-
<Button variant="ghost" iconOnly ariaLabel="Close request details" @click="closeInspector">
|
|
240
|
-
<span class="h-5 w-5 i-hugeicons-cancel-01"></span>
|
|
241
|
-
</Button>
|
|
242
|
-
</div>
|
|
243
|
-
<dl class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-6 text-sm">
|
|
244
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Status</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.status }}</dd></div>
|
|
245
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Duration</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.durationMs.toFixed(1) }} ms</dd></div>
|
|
246
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">IP address</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.ipAddress || 'Not recorded' }}</dd></div>
|
|
247
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Memory usage</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.memoryUsageMb || 0 }} MB</dd></div>
|
|
248
|
-
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">User agent</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ selectedRequest()?.userAgent || 'Not recorded' }}</dd></div>
|
|
249
|
-
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Error</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ selectedRequest()?.errorMessage || 'No error recorded' }}</dd></div>
|
|
250
|
-
</dl>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
232
|
+
<RequestDetailsDialog :request="selectedRequest()" @close="closeInspector" />
|
|
253
233
|
</div>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface ScopeOption {
|
|
3
|
+
name: string
|
|
4
|
+
description: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface TokenDraft {
|
|
8
|
+
name: string
|
|
9
|
+
scopes: string[]
|
|
10
|
+
expiresInMinutes: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const availableScopes: ScopeOption[] = [
|
|
14
|
+
{ name: 'read', description: 'Read application resources' },
|
|
15
|
+
{ name: 'write', description: 'Create and update resources' },
|
|
16
|
+
{ name: 'delete', description: 'Delete application resources' },
|
|
17
|
+
{ name: 'deploy', description: 'Run deployment operations' },
|
|
18
|
+
{ name: 'admin', description: 'Use administrative operations' },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const open = useReactiveProp('open', false)
|
|
22
|
+
const busy = useReactiveProp('busy', false)
|
|
23
|
+
const createdToken = useReactiveProp('createdToken', '')
|
|
24
|
+
const message = useReactiveProp('message', '')
|
|
25
|
+
const error = useReactiveProp('error', '')
|
|
26
|
+
const tokenName = state('')
|
|
27
|
+
const expiryMinutes = state('10080')
|
|
28
|
+
const selectedScopes = state<string[]>(['read'])
|
|
29
|
+
const localError = state('')
|
|
30
|
+
const copied = state(false)
|
|
31
|
+
const emit = defineEmits()
|
|
32
|
+
let wasOpen = false
|
|
33
|
+
|
|
34
|
+
effect(() => {
|
|
35
|
+
const isOpen = open()
|
|
36
|
+
if (isOpen && !wasOpen) {
|
|
37
|
+
tokenName.set('')
|
|
38
|
+
expiryMinutes.set('10080')
|
|
39
|
+
selectedScopes.set(['read'])
|
|
40
|
+
localError.set('')
|
|
41
|
+
copied.set(false)
|
|
42
|
+
}
|
|
43
|
+
wasOpen = isOpen
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
function toggleScope(event: Event, scope: string): void {
|
|
47
|
+
const enabled = (event.target as HTMLInputElement).checked
|
|
48
|
+
selectedScopes.set(enabled
|
|
49
|
+
? [...new Set([...selectedScopes(), scope])]
|
|
50
|
+
: selectedScopes().filter(item => item !== scope))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function submit(): void {
|
|
54
|
+
localError.set('')
|
|
55
|
+
if (!tokenName().trim()) {
|
|
56
|
+
localError.set('Enter a name for this token.')
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (selectedScopes().length === 0) {
|
|
60
|
+
localError.set('Select at least one scope.')
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const draft: TokenDraft = {
|
|
65
|
+
name: tokenName().trim(),
|
|
66
|
+
scopes: [...selectedScopes()],
|
|
67
|
+
expiresInMinutes: Number(expiryMinutes()),
|
|
68
|
+
}
|
|
69
|
+
emit('submit', draft)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function close(): void {
|
|
73
|
+
if (!busy())
|
|
74
|
+
emit('close')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function copyToken(): Promise<void> {
|
|
78
|
+
localError.set('')
|
|
79
|
+
try {
|
|
80
|
+
await globalThis.navigator.clipboard.writeText(createdToken())
|
|
81
|
+
copied.set(true)
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
localError.set('Clipboard access is unavailable. Select and copy the token manually.')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<Modal
|
|
90
|
+
:isOpen="open()"
|
|
91
|
+
title="Create Access Token"
|
|
92
|
+
description="Grant only the scopes this integration needs."
|
|
93
|
+
:closable="!busy()"
|
|
94
|
+
@close="close"
|
|
95
|
+
>
|
|
96
|
+
<div :if="createdToken()" class="space-y-4">
|
|
97
|
+
<div role="status" class="px-4 py-3 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/40 border border-amber-200 rounded-lg dark:border-amber-900">
|
|
98
|
+
{{ message() }}
|
|
99
|
+
</div>
|
|
100
|
+
<label class="block">
|
|
101
|
+
<span class="font-medium text-neutral-700 text-sm dark:text-neutral-300">New token</span>
|
|
102
|
+
<textarea readonly rows="4" aria-label="New access token" class="block mt-2 p-3 w-full font-mono text-neutral-900 text-xs dark:text-white bg-neutral-50 dark:bg-neutral-950 border border-neutral-300 rounded-lg dark:border-neutral-700">{{ createdToken() }}</textarea>
|
|
103
|
+
</label>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<form :if="!createdToken()" id="access-token-create-form" class="space-y-5" @submit.prevent="submit">
|
|
107
|
+
<label class="block">
|
|
108
|
+
<span class="font-medium text-neutral-700 text-sm dark:text-neutral-300">Token name</span>
|
|
109
|
+
<input x-model="tokenName" name="name" type="text" maxlength="255" required placeholder="CI deployment" class="block mt-2 px-3 py-2 w-full text-neutral-900 text-sm dark:text-white bg-white dark:bg-neutral-950 border border-neutral-300 rounded-lg dark:border-neutral-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
|
|
110
|
+
</label>
|
|
111
|
+
|
|
112
|
+
<label class="block">
|
|
113
|
+
<span class="font-medium text-neutral-700 text-sm dark:text-neutral-300">Expires after</span>
|
|
114
|
+
<select x-model="expiryMinutes" name="expires_in_minutes" class="block mt-2 px-3 py-2 w-full text-neutral-900 text-sm dark:text-white bg-white dark:bg-neutral-950 border border-neutral-300 rounded-lg dark:border-neutral-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
|
|
115
|
+
<option value="60">1 hour</option>
|
|
116
|
+
<option value="1440">1 day</option>
|
|
117
|
+
<option value="10080">7 days</option>
|
|
118
|
+
<option value="43200">30 days</option>
|
|
119
|
+
</select>
|
|
120
|
+
</label>
|
|
121
|
+
|
|
122
|
+
<fieldset>
|
|
123
|
+
<legend class="font-medium text-neutral-700 text-sm dark:text-neutral-300">Scopes</legend>
|
|
124
|
+
<div class="grid gap-2 grid-cols-1 sm:grid-cols-2 mt-2">
|
|
125
|
+
<label :for="scope in availableScopes" class="flex gap-3 items-start p-3 border border-neutral-200 rounded-lg dark:border-neutral-700">
|
|
126
|
+
<input name="scopes" type="checkbox" :value="scope.name" :checked="selectedScopes().includes(scope.name)" class="mt-0.5 h-4 w-4 text-blue-600 rounded" @change="toggleScope($event, scope.name)">
|
|
127
|
+
<span>
|
|
128
|
+
<span class="block font-medium text-neutral-900 text-sm dark:text-white">{{ scope.name }}</span>
|
|
129
|
+
<span class="block mt-0.5 text-neutral-500 text-xs dark:text-neutral-400">{{ scope.description }}</span>
|
|
130
|
+
</span>
|
|
131
|
+
</label>
|
|
132
|
+
</div>
|
|
133
|
+
</fieldset>
|
|
134
|
+
</form>
|
|
135
|
+
|
|
136
|
+
<div :if="localError() || error()" role="alert" class="mt-4 px-3 py-2 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/40 rounded-lg">
|
|
137
|
+
{{ localError() || error() }}
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<template #footer>
|
|
141
|
+
<div :if="createdToken()" class="flex gap-2 justify-end w-full">
|
|
142
|
+
<Button variant="secondary" @click="close">Done</Button>
|
|
143
|
+
<Button @click="copyToken">
|
|
144
|
+
<span class="h-4 w-4 i-hugeicons-copy-01"></span>
|
|
145
|
+
{{ copied() ? 'Copied' : 'Copy Token' }}
|
|
146
|
+
</Button>
|
|
147
|
+
</div>
|
|
148
|
+
<div :if="!createdToken()" class="flex gap-2 justify-end w-full">
|
|
149
|
+
<Button variant="secondary" :disabled="busy()" @click="close">Cancel</Button>
|
|
150
|
+
<Button type="submit" form="access-token-create-form" :loading="busy()">
|
|
151
|
+
{{ busy() ? 'Creating...' : 'Create Token' }}
|
|
152
|
+
</Button>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
</Modal>
|