@stacksjs/defaults 0.70.354 → 0.70.356
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 +17 -2
- package/ai/skills/stacks-browse/scripts/browse.ts +207 -6
- package/ai/skills/stacks-email/SKILL.md +73 -16
- package/app/Actions/ContactAction.ts +2 -2
- package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +9 -3
- package/app/Actions/Dashboard/Auth/MeAction.ts +12 -14
- package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
- package/app/Actions/Dashboard/Ci/RepoRunJobsAction.ts +10 -15
- package/app/Actions/Dashboard/Ci/RepoRunsAction.ts +12 -14
- package/app/Actions/Dashboard/Ci/RunnerHistoryAction.ts +11 -10
- package/app/Actions/Dashboard/Ci/StatusAction.ts +2 -11
- package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +8 -1
- package/app/Actions/Dashboard/Cloud/ServerlessIndexAction.ts +15 -8
- package/app/Actions/Dashboard/Commerce/CommerceCategoriesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +76 -61
- package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +4 -7
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +4 -4
- package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DigitalDeliveryIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/LicenseKeyIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/LicenseKeyOptionsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ManufacturerIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/PaymentRefundAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/ProductUnitIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductVariantIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductWaitlistIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/RestaurantWaitlistIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingMethodIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingRateIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingZoneIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +1 -0
- package/app/Actions/Dashboard/DashboardActivityAction.ts +2 -5
- package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/TeamIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/UserIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +26 -21
- package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +8 -2
- package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +3 -6
- package/app/Actions/Dashboard/Deployments/GetDeployment.ts +5 -5
- package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +7 -1
- package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +10 -9
- package/app/Actions/Dashboard/Deployments/GetDeployments.ts +2 -4
- package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +8 -2
- package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +21 -7
- package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
- package/app/Actions/Dashboard/Email/InboxActivityAction.ts +28 -16
- package/app/Actions/Dashboard/Email/InboxAttachmentDownloadAction.ts +45 -0
- package/app/Actions/Dashboard/Email/InboxDeleteAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxIndexAction.ts +5 -7
- package/app/Actions/Dashboard/Email/InboxMarkReadAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxMarkUnreadAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxPreferenceShowAction.ts +10 -4
- package/app/Actions/Dashboard/Email/InboxPreferenceUpdateAction.ts +8 -9
- package/app/Actions/Dashboard/Email/InboxShowAction.ts +7 -7
- package/app/Actions/Dashboard/Email/InboxStatsAction.ts +5 -7
- package/app/Actions/Dashboard/Email/inbox-request.ts +33 -0
- package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +4 -11
- package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +3 -6
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +9 -18
- package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +4 -3
- package/app/Actions/Dashboard/Jobs/JobShowAction.ts +16 -11
- package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +11 -19
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +3 -5
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +13 -12
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +12 -15
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +21 -46
- package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +43 -0
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +8 -9
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +6 -7
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +9 -9
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +6 -7
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +3 -4
- package/app/Actions/Dashboard/Kanban/kanban-comment.ts +14 -0
- package/app/Actions/Dashboard/Kanban/kanban-model.test.ts +36 -0
- package/app/Actions/Dashboard/Kanban/kanban-model.ts +14 -7
- package/app/Actions/Dashboard/Kanban/kanban-response.ts +5 -0
- package/app/Actions/Dashboard/Library/CreateComponent.ts +15 -6
- package/app/Actions/Dashboard/Library/CreateFunction.ts +15 -6
- package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +88 -77
- package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +21 -15
- package/app/Actions/Dashboard/Library/GetComponents.ts +14 -7
- package/app/Actions/Dashboard/Library/GetDownloadCount.ts +14 -5
- package/app/Actions/Dashboard/Library/GetFunctions.ts +12 -5
- package/app/Actions/Dashboard/Library/GetReleaseCount.ts +14 -5
- package/app/Actions/Dashboard/Library/GetReleases.ts +17 -10
- package/app/Actions/Dashboard/Library/PackageIndexAction.ts +14 -7
- package/app/Actions/Dashboard/Models/ModelDestroyAction.ts +11 -8
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +97 -192
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +14 -12
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +18 -16
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +50 -19
- package/app/Actions/Dashboard/Models/model-api.ts +27 -0
- package/app/Actions/Dashboard/Models/model-write.ts +22 -15
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +49 -40
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +20 -10
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +96 -84
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryRetryAction.ts +34 -21
- package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +2 -4
- package/app/Actions/Dashboard/Queries/QueryShowAction.ts +3 -3
- package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +2 -4
- package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +5 -4
- package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +6 -9
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +5 -4
- package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +6 -7
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +6 -13
- package/app/Actions/Dashboard/Rbac/UsersListAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/rbac-response.ts +10 -0
- package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +69 -55
- package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +34 -16
- package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +52 -29
- package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +104 -73
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +41 -28
- package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +71 -58
- package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +29 -7
- package/app/Actions/Dashboard/Teams/team-member-count.ts +4 -2
- package/app/Actions/Dashboard/Teams/team-records.test.ts +2 -0
- package/app/Actions/Dashboard/Teams/team-records.ts +2 -0
- package/app/Actions/Dashboard/dashboard-request.ts +13 -0
- package/app/Actions/Dashboard/dashboard-response.ts +16 -0
- package/app/Actions/SendWelcomeEmail.ts +1 -1
- package/app/Actions/Teams/AcceptInvitationAction.ts +74 -51
- package/app/Actions/Teams/ShowInvitationAction.ts +21 -14
- package/app/Actions/Teams/team-response.ts +23 -0
- package/app/Mail/OrderConfirmation.ts +1 -1
- package/app/Mail/SubscriptionConfirmation.ts +1 -1
- package/app/Mail/TeamInvitation.ts +1 -1
- package/app/Models/CardComment.ts +3 -4
- package/app/Models/EmailIdempotency.ts +64 -0
- package/app/Models/EmailSuppression.ts +52 -0
- package/app/Models/EmailWebhookEvent.ts +51 -0
- package/app/Models/TeamInvitation.ts +13 -0
- package/functions/dashboard-api.ts +51 -10
- package/functions/data.ts +0 -1
- package/functions/inbox.test.ts +2 -0
- package/functions/inbox.ts +27 -2
- package/functions/jobs.ts +0 -1
- package/functions/public-application-url.test.ts +13 -0
- package/functions/public-application-url.ts +27 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Auth/AccessTokens.stx +15 -0
- package/resources/components/Dashboard/Ci/CiDashboard.stx +24 -6
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +8 -7
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +8 -7
- package/resources/components/Dashboard/Content/BlogDashboard.stx +14 -2
- package/resources/components/Dashboard/Content/PostsDashboard.stx +7 -1
- package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
- package/resources/components/Dashboard/Deployments/DeploymentDetail.stx +16 -2
- package/resources/components/Dashboard/Email/EmailNavigation.stx +5 -5
- package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
- package/resources/components/Dashboard/Email/InboxAttachmentList.stx +53 -0
- package/resources/components/Dashboard/Email/InboxDashboard.stx +5 -4
- package/resources/components/Dashboard/Email/InboxMessageDetail.stx +6 -4
- package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
- package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +63 -6
- package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +3 -6
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +4 -4
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
- package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
- package/resources/components/Dashboard/NotFoundDashboard.stx +2 -5
- package/resources/components/Dashboard/Queries/QueryDashboard.stx +6 -2
- package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +4 -4
- package/resources/components/Dashboard/UI/EmptyState.stx +2 -2
- package/resources/components/Dashboard/UI/PageLayout.stx +3 -3
- package/resources/functions/dashboard/sidebar.ts +4 -4
- package/routes/dashboard-api.ts +8 -20
- package/views/dashboard/.discovered-models.json +54 -0
- package/views/dashboard/layouts/default.stx +19 -16
- package/views/dashboard/stores/auth.ts +15 -14
- package/views/dashboard/stores/ci.ts +13 -7
- package/views/dashboard/stores/kanban.ts +48 -11
|
@@ -37,16 +37,9 @@ export class DashboardApiError extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
* Call a dashboard API endpoint and return its parsed JSON.
|
|
42
|
-
*
|
|
43
|
-
* Throws on a non-2xx response, using the server's `message` / `error`
|
|
44
|
-
* field when there is one so callers can surface something better than a
|
|
45
|
-
* bare status code.
|
|
46
|
-
*/
|
|
47
|
-
export async function dashboardApi<T = unknown>(path: string, options: ApiRequestOptions = {}): Promise<T> {
|
|
40
|
+
async function dashboardResponse(path: string, options: ApiRequestOptions, accept: string): Promise<Response> {
|
|
48
41
|
const method = options.method ?? 'GET'
|
|
49
|
-
const headers: Record<string, string> = { accept
|
|
42
|
+
const headers: Record<string, string> = { accept }
|
|
50
43
|
const authToken = useAuth().getToken()
|
|
51
44
|
|
|
52
45
|
if (options.body !== undefined && options.formData)
|
|
@@ -62,7 +55,7 @@ export async function dashboardApi<T = unknown>(path: string, options: ApiReques
|
|
|
62
55
|
? headers
|
|
63
56
|
: withCsrfHeader(headers)
|
|
64
57
|
|
|
65
|
-
|
|
58
|
+
return fetch(path, {
|
|
66
59
|
method,
|
|
67
60
|
headers: requestHeaders,
|
|
68
61
|
credentials: 'same-origin',
|
|
@@ -70,6 +63,33 @@ export async function dashboardApi<T = unknown>(path: string, options: ApiReques
|
|
|
70
63
|
...(options.body !== undefined && { body: JSON.stringify(options.body) }),
|
|
71
64
|
...(options.formData && { body: options.formData }),
|
|
72
65
|
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function dashboardResponseError(res: Response): Promise<DashboardApiError> {
|
|
69
|
+
const text = await res.text()
|
|
70
|
+
let payload: ApiErrorBody | null = null
|
|
71
|
+
try {
|
|
72
|
+
payload = text ? JSON.parse(text) as ApiErrorBody : null
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
payload = null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const failure = describeResponseError(res.status, payload)
|
|
79
|
+
if (failure.unexpected)
|
|
80
|
+
console.error('[dashboard-api] Unexpected request failure:', failure.cause)
|
|
81
|
+
return new DashboardApiError(failure.message, res.status, failure.fields)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Call a dashboard API endpoint and return its parsed JSON.
|
|
86
|
+
*
|
|
87
|
+
* Throws on a non-2xx response, using the server's `message` / `error`
|
|
88
|
+
* field when there is one so callers can surface something better than a
|
|
89
|
+
* bare status code.
|
|
90
|
+
*/
|
|
91
|
+
export async function dashboardApi<T = unknown>(path: string, options: ApiRequestOptions = {}): Promise<T> {
|
|
92
|
+
const res = await dashboardResponse(path, options, 'application/json')
|
|
73
93
|
|
|
74
94
|
const text = await res.text()
|
|
75
95
|
let payload: any = null
|
|
@@ -89,3 +109,24 @@ export async function dashboardApi<T = unknown>(path: string, options: ApiReques
|
|
|
89
109
|
|
|
90
110
|
return payload as T
|
|
91
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Download a guarded dashboard resource while preserving bearer and session
|
|
115
|
+
* authentication. The object URL only exists for the duration of the browser
|
|
116
|
+
* download and is then released.
|
|
117
|
+
*/
|
|
118
|
+
export async function dashboardDownload(path: string, filename: string): Promise<void> {
|
|
119
|
+
const res = await dashboardResponse(path, {}, 'application/octet-stream')
|
|
120
|
+
if (!res.ok)
|
|
121
|
+
throw await dashboardResponseError(res)
|
|
122
|
+
|
|
123
|
+
const objectUrl = URL.createObjectURL(await res.blob())
|
|
124
|
+
const anchor = document.createElement('a')
|
|
125
|
+
anchor.href = objectUrl
|
|
126
|
+
anchor.download = filename
|
|
127
|
+
anchor.hidden = true
|
|
128
|
+
document.body.append(anchor)
|
|
129
|
+
anchor.click()
|
|
130
|
+
anchor.remove()
|
|
131
|
+
setTimeout(() => URL.revokeObjectURL(objectUrl), 0)
|
|
132
|
+
}
|
package/functions/data.ts
CHANGED
package/functions/inbox.test.ts
CHANGED
package/functions/inbox.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { ref } from '@stacksjs/stx'
|
|
8
8
|
import { get } from './api'
|
|
9
|
-
import { dashboardApi } from './dashboard-api'
|
|
9
|
+
import { dashboardApi, dashboardDownload } from './dashboard-api'
|
|
10
10
|
import { pushToast } from './toasts'
|
|
11
11
|
|
|
12
12
|
export type InboxActivityRange = 'day' | 'week' | 'month' | 'year'
|
|
@@ -71,6 +71,15 @@ export interface DashboardInboxEmail {
|
|
|
71
71
|
date: string
|
|
72
72
|
read: boolean
|
|
73
73
|
hasAttachments: boolean
|
|
74
|
+
attachments: DashboardInboxAttachment[]
|
|
75
|
+
detailsLoaded: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface DashboardInboxAttachment {
|
|
79
|
+
id: string
|
|
80
|
+
name: string
|
|
81
|
+
size: number
|
|
82
|
+
lastModified?: string
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
interface DashboardInboxEntry {
|
|
@@ -94,6 +103,7 @@ interface DashboardInboxResponse {
|
|
|
94
103
|
interface DashboardInboxBodyResponse {
|
|
95
104
|
html?: string
|
|
96
105
|
text?: string
|
|
106
|
+
attachments?: DashboardInboxAttachment[]
|
|
97
107
|
error?: string
|
|
98
108
|
}
|
|
99
109
|
|
|
@@ -166,6 +176,8 @@ export function mapDashboardInboxEntry(item: DashboardInboxEntry): DashboardInbo
|
|
|
166
176
|
date: item.date,
|
|
167
177
|
read: item.read === true,
|
|
168
178
|
hasAttachments: item.hasAttachments === true,
|
|
179
|
+
attachments: [],
|
|
180
|
+
detailsLoaded: false,
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
183
|
|
|
@@ -179,7 +191,7 @@ export async function fetchDashboardInbox(mailbox?: string): Promise<LoadedDashb
|
|
|
179
191
|
}
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
export async function fetchDashboardInboxBody(messageId: string, mailbox?: string): Promise<Pick<DashboardInboxEmail, 'bodyHtml' | 'bodyText'>> {
|
|
194
|
+
export async function fetchDashboardInboxBody(messageId: string, mailbox?: string): Promise<Pick<DashboardInboxEmail, 'attachments' | 'bodyHtml' | 'bodyText' | 'detailsLoaded' | 'hasAttachments'>> {
|
|
183
195
|
const query = mailbox ? `?mailbox=${encodeURIComponent(mailbox)}` : ''
|
|
184
196
|
const data = await dashboardApi<DashboardInboxBodyResponse>(`/api/dashboard/email/inbox/${encodeURIComponent(messageId)}${query}`)
|
|
185
197
|
if (data.error)
|
|
@@ -187,9 +199,22 @@ export async function fetchDashboardInboxBody(messageId: string, mailbox?: strin
|
|
|
187
199
|
return {
|
|
188
200
|
bodyHtml: data.html || '',
|
|
189
201
|
bodyText: data.text || '',
|
|
202
|
+
attachments: data.attachments || [],
|
|
203
|
+
detailsLoaded: true,
|
|
204
|
+
hasAttachments: (data.attachments || []).length > 0,
|
|
190
205
|
}
|
|
191
206
|
}
|
|
192
207
|
|
|
208
|
+
export async function downloadDashboardInboxAttachment(
|
|
209
|
+
messageId: string,
|
|
210
|
+
attachment: DashboardInboxAttachment,
|
|
211
|
+
mailbox?: string,
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
const query = mailbox ? `?mailbox=${encodeURIComponent(mailbox)}` : ''
|
|
214
|
+
const path = `/api/dashboard/email/inbox/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachment.id)}${query}`
|
|
215
|
+
await dashboardDownload(path, attachment.name)
|
|
216
|
+
}
|
|
217
|
+
|
|
193
218
|
export async function fetchInboxActivity(range: InboxActivityRange): Promise<InboxActivity | null> {
|
|
194
219
|
try {
|
|
195
220
|
return await dashboardApi<InboxActivity>(`/api/dashboard/email/activity?range=${range}`)
|
package/functions/jobs.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { publicApplicationUrl } from './public-application-url'
|
|
3
|
+
|
|
4
|
+
describe('publicApplicationUrl', () => {
|
|
5
|
+
it('uses HTTPS for application domains and removes duplicate separators', () => {
|
|
6
|
+
expect(publicApplicationUrl('/blog', 'stacks.localhost/')).toBe('https://stacks.localhost/blog')
|
|
7
|
+
expect(publicApplicationUrl('/blog/post', 'https://stacksjs.com/')).toBe('https://stacksjs.com/blog/post')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('keeps explicit local development protocols', () => {
|
|
11
|
+
expect(publicApplicationUrl('/blog', 'http://localhost:3000')).toBe('http://localhost:3000/blog')
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
function configuredApplicationUrl(): string {
|
|
4
|
+
const frontendUrl = process.env.FRONTEND_APP_URL?.trim()
|
|
5
|
+
if (frontendUrl && !frontendUrl.includes('${'))
|
|
6
|
+
return frontendUrl
|
|
7
|
+
|
|
8
|
+
return process.env.APP_URL?.trim() || 'http://localhost:3000'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function withProtocol(baseUrl: string): string {
|
|
12
|
+
if (/^https?:\/\//i.test(baseUrl))
|
|
13
|
+
return baseUrl
|
|
14
|
+
|
|
15
|
+
const host = baseUrl.split('/')[0].split(':')[0].toLowerCase()
|
|
16
|
+
const protocol = ['localhost', '127.0.0.1', '0.0.0.0', '[::1]'].includes(host)
|
|
17
|
+
? 'http'
|
|
18
|
+
: 'https'
|
|
19
|
+
return `${protocol}://${baseUrl}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Build an absolute URL for a page served by the public Stacks application. */
|
|
23
|
+
export function publicApplicationUrl(pathname = '/', baseUrl = configuredApplicationUrl()): string {
|
|
24
|
+
const normalizedBase = withProtocol(baseUrl.trim()).replace(/\/+$/, '')
|
|
25
|
+
const normalizedPath = pathname.replace(/^\/+/, '')
|
|
26
|
+
return new URL(normalizedPath, `${normalizedBase}/`).toString()
|
|
27
|
+
}
|
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.356",
|
|
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",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script client>
|
|
2
2
|
import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
|
|
3
|
+
import { authStore } from '../../../../views/dashboard/stores/auth'
|
|
3
4
|
|
|
4
5
|
interface AccessTokenRow {
|
|
5
6
|
id: number
|
|
@@ -106,6 +107,20 @@ async function loadTokens(): Promise<void> {
|
|
|
106
107
|
loading.set(true)
|
|
107
108
|
loadError.set('')
|
|
108
109
|
try {
|
|
110
|
+
await authStore.load()
|
|
111
|
+
|
|
112
|
+
if (authStore.error()) {
|
|
113
|
+
tokens.set([])
|
|
114
|
+
loadError.set(authStore.error() || 'The signed-in account could not be verified.')
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (authStore.unauthenticated()) {
|
|
119
|
+
tokens.set([])
|
|
120
|
+
authenticationRequired.set(true)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
109
124
|
const response = await dashboardApi<TokenListResponse>('/auth/tokens')
|
|
110
125
|
tokens.set(response.tokens || [])
|
|
111
126
|
authenticationRequired.set(false)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// (Action: Dashboard/Ci/StatusAction → @stacksjs/github). Active tab +
|
|
6
6
|
// status filter live in the `ciStore` so they survive SPA navigation.
|
|
7
7
|
//
|
|
8
|
-
// No <script server>, no window./document. reads
|
|
8
|
+
// No <script server>, no window./document. reads - per #1838 the page
|
|
9
9
|
// is pure presentation against the store.
|
|
10
10
|
import { useRole } from '~/storage/framework/defaults/views/dashboard/composables/useRole'
|
|
11
11
|
import { ciStore } from '~/storage/framework/defaults/views/dashboard/stores/ci'
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// Role check is now backed by the real RBAC store + `/api/dashboard/auth/me`
|
|
18
18
|
// (stacksjs/stacks#1843). `isDev()` returns true if the authenticated user
|
|
19
19
|
// holds the `admin` or `dev` role, OR if the request is unauthenticated
|
|
20
|
-
// (i.e. local dev dashboard with no bearer token)
|
|
20
|
+
// (i.e. local dev dashboard with no bearer token) - see `useRole.ts` for
|
|
21
21
|
// the full resolution chain.
|
|
22
22
|
const { isDev } = useRole()
|
|
23
23
|
const canView = derived(() => isDev())
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
|
|
130
130
|
// ─── Runner pressure sparkline (stacksjs/stacks#1850) ────────────
|
|
131
131
|
//
|
|
132
|
-
// Inline SVG sparkline
|
|
132
|
+
// Inline SVG sparkline - no chart lib. The path is built off the
|
|
133
133
|
// active tab's sample history with a simple linear scale: x maps
|
|
134
134
|
// to sample index (oldest left, newest right), y maps to queue
|
|
135
135
|
// depth normalised against the larger of the org's cap or the
|
|
@@ -139,6 +139,8 @@
|
|
|
139
139
|
const tab = activeTab()
|
|
140
140
|
return (ci.runnerHistoryByOrg()[tab] || [])
|
|
141
141
|
})
|
|
142
|
+
const runnerHistoryError = derived(() => ci.runnerHistoryErrors()[activeTab()] || '')
|
|
143
|
+
const loadingHistory = derived(() => ci.loadingRunnerHistory()[activeTab()] === true)
|
|
142
144
|
|
|
143
145
|
effect(() => {
|
|
144
146
|
const tab = activeTab()
|
|
@@ -173,7 +175,12 @@
|
|
|
173
175
|
return samples && samples.length >= 2
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
|
|
178
|
+
function retryRunnerHistory() {
|
|
179
|
+
const tab = activeTab()
|
|
180
|
+
if (tab) ci.loadRunnerHistory(tab, true)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Top-level fetch - runs on both hard reload (SSR hydration) and SPA
|
|
177
184
|
// mount, since `<script client>` re-executes on every entry. The store
|
|
178
185
|
// tracks its own loading flag so back-to-back calls don't double-fire.
|
|
179
186
|
ci.load()
|
|
@@ -195,7 +202,7 @@
|
|
|
195
202
|
<p class="mt-1 text-sm text-zinc-500">GitHub Actions health across the configured orgs. Configure in <code class="text-xs">config/dashboard.ts</code>.</p>
|
|
196
203
|
</header>
|
|
197
204
|
|
|
198
|
-
<!-- Disabled state
|
|
205
|
+
<!-- Disabled state - the surface is off in config/dashboard.ts -->
|
|
199
206
|
<div :if="disabled" class="px-5 py-8 text-center bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
|
|
200
207
|
<p class="text-sm text-zinc-600 dark:text-zinc-400">CI tracking is disabled.</p>
|
|
201
208
|
<p class="mt-1 text-xs text-zinc-500">Enable it in <code>config/dashboard.ts</code> by setting <code>ci.enabled = true</code> and listing one or more orgs.</p>
|
|
@@ -247,7 +254,7 @@
|
|
|
247
254
|
<span class="text-zinc-300 dark:text-zinc-700">·</span>
|
|
248
255
|
<span class="text-zinc-500 dark:text-zinc-400" :text="runnersText"></span>
|
|
249
256
|
</div>
|
|
250
|
-
<!-- Sparkline showing recent queue depth for the active org
|
|
257
|
+
<!-- Sparkline showing recent queue depth for the active org -
|
|
251
258
|
appears only when alerts are on (history collection is gated
|
|
252
259
|
on `ci.alerts.enabled`). Drawn inline as a single SVG path
|
|
253
260
|
so we don't pull a chart library for 20 datapoints. -->
|
|
@@ -257,6 +264,17 @@
|
|
|
257
264
|
<path :d="sparklinePath()" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
|
258
265
|
</svg>
|
|
259
266
|
</div>
|
|
267
|
+
<div :if="loadingHistory() && !hasHistory()" role="status" class="flex gap-2 items-center mb-5 text-zinc-500 text-xs dark:text-zinc-400">
|
|
268
|
+
<span aria-hidden="true" class="h-3.5 w-3.5 animate-spin i-hugeicons-loading-03"></span>
|
|
269
|
+
<span>Loading runner history</span>
|
|
270
|
+
</div>
|
|
271
|
+
<div :if="runnerHistoryError()" role="alert" class="flex flex-wrap gap-3 items-center justify-between mb-5 px-3 py-2 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-lg dark:border-amber-900/50">
|
|
272
|
+
<div>
|
|
273
|
+
<p class="font-medium text-amber-800 text-xs dark:text-amber-200">Runner history unavailable</p>
|
|
274
|
+
<p class="mt-0.5 text-amber-700 text-xs dark:text-amber-300" :text="runnerHistoryError"></p>
|
|
275
|
+
</div>
|
|
276
|
+
<Button variant="secondary" size="xs" :loading="loadingHistory" @click="retryRunnerHistory()">Retry</Button>
|
|
277
|
+
</div>
|
|
260
278
|
|
|
261
279
|
<!-- Repo cards -->
|
|
262
280
|
<div class="grid gap-2" style="grid-template-columns: repeat(auto-fill, minmax(340px, 1fr))">
|
|
@@ -132,7 +132,7 @@ function errorMessages(): string[] {
|
|
|
132
132
|
<input x-model="exemptible" type="checkbox" class="mt-0.5 h-4 w-4 text-blue-600 border-gray-300 rounded" />
|
|
133
133
|
<span>
|
|
134
134
|
<span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Can be exempted</span>
|
|
135
|
-
<span class="block mt-0.5 text-gray-500 text-xs dark:text-neutral-400">Customers who qualify for an exemption are not charged this component. Leave off for anything owed regardless
|
|
135
|
+
<span class="block mt-0.5 text-gray-500 text-xs dark:text-neutral-400">Customers who qualify for an exemption are not charged this component. Leave off for anything owed regardless - an excise or a city levy is usually still due.</span>
|
|
136
136
|
</span>
|
|
137
137
|
</label>
|
|
138
138
|
</div>
|
|
@@ -23,6 +23,13 @@ const deleteRecord = state<ProductWaitlistRecord | null>(null)
|
|
|
23
23
|
const isSaving = state(false)
|
|
24
24
|
const isDeleting = state(false)
|
|
25
25
|
const validationErrors = state<Record<string, string>>({})
|
|
26
|
+
const metrics = derived(() => [
|
|
27
|
+
{ label: 'Recorded', value: summary().total },
|
|
28
|
+
{ label: 'Waiting', value: summary().waiting },
|
|
29
|
+
{ label: 'Notified', value: summary().notified },
|
|
30
|
+
{ label: 'Purchased', value: summary().purchased },
|
|
31
|
+
{ label: 'Conversion', value: `${summary().conversionRate}%` },
|
|
32
|
+
])
|
|
26
33
|
|
|
27
34
|
function rows(): ProductWaitlistRecord[] {
|
|
28
35
|
const query = search().trim().toLowerCase()
|
|
@@ -166,13 +173,7 @@ onMount(() => {
|
|
|
166
173
|
</div>
|
|
167
174
|
|
|
168
175
|
<section class="grid gap-3 grid-cols-2 lg:grid-cols-5">
|
|
169
|
-
<template :for="item in
|
|
170
|
-
{ label: 'Recorded', value: summary().total },
|
|
171
|
-
{ label: 'Waiting', value: summary().waiting },
|
|
172
|
-
{ label: 'Notified', value: summary().notified },
|
|
173
|
-
{ label: 'Purchased', value: summary().purchased },
|
|
174
|
-
{ label: 'Conversion', value: summary().conversionRate + '%' }
|
|
175
|
-
]">
|
|
176
|
+
<template :for="item in metrics()">
|
|
176
177
|
<div class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
|
|
177
178
|
<p class="text-gray-500 text-xs uppercase dark:text-neutral-400">{{ item.label }}</p>
|
|
178
179
|
<p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
|
|
@@ -26,6 +26,13 @@ const isSaving = state(false)
|
|
|
26
26
|
const isDeleting = state(false)
|
|
27
27
|
const busyId = state('')
|
|
28
28
|
const validationErrors = state<Record<string, string>>({})
|
|
29
|
+
const metrics = derived(() => [
|
|
30
|
+
{ label: 'Waiting', value: summary().waiting },
|
|
31
|
+
{ label: 'Quoted wait', value: `${summary().averageQuotedWait} min` },
|
|
32
|
+
{ label: 'Seated', value: summary().seated },
|
|
33
|
+
{ label: 'No show', value: summary().noShow },
|
|
34
|
+
{ label: 'Seating rate', value: `${summary().seatingRate}%` },
|
|
35
|
+
])
|
|
29
36
|
|
|
30
37
|
function rows(): RestaurantWaitlistRecord[] {
|
|
31
38
|
const query = search().trim().toLowerCase()
|
|
@@ -220,13 +227,7 @@ onMount(() => {
|
|
|
220
227
|
<div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
|
|
221
228
|
|
|
222
229
|
<section class="grid gap-3 grid-cols-2 lg:grid-cols-5">
|
|
223
|
-
<template :for="item in
|
|
224
|
-
{ label: 'Waiting', value: summary().waiting },
|
|
225
|
-
{ label: 'Quoted wait', value: summary().averageQuotedWait + ' min' },
|
|
226
|
-
{ label: 'Seated', value: summary().seated },
|
|
227
|
-
{ label: 'No show', value: summary().noShow },
|
|
228
|
-
{ label: 'Seating rate', value: summary().seatingRate + '%' }
|
|
229
|
-
]">
|
|
230
|
+
<template :for="item in metrics()">
|
|
230
231
|
<div class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
|
|
231
232
|
<p class="text-gray-500 text-xs uppercase dark:text-neutral-400">{{ item.label }}</p>
|
|
232
233
|
<p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
import { publicApplicationUrl } from '../../../../functions/public-application-url'
|
|
3
|
+
|
|
4
|
+
export const blogUrl = publicApplicationUrl('/blog')
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<template>
|
|
2
8
|
<div class="p-6 min-h-screen max-w-full text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
|
|
3
9
|
<div class="flex flex-col gap-6 mx-auto min-w-0 max-w-7xl w-full">
|
|
@@ -10,7 +16,7 @@
|
|
|
10
16
|
</p>
|
|
11
17
|
</div>
|
|
12
18
|
<div class="flex flex-wrap gap-2">
|
|
13
|
-
<Button tag="a" href="
|
|
19
|
+
<Button tag="a" href="{{ blogUrl }}" target="_blank" rel="noopener" variant="secondary">
|
|
14
20
|
<span class="h-4 w-4 i-hugeicons-view"></span>
|
|
15
21
|
View blog
|
|
16
22
|
</Button>
|
|
@@ -123,7 +129,7 @@
|
|
|
123
129
|
<div class="flex gap-1 justify-end">
|
|
124
130
|
<a
|
|
125
131
|
x-show="!post.draft"
|
|
126
|
-
:href="
|
|
132
|
+
:href="publicPostUrl(post.slug)"
|
|
127
133
|
target="_blank"
|
|
128
134
|
rel="noopener"
|
|
129
135
|
:title="'View ' + post.title"
|
|
@@ -264,6 +270,8 @@
|
|
|
264
270
|
// markdown files in content/blog. It owns no state; the signals below do.
|
|
265
271
|
import { deleteBlogPost, fetchBlogPost, fetchBlogPosts, saveBlogPost } from '~/storage/framework/defaults/functions/blog'
|
|
266
272
|
|
|
273
|
+
const publicBlogBaseUrl = {{ blogUrl }}
|
|
274
|
+
|
|
267
275
|
// Fixed-length list purely to render the loading skeleton rows.
|
|
268
276
|
const skeletonRows = [1, 2, 3, 4]
|
|
269
277
|
|
|
@@ -291,6 +299,10 @@ const formFeatured = state(false)
|
|
|
291
299
|
|
|
292
300
|
const postToDelete = state(null)
|
|
293
301
|
|
|
302
|
+
function publicPostUrl(slug) {
|
|
303
|
+
return `${publicBlogBaseUrl}/${encodeURIComponent(String(slug))}`
|
|
304
|
+
}
|
|
305
|
+
|
|
294
306
|
// Read by the delete dialog's copy; kept as a derived signal so the template
|
|
295
307
|
// never has to reach through a possibly-null post.
|
|
296
308
|
const deleteTargetSlug = derived(() => {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
import { publicApplicationUrl } from '../../../../functions/public-application-url'
|
|
3
|
+
|
|
4
|
+
export const blogUrl = publicApplicationUrl('/blog')
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<script client>
|
|
2
8
|
import { dashboardApi } from '../../../../functions/dashboard-api'
|
|
3
9
|
import { pushToast } from '../../../../functions/toasts'
|
|
@@ -229,7 +235,7 @@ onMount(() => { void loadPosts() })
|
|
|
229
235
|
<div class="space-y-6">
|
|
230
236
|
<header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
231
237
|
<div><p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p><h1 class="mt-1 font-bold text-2xl text-gray-900 dark:text-white">Posts</h1><p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage persisted articles, publishing, authors, and native taxonomies.</p></div>
|
|
232
|
-
<div class="flex flex-wrap gap-2"><Button href="
|
|
238
|
+
<div class="flex flex-wrap gap-2"><Button href="{{ blogUrl }}" target="_blank" rel="noreferrer" variant="secondary"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-view"></span>View blog</Button><Button :loading="loading()" variant="secondary" @click="loadPosts()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button><Button @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>New post</Button></div>
|
|
233
239
|
</header>
|
|
234
240
|
|
|
235
241
|
<div :if="loadError()" role="alert" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadPosts()">Retry</Button></div>
|
|
@@ -98,25 +98,21 @@ async function loadRecords(): Promise<void> {
|
|
|
98
98
|
const response = await fetchDashboardData<DashboardActivity>('activity')
|
|
99
99
|
records.set(response.activities || [])
|
|
100
100
|
stats.set(response.stats)
|
|
101
|
-
loadError.set(response.error || '')
|
|
102
101
|
}
|
|
103
102
|
else if (mode() === 'users') {
|
|
104
103
|
const response = await fetchDashboardData<DashboardUser>('users')
|
|
105
104
|
records.set(response.users || [])
|
|
106
105
|
stats.set(response.stats)
|
|
107
|
-
loadError.set(response.error || '')
|
|
108
106
|
}
|
|
109
107
|
else if (mode() === 'teams') {
|
|
110
108
|
const response = await fetchDashboardData<DashboardTeam>('teams')
|
|
111
109
|
records.set(response.teams || [])
|
|
112
110
|
stats.set(response.stats)
|
|
113
|
-
loadError.set(response.error || '')
|
|
114
111
|
}
|
|
115
112
|
else {
|
|
116
113
|
const response = await fetchDashboardData<DashboardSubscriber>('subscribers')
|
|
117
114
|
records.set(response.subscribers || [])
|
|
118
115
|
stats.set(response.stats)
|
|
119
|
-
loadError.set(response.error || '')
|
|
120
116
|
}
|
|
121
117
|
}
|
|
122
118
|
catch (error) {
|
|
@@ -44,6 +44,20 @@ function statusLabel(status: string): string {
|
|
|
44
44
|
return status.charAt(0).toUpperCase() + status.slice(1)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function deploymentUrl(): string {
|
|
48
|
+
const value = record()?.url?.trim()
|
|
49
|
+
if (!value)
|
|
50
|
+
return ''
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const url = new URL(value)
|
|
54
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : ''
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return ''
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
async function loadDeployment(): Promise<void> {
|
|
48
62
|
const route = useRoute()
|
|
49
63
|
const id = String(route.params.id || '')
|
|
@@ -131,9 +145,9 @@ onMount(() => {
|
|
|
131
145
|
<dt class="text-gray-500 dark:text-neutral-400">Created</dt>
|
|
132
146
|
<dd class="mt-1 text-gray-900 dark:text-white">{{ formatDate(record()?.createdAt || '') }}</dd>
|
|
133
147
|
</div>
|
|
134
|
-
<div :if="
|
|
148
|
+
<div :if="deploymentUrl()">
|
|
135
149
|
<dt class="text-gray-500 dark:text-neutral-400">Deployment URL</dt>
|
|
136
|
-
<dd class="mt-1"><a :href="
|
|
150
|
+
<dd class="mt-1"><a :href="deploymentUrl()" target="_blank" rel="noopener noreferrer" class="font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500">{{ deploymentUrl() }}</a></dd>
|
|
137
151
|
</div>
|
|
138
152
|
</dl>
|
|
139
153
|
</div>
|
|
@@ -8,17 +8,17 @@ function linkClass(name: string): string {
|
|
|
8
8
|
}
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
|
-
<nav aria-label="Mail sections" class="
|
|
12
|
-
<StxLink to="/inbox" :class="'px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('inbox')">
|
|
11
|
+
<nav aria-label="Mail sections" class="flex overflow-x-auto gap-0.5 sm:gap-1 p-1 max-w-full bg-gray-100 dark:bg-neutral-900 rounded-lg">
|
|
12
|
+
<StxLink to="/inbox" :class="'px-2 sm:px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('inbox')">
|
|
13
13
|
Inbox
|
|
14
14
|
</StxLink>
|
|
15
|
-
<StxLink to="/inbox/activity" :class="'px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('activity')">
|
|
15
|
+
<StxLink to="/inbox/activity" :class="'px-2 sm:px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('activity')">
|
|
16
16
|
Activity
|
|
17
17
|
</StxLink>
|
|
18
|
-
<StxLink to="/inbox/captured" :class="'px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('captured')">
|
|
18
|
+
<StxLink to="/inbox/captured" :class="'px-2 sm:px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('captured')">
|
|
19
19
|
Captured
|
|
20
20
|
</StxLink>
|
|
21
|
-
<StxLink to="/inbox/settings" :class="'px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('settings')">
|
|
21
|
+
<StxLink to="/inbox/settings" :class="'px-2 sm:px-3 py-1.5 font-medium text-sm rounded-md transition-colors ' + linkClass('settings')">
|
|
22
22
|
Settings
|
|
23
23
|
</StxLink>
|
|
24
24
|
</nav>
|
|
@@ -47,7 +47,7 @@ function mailboxLabel(): string {
|
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
49
|
|
|
50
|
-
<aside class="flex flex-col flex-shrink-0 w-64 bg-white dark:bg-neutral-800 border-gray-200 border-r dark:border-neutral-600">
|
|
50
|
+
<aside class="flex flex-col flex-shrink-0 w-full md:w-64 bg-white dark:bg-neutral-800 border-b border-gray-200 md:border-b-0 md:border-r dark:border-neutral-600">
|
|
51
51
|
<div class="p-3 border-b border-gray-200 dark:border-neutral-600">
|
|
52
52
|
<div class="flex items-center space-x-2">
|
|
53
53
|
<div class="flex flex-shrink-0 items-center justify-center h-8 w-8 text-gray-700 dark:text-white bg-gray-200 dark:bg-neutral-700 rounded">
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
import type { DashboardInboxAttachment } from '../../../../functions/inbox'
|
|
3
|
+
import { downloadDashboardInboxAttachment } from '../../../../functions/inbox'
|
|
4
|
+
import { pushToast } from '../../../../functions/toasts'
|
|
5
|
+
|
|
6
|
+
const attachments = useReactiveProp<DashboardInboxAttachment[]>('attachments', [])
|
|
7
|
+
const mailbox = useReactiveProp('mailbox', '')
|
|
8
|
+
const messageId = useReactiveProp('messageId', '')
|
|
9
|
+
const downloadingId = state('')
|
|
10
|
+
|
|
11
|
+
function formatBytes(bytes: number): string {
|
|
12
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
13
|
+
return 'Unknown size'
|
|
14
|
+
|
|
15
|
+
const units = ['B', 'KB', 'MB', 'GB']
|
|
16
|
+
const unitIndex = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1)
|
|
17
|
+
const value = bytes / 1024 ** unitIndex
|
|
18
|
+
return `${value >= 10 || unitIndex === 0 ? Math.round(value) : value.toFixed(1)} ${units[unitIndex]}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function download(attachment: DashboardInboxAttachment): Promise<void> {
|
|
22
|
+
downloadingId.set(attachment.id)
|
|
23
|
+
try {
|
|
24
|
+
await downloadDashboardInboxAttachment(messageId(), attachment, mailbox())
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
pushToast('error', 'Could not download attachment', { detail: String(error) })
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
downloadingId.set('')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<section :if="attachments().length" aria-label="Message attachments" class="mb-4 mx-4 p-3 bg-gray-50 dark:bg-neutral-800 border border-gray-200 rounded-md dark:border-neutral-600">
|
|
36
|
+
<div class="flex gap-2 items-center text-gray-800 dark:text-neutral-200">
|
|
37
|
+
<span aria-hidden="true" class="h-5 w-5 i-hugeicons-attachment-01"></span>
|
|
38
|
+
<h3 class="font-medium text-sm">Attachments</h3>
|
|
39
|
+
<span class="text-gray-500 text-xs dark:text-neutral-400">{{ attachments().length }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<ul class="mt-3 space-y-2">
|
|
42
|
+
<li :for="attachment in attachments()" class="flex gap-3 items-center justify-between p-2 bg-white dark:bg-neutral-700 border border-gray-200 rounded-md dark:border-neutral-600">
|
|
43
|
+
<div class="min-w-0">
|
|
44
|
+
<p class="font-medium text-gray-900 text-sm truncate dark:text-white">{{ attachment.name }}</p>
|
|
45
|
+
<p class="mt-0.5 text-gray-500 text-xs dark:text-neutral-400">{{ formatBytes(attachment.size) }}</p>
|
|
46
|
+
</div>
|
|
47
|
+
<Button size="sm" :loading="downloadingId() === attachment.id" :disabled="Boolean(downloadingId())" @click="download(attachment)">
|
|
48
|
+
<span :if="downloadingId() !== attachment.id" aria-hidden="true" class="h-4 w-4 i-hugeicons-download-04"></span>
|
|
49
|
+
Download
|
|
50
|
+
</Button>
|
|
51
|
+
</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</section>
|