@stacksjs/defaults 0.70.358 → 0.70.363
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 +7 -5
- package/ai/skills/stacks-browse/scripts/browse.ts +68 -9
- package/ai/skills/stacks-dashboard/SKILL.md +4 -0
- package/app/Actions/AI/AskAction.ts +12 -17
- package/app/Actions/AI/SummaryAction.ts +12 -17
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
- package/app/Actions/Dashboard/Models/model-write.ts +5 -0
- package/app/Actions/LogAction.ts +11 -11
- 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/CampaignDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
- package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +15 -15
- package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +11 -12
- 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 +55 -53
- package/resources/components/Dashboard/UI/Drawer.stx +114 -0
- package/resources/components/Dashboard/UI/Modal.stx +32 -21
- 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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface EditField {
|
|
3
|
+
name: string
|
|
4
|
+
label: string
|
|
5
|
+
value: string
|
|
6
|
+
readonly: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const open = useReactiveProp('open', false)
|
|
10
|
+
const modelName = useReactiveProp('modelName', 'Record')
|
|
11
|
+
const fields = useReactiveProp('fields', [] as EditField[])
|
|
12
|
+
const busy = useReactiveProp('busy', false)
|
|
13
|
+
const values = state<Record<string, string>>({})
|
|
14
|
+
const emit = defineEmits()
|
|
15
|
+
let hydrationKey = ''
|
|
16
|
+
|
|
17
|
+
effect(() => {
|
|
18
|
+
if (!open()) {
|
|
19
|
+
hydrationKey = ''
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const key = fields().map(field => `${field.name}:${field.value}`).join('|')
|
|
24
|
+
if (key === hydrationKey)
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
hydrationKey = key
|
|
28
|
+
values.set(Object.fromEntries(fields().map(field => [field.name, field.value])))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function updateValue(field: string, event: Event): void {
|
|
32
|
+
values.set({ ...values(), [field]: (event.target as HTMLInputElement).value })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function submit(): void {
|
|
36
|
+
emit('submit', Object.fromEntries(
|
|
37
|
+
fields()
|
|
38
|
+
.filter(field => !field.readonly)
|
|
39
|
+
.map(field => [field.name, values()[field.name]]),
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function close(): void {
|
|
44
|
+
if (!busy())
|
|
45
|
+
emit('close')
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<Modal
|
|
50
|
+
:isOpen="open()"
|
|
51
|
+
:title="'Edit ' + modelName()"
|
|
52
|
+
description="Only changed writable fields will be sent through the model API."
|
|
53
|
+
:closable="!busy()"
|
|
54
|
+
@close="close"
|
|
55
|
+
>
|
|
56
|
+
<form id="model-edit-form" class="overflow-y-auto space-y-3 max-h-[65vh]" @submit.prevent="submit">
|
|
57
|
+
<template :for="field in fields()">
|
|
58
|
+
<div class="flex flex-col gap-1.5">
|
|
59
|
+
<label :for="'model-edit-' + field.name" class="font-medium text-[12px] text-neutral-600 dark:text-neutral-300">{{ field.label }}</label>
|
|
60
|
+
<input
|
|
61
|
+
:id="'model-edit-' + field.name"
|
|
62
|
+
:name="field.readonly ? undefined : field.name"
|
|
63
|
+
type="text"
|
|
64
|
+
:value="values()[field.name] ?? field.value"
|
|
65
|
+
:readonly="field.readonly"
|
|
66
|
+
class="px-2.5 py-1.5 text-[13px] text-neutral-900 dark:text-neutral-100 read-only:text-neutral-500 dark:read-only:text-neutral-400 bg-white dark:bg-white/10 read-only:bg-neutral-50 dark:read-only:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
|
67
|
+
@input="updateValue(field.name, $event)"
|
|
68
|
+
>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
</form>
|
|
72
|
+
|
|
73
|
+
<template #footer>
|
|
74
|
+
<div class="flex gap-2 items-center justify-end w-full">
|
|
75
|
+
<Button :disabled="busy()" variant="secondary" @click="close">Cancel</Button>
|
|
76
|
+
<Button type="submit" form="model-edit-form" :loading="busy()">
|
|
77
|
+
{{ busy() ? 'Saving' : 'Save changes' }}
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
</Modal>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
const record = useReactiveProp('record', {} as Record<string, unknown>)
|
|
3
|
+
const modelName = useReactiveProp('modelName', 'Record')
|
|
4
|
+
const canUpdate = useReactiveProp('canUpdate', false)
|
|
5
|
+
const canDestroy = useReactiveProp('canDestroy', false)
|
|
6
|
+
const emit = defineEmits()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class="flex gap-1 items-center justify-end">
|
|
10
|
+
<Button
|
|
11
|
+
:if="canUpdate()"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
size="sm"
|
|
14
|
+
iconOnly
|
|
15
|
+
:ariaLabel="'Edit ' + modelName() + ' ' + record().id"
|
|
16
|
+
@click="emit('edit', record())"
|
|
17
|
+
>
|
|
18
|
+
<span class="h-4 w-4 i-hugeicons-edit-02"></span>
|
|
19
|
+
</Button>
|
|
20
|
+
<Button
|
|
21
|
+
:if="canDestroy()"
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size="sm"
|
|
24
|
+
iconOnly
|
|
25
|
+
:ariaLabel="'Delete ' + modelName() + ' ' + record().id"
|
|
26
|
+
@click="emit('delete', record())"
|
|
27
|
+
>
|
|
28
|
+
<span class="h-4 w-4 text-red-500 i-hugeicons-delete-02"></span>
|
|
29
|
+
</Button>
|
|
30
|
+
</div>
|
|
@@ -46,6 +46,7 @@ interface ResponseShape {
|
|
|
46
46
|
writable: boolean
|
|
47
47
|
writeCapabilities: WriteCapabilities
|
|
48
48
|
createFields: CreateField[]
|
|
49
|
+
updateColumns: string[]
|
|
49
50
|
error: string | null
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -66,15 +67,14 @@ const search = state(navigation.urlQuery('q') || '')
|
|
|
66
67
|
const filters = state<Record<string, string>>({})
|
|
67
68
|
const writeCapabilities = state<WriteCapabilities>({ create: false, update: false, destroy: false })
|
|
68
69
|
const createFields = state<CreateField[]>([])
|
|
70
|
+
const updateColumns = state<string[]>([])
|
|
69
71
|
const error = state<string | null>(null)
|
|
70
72
|
const loading = state(true)
|
|
71
73
|
const showFilters = state(false)
|
|
72
74
|
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// it was and a failed save does not desync the two.
|
|
75
|
+
// The row being edited. `null` closes the editor; EditRecordModal owns the
|
|
76
|
+
// working copy so cancelling leaves the table row unchanged.
|
|
76
77
|
const editing = state<Record<string, unknown> | null>(null)
|
|
77
|
-
const editValues = state<Record<string, string>>({})
|
|
78
78
|
const saving = state(false)
|
|
79
79
|
const confirmingDelete = state<Record<string, unknown> | null>(null)
|
|
80
80
|
const showCreate = state(false)
|
|
@@ -113,9 +113,9 @@ const deleteDescription = derived(() => {
|
|
|
113
113
|
return `Row ${row.id} will be removed through the model, so soft deletes and observers apply.`
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
// Fields for the edit
|
|
117
|
-
// row being edited rather than from
|
|
118
|
-
// once per open: deriving it from the working copy
|
|
116
|
+
// Fields for the edit modal, carrying their starting value. Derived from the
|
|
117
|
+
// row being edited rather than from its mutable draft, so the list changes
|
|
118
|
+
// exactly once per open: deriving it from the working copy would rebuild
|
|
119
119
|
// every input on each keystroke and steal focus mid-word.
|
|
120
120
|
const editFields = derived(() => {
|
|
121
121
|
const row = editing()
|
|
@@ -124,10 +124,9 @@ const editFields = derived(() => {
|
|
|
124
124
|
name: column.name,
|
|
125
125
|
label: column.label,
|
|
126
126
|
value: row[column.name] === null || row[column.name] === undefined ? '' : String(row[column.name]),
|
|
127
|
-
// The
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
readonly: column.name === 'id' || column.name === 'uuid' || column.name.endsWith('_at'),
|
|
127
|
+
// The model API accepts only fillable attributes and declared belongs-to
|
|
128
|
+
// keys. Every other database column stays visible as read-only context.
|
|
129
|
+
readonly: !updateColumns().includes(column.name),
|
|
131
130
|
}))
|
|
132
131
|
})
|
|
133
132
|
|
|
@@ -213,6 +212,7 @@ async function load() {
|
|
|
213
212
|
sort.set(data.sort ?? '')
|
|
214
213
|
writeCapabilities.set(data.writeCapabilities ?? { create: false, update: false, destroy: false })
|
|
215
214
|
createFields.set(data.createFields ?? [])
|
|
215
|
+
updateColumns.set(data.updateColumns ?? [])
|
|
216
216
|
error.set(data.error ?? null)
|
|
217
217
|
}
|
|
218
218
|
catch (e) {
|
|
@@ -230,26 +230,17 @@ function toggleSort(column: ColumnMeta) {
|
|
|
230
230
|
void load()
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
// Debounced so a query is not fired per keystroke.
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
searchTimer = setTimeout(() => {
|
|
241
|
-
page.set(1)
|
|
242
|
-
void load()
|
|
243
|
-
}, 250)
|
|
244
|
-
}
|
|
233
|
+
// Debounced so a query is not fired per keystroke. Search and the per-column
|
|
234
|
+
// filters deliberately share one debounce, the way they shared one timer id
|
|
235
|
+
// before: typing in either should schedule a single reload, not two.
|
|
236
|
+
const onSearchInput = useDebounce(() => {
|
|
237
|
+
page.set(1)
|
|
238
|
+
void load()
|
|
239
|
+
}, 250)
|
|
245
240
|
|
|
246
241
|
function onFilterInput(column: string, event: Event) {
|
|
247
242
|
filters.set({ ...filters(), [column]: (event.target as HTMLInputElement).value })
|
|
248
|
-
|
|
249
|
-
searchTimer = setTimeout(() => {
|
|
250
|
-
page.set(1)
|
|
251
|
-
void load()
|
|
252
|
-
}, 250)
|
|
243
|
+
onSearchInput()
|
|
253
244
|
}
|
|
254
245
|
|
|
255
246
|
function clearFilters() {
|
|
@@ -272,12 +263,6 @@ function onPerPageChange() {
|
|
|
272
263
|
}
|
|
273
264
|
|
|
274
265
|
function startEdit(row: Record<string, unknown>) {
|
|
275
|
-
const values: Record<string, string> = {}
|
|
276
|
-
for (const column of columnMeta()) {
|
|
277
|
-
const raw = row[column.name]
|
|
278
|
-
values[column.name] = raw === null || raw === undefined ? '' : String(raw)
|
|
279
|
-
}
|
|
280
|
-
editValues.set(values)
|
|
281
266
|
editing.set(row)
|
|
282
267
|
}
|
|
283
268
|
|
|
@@ -319,17 +304,13 @@ async function createRecord(fields: Record<string, unknown>): Promise<void> {
|
|
|
319
304
|
}
|
|
320
305
|
}
|
|
321
306
|
|
|
322
|
-
function
|
|
323
|
-
editValues.set({ ...editValues(), [column]: (event.target as HTMLInputElement).value })
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function saveEdit() {
|
|
307
|
+
async function saveEdit(values: Record<string, string>) {
|
|
327
308
|
const row = editing()
|
|
328
309
|
if (!row) return
|
|
329
310
|
// Send only what actually changed, so a save never rewrites columns the
|
|
330
311
|
// editor merely displayed.
|
|
331
312
|
const fields: Record<string, unknown> = {}
|
|
332
|
-
for (const [column, value] of Object.entries(
|
|
313
|
+
for (const [column, value] of Object.entries(values)) {
|
|
333
314
|
const original = row[column]
|
|
334
315
|
const originalText = original === null || original === undefined ? '' : String(original)
|
|
335
316
|
if (value !== originalText) fields[column] = value === '' ? null : value
|
|
@@ -492,7 +473,7 @@ void load()
|
|
|
492
473
|
</button>
|
|
493
474
|
</th>
|
|
494
475
|
</template>
|
|
495
|
-
<th :if="canManageRows()" scope="col" class="px-3 py-2 w-24 font-medium text-[11px] text-neutral-500 text-right tracking-wide uppercase dark:text-neutral-400">Actions</th>
|
|
476
|
+
<th :if="canManageRows()" scope="col" class="right-0 sticky z-20 px-3 py-2 w-24 font-medium text-[11px] text-neutral-500 text-right tracking-wide uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-800 border-black/5 border-l dark:border-white/10">Actions</th>
|
|
496
477
|
</tr>
|
|
497
478
|
|
|
498
479
|
<!-- Per-column filter row -->
|
|
@@ -508,7 +489,7 @@ void load()
|
|
|
508
489
|
>
|
|
509
490
|
</th>
|
|
510
491
|
</template>
|
|
511
|
-
<th :if="canManageRows()"></th>
|
|
492
|
+
<th :if="canManageRows()" class="right-0 sticky z-20 bg-white dark:bg-neutral-900 border-black/5 border-l dark:border-white/10"></th>
|
|
512
493
|
</tr>
|
|
513
494
|
</thead>
|
|
514
495
|
|
|
@@ -524,7 +505,7 @@ void load()
|
|
|
524
505
|
<div class="h-3 bg-neutral-200 dark:bg-white/10 rounded animate-pulse"></div>
|
|
525
506
|
</td>
|
|
526
507
|
</template>
|
|
527
|
-
<td :if="canManageRows()" class="px-3 py-2">
|
|
508
|
+
<td :if="canManageRows()" class="right-0 sticky z-10 px-3 py-2 bg-white dark:bg-neutral-900 border-black/5 border-l dark:border-white/10">
|
|
528
509
|
<div class="ml-auto h-3 w-16 bg-neutral-200 dark:bg-white/10 rounded animate-pulse"></div>
|
|
529
510
|
</td>
|
|
530
511
|
</tr>
|
|
@@ -533,19 +514,19 @@ void load()
|
|
|
533
514
|
|
|
534
515
|
<tbody :if="!loading()" class="divide-black/5 divide-y dark:divide-white/8">
|
|
535
516
|
<template :for="row in rows()">
|
|
536
|
-
<tr class="hover:bg-neutral-50 dark:hover:bg-white/5 transition-colors">
|
|
517
|
+
<tr class="hover:bg-neutral-50 dark:hover:bg-white/5 transition-colors group">
|
|
537
518
|
<template :for="column in columnMeta()">
|
|
538
519
|
<td :class="cellClass(column)" :title="cellValue(row, column)">{{ cellValue(row, column) }}</td>
|
|
539
520
|
</template>
|
|
540
|
-
<td :if="canManageRows()" class="px-3 py-2 text-right">
|
|
541
|
-
<
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
521
|
+
<td :if="canManageRows()" class="right-0 sticky z-10 px-3 py-2 text-right bg-white dark:bg-neutral-900 group-hover:bg-neutral-50 dark:group-hover:bg-white/5 border-black/5 border-l dark:border-white/10 transition-colors">
|
|
522
|
+
<ModelRecordActions
|
|
523
|
+
:record="row"
|
|
524
|
+
:modelName="modelName()"
|
|
525
|
+
:canUpdate="writeCapabilities().update"
|
|
526
|
+
:canDestroy="writeCapabilities().destroy"
|
|
527
|
+
@edit="startEdit"
|
|
528
|
+
@delete="confirmingDelete.set($event)"
|
|
529
|
+
/>
|
|
549
530
|
</td>
|
|
550
531
|
</tr>
|
|
551
532
|
</template>
|
|
@@ -565,47 +546,14 @@ void load()
|
|
|
565
546
|
</div>
|
|
566
547
|
</div>
|
|
567
548
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
>
|
|
577
|
-
<div class="flex items-center justify-between px-4 py-3 border-b border-black/5 dark:border-white/10">
|
|
578
|
-
<h2 id="model-edit-title" class="font-semibold text-[14px] text-neutral-900 dark:text-white">Edit <span :text="modelName()"></span></h2>
|
|
579
|
-
<Button variant="ghost" size="sm" iconOnly ariaLabel="Close record editor" @click="editing.set(null)">
|
|
580
|
-
<span class="h-4 w-4 i-hugeicons-cancel-01"></span>
|
|
581
|
-
</Button>
|
|
582
|
-
</div>
|
|
583
|
-
|
|
584
|
-
<div class="overflow-y-auto flex-1 p-4 space-y-3">
|
|
585
|
-
<template :for="field in editFields()">
|
|
586
|
-
<div class="flex flex-col gap-1.5">
|
|
587
|
-
<label :for="'model-edit-' + field.name" class="font-medium text-[12px] text-neutral-600 dark:text-neutral-300">{{ field.label }}</label>
|
|
588
|
-
<input
|
|
589
|
-
:id="'model-edit-' + field.name"
|
|
590
|
-
type="text"
|
|
591
|
-
:value="field.value"
|
|
592
|
-
@input="onEditInput(field.name, $event)"
|
|
593
|
-
:readonly="field.readonly"
|
|
594
|
-
class="px-2.5 py-1.5 text-[13px] text-neutral-900 dark:text-neutral-100 read-only:text-neutral-500 dark:read-only:text-neutral-400 bg-white dark:bg-white/10 read-only:bg-neutral-50 dark:read-only:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
|
595
|
-
>
|
|
596
|
-
</div>
|
|
597
|
-
</template>
|
|
598
|
-
</div>
|
|
599
|
-
|
|
600
|
-
<div class="flex gap-2 items-center justify-end px-4 py-3 bg-neutral-50 dark:bg-white/5 border-black/5 border-t dark:border-white/10">
|
|
601
|
-
<Button :disabled="saving()" variant="secondary" @click="editing.set(null)">Cancel</Button>
|
|
602
|
-
<Button :loading="saving()" @click="saveEdit()">
|
|
603
|
-
<span :if="!saving()">Save changes</span>
|
|
604
|
-
<span :if="saving()">Saving</span>
|
|
605
|
-
</Button>
|
|
606
|
-
</div>
|
|
607
|
-
</div>
|
|
608
|
-
</div>
|
|
549
|
+
<EditRecordModal
|
|
550
|
+
:open="Boolean(editing())"
|
|
551
|
+
:modelName="modelName()"
|
|
552
|
+
:fields="editFields()"
|
|
553
|
+
:busy="saving()"
|
|
554
|
+
@close="editing.set(null)"
|
|
555
|
+
@submit="saveEdit($event)"
|
|
556
|
+
/>
|
|
609
557
|
|
|
610
558
|
<ConfirmDialog
|
|
611
559
|
:isOpen="Boolean(confirmingDelete())"
|
|
@@ -69,27 +69,27 @@ useClickOutside(userMenu, () => setDropdownOpen(false))
|
|
|
69
69
|
</StxLink>
|
|
70
70
|
|
|
71
71
|
<nav class="flex gap-x-4 items-center lg:gap-x-6" aria-label="Dashboard utilities">
|
|
72
|
-
<a href="/errors" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/errors') ? 'active-header-link' : '' }}" title="View errors">
|
|
72
|
+
<a href="/errors" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/errors') ? 'active-header-link' : '' }}" x-tooltip="View errors" title="View errors">
|
|
73
73
|
<span class="sr-only">View errors</span>
|
|
74
74
|
<span aria-hidden="true" class="h-6 w-6 duration-150 ease-in-out transition i-hugeicons-alert-01"></span>
|
|
75
75
|
</a>
|
|
76
76
|
|
|
77
|
-
<a href="/health" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/health') ? 'active-header-link' : '' }}" title="View health">
|
|
77
|
+
<a href="/health" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/health') ? 'active-header-link' : '' }}" x-tooltip="View health" title="View health">
|
|
78
78
|
<span class="sr-only">View health</span>
|
|
79
79
|
<span aria-hidden="true" class="h-6 w-6 duration-150 ease-in-out transition i-hugeicons-health"></span>
|
|
80
80
|
</a>
|
|
81
81
|
|
|
82
|
-
<a href="/insights" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/insights') ? 'active-header-link' : '' }}" title="View cloud insights">
|
|
82
|
+
<a href="/insights" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/insights') ? 'active-header-link' : '' }}" x-tooltip="View cloud insights" title="View cloud insights">
|
|
83
83
|
<span class="sr-only">View cloud insights</span>
|
|
84
84
|
<span aria-hidden="true" class="h-6 w-6 duration-150 ease-in-out transition i-hugeicons-pulse-01"></span>
|
|
85
85
|
</a>
|
|
86
86
|
|
|
87
|
-
<a href="/inbox" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/inbox') ? 'active-header-link' : '' }}" title="View inbox">
|
|
87
|
+
<a href="/inbox" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/inbox') ? 'active-header-link' : '' }}" x-tooltip="View inbox" title="View inbox">
|
|
88
88
|
<span class="sr-only">View inbox</span>
|
|
89
89
|
<span aria-hidden="true" class="h-6 w-6 duration-150 ease-in-out transition i-hugeicons-mail-02"></span>
|
|
90
90
|
</a>
|
|
91
91
|
|
|
92
|
-
<a href="/notifications/dashboard" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/notifications/dashboard') ? 'active-header-link' : '' }}" title="View notifications">
|
|
92
|
+
<a href="/notifications/dashboard" data-stx-link class="header-icon-button header-link {{ isActiveRoute('/notifications/dashboard') ? 'active-header-link' : '' }}" x-tooltip="View notifications" title="View notifications">
|
|
93
93
|
<span class="sr-only">View notifications</span>
|
|
94
94
|
<span aria-hidden="true" class="h-6 w-6 i-hugeicons-notification-02"></span>
|
|
95
95
|
</a>
|
|
@@ -21,64 +21,44 @@ function metadataText(): string {
|
|
|
21
21
|
}
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
|
-
<
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
<Modal
|
|
25
|
+
:isOpen="open()"
|
|
26
|
+
:title="`Delivery attempt #${delivery()?.id || 'not recorded'}`"
|
|
27
|
+
:description="delivery()?.channel || 'Notification'"
|
|
28
|
+
size="lg"
|
|
29
|
+
@close="emit('close')"
|
|
30
30
|
>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<dd class="mt-1 p-3 text-gray-800 text-sm whitespace-pre-wrap dark:text-neutral-200 bg-gray-50 dark:bg-neutral-900 rounded-md">{{ delivery?.body || 'No body was recorded.' }}</dd>
|
|
66
|
-
</div>
|
|
67
|
-
<div :if="delivery?.error" class="sm:col-span-2">
|
|
68
|
-
<dt class="font-medium text-red-600 text-xs uppercase dark:text-red-400">Error</dt>
|
|
69
|
-
<dd class="mt-1 p-3 text-red-800 text-sm whitespace-pre-wrap dark:text-red-200 bg-red-50 dark:bg-red-950/40 rounded-md">{{ delivery?.error }}</dd>
|
|
70
|
-
</div>
|
|
71
|
-
<div class="sm:col-span-2">
|
|
72
|
-
<dt class="font-medium text-gray-500 text-xs uppercase dark:text-neutral-400">Metadata</dt>
|
|
73
|
-
<dd class="overflow-x-auto mt-1 p-3 font-mono text-gray-700 text-xs whitespace-pre dark:text-neutral-300 bg-gray-50 dark:bg-neutral-900 rounded-md">{{ metadataText() }}</dd>
|
|
74
|
-
</div>
|
|
75
|
-
</dl>
|
|
76
|
-
|
|
77
|
-
<div class="flex justify-end mt-6">
|
|
78
|
-
<Button variant="secondary" @click="emit('close')">
|
|
79
|
-
Close
|
|
80
|
-
</Button>
|
|
81
|
-
</div>
|
|
82
|
-
</section>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
31
|
+
<dl class="grid gap-x-6 gap-y-4 grid-cols-1 sm:grid-cols-2">
|
|
32
|
+
<div>
|
|
33
|
+
<dt class="font-medium text-gray-500 text-xs uppercase dark:text-neutral-400">Recipient</dt>
|
|
34
|
+
<dd class="mt-1 break-all text-gray-900 text-sm dark:text-neutral-100">{{ delivery()?.recipient || 'Not recorded' }}</dd>
|
|
35
|
+
</div>
|
|
36
|
+
<div>
|
|
37
|
+
<dt class="font-medium text-gray-500 text-xs uppercase dark:text-neutral-400">Attempted</dt>
|
|
38
|
+
<dd class="mt-1 text-gray-900 text-sm dark:text-neutral-100">{{ formatDate(delivery()?.sent_at) }}</dd>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="sm:col-span-2">
|
|
41
|
+
<dt class="font-medium text-gray-500 text-xs uppercase dark:text-neutral-400">Subject</dt>
|
|
42
|
+
<dd class="mt-1 text-gray-900 text-sm dark:text-neutral-100">{{ delivery()?.subject || 'No subject' }}</dd>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="sm:col-span-2">
|
|
45
|
+
<dt class="font-medium text-gray-500 text-xs uppercase dark:text-neutral-400">Body</dt>
|
|
46
|
+
<dd class="mt-1 p-3 text-gray-800 text-sm whitespace-pre-wrap dark:text-neutral-200 bg-gray-50 dark:bg-neutral-900 rounded-md">{{ delivery()?.body || 'No body was recorded.' }}</dd>
|
|
47
|
+
</div>
|
|
48
|
+
<div :if="delivery()?.error" class="sm:col-span-2">
|
|
49
|
+
<dt class="font-medium text-red-600 text-xs uppercase dark:text-red-400">Error</dt>
|
|
50
|
+
<dd class="mt-1 p-3 text-red-800 text-sm whitespace-pre-wrap dark:text-red-200 bg-red-50 dark:bg-red-950/40 rounded-md">{{ delivery()?.error }}</dd>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="sm:col-span-2">
|
|
53
|
+
<dt class="font-medium text-gray-500 text-xs uppercase dark:text-neutral-400">Metadata</dt>
|
|
54
|
+
<dd class="overflow-x-auto mt-1 p-3 font-mono text-gray-700 text-xs whitespace-pre dark:text-neutral-300 bg-gray-50 dark:bg-neutral-900 rounded-md">{{ metadataText() }}</dd>
|
|
55
|
+
</div>
|
|
56
|
+
</dl>
|
|
57
|
+
<template #footer>
|
|
58
|
+
<div class="flex justify-end w-full">
|
|
59
|
+
<Button variant="secondary" @click="emit('close')">
|
|
60
|
+
Close
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
</Modal>
|
|
@@ -15,6 +15,7 @@ interface ButtonProps {
|
|
|
15
15
|
rel?: string
|
|
16
16
|
ariaLabel?: string
|
|
17
17
|
type?: string
|
|
18
|
+
form?: string
|
|
18
19
|
class?: string
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -83,6 +84,7 @@ const liveDownload = useReactiveProp('download', '')
|
|
|
83
84
|
const liveTarget = useReactiveProp('target', '')
|
|
84
85
|
const liveRel = useReactiveProp('rel', '')
|
|
85
86
|
const liveType = useReactiveProp('type', 'button')
|
|
87
|
+
const liveForm = useReactiveProp('form', '')
|
|
86
88
|
const liveAriaLabel = useReactiveProp('ariaLabel', '')
|
|
87
89
|
</script>
|
|
88
90
|
@if(tag === 'a' || (href && href.length > 0))
|
|
@@ -106,6 +108,7 @@ const liveAriaLabel = useReactiveProp('ariaLabel', '')
|
|
|
106
108
|
@if(interaction === 'toggle')
|
|
107
109
|
<button
|
|
108
110
|
:type="liveType()"
|
|
111
|
+
:form="liveForm() || undefined"
|
|
109
112
|
class="{{ buttonClass }}"
|
|
110
113
|
:aria-label="liveAriaLabel() || undefined"
|
|
111
114
|
:aria-busy="liveLoading() ? 'true' : 'false'"
|
|
@@ -120,6 +123,7 @@ const liveAriaLabel = useReactiveProp('ariaLabel', '')
|
|
|
120
123
|
@else
|
|
121
124
|
<button
|
|
122
125
|
:type="liveType()"
|
|
126
|
+
:form="liveForm() || undefined"
|
|
123
127
|
class="{{ buttonClass }}"
|
|
124
128
|
:aria-label="liveAriaLabel() || undefined"
|
|
125
129
|
:aria-busy="liveLoading() ? 'true' : 'false'"
|