@stacksjs/defaults 0.75.263 → 0.75.267
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 +3 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +73 -14
- package/ai/skills/stacks-chat/SKILL.md +32 -0
- package/ai/skills/stacks-native/CATALOG.md +8 -8
- package/ide/vscode/package.json +1 -1
- package/package.json +4 -4
- package/resources/components/Dashboard/App/RequestsOverview.stx +75 -58
- package/resources/components/Dashboard/App/SourceInventory.stx +68 -59
- package/resources/components/Dashboard/Auth/AccessTokens.stx +57 -37
- package/resources/components/Dashboard/Billing/BillingTransactionHistory.stx +27 -33
- package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +85 -49
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +63 -66
- package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +67 -23
- package/resources/components/Dashboard/Content/BlogDashboard.stx +73 -67
- package/resources/components/Dashboard/Content/FileManagerDashboard.stx +39 -12
- package/resources/components/Dashboard/Data/DataRecords.stx +85 -88
- package/resources/components/Dashboard/Deployments/DeploymentTable.stx +49 -64
- package/resources/components/Dashboard/Infrastructure/DnsDashboard.stx +46 -28
- package/resources/components/Dashboard/Operations/RecoveryPointTable.stx +72 -61
- package/resources/components/Dashboard/Operations/SchedulerTaskTable.stx +74 -70
- package/resources/components/Dashboard/Queue/QueueDashboard.stx +27 -61
- package/resources/components/Dashboard/Releases/ReleaseDashboard.stx +35 -61
- package/resources/components/Dashboard/Teams/TeamPeopleDashboard.stx +127 -143
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import('../UI/Button', '../UI/ConfirmDialog')
|
|
1
|
+
@import('../UI/Button', '../UI/ConfirmDialog', '../UI/Table')
|
|
2
2
|
|
|
3
3
|
<script client>
|
|
4
4
|
import { dashboardApi } from '../../../../functions/dashboard-api'
|
|
@@ -53,8 +53,6 @@ const memberToRemove = state<TeamMember | null>(null)
|
|
|
53
53
|
const invitationToRevoke = state<TeamInvitation | null>(null)
|
|
54
54
|
|
|
55
55
|
const teamId = derived(() => Number(route.params.id))
|
|
56
|
-
const hasMembers = derived(() => members().length > 0)
|
|
57
|
-
const hasInvitations = derived(() => invitations().length > 0)
|
|
58
56
|
|
|
59
57
|
function roleLabel(role: string): string {
|
|
60
58
|
return role.charAt(0).toUpperCase() + role.slice(1)
|
|
@@ -65,24 +63,64 @@ function memberCountLabel(): string {
|
|
|
65
63
|
return `${count} team ${count === 1 ? 'member' : 'members'}`
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
/**
|
|
67
|
+
* One palette for member status and invitation delivery, keyed by value, so
|
|
68
|
+
* a status the server adds later still reads, in the neutral pill.
|
|
69
|
+
*/
|
|
70
|
+
const STATUS_CLASSES = {
|
|
71
|
+
active: 'bg-green-50 text-green-700 dark:bg-green-500/10 dark:text-green-400',
|
|
72
|
+
sent: 'bg-green-50 text-green-700 dark:bg-green-500/10 dark:text-green-400',
|
|
73
|
+
suspended: 'bg-red-50 text-red-700 dark:bg-red-500/10 dark:text-red-400',
|
|
74
|
+
failed: 'bg-red-50 text-red-700 dark:bg-red-500/10 dark:text-red-400',
|
|
75
|
+
expired: 'bg-amber-50 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400',
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
78
|
+
/**
|
|
79
|
+
* The member and role columns are slotted: the first for its avatar, the
|
|
80
|
+
* second because an owner's role is fixed and everyone else's is a select.
|
|
81
|
+
*/
|
|
82
|
+
const MEMBER_COLUMNS = [
|
|
83
|
+
{ key: 'name', label: 'Member', sortable: true, slotted: true },
|
|
84
|
+
{ key: 'role', label: 'Role', sortable: true, slotted: true },
|
|
85
|
+
{ key: 'status', label: 'Status', type: 'badge' as const, sortable: true, badgeLabelKey: 'statusLabel', badgeClasses: STATUS_CLASSES },
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* An owner can be neither suspended nor removed. Anyone else is offered the
|
|
90
|
+
* opposite of their current state, and every status but `active` reads as
|
|
91
|
+
* one to activate, as the hand-rolled button did.
|
|
92
|
+
*/
|
|
93
|
+
const MEMBER_ACTIONS = [
|
|
94
|
+
{ action: 'suspend', label: 'Suspend', icon: 'i-hugeicons-pause', class: 'text-amber-500', when: { key: 'statusAction', equals: 'suspend' } },
|
|
95
|
+
{ action: 'activate', label: 'Activate', icon: 'i-hugeicons-play', class: 'text-green-500', when: { key: 'statusAction', equals: 'activate' } },
|
|
96
|
+
{ action: 'remove', label: 'Remove', icon: 'i-hugeicons-delete-02', class: 'text-red-500', when: { key: 'removable', equals: true } },
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
const INVITATION_COLUMNS = [
|
|
100
|
+
{ key: 'email', label: 'Recipient', sortable: true, class: 'text-neutral-900 dark:text-neutral-100' },
|
|
101
|
+
{ key: 'roleLabel', label: 'Role', sortable: true },
|
|
102
|
+
{ key: 'deliveryStatus', label: 'Delivery', type: 'badge' as const, sortable: true, badgeLabelKey: 'deliveryLabel', badgeClasses: STATUS_CLASSES },
|
|
103
|
+
{ key: 'expiresAt', label: 'Expires', type: 'date' as const, sortable: true, emptyText: 'Not available' },
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
const INVITATION_ACTIONS = [
|
|
107
|
+
{ action: 'resend', label: 'Resend invitation to', icon: 'i-hugeicons-mail-send-01' },
|
|
108
|
+
{ action: 'revoke', label: 'Revoke invitation to', icon: 'i-hugeicons-cancel-01', class: 'text-red-500' },
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
const memberRows = derived(() => members().map(member => ({
|
|
112
|
+
...member,
|
|
113
|
+
initial: member.name.charAt(0).toUpperCase(),
|
|
114
|
+
statusLabel: roleLabel(member.status),
|
|
115
|
+
statusAction: member.role === 'owner' ? 'none' : member.status === 'active' ? 'suspend' : 'activate',
|
|
116
|
+
removable: member.role !== 'owner',
|
|
117
|
+
})))
|
|
118
|
+
|
|
119
|
+
const invitationRows = derived(() => invitations().map(invitation => ({
|
|
120
|
+
...invitation,
|
|
121
|
+
roleLabel: roleLabel(invitation.role),
|
|
122
|
+
deliveryLabel: roleLabel(invitation.deliveryStatus),
|
|
123
|
+
})))
|
|
86
124
|
|
|
87
125
|
async function fetchPeople(): Promise<void> {
|
|
88
126
|
isLoading.set(true)
|
|
@@ -233,6 +271,20 @@ async function revokeInvitation(): Promise<void> {
|
|
|
233
271
|
}
|
|
234
272
|
}
|
|
235
273
|
|
|
274
|
+
function handleMemberAction(payload: { action: string, row: TeamMember }): void {
|
|
275
|
+
if (payload.action === 'remove')
|
|
276
|
+
memberToRemove.set(payload.row)
|
|
277
|
+
else
|
|
278
|
+
void toggleMemberStatus(payload.row)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function handleInvitationAction(payload: { action: string, row: TeamInvitation }): void {
|
|
282
|
+
if (payload.action === 'resend')
|
|
283
|
+
void resendInvitation(payload.row)
|
|
284
|
+
else if (payload.action === 'revoke')
|
|
285
|
+
invitationToRevoke.set(payload.row)
|
|
286
|
+
}
|
|
287
|
+
|
|
236
288
|
onMount(() => {
|
|
237
289
|
fetchPeople()
|
|
238
290
|
})
|
|
@@ -307,79 +359,48 @@ onMount(() => {
|
|
|
307
359
|
</div>
|
|
308
360
|
</div>
|
|
309
361
|
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
<td class="px-6 py-4">
|
|
353
|
-
<span class="inline-flex px-2 py-1 font-medium text-xs rounded-full" :class="statusClass(member.status)">
|
|
354
|
-
{{ roleLabel(member.status) }}
|
|
355
|
-
</span>
|
|
356
|
-
</td>
|
|
357
|
-
<td class="px-6 py-4">
|
|
358
|
-
<div :show="member.role !== 'owner'" class="flex gap-2 items-center justify-end">
|
|
359
|
-
<Button
|
|
360
|
-
variant="secondary"
|
|
361
|
-
size="sm"
|
|
362
|
-
:loading="busyMemberId() === member.id"
|
|
363
|
-
:disabled="busyMemberId() === member.id"
|
|
364
|
-
@click="toggleMemberStatus(member)"
|
|
365
|
-
>
|
|
366
|
-
{{ member.status === 'active' ? 'Suspend' : 'Activate' }}
|
|
367
|
-
</Button>
|
|
368
|
-
<Button
|
|
369
|
-
variant="ghost"
|
|
370
|
-
size="sm"
|
|
371
|
-
:disabled="busyMemberId() === member.id"
|
|
372
|
-
@click="memberToRemove.set(member)"
|
|
373
|
-
>
|
|
374
|
-
Remove
|
|
375
|
-
</Button>
|
|
376
|
-
</div>
|
|
377
|
-
</td>
|
|
378
|
-
</tr>
|
|
379
|
-
</template>
|
|
380
|
-
</tbody>
|
|
381
|
-
</table>
|
|
382
|
-
</div>
|
|
362
|
+
<Table
|
|
363
|
+
:columns="MEMBER_COLUMNS"
|
|
364
|
+
:data="memberRows()"
|
|
365
|
+
:loading="isLoading()"
|
|
366
|
+
:row-actions="MEMBER_ACTIONS"
|
|
367
|
+
action-label-key="name"
|
|
368
|
+
busy-key="id"
|
|
369
|
+
:busy-value="busyMemberId()"
|
|
370
|
+
paginated
|
|
371
|
+
:page-size="25"
|
|
372
|
+
empty-title="No members yet"
|
|
373
|
+
empty-description="No members have joined this team yet."
|
|
374
|
+
@row-action="handleMemberAction"
|
|
375
|
+
>
|
|
376
|
+
<template #cell="{ row, column }">
|
|
377
|
+
<template :if="column.key === 'name'">
|
|
378
|
+
<div class="flex gap-3 items-center">
|
|
379
|
+
<div class="flex items-center justify-center shrink-0 h-9 w-9 font-semibold text-blue-700 text-sm dark:text-blue-300 bg-blue-50 dark:bg-blue-500/10 rounded-full">{{ row.initial }}</div>
|
|
380
|
+
<div>
|
|
381
|
+
<p class="font-medium text-neutral-900 text-sm dark:text-white">{{ row.name }}</p>
|
|
382
|
+
<p class="mt-0.5 text-neutral-500 text-xs dark:text-neutral-400">{{ row.email }}</p>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</template>
|
|
386
|
+
<template :if="column.key === 'role' && row.role === 'owner'">
|
|
387
|
+
<span class="text-neutral-700 text-sm dark:text-neutral-300">Owner</span>
|
|
388
|
+
</template>
|
|
389
|
+
<template :if="column.key === 'role' && row.role !== 'owner'">
|
|
390
|
+
<select
|
|
391
|
+
:value="row.role"
|
|
392
|
+
:aria-label="'Role for ' + row.name"
|
|
393
|
+
:disabled="busyMemberId() === row.id"
|
|
394
|
+
class="px-2.5 h-8 text-neutral-800 text-sm dark:text-neutral-100 bg-white dark:bg-neutral-950 border border-neutral-300 rounded-lg dark:border-neutral-700"
|
|
395
|
+
@change="updateMemberRole(row, $event)"
|
|
396
|
+
>
|
|
397
|
+
<option value="admin">Admin</option>
|
|
398
|
+
<option value="member">Member</option>
|
|
399
|
+
<option value="viewer">Viewer</option>
|
|
400
|
+
</select>
|
|
401
|
+
</template>
|
|
402
|
+
</template>
|
|
403
|
+
</Table>
|
|
383
404
|
</section>
|
|
384
405
|
|
|
385
406
|
<section class="overflow-hidden bg-white dark:bg-neutral-900 border border-neutral-200/70 rounded-xl dark:border-neutral-800">
|
|
@@ -388,57 +409,20 @@ onMount(() => {
|
|
|
388
409
|
<p class="mt-0.5 text-neutral-500 text-sm dark:text-neutral-400">Track delivery, resend links, or revoke access before acceptance.</p>
|
|
389
410
|
</div>
|
|
390
411
|
|
|
391
|
-
<
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
<tbody class="divide-neutral-200 divide-y dark:divide-neutral-800">
|
|
406
|
-
<template :for="invitation in invitations">
|
|
407
|
-
<tr>
|
|
408
|
-
<td class="px-6 py-4 text-neutral-900 text-sm dark:text-neutral-100">{{ invitation.email }}</td>
|
|
409
|
-
<td class="px-6 py-4 text-neutral-600 text-sm dark:text-neutral-300">{{ roleLabel(invitation.role) }}</td>
|
|
410
|
-
<td class="px-6 py-4">
|
|
411
|
-
<span class="inline-flex px-2 py-1 font-medium text-xs rounded-full" :class="statusClass(invitation.deliveryStatus)">
|
|
412
|
-
{{ roleLabel(invitation.deliveryStatus) }}
|
|
413
|
-
</span>
|
|
414
|
-
</td>
|
|
415
|
-
<td class="px-6 py-4 text-neutral-500 text-sm dark:text-neutral-400">{{ formatDate(invitation.expiresAt) }}</td>
|
|
416
|
-
<td class="px-6 py-4">
|
|
417
|
-
<div class="flex gap-2 items-center justify-end">
|
|
418
|
-
<Button
|
|
419
|
-
variant="secondary"
|
|
420
|
-
size="sm"
|
|
421
|
-
:loading="busyInvitationId() === invitation.id"
|
|
422
|
-
:disabled="busyInvitationId() === invitation.id"
|
|
423
|
-
@click="resendInvitation(invitation)"
|
|
424
|
-
>
|
|
425
|
-
Resend
|
|
426
|
-
</Button>
|
|
427
|
-
<Button
|
|
428
|
-
variant="ghost"
|
|
429
|
-
size="sm"
|
|
430
|
-
:disabled="busyInvitationId() === invitation.id"
|
|
431
|
-
@click="invitationToRevoke.set(invitation)"
|
|
432
|
-
>
|
|
433
|
-
Revoke
|
|
434
|
-
</Button>
|
|
435
|
-
</div>
|
|
436
|
-
</td>
|
|
437
|
-
</tr>
|
|
438
|
-
</template>
|
|
439
|
-
</tbody>
|
|
440
|
-
</table>
|
|
441
|
-
</div>
|
|
412
|
+
<Table
|
|
413
|
+
:columns="INVITATION_COLUMNS"
|
|
414
|
+
:data="invitationRows()"
|
|
415
|
+
:loading="isLoading()"
|
|
416
|
+
:row-actions="INVITATION_ACTIONS"
|
|
417
|
+
action-label-key="email"
|
|
418
|
+
busy-key="id"
|
|
419
|
+
:busy-value="busyInvitationId()"
|
|
420
|
+
paginated
|
|
421
|
+
:page-size="25"
|
|
422
|
+
empty-title="No pending invitations"
|
|
423
|
+
empty-description="Invitations you send appear here until they are accepted."
|
|
424
|
+
@row-action="handleInvitationAction"
|
|
425
|
+
/>
|
|
442
426
|
</section>
|
|
443
427
|
</div>
|
|
444
428
|
|