@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.
Files changed (141) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +7 -5
  2. package/ai/skills/stacks-browse/scripts/browse.ts +68 -9
  3. package/ai/skills/stacks-dashboard/SKILL.md +4 -0
  4. package/app/Actions/AI/AskAction.ts +12 -17
  5. package/app/Actions/AI/SummaryAction.ts +12 -17
  6. package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
  7. package/app/Actions/Dashboard/Models/model-write.ts +5 -0
  8. package/app/Actions/LogAction.ts +11 -11
  9. package/ide/vscode/package.json +1 -1
  10. package/package.json +1 -1
  11. package/resources/components/Buttons/Counter.stx +1 -1
  12. package/resources/components/Buttons/ToggleDark.stx +1 -1
  13. package/resources/components/Dashboard/App/RequestDetailsDialog.stx +42 -0
  14. package/resources/components/Dashboard/App/RequestsOverview.stx +1 -21
  15. package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +155 -0
  16. package/resources/components/Dashboard/Auth/AccessTokens.stx +41 -129
  17. package/resources/components/Dashboard/Ci/CiDashboard.stx +1 -157
  18. package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +127 -0
  19. package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +10 -8
  20. package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +22 -17
  21. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +14 -13
  22. package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +10 -7
  23. package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +25 -19
  24. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +14 -11
  25. package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +10 -8
  26. package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +24 -16
  27. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +14 -13
  28. package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +10 -8
  29. package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +16 -14
  30. package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +17 -16
  31. package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +14 -16
  32. package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +17 -16
  33. package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +10 -8
  34. package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +15 -10
  35. package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +14 -13
  36. package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +30 -25
  37. package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +27 -20
  38. package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +14 -14
  39. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +14 -13
  40. package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +10 -18
  41. package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +17 -16
  42. package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +14 -14
  43. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +17 -16
  44. package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
  45. package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +14 -17
  46. package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +17 -16
  47. package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +10 -8
  48. package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +17 -16
  49. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +11 -15
  50. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +17 -16
  51. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +11 -15
  52. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +17 -16
  53. package/resources/components/Dashboard/Commerce/Delivery/DriverDeleteDialog.stx +11 -15
  54. package/resources/components/Dashboard/Commerce/Delivery/DriverDialog.stx +17 -16
  55. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +11 -15
  56. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +17 -16
  57. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +11 -15
  58. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +17 -16
  59. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +11 -15
  60. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +17 -16
  61. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +11 -15
  62. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +17 -16
  63. package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +16 -15
  64. package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +16 -15
  65. package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +10 -11
  66. package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +16 -14
  67. package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +17 -16
  68. package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +10 -7
  69. package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +13 -10
  70. package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +10 -18
  71. package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +96 -105
  72. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +10 -18
  73. package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +92 -95
  74. package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +94 -0
  75. package/resources/components/Dashboard/Content/AuthorsDashboard.stx +39 -31
  76. package/resources/components/Dashboard/Content/BlogDashboard.stx +87 -176
  77. package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +195 -0
  78. package/resources/components/Dashboard/Content/CommentsDashboard.stx +14 -4
  79. package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +40 -55
  80. package/resources/components/Dashboard/Content/CreateFolderDialog.stx +45 -0
  81. package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +31 -0
  82. package/resources/components/Dashboard/Content/FileManagerDashboard.stx +47 -107
  83. package/resources/components/Dashboard/Content/FileUploadDialog.stx +89 -0
  84. package/resources/components/Dashboard/Content/PageEditorDialog.stx +93 -0
  85. package/resources/components/Dashboard/Content/PagesDashboard.stx +36 -26
  86. package/resources/components/Dashboard/Content/PostEditorDialog.stx +176 -0
  87. package/resources/components/Dashboard/Content/PostsDashboard.stx +40 -64
  88. package/resources/components/Dashboard/Content/SeoDashboard.stx +18 -84
  89. package/resources/components/Dashboard/Content/SeoEditorDialog.stx +108 -0
  90. package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +106 -0
  91. package/resources/components/Dashboard/CreateRecordModal.stx +72 -83
  92. package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
  93. package/resources/components/Dashboard/GlobalSearch.stx +3 -9
  94. package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +2 -16
  95. package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +4 -16
  96. package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +4 -19
  97. package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +6 -16
  98. package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +105 -0
  99. package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -59
  100. package/resources/components/Dashboard/Jobs/JobHistory.stx +1 -11
  101. package/resources/components/Dashboard/Library/ComponentsDashboard.stx +11 -60
  102. package/resources/components/Dashboard/Library/FunctionsDashboard.stx +11 -15
  103. package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +95 -0
  104. package/resources/components/Dashboard/Management/AssignmentDrawer.stx +93 -0
  105. package/resources/components/Dashboard/Management/PermissionsDashboard.stx +47 -129
  106. package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +11 -12
  107. package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
  108. package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
  109. package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +15 -15
  110. package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
  111. package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
  112. package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +11 -12
  113. package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +17 -16
  114. package/resources/components/Dashboard/Models/EditRecordModal.stx +81 -0
  115. package/resources/components/Dashboard/Models/ModelRecordActions.stx +30 -0
  116. package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +42 -94
  117. package/resources/components/Dashboard/Navbar.stx +5 -5
  118. package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +40 -60
  119. package/resources/components/Dashboard/UI/Button.stx +4 -0
  120. package/resources/components/Dashboard/UI/ConfirmDialog.stx +55 -53
  121. package/resources/components/Dashboard/UI/Drawer.stx +114 -0
  122. package/resources/components/Dashboard/UI/Modal.stx +32 -21
  123. package/resources/components/Docs/Demo/ComboboxDemo.stx +4 -5
  124. package/resources/components/Docs/Demo/DropdownDemo.stx +12 -5
  125. package/resources/components/Docs/Demo/ListboxDemo.stx +14 -6
  126. package/resources/components/Docs/Demo/PopoverDemo.stx +10 -5
  127. package/resources/components/Docs/Demo/RadioGroupDemo.stx +14 -6
  128. package/resources/components/Docs/Demo/StepperDemo.stx +4 -5
  129. package/resources/components/Docs/TeamMember.stx +16 -9
  130. package/resources/components/Forum/ForumReplyForm.stx +7 -7
  131. package/resources/components/Forum/ForumSidebar.stx +10 -6
  132. package/resources/components/Marketing/Faqs.stx +5 -5
  133. package/resources/components/Marketing/Feature.stx +11 -6
  134. package/resources/components/Marketing/Layout.stx +12 -8
  135. package/resources/components/Marketing/Pricing.stx +11 -5
  136. package/resources/components/Marketing/PrimaryFeatures.stx +4 -5
  137. package/resources/components/Marketing/PrimaryFeatures2.stx +6 -5
  138. package/resources/components/Marketing/SecondaryFeatures.stx +10 -5
  139. package/resources/components/Marketing/Testimonials.stx +8 -5
  140. package/resources/views/index.stx +4 -4
  141. package/views/system-tray/index.stx +1 -1
@@ -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>
@@ -28,13 +28,11 @@ interface CreatedTokenResponse {
28
28
  }
29
29
  }
30
30
 
31
- const availableScopes = [
32
- { name: 'read', description: 'Read application resources' },
33
- { name: 'write', description: 'Create and update resources' },
34
- { name: 'delete', description: 'Delete application resources' },
35
- { name: 'deploy', description: 'Run deployment operations' },
36
- { name: 'admin', description: 'Use administrative operations' },
37
- ]
31
+ interface TokenDraft {
32
+ name: string
33
+ scopes: string[]
34
+ expiresInMinutes: number
35
+ }
38
36
 
39
37
  const tokens = state<AccessTokenRow[]>([])
40
38
  const loading = state(true)
@@ -44,16 +42,16 @@ const actionError = state('')
44
42
  const actionMessage = state('')
45
43
  const showCreate = state(false)
46
44
  const creating = state(false)
47
- const tokenName = state('')
48
- const expiryMinutes = state('10080')
49
- const selectedScopes = state<string[]>(['read'])
50
45
  const createdToken = state('')
51
- const copied = state(false)
52
46
  const confirmingRevoke = state<number | null>(null)
53
47
  const revoking = state<number | null>(null)
54
48
 
55
49
  const activeCount = derived(() => tokens().filter(token => tokenStatus(token) === 'active').length)
56
50
  const expiredCount = derived(() => tokens().filter(token => tokenStatus(token) === 'expired').length)
51
+ const tokenToRevoke = derived(() => tokens().find(token => token.id === confirmingRevoke()) ?? null)
52
+ const revokeDescription = derived(() => tokenToRevoke()
53
+ ? `${tokenToRevoke()?.name} will immediately stop authorizing API requests.`
54
+ : 'This access token will immediately stop authorizing API requests.')
57
55
 
58
56
  function tokenStatus(token: AccessTokenRow): 'active' | 'expired' {
59
57
  if (!token.expires_at)
@@ -74,33 +72,18 @@ function formatDate(value: string | null): string {
74
72
  return Number.isNaN(date.getTime()) ? 'Unknown' : date.toLocaleString()
75
73
  }
76
74
 
77
- function toggleScope(event: Event, scope: string): void {
78
- const enabled = (event.target as HTMLInputElement).checked
79
- selectedScopes.set(enabled
80
- ? [...new Set([...selectedScopes(), scope])]
81
- : selectedScopes().filter(item => item !== scope))
82
- }
83
-
84
- function resetCreateForm(): void {
85
- tokenName.set('')
86
- expiryMinutes.set('10080')
87
- selectedScopes.set(['read'])
88
- createdToken.set('')
89
- copied.set(false)
90
- }
91
-
92
75
  function openCreate(): void {
93
76
  if (authenticationRequired())
94
77
  return
95
78
 
96
- resetCreateForm()
79
+ createdToken.set('')
97
80
  actionError.set('')
98
81
  showCreate.set(true)
99
82
  }
100
83
 
101
84
  function closeCreate(): void {
102
85
  showCreate.set(false)
103
- resetCreateForm()
86
+ createdToken.set('')
104
87
  }
105
88
 
106
89
  async function loadTokens(): Promise<void> {
@@ -139,27 +122,18 @@ async function loadTokens(): Promise<void> {
139
122
  }
140
123
  }
141
124
 
142
- async function createAccessToken(): Promise<void> {
125
+ async function createAccessToken(draft: TokenDraft): Promise<void> {
143
126
  actionError.set('')
144
127
  actionMessage.set('')
145
128
 
146
- if (!tokenName().trim()) {
147
- actionError.set('Enter a name for this token.')
148
- return
149
- }
150
- if (selectedScopes().length === 0) {
151
- actionError.set('Select at least one scope.')
152
- return
153
- }
154
-
155
129
  creating.set(true)
156
130
  try {
157
131
  const response = await dashboardApi<CreatedTokenResponse>('/auth/token', {
158
132
  method: 'POST',
159
133
  body: {
160
- name: tokenName().trim(),
161
- scopes: selectedScopes(),
162
- expires_in_minutes: Number(expiryMinutes()),
134
+ name: draft.name,
135
+ scopes: draft.scopes,
136
+ expires_in_minutes: draft.expiresInMinutes,
163
137
  with_refresh_token: false,
164
138
  },
165
139
  })
@@ -182,22 +156,10 @@ async function createAccessToken(): Promise<void> {
182
156
  }
183
157
  }
184
158
 
185
- async function copyCreatedToken(): Promise<void> {
186
- actionError.set('')
187
- try {
188
- await globalThis.navigator.clipboard.writeText(createdToken())
189
- copied.set(true)
190
- }
191
- catch {
192
- actionError.set('Clipboard access is unavailable. Select and copy the token manually.')
193
- }
194
- }
195
-
196
- async function revokeToken(id: number): Promise<void> {
197
- if (confirmingRevoke() !== id) {
198
- confirmingRevoke.set(id)
159
+ async function revokeToken(): Promise<void> {
160
+ const id = confirmingRevoke()
161
+ if (id === null)
199
162
  return
200
- }
201
163
 
202
164
  actionError.set('')
203
165
  actionMessage.set('')
@@ -260,8 +222,8 @@ onMount(() => {
260
222
  <Button variant="secondary" size="sm" @click="loadTokens">Try again</Button>
261
223
  </div>
262
224
 
263
- <div :if="actionError()" role="alert" class="px-4 py-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/40 border border-red-200 rounded-lg dark:border-red-900">{{ actionError() }}</div>
264
- <div :if="actionMessage()" role="status" class="px-4 py-3 text-green-700 text-sm dark:text-green-300 bg-green-50 dark:bg-green-950/40 border border-green-200 rounded-lg dark:border-green-900">{{ actionMessage() }}</div>
225
+ <div :if="actionError() && !showCreate()" role="alert" class="px-4 py-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/40 border border-red-200 rounded-lg dark:border-red-900">{{ actionError() }}</div>
226
+ <div :if="actionMessage() && !showCreate()" role="status" class="px-4 py-3 text-green-700 text-sm dark:text-green-300 bg-green-50 dark:bg-green-950/40 border border-green-200 rounded-lg dark:border-green-900">{{ actionMessage() }}</div>
265
227
 
266
228
  <Card padding="none">
267
229
  <div class="flex items-center justify-between p-5 border-b border-neutral-200 dark:border-neutral-700">
@@ -333,8 +295,8 @@ onMount(() => {
333
295
  <td class="px-4 py-3.5"><span :class="'inline-flex px-2 py-0.5 font-medium text-xs capitalize rounded ' + statusClass(token)">{{ tokenStatus(token) }}</span></td>
334
296
  <td class="px-4 py-3.5 text-neutral-500 text-right text-xs whitespace-nowrap dark:text-neutral-400">{{ formatDate(token.expires_at) }}</td>
335
297
  <td class="pr-5 py-3.5 text-right whitespace-nowrap">
336
- <Button variant="danger" size="sm" :loading="revoking() === token.id" @click="revokeToken(token.id)">
337
- {{ revoking() === token.id ? 'Revoking...' : confirmingRevoke() === token.id ? 'Confirm Revoke' : 'Revoke' }}
298
+ <Button variant="danger" size="sm" :loading="revoking() === token.id" @click="confirmingRevoke.set(token.id)">
299
+ {{ revoking() === token.id ? 'Revoking...' : 'Revoke' }}
338
300
  </Button>
339
301
  </td>
340
302
  </tr>
@@ -343,73 +305,23 @@ onMount(() => {
343
305
  </div>
344
306
  </Card>
345
307
 
346
- <div :if="showCreate()" class="flex fixed inset-0 z-50 items-center justify-center p-4 bg-black/50 dashboard-modal-layer" @click.self="closeCreate">
347
- <div role="dialog" aria-modal="true" aria-labelledby="create-token-title" class="p-6 max-w-lg w-full bg-white dark:bg-neutral-900 border border-neutral-200 rounded-xl dark:border-neutral-700 shadow-xl">
348
- <div class="flex gap-4 items-start justify-between">
349
- <div>
350
- <h2 id="create-token-title" class="font-semibold text-lg text-neutral-900 dark:text-white">Create Access Token</h2>
351
- <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Grant only the scopes this integration needs.</p>
352
- </div>
353
- <Button variant="ghost" iconOnly ariaLabel="Close create token dialog" @click="closeCreate">
354
- <span class="h-5 w-5 i-hugeicons-cancel-01"></span>
355
- </Button>
356
- </div>
357
-
358
- <div :if="createdToken()" class="mt-6 space-y-4">
359
- <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">
360
- {{ actionMessage() }}
361
- </div>
362
- <label class="block">
363
- <span class="font-medium text-neutral-700 text-sm dark:text-neutral-300">New token</span>
364
- <textarea readonly rows="4" 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>
365
- </label>
366
- <div class="flex gap-2 justify-end">
367
- <Button variant="secondary" @click="closeCreate">Done</Button>
368
- <Button @click="copyCreatedToken">
369
- <span class="h-4 w-4 i-hugeicons-copy-01"></span>
370
- {{ copied() ? 'Copied' : 'Copy Token' }}
371
- </Button>
372
- </div>
373
- </div>
374
-
375
- <form :if="!createdToken()" class="mt-6 space-y-5" @submit.prevent="createAccessToken">
376
- <label class="block">
377
- <span class="font-medium text-neutral-700 text-sm dark:text-neutral-300">Token name</span>
378
- <input x-model="tokenName" 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">
379
- </label>
380
-
381
- <label class="block">
382
- <span class="font-medium text-neutral-700 text-sm dark:text-neutral-300">Expires after</span>
383
- <select x-model="expiryMinutes" 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">
384
- <option value="60">1 hour</option>
385
- <option value="1440">1 day</option>
386
- <option value="10080">7 days</option>
387
- <option value="43200">30 days</option>
388
- </select>
389
- </label>
390
-
391
- <fieldset>
392
- <legend class="font-medium text-neutral-700 text-sm dark:text-neutral-300">Scopes</legend>
393
- <div class="grid gap-2 grid-cols-1 sm:grid-cols-2 mt-2">
394
- <label :for="scope in availableScopes" class="flex gap-3 items-start p-3 border border-neutral-200 rounded-lg dark:border-neutral-700">
395
- <input type="checkbox" :checked="selectedScopes().includes(scope.name)" class="mt-0.5 h-4 w-4 text-blue-600 rounded" @change="toggleScope($event, scope.name)">
396
- <span>
397
- <span class="block font-medium text-neutral-900 text-sm dark:text-white">{{ scope.name }}</span>
398
- <span class="block mt-0.5 text-neutral-500 text-xs dark:text-neutral-400">{{ scope.description }}</span>
399
- </span>
400
- </label>
401
- </div>
402
- </fieldset>
403
-
404
- <div :if="actionError()" role="alert" class="px-3 py-2 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/40 rounded-lg">{{ actionError() }}</div>
405
-
406
- <div class="flex gap-2 justify-end pt-2">
407
- <Button variant="secondary" :disabled="creating()" @click="closeCreate">Cancel</Button>
408
- <Button type="submit" :loading="creating()">
409
- {{ creating() ? 'Creating...' : 'Create Token' }}
410
- </Button>
411
- </div>
412
- </form>
413
- </div>
414
- </div>
308
+ <AccessTokenCreateDialog
309
+ :open="showCreate()"
310
+ :busy="creating()"
311
+ :createdToken="createdToken()"
312
+ :message="actionMessage()"
313
+ :error="actionError()"
314
+ @close="closeCreate"
315
+ @submit="createAccessToken($event)"
316
+ />
317
+
318
+ <ConfirmDialog
319
+ :isOpen="confirmingRevoke() !== null"
320
+ title="Revoke this access token?"
321
+ :description="revokeDescription()"
322
+ :busy="revoking() !== null"
323
+ confirmLabel="Revoke"
324
+ @close="confirmingRevoke.set(null)"
325
+ @confirm="revokeToken"
326
+ />
415
327
  </div>
@@ -83,50 +83,6 @@
83
83
  return str.length > len ? str.slice(0, len) + '...' : str
84
84
  }
85
85
 
86
- // ─── Drilldown drawer state (stacksjs/stacks#1848) ──────────────
87
- const drilldownTarget = derived(() => ci.drilldown())
88
- const drilldownRuns = derived(() => ci.drilldownRuns())
89
- const loadingDrilldown = derived(() => ci.loadingDrilldown())
90
- const drilldownError = derived(() => ci.drilldownError())
91
- const expandedRunId = derived(() => ci.expandedRunId())
92
- const jobsByRunId = derived(() => ci.drilldownJobsByRunId())
93
- const loadingJobsRunId = derived(() => ci.loadingJobsForRunId())
94
-
95
- function runRowClass(conclusion, status) {
96
- const base = 'px-3 py-2 -mx-3 border-l-2 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-colors '
97
- if (status !== 'completed') return base + 'border-yellow-400'
98
- if (conclusion === 'success') return base + 'border-emerald-400'
99
- if (conclusion === 'failure' || conclusion === 'timed_out') return base + 'border-red-400'
100
- if (conclusion === 'cancelled') return base + 'border-zinc-400'
101
- return base + 'border-zinc-200 dark:border-zinc-800'
102
- }
103
-
104
- function runStatusBadge(conclusion, status) {
105
- if (status !== 'completed') return { label: status, cls: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300' }
106
- if (conclusion === 'success') return { label: 'success', cls: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300' }
107
- if (conclusion === 'failure') return { label: 'failure', cls: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300' }
108
- return { label: conclusion || 'unknown', cls: 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300' }
109
- }
110
-
111
- function formatDuration(ms) {
112
- if (ms == null) return ''
113
- const s = Math.floor(ms / 1000)
114
- if (s < 60) return s + 's'
115
- const m = Math.floor(s / 60)
116
- const rem = s % 60
117
- if (m < 60) return m + 'm' + (rem ? ' ' + rem + 's' : '')
118
- const h = Math.floor(m / 60)
119
- return h + 'h ' + (m % 60) + 'm'
120
- }
121
-
122
- function jobsForRun(runId) {
123
- return jobsByRunId()[runId] || []
124
- }
125
-
126
- function failedJobs(runId) {
127
- return jobsForRun(runId).filter(j => j.conclusion && j.conclusion !== 'success' && j.conclusion !== 'skipped' && j.conclusion !== 'neutral')
128
- }
129
-
130
86
  // ─── Runner pressure sparkline (stacksjs/stacks#1850) ────────────
131
87
  //
132
88
  // Inline SVG sparkline - no chart lib. The path is built off the
@@ -342,118 +298,6 @@
342
298
  </div>
343
299
  </div>
344
300
 
345
- <!-- ────────── Drilldown drawer (#1848) ────────── -->
346
- <!-- Slide-in side panel triggered by a repo-card click. Lists the
347
- last 20 workflow runs as a vertical timeline; failing runs
348
- expand inline to their failed-jobs list. Closes on backdrop
349
- click + Escape. -->
350
- <div
351
- :if="drilldownTarget()"
352
- class="flex fixed inset-0 z-50 justify-end dashboard-modal-layer"
353
- @click="ci.closeDrilldown()"
354
- @keydown.escape.window="ci.closeDrilldown()"
355
- >
356
- <div class="absolute inset-0 bg-black/40"></div>
357
- <aside
358
- @click.stop
359
- class="overflow-y-auto relative h-full max-w-2xl w-full bg-white dark:bg-zinc-950 border-l border-zinc-200 dark:border-zinc-800 shadow-2xl"
360
- >
361
- <!-- Header -->
362
- <div class="flex sticky top-0 z-10 gap-3 items-start px-5 py-4 bg-white dark:bg-zinc-950 border-b border-zinc-200 dark:border-zinc-800">
363
- <div class="flex-1 min-w-0">
364
- <p class="text-xs text-zinc-500 tracking-wide uppercase">Run history</p>
365
- <h3 class="font-semibold text-lg text-zinc-900 truncate dark:text-zinc-100" :text="drilldownTarget().fullName"></h3>
366
- </div>
367
- <Button variant="ghost" size="sm" iconOnly ariaLabel="Close run history" @click="ci.closeDrilldown()">
368
- <span aria-hidden="true" class="h-5 w-5 i-hugeicons-cancel-01"></span>
369
- </Button>
370
- </div>
371
-
372
- <!-- Error -->
373
- <div :if="drilldownError()" class="mt-4 mx-5 px-4 py-3 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900/40">
374
- <p class="text-red-700 text-xs dark:text-red-300" :text="drilldownError()"></p>
375
- </div>
376
-
377
- <!-- Loading -->
378
- <div :if="loadingDrilldown() && drilldownRuns().length === 0" class="px-5 py-12 text-center">
379
- <span aria-hidden="true" class="mx-auto h-6 w-6 text-violet-500 animate-spin i-hugeicons-loading-03"></span>
380
- <p class="mt-2 text-xs text-zinc-500">Fetching recent runs…</p>
381
- </div>
382
-
383
- <!-- Runs list -->
384
- <ul :if="drilldownRuns().length > 0" class="px-5 py-3">
385
- <li
386
- :for="run in drilldownRuns"
387
- class="border-b border-zinc-100 dark:border-zinc-900 last:border-0"
388
- >
389
- <div
390
- @click="ci.toggleRunJobs(run.id)"
391
- :class="runRowClass(run.conclusion, run.status)"
392
- >
393
- <div class="flex gap-3 items-start justify-between">
394
- <div class="flex-1 min-w-0">
395
- <div class="flex gap-2 items-center">
396
- <span
397
- :class="`px-1.5 py-0.5 text-[10px] font-medium rounded ${runStatusBadge(run.conclusion, run.status).cls}`"
398
- :text="runStatusBadge(run.conclusion, run.status).label"
399
- ></span>
400
- <span class="font-medium text-sm text-zinc-800 truncate dark:text-zinc-200" :text="run.name"></span>
401
- </div>
402
- <p :if="run.commitMessage" class="mt-1 text-xs text-zinc-500 truncate" :text="truncate(run.commitMessage, 80)"></p>
403
- <div class="flex gap-2 items-center mt-1 text-[10px] text-zinc-400">
404
- <span class="font-mono" :text="run.headShaShort"></span>
405
- <span :if="run.headBranch" :text="run.headBranch"></span>
406
- <span :if="run.commitAuthor">·</span>
407
- <span :if="run.commitAuthor" :text="run.commitAuthor"></span>
408
- <span>·</span>
409
- <span :text="timeAgo(run.updatedAt)"></span>
410
- <span :if="run.durationMs">·</span>
411
- <span :if="run.durationMs" :text="formatDuration(run.durationMs)"></span>
412
- </div>
413
- </div>
414
- <a
415
- :href="run.url"
416
- target="_blank"
417
- @click.stop
418
- class="flex-shrink-0 no-underline text-violet-600 text-xs dark:text-violet-400 hover:underline"
419
- >view ↗</a>
420
- </div>
421
- </div>
422
-
423
- <!-- Expanded: failed job list -->
424
- <div :if="expandedRunId() === run.id" class="px-3 py-2 bg-zinc-50 dark:bg-zinc-900/50 border-t border-zinc-100 dark:border-zinc-900">
425
- <div :if="loadingJobsRunId() === run.id" class="py-2 text-xs text-zinc-500">
426
- Loading jobs…
427
- </div>
428
- <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length === 0" class="py-1 italic text-xs text-zinc-400">
429
- No job detail.
430
- </div>
431
- <div :if="loadingJobsRunId() !== run.id && failedJobs(run.id).length > 0" class="space-y-1.5">
432
- <p class="text-[10px] text-zinc-500 tracking-wide uppercase">Failed jobs</p>
433
- <a
434
- :for="job in failedJobs(run.id)"
435
- :href="job.url"
436
- target="_blank"
437
- @click.stop
438
- class="flex gap-1.5 items-center no-underline text-red-700 text-xs dark:text-red-400 hover:underline"
439
- >
440
- <span aria-hidden="true" class="h-2.5 w-2.5 i-hugeicons-cancel-circle"></span>
441
- <span :text="job.name"></span>
442
- <span :if="job.durationMs" class="text-zinc-400" :text="'(' + formatDuration(job.durationMs) + ')'"></span>
443
- </a>
444
- </div>
445
- <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length > 0 && failedJobs(run.id).length === 0" class="py-1 text-xs text-zinc-500">
446
- All <span :text="jobsForRun(run.id).length"></span> jobs passed.
447
- </div>
448
- </div>
449
- </li>
450
- </ul>
451
-
452
- <!-- Empty -->
453
- <div :if="!loadingDrilldown() && !drilldownError() && drilldownRuns().length === 0" class="px-5 py-12 text-center">
454
- <p class="text-sm text-zinc-500">No recent runs.</p>
455
- </div>
456
- </aside>
457
- </div>
301
+ <CiRunHistoryDrawer />
458
302
  </div>
459
303
  </template>